CSharp - Other

Identifying slow database commands via logging in ASP.NET Core

we can log SQL generate by Linq and can check for performance

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;ConnectRetryCount=0")
        .LogTo(Console.WriteLine, LogLevel.Information);
}


When the logging level is set at LogLevel.Information, EF emits a log message for each command execution with the time taken:

info: 06/12/2020 09:12:36.117 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [b].[Id], [b].[Name]
      FROM [Blogs] AS [b]
      WHERE [b].[Name] = N'foo'


With logging we can see we are getting Execution time :Executed DbCommand (4ms)

IIS - "This operation requires IIS integrated pipeline mode" error when using AntiForgeryToken

Change the IIS Managed Pipeline Mode to Integrated.

Improving security in ASP.NET MVC using custom headers

X-Frame-Options

The X-Frame-Options header ensure, that hackers don't iframe your site, in order to trick you into clicking links which you never intended to. If you are using ASP.NET MVC 5 or newer, this header is added automatically. Adding the header in previous versions or other web frameworks is easy using web.config:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="X-Frame-Options" value="DENY" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

In this example, I deny any possibility of iframing the website. If you are using iframes on the same domain, you can change the value to SAMEORIGIN.


X-Xss-Protection

The X-Xss-Protection is a feature implemented in most modern browser, which will stop loading the page when a cross-site scripting attack is detected. Adding the header happens through web.config as well:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="X-Xss-Protection" value="1; mode=block" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

The value if 1 simply marks the protection as enabled.


X-Content-Type-Options

To avoid MIME type sniffing, you can add the X-Content-Type-Options header. This makes it harder for hackers to guess the right mime type, by inspecting the content. Adding the header is easily done through web.config:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="X-Content-Type-Options" value="nosniff" />
    </customHeaders>
  </httpProtocol>
</system.webServer>


Referrer-Policy

Browsers automatically add the Referrer header, when a user click a link on your site. This means that a linked website, will be able to see where the users are coming from. While this is a great feature for Analytics, you may have sensitive information in your URLs, which you don't want to forward to other domains. To remove the referrer entirely, add the following header to web.config:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Referrer-Policy" value="no-referrer" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

In real life, you may want another value for Referrer-Policy. Removing the referrer entirely, makes it impossible to see the internal traffic flow on your website.


X-Permitted-Cross-Domain-Policies

To restrict Flash components to make cross-origin requests, you should disable it entirely (unless you are using Flash of course). To do so, add the X-Permitted-Cross-Domain-Policies to web.config:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="X-Permitted-Cross-Domain-Policies" value="none" />
    </customHeaders>
  </httpProtocol>
</system.webServer>


Strict-Transport-Security

If you haven't implemented HTTPS on your website, you should. After doing so, you can prevent any communication happening over HTTP using the Strict-Transport-Security header:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

The max-age value tells browsers to use this setting for the specified number of seconds. In this case a year. The includeSubDomains part can be excluded, if you are hosting non-HTTPS websites on subdomains (like a blog).


X-Powered-By

The X-Powered-By header is automatically added by ASP.NET. To make it less obvious which technology you are using to host your website, you should remove this header through web.config:

<system.webServer>
  <httpProtocol>
    <customHeaders>
        <remove name="X-Powered-By" />
    </customHeaders>
  </httpProtocol>
</system.webServer>


X-AspNetMvc-Version

Much like X-Powered-By, X-AspNetMvc-Version is a header automatically added by the framework. To avoid telling hackers that you use MVC and which version, you should remove it. X-AspNetMvc-Version cannot be removed through web.config, but you can disable it from code. Add the following to your Startup.cs or Global.asax.cs:

MvcHandler.DisableMvcResponseHeader = true;


Server

ASP.NET also reveals the server hosting the application. If a hacker know that you are using IIS, this narrows the number of weaknesses that he/she needs to try. To remove the Server header, remove it from code in either a filter or through Global.asax.cs:

protected void Application_PreSendRequestHeaders()
{
    if (HttpContext.Current != null)
    {
        HttpContext.Current.Response.Headers.Remove("Server");
    }
}


Permissions-Policy

The Permissions-Policy header (formerly known as Feature-Policy), is a recent addition to the range of security-related headers. When specifying the header, you tell the browser which features your site uses or not. This is a great feature, especially if you embed other websites. To add the header, make the following change in web.config:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Permissions-Policy" value="accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

Here, we tell the browser that our site shouldn't allow use of the accelerometer, camera, and more. Which features you need to add, totally depend on your site.


Content-Security-Policy

The Content-Security-Policy header, is a HTTP response header much like the ones from the previous post. The header helps to prevent code injection attacks like cross-site scripting and clickjacking, by telling the browser which dynamic resources that are allowed to load.

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Content-Security-Policy" value="default-src 'self'" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

The value of the Content-Security-Policy header is made up of x segments separated by a semicolon. In the example above, we only specify a single segment, saying only load resources from self. self translates to the same origin as the HTML resource. With this minimum configuration, your HTML are allowed to fetch JavaScript, stylesheets etc. from the same domain that served the HTML referencing the resources. You won't be able to include external scripts from CDNs and similar.


Content-Security-Policy-Report-Only

