CSharp - Other

ValidateAntiForgeryToken in ASP.NET Core

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Enable anti-forgery middleware
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.Use(async (context, next) =>
{
    // Generates an anti-forgery token
    var antiForgery = context.RequestServices.GetRequiredService();
    var tokens = antiForgery.GetTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions { HttpOnly = false });
    
    await next();
});

// Configure endpoints
app.MapControllers();
app.Run();


Controller

[HttpPost]
[ValidateAntiForgeryToken] // Validates the anti-forgery token
public IActionResult SubmitForm(string someInput)
{
    // Process the form data
    return RedirectToAction("Index");
}


Razor View

@Html.AntiForgeryToken() 


AJAX Requests

$.ajax({
    type: "POST",
    url: "/MyController/SubmitForm",
    data: {
        someInput: "value"
    },
    headers: {
        "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() // Include the token
    },
    success: function (response) {
        // Handle success
    }
});

Validating Connection Strings on .NET Startup

Initial Host Configuration

using System.Data.Common;
using Microsoft.Data.SqlClient;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using WorkerServiceDatabase;


IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services 
            // validate connection strings
            .ValidateConnectionStrings()
            .ValidateOnStart();
        
        services.AddHostedService<Worker>();
    })
    .Build();


host.Run();


public static class ConnectionStringExtensions
{
    public static OptionsBuilder<ConnectionStrings> 
        ValidateConnectionStrings(this IServiceCollection services)
    {
        return services
            .AddOptions<ConnectionStrings>()
            .BindConfiguration("ConnectionStrings")
            .Validate(c => c.Validate(), "Could not connect to 1 or more databases.");
    }
}


public class ConnectionStrings
    : Dictionary<string,string>
{
    public ConnectionStrings()
    {
        // these are the key names
        DbProviderFactories.RegisterFactory("Sqlite", SqliteFactory.Instance);
        DbProviderFactories.RegisterFactory("SqlServer", SqlClientFactory.Instance);
    }
    
    public bool Validate()
    {
        // can't inject logger :(
        var logger = LoggerFactory
            .Create(cfg => cfg.AddConsole().AddDebug())
            .CreateLogger("ConnectionStrings");
        
        List<Exception> errors = new();
        foreach (var (key, connectionString) in this)
        {
            try
            {
                var factory = DbProviderFactories.GetFactory(key);
                using var connection = factory.CreateConnection();
                if (connection is null) {
                    throw new Exception($"\"{key}\" did not have a valid database provider registered");
                }


                connection.ConnectionString = connectionString;
                connection.Open();
            }
            catch (Exception e)
            {
                var message = $"Could not connect to \"{key}\".";
                logger.LogError(message);
                errors.Add(new Exception(message, e));
            }
        }


        return errors.IsNullOrEmpty();
    }
}


ConnectionString Configuration

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ConnectionStrings": {
      "Sqlite": "Data Source=database.db",
      "SqlServer": "Data Source=localhost,11433;Initial Catalog=DBName;User Id=sa;Password=password;Encrypt=false"
    }
}

Validation failed for one or more entities. See 'EntityValidationErrors' property for more details [duplicate]

Just add ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors in a Quick Watch window and click Reevaluate.

ValueTask - Reduces Memory Allocations

ValueTask<T> avoids unnecessary heap allocations compared to Task<T>.


Before (More Allocations)

Returning Task<T> always causes heap allocation, even when returning a cached result.

public async Task<int> GetValueAsync()
{
    return await Task.FromResult(42); // Creates unnecessary Task object
}


After (Reduced Allocations with ValueTask<T>)

Using ValueTask<T>, allocations are avoided for completed tasks.

public ValueTask<int> GetValueAsync()
{
    return new ValueTask<int>(42); // No heap allocation
}


What .NET collection provides the fastest search

Working With Zip Files

Read Zip Files


List All Files Contained in the Zip File

using var zipFile = ZipFile.OpenRead("multi-folder.zip");

var counter = 0;
foreach (var entry in zipFile.Entries)
    Console.WriteLine($"{++counter,3}: {entry.Name}");


Extract Files From Zip File With ExtractToDirectory() Method

ZipFile.ExtractToDirectory("multi-folder.zip", "extracted-files", true);


Extract Files From Zip File With ExtractToFile() Method

using var zipFile = ZipFile.OpenRead("multi-folder.zip");
var rootFolder = "extracted-files";
foreach (var entry in zipFile.Entries)
{
    var wholePath = Path.Combine(
        rootFolder,
        Path.GetDirectoryName(entry.FullName) ?? string.Empty);

    if (!Directory.Exists(wholePath))
        Directory.CreateDirectory(wholePath);

    if (!string.IsNullOrEmpty(entry.Name))
    {
        var wholeFileName = Path.Combine(
            wholePath,
            Path.GetFileName(entry.FullName));

        entry.ExtractToFile(wholeFileName, true);
    }
}


Extract Files With Open() Method

using var zipFile = ZipFile.OpenRead("multi-folder.zip");
foreach (var entry in zipFile.Entries)
{
    if (!string.IsNullOrEmpty(entry.Name))
    {
        using (var stream = entry.Open())
        using (var memoryStream = new MemoryStream())
        {
            stream.CopyTo(memoryStream);
            var bytes = memoryStream.ToArray();
            var base64 = Convert.ToBase64String(bytes);
            Console.WriteLine($"{entry.FullName} => {base64}");
        }
    }
}


Create Zip Files


Compress (Zipping) Whole Folder

ZipFile.CreateFromDirectory(@"c:\temp", "temp.zip");


Add Files to a Zip File

var folder = new DirectoryInfo(".");
FileInfo[] files = folder.GetFiles("*.txt", SearchOption.AllDirectories);

using var archive = ZipFile.Open(@"..\parent.zip", ZipArchiveMode.Create);

foreach (var file in files)
{
    archive.CreateEntryFromFile(
        file.FullName,
        Path.GetRelativePath(folder.FullName, file.FullName)
    );
}


Create a Zip File With Stream

var helloText = "Hello world!";

using var archive = ZipFile.Open(@"..\test.zip", ZipArchiveMode.Create);

var entry = archive.CreateEntry("hello.txt");

using (Stream st = entry.Open())
using (StreamWriter writerManifest = new StreamWriter(st))
writerManifest.WriteLine(helloText);


Compression Level

var entry = archive.CreateEntry("hello.txt", CompressionLevel.SmallestSize);


Delete Entries From Zip Files


using var zipFile = ZipFile.Open("multi-folder.zip", ZipArchiveMode.Update);

var images = zipFile.Entries.Where(e => e.Name == "image.png").ToList();

for (int i = images.Count - 1; i >= 0; --i)
    images[i].Delete();