CSharp - Other

MVC Editor Templates for Date Ranges

Date Range VM

public class DateRangeVM
{
    public DateTime? DateFrom { get; set; }
    public DateTime? DateTo { get; set; }
}


Editor Templates

@model DateRangeVM

<div class="d-flex justify-content-between align-items-center">
    @Html.TextBoxFor(x => x.DateFrom, new { @class = "form-control", @type = "date" })
    <label class="mx-1">to</label>
    @Html.TextBoxFor(x => x.DateTo, new { @class = "form-control", @type = "date" })
</div>

NUnit vs. xUnit vs. MSTest

What is NUnit ?

NUnit is a free, open-source unit testing framework for .NET languages, inspired by JUnit. It allows you to test individual parts of your application separately to ensure they work correctly. NUnit provides custom attributes (like [TestFixture], [Test], and [SetUp]) to define and organize tests. The Assert class helps verify that the code behaves as expected by checking conditions, and if a test fails, it generates an error. NUnit is widely used and supports various .NET platforms.


Sample NUnit Test

using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
namespace WebDriver_CSharp_Example
{
    [TestFixture]
    public class Chrome_Sample_test
    {
        private IWebDriver driver;
        public string homeURL;
        [Test(Description = "Check Homepage for Login Link")]
        publicvoidLogin_is_on_home_page()
        {
            homeURL = "https://...";
            driver.Navigate().GoToUrl(homeURL);
            WebDriverWait wait = new WebDriverWait(driver, System.TimeSpan.FromSeconds(15));
            wait.Until(driver => driver.FindElement(By.XPath("//a[@href='/beta/login']")));
            IWebElement element = driver.FindElement(By.XPath("//a[@href='/beta/login']"));
            Assert.AreEqual("Sign In", element.GetAttribute("text"));
        }
        [TearDown]
        publicvoidTearDownTest()
        {
            driver.Close();
        }
        [SetUp]
        publicvoidSetupTest()
        {
            homeURL = "http://...";
            driver = new ChromeDriver();
        }
    }
}


What is xUnit ?

xUnit.net is a free, open-source unit testing tool for .NET, created by the developers behind NUnit. It's designed to be community-focused and works well with tools like Xamarin, ReSharper, and TestDriven.NET. The "x" in xUnit indicates that it’s part of a family of unit testing frameworks for different programming languages (like JUnit for Java and NUnit for .NET).


xUnit allows you to customize your tests with various attributes and extends the functionality of the Assert class with methods like Contains, Equal, and InRange. It supports two main types of tests:

  • [Fact]: A standard unit test that always runs the same way.
  • [Theory]: A test that runs multiple times with different input data.


By default, xUnit runs tests in parallel across different classes, speeding up the testing process without needing extra configuration.


Sample xUnit Test

public class MathUnitxTest
{
    [Fact]
    public void Task_TestAddition()
    {
        var numA = 12,
            numB = 4,
            result = 16;
        var calculatedSum = MathOperation.Add(numA, numB);
        Assert.Equal(expectedValue, calculatedSum, 1);
    }
    [Fact]
    public void Task_TestMultiplication()
    {
        var numA = 5,
            numB = 4,
            result = 20;
        var calculatedOutput = MathOperation.Multiply(num1, num2);
        Assert.Equal(result, calculatedOutput, 2);
    }
}


What Is MSTest ?

MSTest is Microsoft's unit testing framework, built into Visual Studio, allowing developers to write and run tests directly within the IDE. There's also an open-source version called MSTest V2, available on NuGet, making it accessible for more projects. MSTest provides a variety of attributes to organize and execute tests at both the method and class levels, without needing additional tools.


Sample MSTest Test

namespace Messages.Tests;
using Microsoft.VisualStudio.TestTools.UnitTesting;


[TestClass]
public class ArithmeticMsTest
{
    [DataTestMethod]
    [DataRow(1, 2, 3)]
    [DataRow(2, 2, 4)]
    [DataRow(-1, 4, 3)]
    public void Task_TestAddition(int x, int y, int expected)
    {
        int sum = Basic.add(x, y);
        Assert.AreEqual(sum, expected);
    }
    [DataTestMethod]
    [DataRow(1, 2, -1)]
    [DataRow(2, 2, 0)]
    [DataRow(3, 2, 1)]
    public void Task_TestSubtraction(int x, int y, int expected)
    {
        int res = Basic.sub(x, y);
        Assert.AreEqual(res, expected);
    }
}

