Install the .NET SDK on Fedora

In the ever-evolving world of web development, security is paramount. Here we provide a step-by-step guide to not only installing the .NET Software Development Kit (SDK) on Fedora, but also generating an SSL Certificate for your .NET apps, thus ensuring secure connections between client and server.

Part 1: Installing the .NET SDK on Fedora

Our first step is to install the .NET SDK. The .NET SDK is a set of libraries and tools that allow developers to create .NET apps and libraries. It is the foundation for building applications and libraries with .NET Core.

Here’s how you can install the .NET SDK on your Fedora system:

  1. Open a terminal window.
  2. Input the following command:
    sudo dnf install dotnet-sdk-7.0
  3. Press Enter. You might be asked for your password; if so, provide it and press Enter again.
  4. Let the installation process finish.

After the completion of the above steps, the .NET SDK should be installed successfully on your Fedora system.

Part 2: Creating an SSL Certificate for Your .NET Apps

Creating an SSL certificate for your .NET applications can enhance the security of your applications. Here’s how you can generate an SSL certificate:

  1. First, we need to install Easy-RSA, a CLI utility to build and manage a PKI CA. Run this command:
    sudo dnf install easy-rsa
  2. Now, navigate to the home directory and create a new directory .easyrsa with permissions set to 700:
    cd ~
    mkdir .easyrsa
    chmod 700 .easyrsa
  3. Copy the Easy-RSA scripts to our newly created directory:
    cd .easyrsa
    cp -r /usr/share/easy-rsa/3/* ./
  4. Initialize the Public Key Infrastructure:
    ./easyrsa init-pki
  5. We need to set some variables for our certificate. Create a new file called vars and add the following details in it (You can modify these details according to your requirement):
    cat << EOF > vars
    set_var EASYRSA_REQ_COUNTRY "US"
    set_var EASYRSA_REQ_PROVINCE "Texas"
    set_var EASYRSA_REQ_CITY "Houston"
    set_var EASYRSA_REQ_ORG "Development"
    set_var EASYRSA_REQ_EMAIL "[email protected]"
    set_var EASYRSA_REQ_OU "LocalDevelopment"
    set_var EASYRSA_ALGO "ec"
    set_var EASYRSA_DIGEST "sha512"
    EOF
  6. Now build the CA with nopass option to not secure the CA key with a passphrase:
    ./easyrsa build-ca nopass
    
  7. Copy the generated certificate to the trusted CA directory and update the CA trust on your system:
    sudo cp ./pki/ca.crt /etc/pki/ca-trust/source/anchors/easyrsaca.crt
    sudo update-ca-trust
  8. Generate a new key and a certificate signing request for localhost:
    mkdir req
    cd req
    openssl genrsa -out localhost.key
    openssl req -new -key localhost.key -out localhost.req -subj /C=US/ST=Texas/L=Houston/O=Development/OU=LocalDevelopment/CN=localhost
    cd ..
  9. Import the certificate signing request and sign it:
    ./easyrsa import-req ./req/localhost.req localhost
    ./easyrsa sign-req server localhost
  10. Now, move the server certificate and key to a new directory .certs and convert the certificate to PKCS#12 format:
    cd ~
    mkdir .certs
    cp .easyrsa/pki/issued/localhost.crt .certs/localhost.crt
    cp .easyrsa/req/localhost.key .certs/localhost.key
    cd .certs
    openssl pkcs12 -export -out localhost.pfx -inkey localhost.key -in localhost.crt
  11. Lastly, add the path and the password for the certificate in the .bashrc file so the .NET Core Kestrel server can find it (replace YOUR_USERNAME with your actual username and PASSWORD with the password you want to use for your certificate):
    cat << EOF >> ~/.bashrc
    # .NET
    export ASPNETCORE_Kestrel__Certificates__Default__Password="PASSWORD"
    export ASPNETCORE_Kestrel__Certificates__Default__Path="/home/YOUR_USERNAME/.certs/localhost.pfx"
    EOF

And that’s it! You’ve now installed the .NET SDK and generated an SSL certificate for your .NET apps. Your applications are not only more secure but also more professional, creating trust with users who value their data privacy and security.

ASP.NET Core Blazor file uploads

ASP.NET Core’s Blazor provides a modern, component-based architecture for developing interactive web applications. One of its powerful features is handling file uploads with minimal fuss. This blog post will walk you through the process of setting up file uploads in your Blazor application, ensuring your journey in creating a feature-rich, user-friendly app remains smooth.

Introduction to File Uploads in Blazor

Blazor makes it a breeze to accept file uploads, providing an intuitive approach to handle user interaction. At the heart of this feature is the <InputFile> component, an integral part of Blazor’s component library.

The syntax for utilizing this component is straightforward. You can use the OnChange attribute to specify an event callback, which will be triggered when the user selects a file. To allow multiple file selections, simply add the multiple attribute as shown below:

<InputFile OnChange="@LoadFiles" multiple />

Next, let’s dive into the LoadFiles method, which is invoked when the user selects a file (or files) to upload.

@code {
    private void LoadFiles(InputFileChangeEventArgs e)
    {
        ...
    }
}

InputFileChangeEventArgs gives you access to the selected files. You can then manipulate the file data according to your needs.

Handling File Streams

One common requirement during file uploads is to write the uploaded file data to disk. The Blazor framework provides a convenient method, OpenReadStream(), that returns a Stream. This stream can then be copied to a FileStream to write the data to disk.

Here is a sample code snippet for this:

await using FileStream fs = new(path, FileMode.Create);
await browserFile.OpenReadStream().CopyToAsync(fs);

In this snippet, path is the destination path where the file will be stored, and browserFile is an instance of IBrowserFile representing the uploaded file.

Integration with Azure Blob Storage

If you are using Azure Blob Storage for storing files, Blazor’s integration with Azure makes it easy to upload the file directly. Here’s how you can do this:

await blobContainerClient.UploadBlobAsync(
    trustedFileName, browserFile.OpenReadStream());

blobContainerClient is an instance of BlobContainerClient representing the Azure Blob Storage container, and trustedFileName is the name under which the file will be stored.

Limiting File Sizes

Sometimes, you may want to limit the size of the files that the users can upload. Blazor provides a neat way to do this. You can use the maxAllowedSize parameter in the OpenReadStream method:

// accept a file upto 307200 bytes (300kb) of size
await myFile.OpenReadStream(maxAllowedSize: 1024 * 300).ReadAsync(buffers);

This code will allow only files of size up to 300 KB to be uploaded. If a user tries to upload a larger file, an exception will be thrown.

Wrapping Up

The ability to handle file uploads effectively and efficiently is a key aspect of many web applications. ASP.NET Core Blazor simplifies this process with its robust, developer-friendly components and methods, making file uploads not only functional but also a cinch to implement. Whether you are writing files to disk or using Azure Blob Storage for handling your uploads, Blazor has got you covered. Happy coding!

Clear the NuGet Package Cache using the Command Line

The command dotnet nuget locals all --clear clears all local NuGet resources in the http-request cache, temporary cache, and machine-wide global packages folder.

The dotnet nuget locals command has two options: -c or --clear and -l or --list. The -c option clears the cache, and the -l option lists the cache.

The all argument in the command tells the dotnet nuget locals command to clear all three types of cache: http-request cache, temporary cache, and machine-wide global packages folder.

To clear the NuGet cache using the command line, you can run the following command:

dotnet nuget locals all --clear

This command will clear all local NuGet resources, including the http-request cache, temporary cache, and machine-wide global packages folder.

Note: If you are using Visual Studio, you can also clear the NuGet cache from the Tools menu. In the NuGet Package Manager menu, select Package Manager Settings. In the Options dialog, click the Clear All NuGet Cache(s) button.

Check Authorization Rules Programatically in ASP.NET Blazor

_Imports.razor

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization

Pages/ProceduralLogic.razor:

@page "/procedural-logic"
@inject IAuthorizationService AuthorizationService

<h1>Procedural Logic Example</h1>

<button @onclick="@DoSomething">Do something important</button>

@code {
    [CascadingParameter]
    private Task<AuthenticationState>? authenticationState { get; set; }

    private async Task DoSomething()
    {
        if (authenticationState is not null)
        {
            var authState = await authenticationState;
            var user = authState?.User;

            if (user is not null)
            {
                if (user.Identity is not null && user.Identity.IsAuthenticated)
                {
                    // ...
                }

                if (user.IsInRole("Admin"))
                {
                    // ...
                }

                if ((await AuthorizationService.AuthorizeAsync(user, "content-editor"))
                    .Succeeded)
                {
                    // ...
                }
            }
        }
    }
}

References
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0#procedural-logic

Create Admin Account on ASP.NET Blazor Startup

public class StartupWorker: BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private static readonly string[] Roles = { "Admin", "Manager", "Member" };
    
    public StartupWorker(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await CreateRoles();
        await CreateAdmin();
    }

    private async Task CreateRoles()
    {
        using var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
        var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
        foreach (var role in Roles)
        {
            if (!await roleManager.RoleExistsAsync(role))
            {
                await roleManager.CreateAsync(new IdentityRole(role));
            }
        }
    }

    private async Task CreateAdmin()
    {
        using var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
        var userManager = serviceScope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
        var user = await userManager.FindByNameAsync("admin");

        if (user == null)
        {
            var identity = new ApplicationUser("admin") { FirstName = "Admin", LastName = "" };
            var password = "12345";
            await userManager.CreateAsync(identity, password);
            await userManager.AddToRoleAsync(identity, "Admin");
        }
    }
}

References
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data?view=aspnetcore-7.0

Add Role services to Identity in ASP.NET Blazor

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(
    options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

References
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data?view=aspnetcore-7.0
https://stackoverflow.com/questions/52522248/store-does-not-implement-iuserrolestoretuser-asp-net-core-identity

Configure Send Endpoint in MassTransit

public record SubmitOrder
{
    public string OrderId { get; init; }
}

public async Task SendOrder(ISendEndpointProvider sendEndpointProvider)
{
    var endpoint = await sendEndpointProvider.GetSendEndpoint(_serviceAddress);

    await endpoint.Send(new SubmitOrder { OrderId = "123" });
}

Endpoint Address

rabbitmq://localhost/input-queue
rabbitmq://localhost/input-queue?durable=false

Short Addresses

GetSendEndpoint(new Uri("queue:input-queue"))

Address Conventions

Using send endpoints might seem too verbose, because before sending any message, you need to get the send endpoint and to do that you need to have an endpoint address. Usually, addresses are kept in the configuration and accessing the configuration from all over the application is not a good practice.

Endpoint conventions solve this issue by allowing you to configure the mapping between message types and endpoint addresses. A potential downside here that you will not be able to send messages of the same type to different endpoints by using conventions. If you need to do this, keep using the GetSendEndpoint method.

EndpointConvention.Map<SubmitOrder>(new Uri("rabbitmq://mq.acme.com/order/order_processing"));
public async Task Post(SubmitOrderRequest request)
{
    if (AllGoodWith(request))
        await _bus.Send(ConvertToCommand(request));
}

Also, from inside the consumer, you can do the same using the ConsumeContext.Send overload:

EndpointConvention.Map<StartDelivery>(new Uri(ConfigurationManager.AppSettings["deliveryServiceQueue"]));

The EndpointConvention.Map<T> method is static, so it can be called from everywhere. It is important to remember that you cannot configure conventions for the same message twice. If you try to do this – the Map method will throw an exception. This is also important when writing tests, so you need to configure the conventions at the same time as you configure your test bus (harness).

It is better to configure send conventions before you start the bus.

References
https://masstransit.io/documentation/concepts/producers#send-endpoint
https://stackoverflow.com/questions/62713786/masstransit-endpointconvention-azure-service-bus/

Configure Receive Endpoints in MassTransit

Explicitly Configure Endpoints

services.AddMassTransit(x =>
{
    x.AddConsumer<SubmitOrderConsumer>();
    
    x.UsingRabbitMq((context, cfg) =>
    {
        cfg.ReceiveEndpoint("order-service", e =>
        {
            e.ConfigureConsumer<SubmitOrderConsumer>(context);
        });
    });
});

Temporary Endpoints

Some consumers only need to receive messages while connected, and any messages published while disconnected should be discarded. This can be achieved by using a TemporaryEndpointDefinition to configure the receive endpoint.

services.AddMassTransit(x =>
{
    x.AddConsumer<SubmitOrderConsumer>();

    x.UsingInMemory((context, cfg) =>
    {
        cfg.ReceiveEndpoint(new TemporaryEndpointDefinition(), e =>
        {
            e.ConfigureConsumer<SubmitOrderConsumer>(context);
        });

        cfg.ConfigureEndpoints(context);
    });
});

Endpoint Configuration

services.AddMassTransit(x =>
{
    x.AddConsumer<SubmitOrderConsumer>(typeof(SubmitOrderConsumerDefinition))
        .Endpoint(e =>
        {
            // override the default endpoint name
            e.Name = "order-service-extreme";

            // specify the endpoint as temporary (may be non-durable, auto-delete, etc.)
            e.Temporary = false;

            // specify an optional concurrent message limit for the consumer
            e.ConcurrentMessageLimit = 8;

            // only use if needed, a sensible default is provided, and a reasonable
            // value is automatically calculated based upon ConcurrentMessageLimit if
            // the transport supports it.
            e.PrefetchCount = 16;

            // set if each service instance should have its own endpoint for the consumer
            // so that messages fan out to each instance.
            e.InstanceId = "something-unique";
        });

    x.UsingRabbitMq((context, cfg) => cfg.ConfigureEndpoints(context));
});

References
https://masstransit.io/documentation/configuration#receive-endpoints