<add name="Content-Security-Policy-Report-Only" value="default-src 'self'" />

By adding this header instead of Content-Security-Policy, the browser will keep telling when something isn't allowed, but allow it anyway. This way you can keep an eye on the console, when running your website in production. When all error messages in the console are gone, you switch back to the original header.

Join tables using LINQ

Table1.Join(Table2, x => x.Table1Column, y => y.Table2Column, (x, y) => x).ToList();


Large Data CRUD in .NET: Batching Techniques to Avoid Out of Memory

BatchHelper

public static class BatchHelper
{
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
    {
        using var e = source.GetEnumerator();
        while (e.MoveNext())
            yield return GetBatch(e, size);
    }


    private static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, int size)
    {
        do yield return e.Current;
        while (--size > 0 && e.MoveNext());
    }
}


Batched Create (Insert)

public async Task BulkInsertAsync(IEnumerable<MyModel> data, MyDbContext dbContext)
{
    foreach (var batch in data.Batch(1000))
    {
        dbContext.MyModels.AddRange(batch);
        await dbContext.SaveChangesAsync();
    }
}


Batched Update

public async Task BulkUpdateAsync(IEnumerable<MyModel> updatedData, MyDbContext dbContext)
{
    foreach (var batch in updatedData.Batch(1000))
    {
        dbContext.MyModels.UpdateRange(batch);
        await dbContext.SaveChangesAsync();
    }
}


⚠️ If you're using detached objects or want to attach only modified values, you'll need to Attach() them and set their state to Modified.

// Alternative: attach manually
foreach (var item in batch)
{
    dbContext.MyModels.Attach(item);
    dbContext.Entry(item).State = EntityState.Modified;
}


Batched Delete

public async Task BulkDeleteAsync(IEnumerable<int> idsToDelete, MyDbContext dbContext)
{
    foreach (var batch in idsToDelete.Batch(1000))
    {
        var records = await dbContext.MyModels
            .Where(m => batch.Contains(m.Id))
            .ToListAsync();


        dbContext.MyModels.RemoveRange(records);
        await dbContext.SaveChangesAsync();
    }
}

Lazy Loading in EF Queries

public class Order
{
    public int OrderId { get; set; }
    public virtual Customer Customer { get; set; }
}

// Enable lazy loading in DbContext
public class MyDbContext : DbContext
{
    public DbSet<Order> Orders { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseLazyLoadingProxies();
    }
}

List to IList

List items = new List();
items.Add(item);...

ILIST Items = items;

Memory-Efficient LINQ Extensions

public static class OptimizedLinqExtensions
{
    public static bool AnyFast<T>(this ICollection<T> collection)
    {
        return collection != null && collection.Count > 0;
    }
    
    public static bool AnyFast<T>(this ICollection<T> collection, Func<T, bool> predicate)
    {
        if (collection == null || predicate == null) return false;
        
        foreach (var item in collection)
        {
            if (predicate(item)) return true;
        }
        return false;
    }
    
    public static T FirstOrDefault<T>(this IEnumerable<T> collection, T defaultValue)
    {
        if (collection == null) return defaultValue;
        
        foreach (var item in collection)
        {
            return item;
        }
        return defaultValue;
    }
    
    public static IEnumerable<T> TakeFast<T>(this IEnumerable<T> collection, int count)
    {
        if (collection == null || count <= 0) yield break;
        
        var taken = 0;
        foreach (var item in collection)
        {
            if (taken >= count) yield break;
            yield return item;
            taken++;
        }
    }
}

MiniProfiler Integration with Entity Framework Core

Step 1: Install NuGet packages

dotnet add package MiniProfiler.AspNetCore.Mvc
dotnet add package MiniProfiler.EntityFrameworkCore


Step 2: Configure services in Startup.cs (or Program.cs in .NET 6+)

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();


    // Add MiniProfiler with EF Core
    services.AddMiniProfiler(options =>
    {
        options.RouteBasePath = "/profiler"; // results available at /profiler
    }).AddEntityFramework();
}


Step 3: Add MiniProfiler middleware

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }


    app.UseHttpsRedirection();
    app.UseStaticFiles();


    app.UseRouting();


    app.UseAuthorization();


    // Enable MiniProfiler
    app.UseMiniProfiler();


    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}


Step 4: Add MiniProfiler UI to MVC Layout (Views/Shared/_Layout.cshtml) before the closing </body> tag

@await this.RenderProfilerAsync()

Multiple types were found that match the controller named 'Home'

This error message often happens when you use areas and you have the same controller name inside the area and the root. For example you have the two :


~/Controllers/HomeController.cs
~/Areas/Admin/Controllers/HomeController.cs


In order to resolve this issue, you could use namespaces when declaring your routes.


RouteConfig.cs

routes.MapRoute(
  "Default",
  "{controller}/{action}/{id}",
  new { controller = "Home", action = "Index", id = UrlParameter.Optional },
  new[] { "AppName.Controllers" }
);


~/Areas/Admin/AdminAreaRegistration.cs

context.MapRoute(
  "Admin_default",
  "Admin/{controller}/{action}/{id}",
  new { action = "Index", id = UrlParameter.Optional },
  new[] { "AppName.Areas.Admin.Controllers" }
);


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);
    }
}