Operator

Arithmetic Operators

Used for mathematical calculations.

+ : Addition
- : Subtraction
* : Multiplication
/ : Division
% : Modulus (remainder after division)
++ : Increment
-- : Decrement


Relational (Comparison) Operators

Used to compare two values.

== : Equal to
!= : Not equal to
> : Greater than
< : Less than
>= : Greater than or equal to
<= : Less than or equal to


Logical Operators

Used to perform logical operations.

&& : Logical AND
|| : Logical OR
! : Logical NOT


Bitwise Operators

Used for manipulating data at the bit level.

& : Bitwise AND
| : Bitwise OR
^ : Bitwise XOR (exclusive OR)
~ : Bitwise NOT (inversion)
<< : Left shift
>> : Right shift


Assignment Operators

Used to assign values to variables.

= : Assignment
+= : Add and assign
-= : Subtract and assign
*= " Multiply and assign
/= : Divide and assign
%= : Modulus and assign
&= : Bitwise AND and assign
|= : Bitwise OR and assign
^= : Bitwise XOR and assign
<<= : Left shift and assign
>>= : Right shift and assign


Null-Coalescing Operators

Handle null values in expressions.

?? : Null-coalescing operator (returns the left operand if it's not null, otherwise the right)
??= : Null-coalescing assignment (assigns the right operand only if the left operand is null)


Conditional (Ternary) Operator

Used to return one of two values depending on a condition.

?: : Ternary operator (condition ? first_expression : second_expression)


Type Operators

Used for working with types and type casting.

is : Checks if an object is compatible with a given type
as : Attempts to cast an object to a type, returns null if the cast fails
typeof : Returns the Type of a class or struct
sizeof : Returns the size in bytes of a value type
checked : Enables overflow checking for arithmetic operations
unchecked : Disables overflow checking for arithmetic operations


Index and Range Operators

[] : Access elements of an array or indexer
^ : Index from the end (C# 8.0+)
.. : Range operator (C# 8.0+)


Lambda Operator

Used in lambda expressions.

=> : Lambda operator


Member Access and Object Operators

Used to access members of objects and handle null reference types.

. : Member access operator (e.g., object.Property)
-> : Pointer-to-member access (unsafe context)
?. : Null-conditional operator (invokes a member only if the object isn't null)
[] : Index access


Pointer Operators (Unsafe Code)

Used in unsafe contexts for working with pointers.

* : Dereference pointer
& : Address-of operator (returns a pointer to the variable)
-> : Pointer member access
[] : Pointer index access


Other Special Operators

new : Creates objects and invokes constructors
sizeof : Returns the size in bytes of a value type
typeof : Returns the type of a class
checked : Enables overflow checking for arithmetic operations
unchecked : Disables overflow checking for arithmetic operations
await : Pauses an async method until a task is complete
yield : Returns values from an iterator method
default : Returns the default value for a type
nameof : Returns the name of a variable, type, or member as a string


Delegate and Event Operators

Used to work with delegates and events.

+= : Add a method to a delegate or event
-= : Remove a method from a delegate or event

Optimize Start-Up Performance in .NET Core

Optimize Garbage Collection (GC)

Tune the garbage collection settings for your application based on its memory usage and workload.


For server workloads, enable server GC for better performance with multi-threaded applications

<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>


GCSettings.LatencyMode: For latency-sensitive applications (e.g., web APIs), you can configure the latency mode to minimize the impact of garbage collection during startup.

GCSettings.LatencyMode = GCLatencyMode.LowLatency;


Optimize Razor Pages and Views (For ASP.NET Core)

If you’re building a web application using Razor Pages or Views, the compilation of Razor files can introduce startup delays.


Precompile Razor Views: Precompile your Razor views to avoid runtime compilation during startup.


Add this to your .csproj file:

<PropertyGroup>
    <RazorCompileOnPublish>true</RazorCompileOnPublish>
</PropertyGroup>


This ensures that views are compiled at build or publish time, reducing startup time.


Reduce the Size of the Application Binary

The larger your application, the more time it takes to load the assemblies.


Trim Unused Libraries: Use ILLinker (available in .NET Core) to trim unused code, removing unnecessary parts of libraries that aren’t used by your application. Add this to your .csproj:

<PropertyGroup>
    <PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>


This feature analyzes dependencies and removes unused parts, reducing the application size and improving startup time.


Single-File Publish: In .NET Core 3.0+ and .NET 5/6/7/8, you can publish your application as a single-file executable to avoid the overhead of loading multiple assemblies at startup.


Add this to your .csproj:

<PropertyGroup>
    <PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>


Use Ahead-of-Time (AOT) Compilation

.NET 7 and .NET 8 offer Native AOT (Ahead-of-Time Compilation), which compiles .NET applications directly into machine code, eliminating the need for JIT (Just-In-Time) compilation during startup.


Native AOT: For applications where startup performance is critical, you can compile your .NET code ahead of time into native code.


Add this to your .csproj:

<PropertyGroup>
    <PublishAot>true</PublishAot>
</PropertyGroup>


This feature greatly improves startup performance by reducing the time spent on JIT compilation.


Use HTTP/2 and Enable Response Compression

If your .NET Core application serves HTTP traffic, enabling HTTP/2 and response compression can improve the startup performance from the user’s perspective.


HTTP/2: HTTP/2 reduces the number of connections and allows multiplexing of requests. Enable it in Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(IPAddress.Any, 5000, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
    });
});


Response Compression: Use response compression to decrease the size of payloads sent to the client, reducing startup time over the network.

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression();
}


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseResponseCompression();
}


Use HTTP/3

HTTP/3 reduces latency and enhances startup performance for web applications with its improved connection model over HTTP/2. .NET 6 and later versions support HTTP/3.


Enable HTTP/3 by configuring Kestrel:

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(IPAddress.Any, 5001, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
    });
});


HTTP/3 offers faster connection establishment, which improves the perceived startup performance from the client’s perspective.


Use Ready-to-Run (R2R) Compilation

Ready-to-Run (R2R) is a feature that pre-compiles your .NET assemblies into native code at build time, reducing the time spent on JIT compilation.


Enable R2R in your .csproj file:

<PropertyGroup>
    <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>


This can significantly improve startup times, especially for large applications, by eliminating the need for JIT compilation at runtime.


Optimizing Performance using Efficient LINQ queries

Combining Where and FirstOrDefault()

Problem: Using Where and then FirstOrDefault results in redundant filtering.

//BAD code
var user = collection.Where(x => x.Id == id).FirstOrDefault();


// Efficient Code
var user = collection.FirstOrDefault(x => x.Id == id);


Efficient Contains Usage

Problem: Checking for existence in lists can be slow.

// Bad code
var result = collection.Where(x => list.Contains(x.Id)).ToList();

// Efficient code
var set = new HashSet<int>(list);
var result = collection.Where(x => set.Contains(x.Id)).ToList();


Efficient Distinct Usage

Problem: Using Distinct on large datasets can be slow.

var result = collection.Select(x => x.Name).Distinct().ToList();


//Efficient code
var result = collection.Select(x => x.Name).ToHashSet();


Using LongCount for Large Collections

Problem: Count on very large collections can overflow.

var count = collection.LongCount();

Perform a computationally intensive operation on a large collection of items in LINQ

AsParallel() in C# is a method in the Parallel LINQ (PLINQ) library, which is a parallel implementation of LINQ (Language Integrated Query). PLINQ enables you to perform queries in parallel, utilizing multiple processors or cores to speed up query execution.

using System;
using System.Linq;


class Program
{
    static void Main()
    {
        // An array of numbers
        int[] numbers = Enumerable.Range(1, 1000000).ToArray();


        // Using AsParallel to enable parallel processing
        var evenNumbers = numbers.AsParallel()
                                 .Where(n => n % 2 == 0)
                                 .ToArray();


        Console.WriteLine($"Found {evenNumbers.Length} even numbers.");
    }
}


If order is important, you can use the AsOrdered() method after AsParallel() to maintain the original order of the elements in the output. However, this might reduce the performance benefits.

var evenNumbers = numbers.AsParallel()
                         .AsOrdered()
                         .Where(n => n % 2 == 0)
                         .ToArray();


You can control the degree of parallelism using the WithDegreeOfParallelism method.

var evenNumbers = numbers.AsParallel()
                         .WithDegreeOfParallelism(4) // Limits to 4 concurrent threads
                         .Where(n => n % 2 == 0)
                         .ToArray();


When to Use AsParallel()

  • You have a large dataset.
  • The operations are CPU-bound (intensive computations).
  • Order of execution is not critical.
  • You have multiple cores or processors available.

Pivot Tables: Transforming Row Data to Columns

Create pivot tables to transform row-based data into column-based summaries:

var sales = new List<Sale>
{
    new Sale { Product = "Laptop", Quarter = "Q1", Amount = 1000 },
    new Sale { Product = "Laptop", Quarter = "Q2", Amount = 1200 },
    new Sale { Product = "Mouse", Quarter = "Q1", Amount = 200 },
    new Sale { Product = "Mouse", Quarter = "Q2", Amount = 300 }
};

// Create a pivot table
var pivot = sales
    .GroupBy(s => s.Product)
    .Select(g => new {
        Product = g.Key,
        Q1 = g.Where(s => s.Quarter == "Q1")
            .Sum(s => s.Amount),
        Q2 = g.Where(s => s.Quarter == "Q2")
            .Sum(s => s.Amount),
        Total = g.Sum(s => s.Amount)
    });

// Results:
// Product  Q1    Q2    Total
// Laptop   1000  1200  2200
// Mouse    200   300   500

Predicate Delegate

Predicate is the delegate like Func and Action delegates. It represents a method containing a set of criteria and checks whether the passed parameter meets those criteria. A predicate delegate methods must take one input parameter and return a Boolean - true or false.


The Predicate delegate is defined in the System namespace, as shown below:


Predicate signature:

public delegate bool Predicate<in T>(T obj);


Same as other delegate types, Predicate can also be used with any method, anonymous method, or lambda expression.


Example:

Predicate delegate

static bool IsUpperCase(string str)
{
  return str.Equals(str.ToUpper());
}

static void Main(string[] args)
{
  Predicate<string> isUpper = IsUpperCase;

  bool result = isUpper("hello world!!");

  Console.WriteLine(result);
}

Output: false


Predicate delegate with anonymous method

static void Main(string[] args)
{
  Predicate<string> isUpper = delegate(string s) { return s.Equals(s.ToUpper());};
  bool result = isUpper("hello world!!");
}

Output: false


Predicate delegate with lambda expression

static void Main(string[] args)
{
  Predicate<string> isUpper = s => s.Equals(s.ToUpper());
  bool result = isUpper("hello world!!");
}

Output: false


Prefer for each loop over for loops when iterating through collections

Do

int[] numbers = { 1, 2, 3, 4, 5 };
foreach (int number in numbers)
{
    Console.WriteLine(number);
}


Don't

int[] numbers = { 1, 2, 3, 4, 5 };
for (int i = 0; i < numbers.Length; i++)
{
    Console.WriteLine(numbers[i]);
}

Prepend

Prepend is used to add a single element to the beginning of a sequence.


If you want to prepend a product to the beginning of the list:

var products = new List<string> { "Product1", "Product2", "Product3" };
var newProduct = "Product0";


var updatedProducts = products.Prepend(newProduct);


foreach (var product in updatedProducts)
{
    Console.WriteLine(product);
}

Publish an ASP.NET Core app to IIS

  1. Install the .NET Core Hosting Bundle on Windows Server.
  2. Create an IIS site in IIS Manager.
  3. Deploy an ASP.NET Core app.

Rate Limiting in ASP.NET Core

Program.cs

var builder = WebApplication.CreateBuilder(args);


// Configure rate limiter
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("Fixed", opt =>
    {
        opt.PermitLimit = 10;           // Maximum 10 requests
        opt.Window = TimeSpan.FromMinutes(1); // In a 1-minute window
    });
});


Rate-Limiting Strategies


Fixed Window

Limits requests in fixed time intervals.

options.AddFixedWindowLimiter("Fixed", opt =>
{
    opt.PermitLimit = 5;
    opt.Window = TimeSpan.FromSeconds(30);
});


Sliding Window

Adjusts limits dynamically based on the sliding window.

options.AddSlidingWindowLimiter("Sliding", opt =>
{
    opt.PermitLimit = 10;
    opt.Window = TimeSpan.FromMinutes(1);
    opt.SegmentsPerWindow = 4;
});


Concurrency Limiting

Restricts the number of concurrent requests.

options.AddConcurrencyLimiter("Concurrent", opt =>
{
    opt.PermitLimit = 5; // Maximum concurrent requests
});


Token Bucket

Uses tokens to track available requests.

options.AddTokenBucketLimiter("TokenBucket", opt =>
{
    opt.TokenLimit = 10;
    opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
    opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
    opt.TokensPerPeriod = 1;
    opt.QueueLimit = 5;
});