<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Fiyaz Hasan]]></title><description><![CDATA[Microsoft MVP | Senior .NET & Cloud Architect (Azure/AWS/Cloudflare) | Kick-ass Gamer]]></description><link>https://fiyazhasan.work/</link><image><url>https://fiyazhasan.work/favicon.png</url><title>Fiyaz Hasan</title><link>https://fiyazhasan.work/</link></image><generator>Ghost 4.36</generator><lastBuildDate>Fri, 10 Apr 2026 13:06:24 GMT</lastBuildDate><atom:link href="https://fiyazhasan.work/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Building AI-Powered Applications with .NET Aspire, Ollama, and ModelContextProtocol]]></title><description><![CDATA[Combining .NET Aspire with Ollama and ModelContextProtocol allows LLMs to perform math operations through typed .NET methods. This architecture separates concerns: Mistral handles reasoning while specialized code handles calculations.]]></description><link>https://fiyazhasan.work/building-ai-powered-applications-with-net-aspire-ollama-and-modelcontextprotocol/</link><guid isPermaLink="false">67f1e9a81c8ced19b441d027</guid><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Sun, 06 Apr 2025 02:48:16 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2025/04/blog-feature-image.png" medium="image"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2><img src="https://fiyazhasan.work/content/images/2025/04/blog-feature-image.png" alt="Building AI-Powered Applications with .NET Aspire, Ollama, and ModelContextProtocol"><p>The intersection of cloud-native architecture and AI capabilities presents exciting opportunities for .NET developers. In this post, I&apos;ll walk through a practical implementation that combines .NET Aspire&apos;s orchestration capabilities with Ollama&apos;s local LLM hosting and the ModelContextProtocol (MCP) for tool usage.</p><p>This architecture allows AI models to leverage strongly-typed .NET methods through a clean, extensible interface - enabling us to build applications where LLMs can reason about problems while delegating specific operations to specialized tools.</p><h2 id="the-components">The Components</h2><p>Our solution consists of three main parts:</p><ol><li><strong>AppHost</strong>: The .NET Aspire orchestrator that coordinates our distributed application</li><li><strong>McpServer</strong>: A server exposing mathematical operations as tools via ModelContextProtocol</li><li><strong>ApiService</strong>: A web API that connects client requests to the LLM and enables function calling</li></ol><p>Let&apos;s explore how these components work together.</p><h2 id="setting-up-the-aspire-host">Setting Up the Aspire Host</h2><p>First, our AppHost project coordinates the various services:</p><pre><code class="language-csharp">// AppHost
var builder = DistributedApplication.CreateBuilder(args);

var ollama = builder.AddOllama(&quot;ollama&quot;)
    .WithDataVolume()
    .WithGPUSupport();

var mistral = ollama.AddModel(&quot;mistral&quot;, &quot;mistral:7b&quot;);

builder.AddProject&lt;Projects.McpServer&gt;(&quot;mcpserver&quot;);

builder.AddProject&lt;Projects.ApiService&gt;(&quot;apiservice&quot;)
    .WithReference(mistral)
    .WaitFor(mistral);

builder.Build().Run();
</code></pre><p>This configuration:</p><ul><li>Sets up an Ollama instance with GPU support and persistent storage</li><li>Loads the Mistral 7B model</li><li>Adds our McpServer and ApiService projects</li><li>Establishes dependencies between services</li></ul><h2 id="creating-the-mcp-server-for-tool-definitions">Creating the MCP Server for Tool Definitions</h2><p>The McpServer project defines our mathematical tools:</p><pre><code class="language-csharp">// McpServer
using ModelContextProtocol.Server;
using System.ComponentModel;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddMcpServer()
    .WithTools&lt;MathTools&gt;();

var app = builder.Build();

app.MapMcp();

app.Run();

[McpServerToolType]
public sealed class MathTools
{
    [McpServerTool, Description(&quot;Calculate the square root of a number.&quot;)]
    public static string SquareRoot(double number)
    {
        return number &lt; 0 
            ? &quot;Error: Cannot compute square root of a negative number.&quot; 
            : Math.Sqrt(number).ToString(&quot;F2&quot;);
    }

    [McpServerTool, Description(&quot;Calculate the factorial of a non-negative integer.&quot;)]
    public static string Factorial(int n)
    {
        if (n &lt; 0)
        {
            return &quot;Error: Factorial is not defined for negative numbers.&quot;;
        }
        if (n &gt; 20)
        {
            return &quot;Error: Input too large; maximum supported value is 20 to avoid overflow.&quot;;
        }

        long result = 1;
        for (int i = 2; i &lt;= n; i++)
        {
            result *= i;
        }
        return result.ToString();
    }

    [McpServerTool, Description(&quot;Raise a number to a power.&quot;)]
    public static string Power(double baseNum, double exponent)
    {
        double result = Math.Pow(baseNum, exponent);
        return double.IsNaN(result) || double.IsInfinity(result) 
            ? &quot;Error: Result is undefined or too large.&quot; 
            : result.ToString(&quot;F2&quot;);
    }
}
</code></pre><p>This server exposes three mathematical operations as tools, each with proper input validation and error handling.</p><h2 id="connecting-everything-with-the-api-service">Connecting Everything with the API Service</h2><p>The ApiService acts as the glue between user requests, the LLM, and our tools:</p><pre><code class="language-csharp">// ApiService
builder
    .AddKeyedOllamaApiClient(ServiceKeys.Mistral)
    .AddKeyedChatClient()
    .UseFunctionInvocation()

app.MapPost(&quot;/ollama&quot;, async ([FromKeyedServices(ServiceKeys.Mistral)] IChatClient client, string prompt) =&gt;
{
    var serverConfig = new McpServerConfiguration
    {
        Name = &quot;Ollama Math MCP Server&quot;,
        Command = &quot;http://localhost:5062/sse&quot;,
        TransportType = McpServerTransportType.Sse
    };

    var mcpTools = await Tools.GetFromMcpServers(serverConfig);

    var tools = mcpTools.Cast&lt;McpClientTool&gt;()
        .Select(t =&gt; new McpFunctionAdapter(t))
        .ToList();

    var response = await client.GetResponseAsync(prompt, new ChatOptions
    {
        Tools = [.. tools]
    });

    return response.Text;
});

public class ServiceKeys
{
    public const string Mistral = &quot;mistral&quot;;
}
</code></pre><h2 id="the-crucial-adapter">The Crucial Adapter</h2><p>The <code>McpFunctionAdapter</code> is key to making this work - it bridges the gap between MCP tools and AI functions:</p><pre><code class="language-csharp">public class McpFunctionAdapter(McpClientTool mcpTool) : AIFunction
{
    private readonly McpClientTool _mcpTool = mcpTool ?? throw new ArgumentNullException(nameof(mcpTool));

    private static readonly JsonSerializerOptions DefaultJsonSerializerOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        Converters = { new JsonStringEnumConverter() }
    };

    public override string Name =&gt; _mcpTool.Function?.Name ?? &quot;Unknown Function&quot;;

    public override string Description =&gt; _mcpTool.Function?.Description ?? &quot;No description available&quot;;

    public override IReadOnlyDictionary&lt;string, object?&gt; AdditionalProperties =&gt;
        _mcpTool.Function?.Parameters?.Properties?.ToDictionary(
            kvp =&gt; kvp.Key, kvp =&gt; new { kvp.Value.Type, kvp.Value.Description }
        ) ?? new Dictionary&lt;string, object?&gt;();

    public override JsonElement JsonSchema =&gt; GetJsonSchema();

    public override JsonSerializerOptions JsonSerializerOptions =&gt; DefaultJsonSerializerOptions;

    protected override async Task&lt;object?&gt; InvokeCoreAsync(IEnumerable&lt;KeyValuePair&lt;string, object?&gt;&gt; arguments,
        CancellationToken cancellationToken)
    {
        var argsDict = arguments.ToDictionary(kvp =&gt; kvp.Key, kvp =&gt; kvp.Value);
        return await _mcpTool.InvokeMethodAsync(argsDict);
    }

    private JsonElement GetJsonSchema()
    {
        if (_mcpTool.Function?.Parameters == null)
        {
            return JsonDocument.Parse(&quot;{}&quot;).RootElement;
        }

        var schema = new
        {
            type = _mcpTool.Function.Parameters.Type,
            properties = _mcpTool.Function.Parameters.Properties?.ToDictionary(
                kvp =&gt; kvp.Key, kvp =&gt; new { type = kvp.Value.Type, description = kvp.Value.Description }
            ) ?? new Dictionary&lt;string, object?&gt;(),
            required = _mcpTool.Function.Parameters.Required
        };

        var json = JsonSerializer.Serialize(schema, JsonSerializerOptions);
        return JsonDocument.Parse(json).RootElement;
    }
}
</code></pre><p>This adapter handles:</p><ul><li>Converting between MCP tool definitions and AI function schemas</li><li>Managing parameter serialization/deserialization</li><li>Proper handling of function invocation</li></ul><h2 id="why-this-architecture-matters">Why This Architecture Matters</h2><p>This implementation pattern offers several advantages:</p><ol><li><strong>Separation of Concerns</strong>: The LLM handles reasoning while specialized .NET code handles computations</li><li><strong>Type Safety</strong>: We get all the benefits of .NET&apos;s strong typing and validation</li><li><strong>Extensibility</strong>: Adding new tools is as simple as creating new methods with appropriate attributes</li><li><strong>Cloud-Native Design</strong>: Using .NET Aspire means this solution is ready for containerized deployments</li></ol><h2 id="example-usage">Example Usage</h2><p>With everything set up, users can send natural language queries to the API endpoint like:</p><ul><li>&quot;What&apos;s the square root of 144?&quot;</li><li>&quot;Calculate 5 to the power of 3&quot;</li><li>&quot;What&apos;s the factorial of 7?&quot;</li></ul><p>The LLM interprets these requests and calls the appropriate mathematical tools to get accurate results.</p><h2 id="technical-details">Technical Details</h2><p>For this implementation, I used:</p><ul><li>ModelContextProtocol 0.1.0-preview.6</li><li>OllamaSharp.ModelContextProtocol 5.1.9</li><li>.NET 8.0</li><li>Mistral 7B model via Ollama</li></ul><h2 id="conclusion">Conclusion</h2><p>The integration of .NET Aspire, Ollama, and ModelContextProtocol demonstrates the power of combining cloud-native architecture with AI capabilities. This pattern enables developers to create sophisticated applications where LLMs work alongside traditional code, each playing to their strengths.</p><p>As these technologies mature, we can expect to see more patterns emerge for effectively incorporating AI into distributed applications. The clean separation between tool definition and tool usage provided by ModelContextProtocol is particularly promising for building maintainable AI-enhanced systems.</p><p>What interesting integrations are you building with these technologies? I&apos;d love to hear about your experiences in the comments below.</p>]]></content:encoded></item><item><title><![CDATA[Blazor State Management With Fluxor]]></title><description><![CDATA[In Blazor development, managing app state demands a structured approach to ensure application robustness and responsiveness. Fluxor, a state management library tailored for Blazor, offers a streamlined solution.]]></description><link>https://fiyazhasan.work/blazor-state-management-with-fluxor/</link><guid isPermaLink="false">65fd7f401394aa0ca4c1cb3b</guid><category><![CDATA[Notebook]]></category><category><![CDATA[Blazor]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Fri, 22 Mar 2024 13:27:02 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--5-.png" medium="image"/><content:encoded><![CDATA[<img src="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--5-.png" alt="Blazor State Management With Fluxor"><p>In Blazor applications, managing state effectively is crucial for building robust and maintainable applications. As applications grow in complexity, handling state becomes increasingly challenging. This is where <code>Fluxor</code>, a state management library for Blazor, comes into play. In this article, we&apos;ll explore how <code>Fluxor</code> simplifies state management in Blazor applications by implementing a simple counter example.</p><p><strong>Understanding Fluxor:</strong></p><p><code>Fluxor</code> follows the Flux architecture, a design pattern introduced by Facebook for managing application state in web applications. At its core, Fluxor enforces a unidirectional data flow pattern, which helps in maintaining a predictable state management process.</p><p><strong>Key Concepts in Fluxor:</strong></p><ol><li><strong>Actions</strong>: Actions represent events or commands that occur within the application. They trigger state changes by dispatching to reducers.</li><li><strong>Reducers</strong>: Reducers are pure functions responsible for updating the application state based on dispatched actions. They take the current state and an action as input and produce a new state as output.</li><li><strong>Features</strong>: Features are logical groupings of related state, actions, and reducers. Each feature encapsulates its own state and behavior.</li><li><strong>Effects</strong>: Effects handle side effects such as asynchronous operations or interacting with external services. They are triggered in response to dispatched actions.</li></ol><p><strong>Implementing a Counter Example with Fluxor:</strong></p><p>Let&apos;s dive into a simple counter example implemented using Fluxor in a Blazor application.</p><p>Register the Fluxor services in the Program.cs file,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">builder.Services.AddFluxor(o =&gt;
{
    o.ScanAssemblies(typeof(Program).Assembly);
    o.UseRouting();
#if DEBUG
    o.UseReduxDevTools();
#endif
});</code></pre><figcaption>Program.cs</figcaption></figure><p>Create a code file and add the following classes and records,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">namespace BlazorFluxorStateManagement.Store.CounterUseCase
{
    public record IncrementCounterAction();
}

namespace BlazorFluxorStateManagement.Store.CounterUseCase
{
    [FeatureState]
    public record CounterState
    {
        public int Count { get; init; }

        private CounterState() { }

        public CounterState(int count)
        {
            Count = count;
        }
    }
}

namespace BlazorFluxorStateManagement.Store.CounterUseCase
{
    public class Reducers
    {
        [ReducerMethod(typeof(IncrementCounterAction))]
        public static CounterState ReduceIncrementCounterAction(CounterState state) =&gt;
               state with { Count = state.Count + 1 };
    }
}
</code></pre><figcaption></figcaption></figure><p>In this example, we define an action <code>IncrementCounterAction</code>, a feature state <code>CounterState</code>, and a reducer <code>Reducers</code> to handle the increment action.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/counter&quot;
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IState&lt;CounterState&gt; CounterState
@inject IDispatcher Dispatcher

&lt;PageTitle&gt;Counter&lt;/PageTitle&gt;

&lt;h1&gt;Counter&lt;/h1&gt;

&lt;p role=&quot;status&quot;&gt;Current count: @CounterState.Value.Count&lt;/p&gt;

&lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;IncrementCount&quot;&gt;Click me&lt;/button&gt;

@code {
    private void IncrementCount()
    {
        Dispatcher.Dispatch(new IncrementCounterAction());
    }
}</code></pre><figcaption>Counter.razor</figcaption></figure><p>This Blazor component represents the counter functionality. It displays the current count and increments it when the button is clicked by dispatching the <code>IncrementCounterAction</code>. Notice that we are inheriting from <code>Fluxor.Blazor.Web.Components.FluxorComponent</code> which is very important.</p><figure class="kg-card kg-code-card"><pre><code class="language-xml">&lt;Fluxor.Blazor.Web.StoreInitializer /&gt;

&lt;Router AppAssembly=&quot;typeof(Program).Assembly&quot;&gt;
    &lt;Found Context=&quot;routeData&quot;&gt;
        &lt;RouteView RouteData=&quot;routeData&quot; DefaultLayout=&quot;typeof(Layout.MainLayout)&quot; /&gt;
        &lt;FocusOnNavigate RouteData=&quot;routeData&quot; Selector=&quot;h1&quot; /&gt;
    &lt;/Found&gt;
&lt;/Router&gt;</code></pre><figcaption>Routes.razor</figcaption></figure><p>Finally, we initialize Fluxor and set up routing in the application.</p><p><strong>Implementing Data Fetching with Fluxor:</strong></p><p>Let&apos;s consider an example where we fetch weather forecast data from a remote service using <code>Fluxor</code>. Here&apos;s how the components and <code>Fluxor</code> features are structured:</p><pre><code class="language-csharp">
public record FetchDataAction();
public record FetchDataSuccessAction(IEnumerable&lt;WeatherForecast&gt; Forecasts);
public record FetchDataErrorAction(string Error);

public class Effects
{
    private readonly WeatherForecastService _weatherForecastService;

    public Effects(WeatherForecastService weatherForecastService)
    {
        _weatherForecastService = weatherForecastService;
    }

    [EffectMethod]
    public async Task HandleAsync(FetchDataAction action, IDispatcher dispatcher)
    {
        try
        {
            var forecasts = await _weatherForecastService.GetForecastAsync();
            dispatcher.Dispatch(new FetchDataSuccessAction(forecasts));
        }
        catch (Exception ex)
        {
            dispatcher.Dispatch(new FetchDataErrorAction(ex.Message));
        }
    }
}

public class Reducers
{
    [ReducerMethod]
    public static FetchDataState ReduceFetchDataAction(FetchDataState state, FetchDataAction action) =&gt;
        new(true, null, null);

    [ReducerMethod]
    public static FetchDataState ReduceFetchDataSuccessAction(FetchDataState state, FetchDataSuccessAction action) =&gt;
        new(false, action.Forecasts, null);

    [ReducerMethod]
    public static FetchDataState ReduceFetchDataErrorAction(FetchDataState state, FetchDataErrorAction action) =&gt;
        new(false, null, action.Error);
}

// Weather Forecast Service
public class WeatherForecastService
{
    // Implementation details omitted for brevity
}
</code></pre><p>In this example, we define actions, effects, reducers, and a service for fetching weather forecast data. The effects handle the asynchronous operation of fetching data and dispatch appropriate success or error actions based on the result.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/weather&quot;
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IState&lt;FetchDataState&gt; FetchDataState
@inject IDispatcher Dispatcher

&lt;PageTitle&gt;Weather&lt;/PageTitle&gt;

&lt;h1&gt;Weather&lt;/h1&gt;

&lt;p&gt;This component demonstrates showing data.&lt;/p&gt;

@if (FetchDataState.Value.IsLoading)
{
    &lt;p&gt;&lt;em&gt;Loading...&lt;/em&gt;&lt;/p&gt;
}
else
{
    if (FetchDataState.Value.Forecasts == null)
    {
        &lt;p&gt;&lt;em&gt;@FetchDataState.Value.Error&lt;/em&gt;&lt;/p&gt;
    }
    else
    {
        &lt;table class=&quot;table&quot;&gt;
            &lt;thead&gt;
                &lt;tr&gt;
                    &lt;th&gt;Date&lt;/th&gt;
                    &lt;th&gt;Temp. (C)&lt;/th&gt;
                    &lt;th&gt;Temp. (F)&lt;/th&gt;
                    &lt;th&gt;Summary&lt;/th&gt;
                &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
                @foreach (var forecast in FetchDataState.Value.Forecasts)
                {
                    &lt;tr&gt;
                        &lt;td&gt;@forecast.Date.ToShortDateString()&lt;/td&gt;
                        &lt;td&gt;@forecast.TemperatureC&lt;/td&gt;
                        &lt;td&gt;@forecast.TemperatureF&lt;/td&gt;
                        &lt;td&gt;@forecast.Summary&lt;/td&gt;
                    &lt;/tr&gt;
                }
            &lt;/tbody&gt;
        &lt;/table&gt;
    }
}

@code {
    protected override void OnInitialized()
    {
        base.OnInitialized();
        Dispatcher.Dispatch(new FetchDataAction());
    }
}
</code></pre><figcaption>Weather.razor</figcaption></figure><p>In the Blazor component, we utilize Fluxor&apos;s integration to access the application state (<code>FetchDataState</code>) and dispatch actions (<code>Dispatcher</code>) as needed.</p><p><strong>GitHub Repository:</strong></p><p><a href="https://github.com/fiyazbinhasan/BlazorFluxorStateManagement">https://github.com/fiyazbinhasan/BlazorFluxorStateManagement</a></p>]]></content:encoded></item><item><title><![CDATA[.NET Aspire Messaging With RabbitMQ & MassTransit]]></title><description><![CDATA[.NET Aspire, Microsoft's cloud-native stack, redefines how we approach distributed applications. With RabbitMQ & MassTransit, it's a powerful combination.]]></description><link>https://fiyazhasan.work/aspire-messaging-with-rabbitmq-and-masstransit/</link><guid isPermaLink="false">65f8cda746a4b81140fca041</guid><category><![CDATA[Notebook]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Tue, 19 Mar 2024 00:29:40 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--4-.png" medium="image"/><content:encoded><![CDATA[<img src="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--4-.png" alt=".NET Aspire Messaging With RabbitMQ &amp; MassTransit"><p>.NET Aspire, Microsoft&apos;s cloud-native stack, redefines how we approach distributed applications. With RabbitMQ &amp; MassTransit, it&apos;s a powerful combination. Let&apos;s harness the potential of .NET Aspire, RabbitMQ &amp; MassTransit to build resilient, cloud-native messaging architectures.</p><!--kg-card-begin: markdown--><ul>
<li>Ensure you have:
<ul>
<li>Latest .NET SDK</li>
<li>Visual Studio 2022 Preview</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><ul><li>From Visual Studio Installer, install .NET Aspire SDK (Preview) by navigating to: <code>Modify -&gt; Individual Components -&gt; .NET Aspire SDK (Preview)</code></li></ul><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-1.png" class="kg-image" alt=".NET Aspire Messaging With RabbitMQ &amp; MassTransit" loading="lazy" width="1280" height="720" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-1.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-1.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-1.png 1280w" sizes="(min-width: 1200px) 1200px"><figcaption>Visual Studio Individual Components Installer Window</figcaption></figure><p><strong>Update .NET Aspire Workload</strong>:</p><ul><li>Update the .NET Aspire workload via terminal with the following commands:</li></ul><p>From a terminal, run the following commands to update the .NET Aspire workload,</p><pre><code class="language-dotnetcli">dotnet workload update
dotnet workload install aspire</code></pre><p><strong>Create a New .NET Aspire Starter Application</strong>:</p><ul><li>Start with a fresh .NET Aspire Starter Application.</li></ul><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-2.png" class="kg-image" alt=".NET Aspire Messaging With RabbitMQ &amp; MassTransit" loading="lazy" width="1014" height="675" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-2.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-2.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-2.png 1014w"><figcaption>Using Visual Studio to Create a .NET Aspire Starter Application&#xA0;</figcaption></figure><p><strong>Update Existing Apps</strong>:</p><ul><li>For existing .NET Aspire apps, after installing the latest workload, update all .NET Aspire package references to: <code>9.0.0-preview.2.24162.2</code>. If you prefer .NET 8, then update to <code>8.0.0-preview.4.24156.9</code></li><li>For example, update package references in the <code>AspireMessaging.AppHost.csproj</code> file for <code>Aspire.Hosting</code>.</li></ul><pre><code>&lt;PackageReference Include=&quot;Aspire.Hosting&quot; Version=&quot;9.0.0-preview.2.24162.2&quot; /&gt;</code></pre><p><strong>Adding RabbitMQ Dependency:</strong></p><ul><li>Add a RabbitMQ resource to the application. It bootstraps a container used for local development. Also add the resource reference to two other projects,</li></ul><figure class="kg-card kg-code-card"><pre><code>var messaging = builder.AddRabbitMQ(&quot;RabbitMQConnection&quot;);

var apiService = builder.AddProject&lt;Projects.AspireMessaging_ApiService&gt;(&quot;apiservice&quot;)
    .WithReference(messaging);

builder.AddProject&lt;Projects.AspireMessaging_Web&gt;(&quot;webfrontend&quot;)
    .WithReference(cache)
    .WithReference(apiService)
    .WithReference(messaging);</code></pre><figcaption>Program.cs</figcaption></figure><ul><li>Your <code>appsettings.json</code> file should contain a <code>RabbitMQConnection</code> property,</li></ul><pre><code class="language-json">&quot;ConnectionStrings&quot;: {
  &quot;RabbitMQConnection&quot;: &quot;amqp://guest:guest@localhost:5672&quot;
}</code></pre><p><strong>Message Contract Library:</strong></p><ul><li>Add a new .NET Class Library e.g. <code>AspireMessaging.Contracts</code>, which will contain the message contract classes. Add a simple record for messaging,</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-csharp">namespace AspireMessaging.Contracts
{
    public record MessageContract
    {
        public Guid Id { get; init; } = Guid.NewGuid();

        public DateTime CreationDate { get; init; } = DateTime.UtcNow;

        public string Message { get; init; } = &quot;&quot;;
    }
}
</code></pre><figcaption>MessageContract.cs</figcaption></figure><ul><li>Add the class library as project dependency to <code>ApiService</code> and <code>Web</code>.</li></ul><p><strong>Installing MassTransit:</strong></p><ul><li>Add the <code>MassTransit.RabbitMQ</code> package to both <code>ApiService</code> and <code>Web</code> using <code>Nuget</code></li></ul><pre><code class="language-xml">&lt;PackageReference Include=&quot;MassTransit.RabbitMQ&quot; Version=&quot;8.1.3&quot; /&gt;</code></pre><ul><li>Register <code>MassTransit</code> service in both <code>ApiService</code> and <code>Web</code> projects,</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-csharp">builder.Services.AddMassTransit(x =&gt;
{
    x.SetKebabCaseEndpointNameFormatter();

    x.UsingRabbitMq((context, cfg) =&gt;
    {
        var configuration = context.GetRequiredService&lt;IConfiguration&gt;();
        var host = configuration.GetConnectionString(&quot;RabbitMQConnection&quot;);
        cfg.Host(host);
        cfg.ConfigureEndpoints(context);
    });
});</code></pre><figcaption>Program.cs</figcaption></figure><blockquote>The .NET Aspire RabbitMQ component supports <a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration">Microsoft.Extensions.Configuration</a>. It loads the <code>RabbitMQClientSettings</code> from configuration.</blockquote><p><strong>Publishing Message:</strong></p><ul><li>In the <code>_Imports.razor</code> component of the <code>Web</code> project, add using statement for <code>MassTransit</code> and the <code>AspireMessaging.Contracts</code> project references,</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-razor">@using MassTransit
@using AspireMessaging.Contracts</code></pre><figcaption><code>_Imports.razor</code></figcaption></figure><ul><li>Replace content of the <code>Home.razor</code> with the following,</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/&quot;
@rendermode InteractiveServer
@inject IBus Bus

&lt;PageTitle&gt;Home&lt;/PageTitle&gt;

&lt;button class=&quot;btn btn-primary mt-4&quot; @onclick=&quot;DispatchMessage&quot;&gt;Publish Hello, world!&lt;/button&gt;

@code {
    private async Task DispatchMessage()
    {
        await Bus.Publish(new MessageContract
        {
            Message = &quot;Hello, world!&quot;
        });
    }
}</code></pre><figcaption>Home.razor</figcaption></figure><p><strong>Consuming Message:</strong></p><ul><li>In the <code>ApiService</code> project, add a class for consuming a message published using the <code>MessageContract</code> class,</li></ul><pre><code class="language-csharp">using AspireMessaging.Contracts;
using MassTransit;
using System.Diagnostics;

namespace AspireMessaging.ApiService
{
    public class HelloWorldMessageConsumer : IConsumer&lt;MessageContract&gt;
    {
        public async Task Consume(ConsumeContext&lt;MessageContract&gt; context)
        {
            Debug.WriteLine($&quot;Received: {context.Message.Message}&quot;);
            await Task.CompletedTask;
        }
    }
}
</code></pre><ul><li>Register this consumer with <code>MassTransit</code> service,</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-csharp">builder.Services.AddMassTransit(x =&gt;
{
    x.SetKebabCaseEndpointNameFormatter();
    x.AddConsumer&lt;HelloWorldMessageConsumer&gt;();

    x.UsingRabbitMq((context, cfg) =&gt;
    {
        var configuration = context.GetRequiredService&lt;IConfiguration&gt;();
        var host = configuration.GetConnectionString(&quot;RabbitMQConnection&quot;);
        cfg.Host(host);
        cfg.ConfigureEndpoints(context);
    });
});</code></pre><figcaption>Program.cs</figcaption></figure><p><strong>Test It Out Yourself:</strong></p><ul><li>Download the repository from the link below,</li></ul><p><a href="https://github.com/fiyazbinhasan/AspireMessaging">https://github.com/fiyazbinhasan/AspireMessaging</a></p><ul><li>Run the Aspire project and from the home page of the <code>Web</code>, click on the <code>Publish Hello, world!</code> button,</li><li>In Visual Studio&apos;s <code>Output</code> panel, you should see the debug message,</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/03/image-3.png" class="kg-image" alt=".NET Aspire Messaging With RabbitMQ &amp; MassTransit" loading="lazy" width="484" height="58"><figcaption>Output</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Integrating Tailwind CSS with Blazor Server App]]></title><description><![CDATA[Integrating Tailwind CSS with a Blazor Server application can enhance its styling capabilities and streamline the development process. In this guide, we'll walk through the steps to integrate Tailwind CSS into a Blazor Server app.]]></description><link>https://fiyazhasan.work/integrating-tailwind-css-with-blazor-server-app/</link><guid isPermaLink="false">65f24b3b230194178832ee87</guid><category><![CDATA[Notebook]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Thu, 14 Mar 2024 01:47:29 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--3-.png" medium="image"/><content:encoded><![CDATA[<img src="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--3-.png" alt="Integrating Tailwind CSS with Blazor Server App"><p>Tailwind CSS is a popular utility-first CSS framework that enables developers to quickly build custom designs without having to write custom CSS. Blazor Server, on the other hand, is a Microsoft framework for building interactive web UIs using C# instead of JavaScript. Integrating Tailwind CSS with a Blazor Server application can enhance its styling capabilities and streamline the development process. In this guide, we&apos;ll walk through the steps to integrate Tailwind CSS into a Blazor Server app.</p><p><strong>Step 1: Create a New Blazor Server Project</strong></p><p>Start by creating a new Blazor Server project in Visual Studio. Alternatively, you can use the command-line interface to create a new project. Open your terminal and run the following command:</p><pre><code class="language-bash">dotnet new blazorserver-empty -o BlazorAndTailwind
cd BlazorAndTailwind</code></pre><p><strong>Step 2: Install Tailwind CSS</strong></p><p>Next, install Tailwind CSS and its dependencies using npm. Navigate to the root of your project directory and run the following commands:</p><pre><code class="language-bash">npm init -y
npm install tailwindcss postcss autoprefixer postcss-cli</code></pre><p><strong>Step 3: Create PostCSS Configuration</strong></p><p>Create a <code>postcss.config.js</code> file in the root of your project and add the following content to configure <code>PostCSS</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">module.exports = {
    plugins: {
      tailwindcss: {},
      autoprefixer: {},
    }
  }</code></pre><figcaption>postcss.config.js</figcaption></figure><p><strong>Step 4: Generate Tailwind CSS Configuration</strong></p><p><code>tailwindcss</code> provides a convenient method to generate its configuration file. Execute the following command within your project directory:</p><pre><code class="language-bash">npx tailwindcss init</code></pre><p>It will generate a <code>tailwind.config.js</code> file. For a Blazor project, the files that Tailwind needs to track are <code>.html</code>, <code>.cshtml</code> or Razor files. Add the template paths to the content section of the <code>tailwind.config.js</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">/** @type {import(&apos;tailwindcss&apos;).Config} */
module.exports = {
  content: [&quot;./**/*.{razor,html,cshtml}&quot;],
  theme: {
    extend: {},
  },
  plugins: [],
}</code></pre><figcaption>tailwind.config.js</figcaption></figure><p><strong>Step 5: Create Tailwind CSS Stylesheet</strong></p><p>You should already have a <code>site.css</code> in the <code>wwwroot/css</code> folder. Include the following directives to the file:<br></p><figure class="kg-card kg-code-card"><pre><code class="language-bash">@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre><figcaption>site.css</figcaption></figure><p><strong>Step 7: Watch Tailwind CSS Output</strong></p><p>To streamline development, watch and compile the Tailwind CSS output with the following command:</p><pre><code class="language-bash">npx tailwindcss -i wwwroot/css/site.css -o wwwroot/css/site.min.css --watch</code></pre><p><strong>Step 6: Reference Tailwind CSS Stylesheet in </strong><code>_Host.cshtml</code></p><p>In the <code>_Host.cshtml</code> file, add a reference to the generated Tailwind CSS:</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;

&lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;base href=&quot;/&quot; /&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;app.min.css&quot; /&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;BlazorAndTailwind.styles.css&quot; /&gt;
    &lt;script src=&quot;app.js&quot;&gt;&lt;/script&gt;
    &lt;HeadOutlet @rendermode=&quot;InteractiveServer&quot; /&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;Routes @rendermode=&quot;InteractiveServer&quot;/&gt;
    &lt;script src=&quot;_framework/blazor.web.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre><figcaption>_Host.cshtml</figcaption></figure><p><strong>Step 7: Testing Tailwind CSS Classes</strong></p><p>For testing, add some tailwind classes to the heading element on the <code>Index.razor</code> file:</p><figure class="kg-card kg-code-card"><pre><code class="language-Index.razor">@page &quot;/&quot;

&lt;h1 class=&quot;text-2xl text-purple-500&quot;&gt;Hello, world!&lt;/h1&gt;</code></pre><figcaption>Index.razor</figcaption></figure><p><strong>Download Source Code:</strong></p><p><a href="https://github.com/fiyazbinhasan/BlazorAndTailwind">https://github.com/fiyazbinhasan/BlazorAndTailwind</a></p>]]></content:encoded></item><item><title><![CDATA[Tailwind Class Based Theme Changer for Blazor Apps]]></title><description><![CDATA[Explore the implementation of a class-based theme changer for Blazor applications using Tailwind CSS. This approach allows us to seamlessly switch between light and dark themes while leveraging the power and simplicity of Tailwind CSS for styling.]]></description><link>https://fiyazhasan.work/tailwind-class-based-theme-changer-for-blazor-apps/</link><guid isPermaLink="false">65ef91f67aba651b1ca77ddf</guid><category><![CDATA[Notebook]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Wed, 13 Mar 2024 13:32:07 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--2-.png" medium="image"/><content:encoded><![CDATA[<blockquote>This post assumes that you know how to add Tailwind CSS in a Blazor Application. Read this <a href="https://fiyazhasan.me/integrating-tailwind-css-with-blazor-server-app/">post</a> if you don&apos;t already!</blockquote><img src="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--2-.png" alt="Tailwind Class Based Theme Changer for Blazor Apps"><p>In this notebook, we&apos;ll explore the implementation of a class-based theme changer for Blazor applications using Tailwind CSS. This approach allows us to seamlessly switch between light and dark themes while leveraging the power and simplicity of Tailwind CSS for styling.</p><p>Tailwind CSS is a popular utility-first CSS framework that enables rapid UI development by providing a set of pre-defined classes. Blazor, on the other hand, is a framework for building interactive web UIs using C# instead of JavaScript. By combining Tailwind CSS with Blazor, we can create modern and responsive web applications efficiently.</p><p>One common feature in web applications is the ability to switch between different themes, such as light and dark themes. In this notebook, we&apos;ll demonstrate how to implement a theme changer using Tailwind CSS classes in a Blazor application.</p><p>Create a Blazor Server App. Create a <code>razor</code> component in a new folder named <code>widget</code>. This is the component that toggles between light and dark theme.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@inject IJSRuntime Js
@inject ProtectedLocalStorage ProtectedLocalStorage
@inject AppState AppState

&lt;button class=&quot;text-gray-500 hover:bg-bg-gray-800 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg text-sm p-2.5 inline-flex items-center&quot;
        @onclick=&quot;ToggleTheme&quot;&gt;
    @if (AppState.Theme == &quot;dark&quot;)
    {
        &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; class=&quot;lucide lucide-moon&quot;&gt;&lt;path d=&quot;M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z&quot; /&gt;&lt;/svg&gt;
    }
    else
    {
        &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; class=&quot;lucide lucide-sun&quot;&gt;&lt;circle cx=&quot;12&quot; cy=&quot;12&quot; r=&quot;4&quot; /&gt;&lt;path d=&quot;M12 2v2&quot; /&gt;&lt;path d=&quot;M12 20v2&quot; /&gt;&lt;path d=&quot;m4.93 4.93 1.41 1.41&quot; /&gt;&lt;path d=&quot;m17.66 17.66 1.41 1.41&quot; /&gt;&lt;path d=&quot;M2 12h2&quot; /&gt;&lt;path d=&quot;M20 12h2&quot; /&gt;&lt;path d=&quot;m6.34 17.66-1.41 1.41&quot; /&gt;&lt;path d=&quot;m19.07 4.93-1.41 1.41&quot; /&gt;&lt;/svg&gt;
    }
&lt;/button&gt;

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await SetTheme();
        }
    }

    private async Task SetTheme()
    {
        var result = await ProtectedLocalStorage.GetAsync&lt;string&gt;(&quot;theme&quot;);
        var theme = result.Success ? result.Value : &quot;light&quot;;
        if (theme != null) AppState.SetTheme(theme);
    }

    private async Task ToggleTheme()
    {
        switch (AppState.Theme)
        {
            case &quot;dark&quot;:
                await Js.InvokeVoidAsync(&quot;toggleTheme&quot;, &quot;dark&quot;);
                await ProtectedLocalStorage.SetAsync(&quot;theme&quot;, &quot;light&quot;);
                AppState.SetTheme(&quot;light&quot;);
                break;
            default:
                await Js.InvokeVoidAsync(&quot;toggleTheme&quot;, &quot;light&quot;);
                await ProtectedLocalStorage.SetAsync(&quot;theme&quot;, &quot;dark&quot;);
                AppState.SetTheme(&quot;dark&quot;);
                break;
        }
    }
}</code></pre><figcaption>ThemeToggler.razor</figcaption></figure><p>We have injected the <code>IJSRuntime</code> so that we can call a javascript function, which will toggle the <code>dark</code> class on the document. For the javascript function i.e. <code>toggleTheme</code>, we have created an <code>app.js</code> file in the <code>wwwroot</code> and added the following script,</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">window.toggleTheme = function (theme) {
    switch (theme) {
        case &quot;dark&quot;:
            document.documentElement.classList.remove(&quot;dark&quot;);
            break;
        default:
            document.documentElement.classList.add(&quot;dark&quot;);
            break;
    }
};</code></pre><figcaption>app.js</figcaption></figure><p>Add the script in the <code>App.razor</code> file. Notice that we have added some classes to the <code>&lt;body&gt;</code>.</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;

&lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;base href=&quot;/&quot; /&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;app.min.css&quot; /&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;BlazorClassBasedThemeChanger.styles.css&quot; /&gt;
    &lt;script src=&quot;app.js&quot;&gt;&lt;/script&gt;
    &lt;HeadOutlet @rendermode=&quot;InteractiveServer&quot; /&gt;
&lt;/head&gt;

&lt;body class=&quot;tracking-tight antialiased text-gray-900 dark:text-slate-300 bg-white dark:bg-gray-800&quot;&gt;
    &lt;Routes @rendermode=&quot;InteractiveServer&quot;/&gt;
    &lt;script src=&quot;_framework/blazor.web.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre><blockquote><code>flowbite</code> is a component library based on Tailwind CSS. It is optional.</blockquote><p>The <code>AppState</code> service is a class having a string property that stores the current theme. It is registered with the scoped lifecycle,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">namespace BlazorClassBasedThemeChanger;

public class AppState
{
    public string Theme { get; private set; } = &quot;light&quot;;
    
    public void SetTheme(string theme)
    {
        Theme = theme;
    }
}</code></pre><figcaption>AppState.cs</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using BlazorClassBasedThemeChanger;
using BlazorClassBasedThemeChanger.Components;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
builder.Services.AddScoped&lt;AppState&gt;();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(&quot;/Error&quot;, createScopeForErrors: true);
}

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents&lt;App&gt;()
    .AddInteractiveServerRenderMode();

app.Run();
</code></pre><figcaption>Program.cs</figcaption></figure><p>We should store the theme in the browser&apos;s local storage, so that when a user re-opens the app, the previously selected theme is persisted. <code>Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage</code> package could be used for that purpose.</p><p>Import it in the <code>_Imports.razor</code> file,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorClassBasedThemeChanger
@using BlazorClassBasedThemeChanger.Components
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
</code></pre><figcaption>_Imports.razor</figcaption></figure><p>The <code>tailwind.config.js</code> file should look like this,</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">/** @type {import(&apos;tailwindcss&apos;).Config} */
const defaultTheme = require(&quot;tailwindcss/defaultTheme&quot;);
const colors = require(&quot;tailwindcss/colors&quot;);

module.exports = {
    content: [&quot;./**/*.{razor,html,cshtml}&quot;, &quot;./node_modules/flowbite/**/*.js&quot;],
    theme: {
        extend: {
            colors: {
                primary: colors.purple,
                secondary: colors.blue,
            },
            fontFamily: {
                sans: [&quot;&apos;Inter Variable&apos;&quot;, ...defaultTheme.fontFamily.sans],
            },
        },
    },
    plugins: [
        require(&quot;@tailwindcss/typography&quot;),
        require(&apos;flowbite/plugin&apos;)
    ],
    darkMode: &quot;class&quot;
}</code></pre><figcaption>tailwind.config.js</figcaption></figure><p>That pretty much it! Download the source code,</p><p><a href="https://github.com/fiyazbinhasan/BlazorClassBasedThemeChanger">https://github.com/fiyazbinhasan/BlazorClassBasedThemeChanger</a></p><p>Run the app and you should have the following UI. Use the toggle button to change between dark and light theme,</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/03/image-2.png" class="kg-image" alt="Tailwind Class Based Theme Changer for Blazor Apps" loading="lazy" width="514" height="252"><figcaption>Light Theme</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/03/image-1.png" class="kg-image" alt="Tailwind Class Based Theme Changer for Blazor Apps" loading="lazy" width="513" height="267"><figcaption>Dark Theme</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Adding an NPM project to .NET Aspire (Preview 3)]]></title><description><![CDATA[In this post, I'll guide you through updating your Aspire application to the latest .NET Aspire Preview 3 and incorporating an NPM project, whether it's Angular, React, Vue, or in my case, Qwik. Ensure you have the most recent .NET SDK and Visual Studio 2022 Preview installed before proceeding.]]></description><link>https://fiyazhasan.work/adding-npm-project-to-dotnet-aspire-preview-3/</link><guid isPermaLink="false">65eecd71791ff6061ce5da97</guid><category><![CDATA[Notebook]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Mon, 11 Mar 2024 10:07:41 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--1-.png" medium="image"/><content:encoded><![CDATA[<img src="https://fiyazhasan.work/content/images/2024/03/UPDATED-11.21.2019--1-.png" alt="Adding an NPM project to .NET Aspire (Preview 3)"><p><br>In this post, I&apos;ll guide you through updating your Aspire application to the latest .NET Aspire Preview 3 and incorporating an NPM project, whether it&apos;s Angular, React, Vue, or in my case, Qwik. Ensure you have the most recent .NET SDK and Visual Studio 2022 Preview installed before proceeding.</p><p><strong>Install Prerequisites</strong>:</p><!--kg-card-begin: markdown--><ul>
<li>Ensure you have:
<ul>
<li>Latest .NET SDK</li>
<li>Visual Studio 2022 Preview</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><ul><li>From Visual Studio Installer, install .NET Aspire SDK (Preview) by navigating to: <code>Modify -&gt; Individual Components -&gt; .NET Aspire SDK (Preview)</code></li></ul><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-1.png" class="kg-image" alt="Adding an NPM project to .NET Aspire (Preview 3)" loading="lazy" width="1280" height="720" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-1.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-1.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-1.png 1280w" sizes="(min-width: 1200px) 1200px"><figcaption>Visual Studio Individual Components Installer Window</figcaption></figure><p><strong>Update .NET Aspire Workload</strong>:</p><ul><li>Update the .NET Aspire workload via terminal with the following commands:</li></ul><p>From a terminal, run the following commands to update the .NET Aspire workload,</p><pre><code class="language-dotnetcli">dotnet workload update
dotnet workload install aspire</code></pre><p><strong>Create a New .NET Aspire Starter Application</strong>:</p><ul><li>Start with a fresh .NET Aspire Starter Application.</li></ul><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-2.png" class="kg-image" alt="Adding an NPM project to .NET Aspire (Preview 3)" loading="lazy" width="1014" height="675" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-2.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-2.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-2.png 1014w"><figcaption>Using Visual Studio to Create a .NET Aspire Starter Application&#xA0;</figcaption></figure><p><strong>Update Existing Apps</strong>:</p><ul><li>For existing .NET Aspire apps, after installing the latest workload, update all .NET Aspire package references to: <code>8.0.0-preview.3.24105.21</code></li><li>For example, update package references in the <code>SampleAspireApp.AppHost.csproj</code> file for Aspire.Hosting.</li></ul><pre><code>&lt;PackageReference Include=&quot;Aspire.Hosting&quot; Version=&quot;8.0.0-preview.3.24105.21&quot; /&gt;</code></pre><p><strong>Bootstrap Qwik Application</strong>:</p><ul><li>Change directory to the created Aspire project in your terminal.</li><li>Use the following command to create a <strong>Qwik</strong> project, specifying the directory name for the project files.</li></ul><pre><code>npm create qwik@latest</code></pre><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-7.png" class="kg-image" alt="Adding an NPM project to .NET Aspire (Preview 3)" loading="lazy" width="1115" height="628" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-7.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-7.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-7.png 1115w"><figcaption>Installing Qwik using Windows Terminal</figcaption></figure><p><strong>Add Dockerfile and Environment Variables</strong>:</p><ul><li>Add a Dockerfile to the Qwik project.</li></ul><pre><code>FROM node:20.7

WORKDIR /app

COPY package.json package.json
COPY package-lock.json package-lock.json

RUN npm i

COPY . .

EXPOSE 5173

CMD [ &quot;npm&quot;, &quot;start&quot; ]</code></pre><ul><li>Also, include a <code>.env</code> file with the environment variable pointing to the Aspire ApiService project running on the Docker container.</li></ul><pre><code>PUBLIC_QWIK_APP_WEATHER_API=$services__apiservice__1</code></pre><p><strong>Update Program.cs File</strong>:</p><ul><li>Add the following to your <code>Program.cs</code> file in the AppHost project:</li></ul><pre><code class="language-csharp">builder.AddNpmApp(&quot;qwik&quot;, &quot;../SampleAspireApp.Spa&quot;)
    .WithReference(apiservice)
    .WithHttpEndpoint(containerPort: 5173, env: &quot;PORT&quot;)
    .AsDockerfileInManifest();</code></pre><ul><li>Use <code>AddNpmApp</code> to incorporate the Qwik app, specifying the node project name and path.</li><li>Set the container port to 5173, meaning the Qwik app will be hosted on <code>http://localhost:5173</code>.</li></ul><p><strong>Final Touches</strong>:</p><ul><li>Run the Aspire project, and two browser windows should pop up.</li></ul><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-8.png" class="kg-image" alt="Adding an NPM project to .NET Aspire (Preview 3)" loading="lazy" width="1512" height="385" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-8.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-8.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-8.png 1512w" sizes="(min-width: 1200px) 1200px"><figcaption>Aspire Dashboard</figcaption></figure><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-9.png" class="kg-image" alt="Adding an NPM project to .NET Aspire (Preview 3)" loading="lazy" width="1510" height="218" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-9.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-9.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-9.png 1510w" sizes="(min-width: 1200px) 1200px"><figcaption>Qwik Application</figcaption></figure><p><strong>Fetching Forecast Data</strong>:</p><ul><li>Fetch the forecast from the ApiService and display it in the UI by adding the necessary code to the <code>index.tsx</code> file.</li></ul><pre><code class="language-jsx">import { Resource, component$, useResource$ } from &quot;@builder.io/qwik&quot;;
import type { DocumentHead } from &quot;@builder.io/qwik-city&quot;;

export default component$(() =&gt; {
  const forecast = useResource$&lt;Forecast[]&gt;(async () =&gt; {
    const response = await fetch(
      `${import.meta.env.PUBLIC_QWIK_APP_WEATHER_API}/weatherforecast`
    );
    const data = await response.json();
    return data as Forecast[];
  });

  return (
    &lt;&gt;
      &lt;Resource
        value={forecast}
        onPending={() =&gt; &lt;p&gt;Loading...&lt;/p&gt;}
        onResolved={(forecast: Forecast[]) =&gt; &lt;table&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Date&lt;/th&gt;
              &lt;th&gt;Temp. (C)&lt;/th&gt;
              &lt;th&gt;Temp. (F)&lt;/th&gt;
              &lt;th&gt;Summary&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            {forecast.map((f: Forecast, i: number) =&gt; {
              return (
                &lt;tr key={i}&gt;
                  &lt;td&gt;{f.date}&lt;/td&gt;
                  &lt;td&gt;{f.temperatureC}&lt;/td&gt;
                  &lt;td&gt;{f.temperatureF}&lt;/td&gt;
                  &lt;td&gt;{f.summary}&lt;/td&gt;
                &lt;/tr&gt;
              );
            })}
          &lt;/tbody&gt;
        &lt;/table&gt;}
      /&gt;
    &lt;/&gt;
  );
});

type Forecast = {
  date: string;
  temperatureC: number;
  temperatureF: number;
  summary: string;
}

export const head: DocumentHead = {
  title: &quot;Welcome to Qwik&quot;,
  meta: [
    {
      name: &quot;description&quot;,
      content: &quot;Qwik site description&quot;,
    },
  ],
};
</code></pre><p><strong>Check Results</strong>:</p><ul><li>Ensure the app displays the forecast in a styled table.</li></ul><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-10.png" class="kg-image" alt="Adding an NPM project to .NET Aspire (Preview 3)" loading="lazy" width="1512" height="352" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-10.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-10.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-10.png 1512w" sizes="(min-width: 1200px) 1200px"><figcaption>Forecast Table</figcaption></figure><p><strong>Resources</strong>:</p><ul><li>For reference, you can find the complete repository and important links below.</li></ul><p><a href="https://github.com/fiyazbinhasan/QwikAspire">QwikAspire</a></p><p>That&apos;s it! You&apos;ve successfully updated your Aspire application and integrated a Qwik project. Enjoy exploring the possibilities of your enhanced application.</p><h3 id="important-links">Important Links</h3><p><a href="https://github.com/dotnet/aspire-samples">Aspire Samples</a></p><p><a href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview">.NET Aspire Overview</a></p>]]></content:encoded></item><item><title><![CDATA[Adding a Qwik project to .NET Aspire]]></title><description><![CDATA[Learn how to add a Qwik project to a .NET Aspire application]]></description><link>https://fiyazhasan.work/adding-a-qwik-project-to-net-aspire/</link><guid isPermaLink="false">65bfd596c2f1881310cd07b5</guid><category><![CDATA[Notebook]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Sun, 04 Feb 2024 20:15:18 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2024/02/UPDATED-11.21.2019.png" medium="image"/><content:encoded><![CDATA[<img src="https://fiyazhasan.work/content/images/2024/02/UPDATED-11.21.2019.png" alt="Adding a Qwik project to .NET Aspire"><p>Make sure you have the latest .NET SDK and Visual Studio 2022 Preview installed. </p><p><a href="https://dotnet.microsoft.com/en-us/download/dotnet/8.0">.NET SDK</a></p><p><a href="https://visualstudio.microsoft.com/vs/preview/">Visual Studio 2022 Preview</a></p><p>From the visual studio installer install <code>.NET Aspire SDK (Preview)</code>.</p><p><code>Modify -&gt; Individual Components -&gt; .NET Aspire SDK (Preview)</code></p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-1.png" class="kg-image" alt="Adding a Qwik project to .NET Aspire" loading="lazy" width="1280" height="720" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-1.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-1.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-1.png 1280w" sizes="(min-width: 1200px) 1200px"><figcaption>Visual Studio Individual Components Installer Window</figcaption></figure><p>Create a new .NET Aspire Starter Application</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-2.png" class="kg-image" alt="Adding a Qwik project to .NET Aspire" loading="lazy" width="1014" height="675" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-2.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-2.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-2.png 1014w"><figcaption>Using Visual Studio to Create a .NET Aspire Starter Application&#xA0;</figcaption></figure><p>The starter template should look like the following.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-3.png" class="kg-image" alt="Adding a Qwik project to .NET Aspire" loading="lazy" width="337" height="633"><figcaption>Solution Explorer</figcaption></figure><p>Right click on the <code>AppHost</code> project (e.g. <code>SampleAspireApp.AppHost</code>) and click <code>Manage NuGet packages...</code> . Go to the updates tab and check the <code>Include prerelease</code> checkbox. Update to the latest <code>Aspire Hosting</code> package.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-4.png" class="kg-image" alt="Adding a Qwik project to .NET Aspire" loading="lazy" width="1938" height="602" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-4.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-4.png 1000w, https://fiyazhasan.work/content/images/size/w1600/2024/02/image-4.png 1600w, https://fiyazhasan.work/content/images/2024/02/image-4.png 1938w" sizes="(min-width: 1200px) 1200px"><figcaption>NuGet Package Manager</figcaption></figure><p>Time to bootstrap a Qwik application. From the terminal change your directory to the created Aspire project.</p><p>Use the following command to create a &#xA0;Qwik project,</p><pre><code>npm create qwik@latest</code></pre><p>Type in the directory name under which the project files will reside.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-7.png" class="kg-image" alt="Adding a Qwik project to .NET Aspire" loading="lazy" width="1115" height="628" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-7.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-7.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-7.png 1115w"><figcaption>Installing Qwik using Windows Terminal</figcaption></figure><p>Add a <code>Dockerfile</code> to the <code>Qwik</code> project,</p><figure class="kg-card kg-code-card"><pre><code>FROM node:20.7

WORKDIR /app

COPY package.json package.json
COPY package-lock.json package-lock.json

RUN npm i

COPY . .

EXPOSE 5173

CMD [ &quot;npm&quot;, &quot;start&quot; ]</code></pre><figcaption>Dockerfile</figcaption></figure><p>Also add a <code>.env</code> file containing the following environment variable that points to the Aspire ApiService project running on the Docker container.</p><pre><code>PUBLIC_QWIK_APP_WEATHER_API=$services__apiservice__1</code></pre><p>Go back to the <code>AppHost</code> project and add the following to your <code>Program.cs</code> file</p><pre><code class="language-csharp">builder.AddNpmApp(&quot;qwik&quot;, &quot;../SampleAspireApp.Spa&quot;)
    .WithReference(apiservice)
    .WithServiceBinding(containerPort: 5173, scheme: &quot;http&quot;, env: &quot;PORT&quot;)
    .AsDockerfileInManifest();</code></pre><p><code>AddNpmApp</code> takes two arguments, first argument is the name of the node project and the second one is the project path to your node project. The container port is set to <code>5173</code> means the <code>Qwik</code> app will be hosted on <code>http://localhost:5173</code>.</p><p>The final <code>Program.cs</code> file should look like this,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedisContainer(&quot;cache&quot;);

var apiservice = builder.AddProject&lt;Projects.SampleAspireApp_ApiService&gt;(&quot;apiservice&quot;);

builder.AddProject&lt;Projects.SampleAspireApp_Web&gt;(&quot;webfrontend&quot;)
    .WithReference(cache)
    .WithReference(apiservice);

builder.AddNpmApp(&quot;qwik&quot;, &quot;../SampleAspireApp.Spa&quot;)
    .WithReference(apiservice)
    .WithServiceBinding(containerPort: 5173, scheme: &quot;http&quot;, env: &quot;PORT&quot;)
    .AsDockerfileInManifest();

builder.Build().Run();
</code></pre><figcaption>Program.cs</figcaption></figure><p>And done, Run the Aspire project and two browser window should pop up.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-8.png" class="kg-image" alt="Adding a Qwik project to .NET Aspire" loading="lazy" width="1512" height="385" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-8.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-8.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-8.png 1512w" sizes="(min-width: 1200px) 1200px"><figcaption>Aspire Dashboard</figcaption></figure><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-9.png" class="kg-image" alt="Adding a Qwik project to .NET Aspire" loading="lazy" width="1510" height="218" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-9.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-9.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-9.png 1510w" sizes="(min-width: 1200px) 1200px"><figcaption>Qwik Application</figcaption></figure><p>Let&apos;s fetch the forcast from the ApiService and show it in the UI. Add the following to the <code>index.tsx</code> file.</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">import { Resource, component$, useResource$ } from &quot;@builder.io/qwik&quot;;
import type { DocumentHead } from &quot;@builder.io/qwik-city&quot;;

export default component$(() =&gt; {
  const forecast = useResource$&lt;Forecast[]&gt;(async () =&gt; {
    const response = await fetch(
      `${import.meta.env.PUBLIC_QWIK_APP_WEATHER_API}/weatherforecast`
    );
    const data = await response.json();
    return data as Forecast[];
  });

  return (
    &lt;&gt;
      &lt;Resource
        value={forecast}
        onPending={() =&gt; &lt;p&gt;Loading...&lt;/p&gt;}
        onResolved={(forecast: Forecast[]) =&gt; &lt;table&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Date&lt;/th&gt;
              &lt;th&gt;Temp. (C)&lt;/th&gt;
              &lt;th&gt;Temp. (F)&lt;/th&gt;
              &lt;th&gt;Summary&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            {forecast.map((f: Forecast, i: number) =&gt; {
              return (
                &lt;tr key={i}&gt;
                  &lt;td&gt;{f.date}&lt;/td&gt;
                  &lt;td&gt;{f.temperatureC}&lt;/td&gt;
                  &lt;td&gt;{f.temperatureF}&lt;/td&gt;
                  &lt;td&gt;{f.summary}&lt;/td&gt;
                &lt;/tr&gt;
              );
            })}
          &lt;/tbody&gt;
        &lt;/table&gt;}
      /&gt;
    &lt;/&gt;
  );
});

type Forecast = {
  date: string;
  temperatureC: number;
  temperatureF: number;
  summary: string;
}

export const head: DocumentHead = {
  title: &quot;Welcome to Qwik&quot;,
  meta: [
    {
      name: &quot;description&quot;,
      content: &quot;Qwik site description&quot;,
    },
  ],
};
</code></pre><figcaption>index.tsx</figcaption></figure><p>The app should show the forecast in a styled table like the following,</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2024/02/image-10.png" class="kg-image" alt="Adding a Qwik project to .NET Aspire" loading="lazy" width="1512" height="352" srcset="https://fiyazhasan.work/content/images/size/w600/2024/02/image-10.png 600w, https://fiyazhasan.work/content/images/size/w1000/2024/02/image-10.png 1000w, https://fiyazhasan.work/content/images/2024/02/image-10.png 1512w" sizes="(min-width: 1200px) 1200px"><figcaption>Forecast Table</figcaption></figure><h3 id="repository">Repository</h3><p><a href="https://github.com/fiyazbinhasan/QwikAspire">QwikAspire</a></p><h3 id="important-links">Important Links</h3><p><a href="https://github.com/dotnet/aspire-samples">Aspire Samples</a></p><p><a href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview">.NET Aspire Overview</a></p>]]></content:encoded></item><item><title><![CDATA[My Notebook: FIDO2 MFA in ASP.NET Core]]></title><description><![CDATA[Fido2 is the umbrella term and branding of two new w3c standards: WebAuthn and CTAP2. WebAuthn is the JS API that allows browser to talk to the operating system to generate assertions and CTAP2 is the API that allows the operating system to talk to Authenticators (usb security keys etc)]]></description><link>https://fiyazhasan.work/fido2-mfa-in-aspnetcore/</link><guid isPermaLink="false">642583a6bf62640ce8d398a8</guid><category><![CDATA[Notebook]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Sat, 01 Apr 2023 23:13:55 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2023/04/UPDATED-11.21.2019.png" medium="image"/><content:encoded><![CDATA[<img src="https://fiyazhasan.work/content/images/2023/04/UPDATED-11.21.2019.png" alt="My Notebook: FIDO2 MFA in ASP.NET Core"><p><a href="https://fidoalliance.org/fido2/">FIDO2</a> adds an extra layer of security to your application. FIDO keys are the only way to guard against online phishing attacks. FIDO is also safe in case of a data breach since only the public keys are exposed. As FIDO keys are physical devices or platform-integrated interfaces, user interaction has to be present to authenticate themself.</p><blockquote>Fido2 is the umbrella term and branding of two new w3c standards: WebAuthn and CTAP2. WebAuthn is the JS API that allows browser to talk to the operating system to generate assertions and CTAP2 is the API that allows the operating system to talk to Authenticators (usb security keys etc)</blockquote><p><a href="https://www.ftsafe.com/Products/FIDO">FEITIAN Technologies Co., Ltd.</a> has many FIDO key solutions, and they were kind enough to send me the FIDO2 NFC K9 to review. I will use this key as an MFA authenticator for an ASP.NET Core application.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://fiyazhasan.work/content/images/2023/03/MicrosoftTeams-image-1.png" class="kg-image" alt="My Notebook: FIDO2 MFA in ASP.NET Core" loading="lazy" width="1088" height="1303" srcset="https://fiyazhasan.work/content/images/size/w600/2023/03/MicrosoftTeams-image-1.png 600w, https://fiyazhasan.work/content/images/size/w1000/2023/03/MicrosoftTeams-image-1.png 1000w, https://fiyazhasan.work/content/images/2023/03/MicrosoftTeams-image-1.png 1088w" sizes="(min-width: 720px) 720px"><figcaption><strong>FEITIAN ePass FIDO2 FIDO U2F USB-A</strong></figcaption></figure><p>Before diving into the code, let&apos;s clarify some terms. </p><h3 id="webauthn">WebAuthn</h3><p>The Web Authentication API (WebAuthn) is a specification written by the W3C and FIDO. The API allows servers to register and authenticate users using public key cryptography instead of passwords.</p><p>It allows servers to integrate with strong authenticators. Instead of a password, a private-public key pair (a credential) is created for a website. The private key is stored securely on the user&apos;s device; a public key and randomly generated credential ID are sent to the server for storage. The server can then use that public key to prove the user&apos;s identity.</p><h3 id="authenticator">Authenticator</h3><p>A cryptographic entity existing in hardware or software that can register a user with a given Relying Party and later assert possession of the registered public key credential, and optionally verify the user, when requested by the Relying Party.</p><h3 id="registration">Registration</h3><p>The ceremony where a user, a Relying Party, and the user&apos;s client (containing at least one authenticator) work to create a public key credential and associate it with the user&apos;s Relying Party account.</p><h3 id="relying-party">Relying Party</h3><p>In the context of the <a href="https://www.w3.org/TR/webauthn/#web-authentication-api">WebAuthn API</a>, a relying party identifier is a valid domain string identifying the WebAuthn Relying Party on whose behalf a given registration or authentication ceremony is being performed.</p><h3 id="attestation">Attestation</h3><p>Attestation is a statement serving to bear witness, confirm, or authenticate. In the WebAuthn context, attestation is employed to <em>attest</em> to the <em>provenance</em> of an authenticator and the data it emits, including, for example, credential IDs, credential key pairs, signature counters, etc.</p><h3 id="assertion">Assertion</h3><p>When a user chooses to log into a service, the server sends a challenge, and the authenticator signs over it with a key pair previously registered to that service. This creates an assertion. Unlike the attestation, the assertion format is always the same regardless of the device used.</p><p>The assertion is returned through the WebAuthn API as the <a href="https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse">AuthenticatorAssertionResponse</a>.</p><h1 id="workflow">Workflow</h1><p>In a regular ASP.NET Core application configured with the Identity platform, users are typically registered using the <code>IdentityUser</code> entity in the <code>AspNetUser</code> table. For the sake of simplicity, this post is <em><strong>not</strong></em> focused on the Identity aspect of the application. It only demonstrates the idea of enabling &#xA0;MFA for a pre-defined user.</p><p><em>TL;DR. </em>Download the source code from here<em>,</em></p><p><a href="https://github.com/fiyazbinhasan/AspNetCoreFido2MFA">https://github.com/fiyazbinhasan/AspNetCoreFido2MFA</a></p><p>Run the following command before running the sample,</p><pre><code>dotnet-ef database update</code></pre><p>The WebAuthn library used for this demo is <a href="https://github.com/passwordless-lib/fido2-net-lib ">fido2-net-lib</a>. In an ASP.NET Core web application (Razor Pages), add these packages,</p><pre><code>Install-Package Fido2
Install-Package Fido2.AspNet</code></pre><p>The required Fido services are registered in the <code>Program.cs</code> file,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">builder.Services.AddMemoryCache();
builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =&gt;
{
    options.IdleTimeout = TimeSpan.FromMinutes(2);
    options.Cookie.HttpOnly = true;
    options.Cookie.SameSite = SameSiteMode.Unspecified;
});

builder.Services.AddFido2(options =&gt;
    {
        options.ServerDomain = builder.Configuration[&quot;Fido2:ServerDomain&quot;];
        options.ServerName = &quot;FIDO2 Test&quot;;
        options.Origins = builder.Configuration.GetSection(&quot;Fido2:Origins&quot;).Get&lt;HashSet&lt;string&gt;&gt;();
        options.TimestampDriftTolerance = builder.Configuration.GetValue&lt;int&gt;(&quot;Fido2:TimestampDriftTolerance&quot;);
        options.MDSCacheDirPath = builder.Configuration[&quot;Fido2:MDSCacheDirPath&quot;];
    })
    .AddCachedMetadataService(config =&gt;
    {
        config.AddFidoMetadataRepository(httpClientBuilder =&gt;
        {
            //TODO: any specific config you want for accessing the MDS
        });
    });</code></pre><figcaption>Program.cs</figcaption></figure><p><code>AddFido2</code> service reads configuration from the <code>appsettings.json</code> file where the necessary information is provided to make the web server act as a Relying Party (RP)</p><figure class="kg-card kg-code-card"><pre><code class="language-json">&quot;Fido2&quot;: {
  &quot;ServerDomain&quot;: &quot;localhost&quot;,
  &quot;Origins&quot;: [ &quot;https://localhost:44388&quot;, &quot;https://localhost:7123&quot; ],
  &quot;TimestampDriftTolerance&quot;: 300000
}</code></pre><figcaption>appsettings.json</figcaption></figure><p>Rather than storing the credentials and user registration info in memory, a relational database is configured with two entities, i.e. <code>Fido2User</code> and <code>StoredCredential</code>. Entity framework is used to configure these two entities,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using System.Text;
using AspNetCoreFido2MFA.Models;
using Fido2NetLib;
using Microsoft.EntityFrameworkCore;

namespace AspNetCoreFido2MFA.Data;

public class AppDbContext : DbContext
{
    public DbSet&lt;Fido2User&gt; Fido2Users =&gt; Set&lt;Fido2User&gt;();
    public DbSet&lt;StoredCredential&gt; StoredCredentials =&gt; Set&lt;StoredCredential&gt;();

    public AppDbContext(DbContextOptions&lt;AppDbContext&gt; optionsBuilder) : base(optionsBuilder) 
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity&lt;Fido2User&gt;()
            .HasData(new List&lt;Fido2User&gt;()
            {
                new()
                {
                    Id = Encoding.UTF8.GetBytes(&quot;fiyazhasan@fido.local&quot;), 
                    DisplayName = &quot;fiyazhasan@fido.local&quot;,
                    Name = &quot;fiyazhasan@fido.local&quot;
                }
            });
    }
}</code></pre><figcaption>AppDbContext.cs</figcaption></figure><p>Note that <code>Fido2User</code> is already available in the <code>Fido2NetLib</code> as well as <code>StoredCredential</code>. But we are using a modified version of <code>StoredCredential</code>,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public class StoredCredential
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string UserName { get; set; } = string.Empty;
    public byte[] UserId { get; set; }
    public byte[] PublicKey { get; set; }
    public byte[] UserHandle { get; set; }
    public uint SignatureCounter { get; set; }
    public string CredType { get; set; }
    public DateTime RegDate { get; set; }
    public Guid AaGuid { get; set; }

    [NotMapped]
    public PublicKeyCredentialDescriptor Descriptor {
        get =&gt; string.IsNullOrWhiteSpace(DescriptorJson) ? null : JsonSerializer.Deserialize&lt;PublicKeyCredentialDescriptor&gt;(DescriptorJson);
        set =&gt; DescriptorJson = JsonSerializer.Serialize(value);
    }

    public string DescriptorJson { get; set; }
}</code></pre><figcaption>StoredCredential.cs</figcaption></figure><p>Storing the <code>PublicKeyCredentialDescriptor</code> as a JSON string in the <code>DescriptorJson</code> column helps filter public keys easier.</p><p>Note that migration on the database creates a pre-defined user with an <em><strong>Id </strong></em>saved as a byte representation of the user&apos;s email address.</p><p>Registration of a user with a Fido credential is a two-step process. </p><h4 id="makecredentialoptions">MakeCredentialOptions</h4><p>We perform an attestation process to add FIDO2 credentials to an existing user account. It starts with returning options to the client.</p><h4 id="makecredential">MakeCredential</h4><p>When the client returns a response, we verify and register the credentials.</p><p>The following shows two action methods of the <code>Fido2MfaController</code>,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">[Route(&quot;api/[controller]&quot;)]
public class Fido2MfaController : Controller
{
    private readonly IFido2 _fido2;
    private readonly AppDbContext _context;

    public Fido2MfaController(IFido2 fido2, AppDbContext context)
    {
        _fido2 = fido2;
        _context = context;
    }

    private string FormatException(Exception e)
    {
        return $&quot;{e.Message}{(e.InnerException != null ? &quot; (&quot; + e.InnerException.Message + &quot;)&quot; : &quot;&quot;)}&quot;;
    }

    [HttpPost]
    [Route(&quot;/makeCredentialOptions&quot;)]
    public async Task&lt;JsonResult&gt; MakeCredentialOptions([FromForm] string username,
        [FromForm] string displayName,
        [FromForm] string attType,
        [FromForm] string authType,
        [FromForm] string userVerification,
        [FromQuery] bool requireResidentKey)
    {
        try
        {
            // 1. Get user from DB by username (in our example, auto create missing users)
            var user = await _context.Fido2Users.SingleOrDefaultAsync(u =&gt; u.Name.Equals(username));

            if (user is null)
                throw new ArgumentException(&quot;User not found&quot;);

            // 2. Get user existing keys by username
            var existingKeys = await _context.StoredCredentials
                .Where(c =&gt; c.UserId.SequenceEqual(user.Id))
                .Select(c =&gt; c.Descriptor)
                .ToListAsync();

            // 3. Create options
            var authenticatorSelection = new AuthenticatorSelection
            {
                RequireResidentKey = requireResidentKey,
                UserVerification = userVerification.ToEnum&lt;UserVerificationRequirement&gt;()
            };

            if (!string.IsNullOrEmpty(authType))
                authenticatorSelection.AuthenticatorAttachment = authType.ToEnum&lt;AuthenticatorAttachment&gt;();

            var extensions = new AuthenticationExtensionsClientInputs()
            {
                Extensions = true,
                UserVerificationMethod = true,
            };

            var options = _fido2.RequestNewCredential(user, existingKeys, authenticatorSelection,
                attType.ToEnum&lt;AttestationConveyancePreference&gt;(), extensions);

            // 4. Temporarily store options, session/in-memory cache/redis/db
            HttpContext.Session.SetString(&quot;fido2.attestationOptions&quot;, options.ToJson());

            // 5. return options to client
            return Json(options);
        }
        catch (Exception e)
        {
            return Json(new CredentialCreateOptions {Status = &quot;error&quot;, ErrorMessage = FormatException(e)});
        }
    }

    [HttpPost]
    [Route(&quot;/makeCredential&quot;)]
    public async Task&lt;JsonResult&gt; MakeCredential([FromBody] AuthenticatorAttestationRawResponse attestationResponse,
        CancellationToken cancellationToken)
    {
        try
        {
            // 1. get the options we sent the client
            var jsonOptions = HttpContext.Session.GetString(&quot;fido2.attestationOptions&quot;);
            var options = CredentialCreateOptions.FromJson(jsonOptions);

            // 2. Create callback so that lib can verify credential id is unique to this user
            async Task&lt;bool&gt; Callback(IsCredentialIdUniqueToUserParams args, CancellationToken token)
            {
                var credentialIdString = Base64Url.Encode(args.CredentialId);

                var cred = await _context.StoredCredentials
                    .Where(c =&gt; c.DescriptorJson.Contains(credentialIdString))
                    .FirstOrDefaultAsync(token);

                if (cred is null)
                    return true;
                
                var users = await _context.Fido2Users
                    .Where(u =&gt; u.Id.SequenceEqual(cred.UserId))
                    .ToListAsync(token);

                return users.Count &lt;= 0;
            }

            // 2. Verify and make the credentials
            var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, Callback,
                cancellationToken: cancellationToken);

            // 3. Store the credentials in db
            if (success.Result != null)
            {
                var credential = new StoredCredential
                {
                    Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId),
                    PublicKey = success.Result.PublicKey,
                    UserHandle = success.Result.User.Id,
                    SignatureCounter = success.Result.Counter,
                    CredType = success.Result.CredType,
                    RegDate = DateTime.Now,
                    AaGuid = success.Result.Aaguid,
                    UserId = options.User.Id,
                    UserName = options.User.Name,
                };

                await _context.StoredCredentials.AddAsync(credential, cancellationToken);
            }

            await _context.SaveChangesAsync(cancellationToken);

            // 4. return &quot;ok&quot; to the client
            return Json(success);
        }
        catch (Exception e)
        {
            return Json(new Fido2.CredentialMakeResult(status: &quot;error&quot;, errorMessage: FormatException(e),
                result: null));
        }
    }
}    </code></pre><figcaption>Fido2MfaController.cs</figcaption></figure><p>The following snippet shows the corresponding javascript code that calls these two action methods to complete the Fido key registration process,</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">document.getElementById(&apos;register&apos;).addEventListener(&apos;submit&apos;, handleRegisterSubmit);

async function handleRegisterSubmit(event) {
    event.preventDefault();

    let username = this.username.value;
    let displayName = this.displayName.value;

    // passwordfield is omitted in demo
    // let password = this.password.value;

    // possible values: none, direct, indirect
    let attestation_type = &quot;none&quot;;
    // possible values: &lt;empty&gt;, platform, cross-platform
    let authenticator_attachment = &quot;&quot;;

    // possible values: preferred, required, discouraged
    let user_verification = &quot;preferred&quot;;

    // possible values: true,false
    let require_resident_key = &quot;false&quot;;

    // prepare form post data
    var data = new FormData();
    data.append(&apos;username&apos;, username);
    data.append(&apos;displayName&apos;, displayName);
    data.append(&apos;attType&apos;, attestation_type);
    data.append(&apos;authType&apos;, authenticator_attachment);
    data.append(&apos;userVerification&apos;, user_verification);

    // send to server for registering
    let makeCredentialOptions;
    try {
        makeCredentialOptions = await fetchMakeCredentialOptions(data, require_resident_key);

    } catch (e) {
        console.error(e);
        let msg = &quot;Something wen&apos;t really wrong&quot;;
        showErrorAlert(msg);
    }


    console.log(&quot;Credential Options Object&quot;, makeCredentialOptions);

    if (makeCredentialOptions.status !== &quot;ok&quot;) {
        console.log(&quot;Error creating credential options&quot;);
        console.log(makeCredentialOptions.errorMessage);
        showErrorAlert(makeCredentialOptions.errorMessage);
        return;
    }

    // Turn the challenge back into the accepted format of padded base64
    makeCredentialOptions.challenge = coerceToArrayBuffer(makeCredentialOptions.challenge);
    // Turn ID into a UInt8Array Buffer for some reason
    makeCredentialOptions.user.id = coerceToArrayBuffer(makeCredentialOptions.user.id);

    makeCredentialOptions.excludeCredentials = makeCredentialOptions.excludeCredentials.map((c) =&gt; {
        c.id = coerceToArrayBuffer(c.id);
        return c;
    });

    if (makeCredentialOptions.authenticatorSelection.authenticatorAttachment === null) makeCredentialOptions.authenticatorSelection.authenticatorAttachment = undefined;

    console.log(&quot;Credential Options Formatted&quot;, makeCredentialOptions);

    Swal.fire({
        title: &apos;Registering...&apos;,
        text: &apos;Tap your security key to finish registration.&apos;,
        imageUrl: &quot;/images/securitykey.min.svg&quot;,
        showCancelButton: true,
        showConfirmButton: false,
        focusConfirm: false,
        focusCancel: false
    });


    console.log(&quot;Creating PublicKeyCredential...&quot;);

    let newCredential;
    try {
        newCredential = await navigator.credentials.create({
            publicKey: makeCredentialOptions
        });
    } catch (e) {
        var msg = &quot;Could not create credentials in browser. Probably because the username is already registered with your authenticator. Please change username or authenticator.&quot;
        console.error(msg, e);
        showErrorAlert(msg, e);
    }


    console.log(&quot;PublicKeyCredential Created&quot;, newCredential);

    try {
        registerNewCredential(newCredential);
    } catch (e) {
        showErrorAlert(err.message ? err.message : err);
    }
}

async function fetchMakeCredentialOptions(formData, requireResidentKey) {
    let response = await fetch(`/makeCredentialOptions?requireResidentKey=${requireResidentKey}`, {
        method: &apos;POST&apos;, // or &apos;PUT&apos;
        body: formData, // data can be `string` or {object}!
        headers: {
            &apos;Accept&apos;: &apos;application/json&apos;
        }
    });

    let data = await response.json();

    return data;
}


// This should be used to verify the auth data with the server
async function registerNewCredential(newCredential) {
    // Move data into Arrays incase it is super long
    let attestationObject = new Uint8Array(newCredential.response.attestationObject);
    let clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
    let rawId = new Uint8Array(newCredential.rawId);

    const data = {
        id: newCredential.id,
        rawId: coerceToBase64Url(rawId),
        type: newCredential.type,
        extensions: newCredential.getClientExtensionResults(),
        response: {
            AttestationObject: coerceToBase64Url(attestationObject),
            clientDataJson: coerceToBase64Url(clientDataJSON)
        }
    };

    let response;
    try {
        response = await registerCredentialWithServer(data);
    } catch (e) {
        showErrorAlert(e);
    }

    console.log(&quot;Credential Object&quot;, response);

    // show error
    if (response.status !== &quot;ok&quot;) {
        console.log(&quot;Error creating credential&quot;);
        console.log(response.errorMessage);
        showErrorAlert(response.errorMessage);
        return;
    }

    // show success 
    Swal.fire({
        title: &apos;Registration Successful!&apos;,
        text: &apos;You\&apos;ve registered successfully.&apos;,
        type: &apos;success&apos;,
        timer: 2000
    });

    // redirect to dashboard?
    //window.location.href = &quot;/dashboard/&quot; + state.user.displayName;
}

async function registerCredentialWithServer(formData) {
    let response = await fetch(&apos;/makeCredential&apos;, {
        method: &apos;POST&apos;, // or &apos;PUT&apos;
        body: JSON.stringify(formData), // data can be `string` or {object}!
        headers: {
            &apos;Accept&apos;: &apos;application/json&apos;,
            &apos;Content-Type&apos;: &apos;application/json&apos;
        }
    });

    let data = await response.json();

    return data;
}</code></pre><figcaption>mfa.register.js</figcaption></figure><p>Public keys are stored in the <code>StoredCredentials</code> table when a user is registered. Using the Fido key to sign in the user is also a two-step process.</p><h4 id="assertionoptionspost">AssertionOptionsPost</h4><p>When a user wants to log a user in, we do an assertion based on the registered credentials. First, we create the assertion options and return them to the client.</p><h4 id="makeassertion">MakeAssertion</h4><p>When the client returns a response, we verify it and accept the login.</p><p>The following shows two action methods of the <code>Fido2MfaController</code> responsible for signing in the user,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">[Route(&quot;api/[controller]&quot;)]
public class Fido2MfaController : Controller
{
    private readonly IFido2 _fido2;
    private readonly AppDbContext _context;

    public Fido2MfaController(IFido2 fido2, AppDbContext context)
    {
        _fido2 = fido2;
        _context = context;
    }

    private string FormatException(Exception e)
    {
        return $&quot;{e.Message}{(e.InnerException != null ? &quot; (&quot; + e.InnerException.Message + &quot;)&quot; : &quot;&quot;)}&quot;;
    }

    [HttpPost]
    [Route(&quot;/assertionOptions&quot;)]
    public async Task&lt;ActionResult&gt; AssertionOptionsPost([FromForm] string username, [FromForm] string userVerification)
    {
        try
        {
            var existingCredentials = new List&lt;PublicKeyCredentialDescriptor&gt;();

            if (!string.IsNullOrEmpty(username))
            {
                // 1. Get user from DB
                var user = await _context.Fido2Users.SingleOrDefaultAsync(u =&gt; u.Name.Equals(username));

                if (user is null) throw new ArgumentException(&quot;Username was not registered&quot;);

                // 2. Get registered credentials from database
                existingCredentials = await _context.StoredCredentials
                    .Where(c =&gt; c.UserName.Equals(username))
                    .Select(c =&gt; c.Descriptor)
                    .ToListAsync();
            }

            var extensions = new AuthenticationExtensionsClientInputs()
            {
                UserVerificationMethod = true
            };

            // 3. Create options
            var uv = string.IsNullOrEmpty(userVerification)
                ? UserVerificationRequirement.Discouraged
                : userVerification.ToEnum&lt;UserVerificationRequirement&gt;();

            var options = _fido2.GetAssertionOptions(
                existingCredentials,
                uv,
                extensions
            );

            // 4. Temporarily store options, session/in-memory cache/redis/db
            HttpContext.Session.SetString(&quot;fido2.assertionOptions&quot;, options.ToJson());

            // 5. Return options to client
            return Json(options);
        }

        catch (Exception e)
        {
            return Json(new AssertionOptions {Status = &quot;error&quot;, ErrorMessage = FormatException(e)});
        }
    }

    [HttpPost]
    [Route(&quot;/makeAssertion&quot;)]
    public async Task&lt;JsonResult&gt; MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse,
        CancellationToken cancellationToken)
    {
        try
        {
            // 1. Get the assertion options we sent the client
            var jsonOptions = HttpContext.Session.GetString(&quot;fido2.assertionOptions&quot;);
            var options = AssertionOptions.FromJson(jsonOptions);

            // 2. Get registered credential from database
            var credentialIdString = Base64Url.Encode(clientResponse.Id);

            var credential = await _context.StoredCredentials
                .Where(c =&gt; c.DescriptorJson.Contains(credentialIdString)).FirstOrDefaultAsync(cancellationToken);

            if (credential is null) throw new Exception(&quot;Unknown credentials&quot;);

            // 3. Get credential counter from database
            var storedCounter = credential.SignatureCounter;

            // 4. Create callback to check if userhandle owns the credentialId
            async Task&lt;bool&gt; Callback(IsUserHandleOwnerOfCredentialIdParams args, CancellationToken token)
            {
                var storedCredentials = await _context.StoredCredentials
                    .Where(c =&gt; c.UserHandle.SequenceEqual(args.UserHandle)).ToListAsync(cancellationToken);
                return storedCredentials.Exists(c =&gt; c.Descriptor.Id.SequenceEqual(args.CredentialId));
            }

            // 5. Make the assertion
            var res = await _fido2.MakeAssertionAsync(clientResponse, options, credential.PublicKey, storedCounter,
                Callback,
                cancellationToken: cancellationToken);

            // 6. Store the updated counter
            var cred = await _context.StoredCredentials
                .Where(c =&gt; c.DescriptorJson.Contains(credentialIdString)).FirstOrDefaultAsync(cancellationToken);

            cred.SignatureCounter = res.Counter;

            await _context.SaveChangesAsync(cancellationToken);

            // 7. return OK to client
            return Json(res);
        }
        catch (Exception e)
        {
            return Json(new AssertionVerificationResult {Status = &quot;error&quot;, ErrorMessage = FormatException(e)});
        }
    }
}</code></pre><figcaption>Fido2MfaController.cs</figcaption></figure><p>The following snippet shows the corresponding javascript code that calls these two action methods to complete the login process using the Fido key,</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">document.getElementById(&apos;signin&apos;).addEventListener(&apos;submit&apos;, handleSignInSubmit);

async function handleSignInSubmit(event) {
    event.preventDefault();

    let username = this.username.value;

    // passwordfield is omitted in demo
    // let password = this.password.value;


    // prepare form post data
    var formData = new FormData();
    formData.append(&apos;username&apos;, username);

    // not done in demo
    // todo: validate username + password with server (has nothing to do with FIDO2/WebAuthn)

    // send to server for registering
    let makeAssertionOptions;
    try {
        var res = await fetch(&apos;/assertionOptions&apos;, {
            method: &apos;POST&apos;, // or &apos;PUT&apos;
            body: formData, // data can be `string` or {object}!
            headers: {
                &apos;Accept&apos;: &apos;application/json&apos;
            }
        });

        makeAssertionOptions = await res.json();
    } catch (e) {
        showErrorAlert(&quot;Request to server failed&quot;, e);
    }

    console.log(&quot;Assertion Options Object&quot;, makeAssertionOptions);

    // show options error to user
    if (makeAssertionOptions.status !== &quot;ok&quot;) {
        console.log(&quot;Error creating assertion options&quot;);
        console.log(makeAssertionOptions.errorMessage);
        showErrorAlert(makeAssertionOptions.errorMessage);
        return;
    }

    // todo: switch this to coercebase64
    const challenge = makeAssertionOptions.challenge.replace(/-/g, &quot;+&quot;).replace(/_/g, &quot;/&quot;);
    makeAssertionOptions.challenge = Uint8Array.from(atob(challenge), c =&gt; c.charCodeAt(0));

    // fix escaping. Change this to coerce
    makeAssertionOptions.allowCredentials.forEach(function (listItem) {
        var fixedId = listItem.id.replace(/\_/g, &quot;/&quot;).replace(/\-/g, &quot;+&quot;);
        listItem.id = Uint8Array.from(atob(fixedId), c =&gt; c.charCodeAt(0));
    });

    console.log(&quot;Assertion options&quot;, makeAssertionOptions);

    Swal.fire({
        title: &apos;Logging In...&apos;,
        text: &apos;Tap your security key to login.&apos;,
        imageUrl: &quot;/images/securitykey.min.svg&quot;,
        showCancelButton: true,
        showConfirmButton: false,
        focusConfirm: false,
        focusCancel: false
    });

    // ask browser for credentials (browser will ask connected authenticators)
    let credential;
    try {
        credential = await navigator.credentials.get({ publicKey: makeAssertionOptions })
    } catch (err) {
        showErrorAlert(err.message ? err.message : err);
    }

    try {
        await verifyAssertionWithServer(credential);
    } catch (e) {
        showErrorAlert(&quot;Could not verify assertion&quot;, e);
    }
}

/**
 * Sends the credential to the the FIDO2 server for assertion
 * @param {any} assertedCredential
 */
async function verifyAssertionWithServer(assertedCredential) {

    // Move data into Arrays incase it is super long
    let authData = new Uint8Array(assertedCredential.response.authenticatorData);
    let clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON);
    let rawId = new Uint8Array(assertedCredential.rawId);
    let sig = new Uint8Array(assertedCredential.response.signature);
    const data = {
        id: assertedCredential.id,
        rawId: coerceToBase64Url(rawId),
        type: assertedCredential.type,
        extensions: assertedCredential.getClientExtensionResults(),
        response: {
            authenticatorData: coerceToBase64Url(authData),
            clientDataJson: coerceToBase64Url(clientDataJSON),
            signature: coerceToBase64Url(sig)
        }
    };

    let response;
    try {
        let res = await fetch(&quot;/makeAssertion&quot;, {
            method: &apos;POST&apos;, // or &apos;PUT&apos;
            body: JSON.stringify(data), // data can be `string` or {object}!
            headers: {
                &apos;Accept&apos;: &apos;application/json&apos;,
                &apos;Content-Type&apos;: &apos;application/json&apos;
            }
        });

        response = await res.json();
    } catch (e) {
        showErrorAlert(&quot;Request to server failed&quot;, e);
        throw e;
    }

    console.log(&quot;Assertion Object&quot;, response);

    // show error
    if (response.status !== &quot;ok&quot;) {
        console.log(&quot;Error doing assertion&quot;);
        console.log(response.errorMessage);
        showErrorAlert(response.errorMessage);
        return;
    }

    // show success message
    await Swal.fire({
        title: &apos;Logged In!&apos;,
        text: &apos;You\&apos;re logged in successfully.&apos;,
        type: &apos;success&apos;,
        timer: 2000
    });


    // redirect to dashboard to show keys
    window.location.href = &quot;/dashboard/&quot; + value(&quot;#login-username&quot;);
}</code></pre><figcaption>mfa.login.js</figcaption></figure><p>There are other essential files available in the repository that are self-explanatory.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://fiyazhasan.work/content/images/2023/04/page.png" class="kg-image" alt="My Notebook: FIDO2 MFA in ASP.NET Core" loading="lazy" width="1522" height="759" srcset="https://fiyazhasan.work/content/images/size/w600/2023/04/page.png 600w, https://fiyazhasan.work/content/images/size/w1000/2023/04/page.png 1000w, https://fiyazhasan.work/content/images/2023/04/page.png 1522w" sizes="(min-width: 1200px) 1200px"></figure><p>Pressing the <code>Create account</code> button after entering the username and display name (password is not mandatory) will prompt you to set a pin for the Fido device. In my case, it&apos;s the <a href="https://shop.ftsafe.us/products/k9"><strong>FEITIAN ePass FIDO2 FIDO U2F USB-A + NFC Security Key | K9</strong></a></p><p>After you set a pin, it will ask you to touch the key for your biometric data. And the registration process is done. Similarly, in the login section, you enter the username (password is not mandatory) and press the <code>Sign in</code> button to prompt you to touch the Fido key. </p><figure class="kg-card kg-image-card"><img src="https://fiyazhasan.work/content/images/2023/04/signin.png" class="kg-image" alt="My Notebook: FIDO2 MFA in ASP.NET Core" loading="lazy" width="456" height="370"></figure><p>Once done, it will take you directly to the dashboard, where you can see some general information related to the public key,</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://fiyazhasan.work/content/images/2023/04/dashboard.png" class="kg-image" alt="My Notebook: FIDO2 MFA in ASP.NET Core" loading="lazy" width="1502" height="382" srcset="https://fiyazhasan.work/content/images/size/w600/2023/04/dashboard.png 600w, https://fiyazhasan.work/content/images/size/w1000/2023/04/dashboard.png 1000w, https://fiyazhasan.work/content/images/2023/04/dashboard.png 1502w" sizes="(min-width: 1200px) 1200px"></figure><h2 id="repository-link">Repository Link</h2><p><a href="https://github.com/fiyazbinhasan/AspNetCoreFido2MFA">https://github.com/fiyazbinhasan/AspNetCoreFido2MFA</a></p><p>To integrate Fido2 MFA with the Identity platform, take a look at this repository from <a href="https://damienbod.com/">Damien Bowden</a>,</p><p><a href="https://github.com/damienbod/AspNetCoreIdentityFido2Mfa">https://github.com/damienbod/AspNetCoreIdentityFido2Mfa</a></p><h2 id="important-links">Important Links</h2><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Attestation_and_Assertion">Attestation and Assertion</a></p><p><a href=" https://webauthn.guide/">WebAuthn Guide</a></p><p><a href="https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-challenge">WebAuthin WC3 Spec</a></p><p><a href="https://shop.ftsafe.us/products/k9">FEITIAN ePass FIDO2 FIDO U2F USB-A</a></p><p><a href="https://damienbod.com/2022/07/04/add-fido2-mfa-to-an-openiddict-identity-provider-using-asp-net-core-identity/" rel="nofollow">Add Fido2 MFA to an OpenIddict identity provider using ASP.NET Core Identity</a></p>]]></content:encoded></item><item><title><![CDATA[Generic MediatR CRUD Feature]]></title><description><![CDATA[The notion of having a generic mediatR feature is that you get to write less code for a bunch of similar features for each domain entitiy]]></description><link>https://fiyazhasan.work/generic-mediatr-crud-feature/</link><guid isPermaLink="false">62fb4d11723615162418be34</guid><category><![CDATA[Notebook]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Wed, 22 Dec 2021 11:11:52 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019.png" medium="image"/><content:encoded><![CDATA[<img src="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019.png" alt="Generic MediatR CRUD Feature"><p>The notion of having a generic repository is that you get to write less code for a bunch of similar functionalities for each entity. For example,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public interface IRepository&lt;in TEntity&gt; where TEntity : Entity
{
    Task Create(TEntity entity);
    Task&lt;List&lt;TEntity&gt;&gt; Read();
}</code></pre><figcaption>IRepository.cs</figcaption></figure><p>No matter how many types you have, if they extend the <code>Entity</code> base class then you have a common read and write functionality, </p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public class GenericRepository&lt;TEntity&gt; : 
    IRepository&lt;TEntity&gt;
    where TEntity : Entity
{
    private readonly ApplicationDbContext _dbContext;

    public GenericRepository(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    public Task Create(TEntity entity)
    {
        _dbContext.Set&lt;TEntity&gt;().AddAsync(entity);
        return _dbContext.SaveChangesAsync();
    }

    public Task&lt;List&lt;TEntity&gt;&gt; Read()
    {
        return _dbContext.Set&lt;TEntity&gt;().ToListAsync();
    }
}</code></pre><figcaption>GenericRepository.cs</figcaption></figure><p>The entity we have in our hand is the <code>WeatherForecast</code> which as stated earlier entends the base <code>Entity</code> class,</p><pre><code class="language-csharp">public abstract class Entity
{
    public int Id { get; set; }
}

public class WeatherForecast : Entity
{
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public string? Summary { get; set; }
}</code></pre><p>In the realm of CQRS, we should consider seperating the read and write functionalities into their respective command and query featues. Using <a href="https://github.com/jbogard/MediatR">MediatR</a>, you would want to create two features to tackle this,</p><pre><code>-  Features                    
  |
   -  Weather                     // Feature for a specific domain entity
     |
      -  Create                   // Generalized command featue
     |  |
     |   - Command             
     |   - CommandHandler
     |   - IRepository            // Feature level repository contract
     | 
      -  Read                     // Generalized query featue
        |
         - Query
         - QueryHandler
         - IRepository</code></pre><p>Here comes the time you should get your thinking cap on! What happens if you a are adding another domain entity? Let&apos;s just say the new entity is <code>FooBar</code>. You end up with a new enity specific feature folder with same old read and write features,</p><pre><code>-  Features                    
  | 
   -  Weather                     // Feature for a specific domain entity
  |  |
  |   -  Create                   // Generalized command featue
  |  |  |
  |  |   - Command             
  |  |   - CommandHandler
  |  |   - IRepository            // Feature level repository contract
  |  | 
  |   -  Read                     // Generalized query featue
  |     |
  |      - Query
  |      - QueryHandler
  |      - IRepository
  |
   -  Foobar                      // Feature for a specific domain entity
     |
      -  Create                   // Generalized command featue
     |  |
     |   - Command             
     |   - CommandHandler
     |   - IRepository            // Feature level repository contract
     | 
      -  Read                     // Generalized query featue
        |
         - Query
         - QueryHandler
         - IRepository</code></pre><p>It completely depends on you whether to go with the pain of creating individual features or make a single generic one to rule them all.</p><p>For the lack of a better name, let&apos;s call the generic feature <code>Crud</code>,</p><pre><code>-  Features                    
  |
   -  Crud                        // Generic feature for all domain entity
     |
      -  Create                   // Generalized command featue
     |  |
     |   - Command             
     |   - CommandHandler
     |   - IRepository            // Feature level repository contract
     | 
      -  Read                     // Generalized query featue
        |
         - Query
         - QueryHandler
         - IRepository</code></pre><p>The generic <code>Query</code> should look like this,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using GenericMediatRFeatures.Entities;
using MediatR;

namespace GenericMediatRFeatures.Features.Crud.Read;

public partial class Read
{
    public record Query&lt;TEntity&gt; : IRequest&lt;List&lt;TEntity&gt;&gt; where TEntity : Entity;
}
</code></pre><figcaption>Query.cs</figcaption></figure><p>The generic <code>QueryHandler</code>,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using AutoMapper;
using GenericMediatRFeatures.Entities;
using GenericMediatRFeatures.ViewModels;
using MediatR;

namespace GenericMediatRFeatures.Features.Crud.Read;

public partial class Read
{
    public class QueryHandler&lt;TEntity&gt; : IRequestHandler&lt;Query&lt;TEntity&gt;, List&lt;TEntity&gt;&gt; 
        where TEntity : Entity
    {
        private readonly IRepository&lt;TEntity&gt; _repository;

        public QueryHandler(IRepository&lt;TEntity&gt; repository)
        {
            _repository = repository;
        }
        
        public async Task&lt;List&lt;TEntity&gt;&gt; Handle(Query&lt;TEntity&gt; request, CancellationToken cancellationToken)
        {
            return await _repository.Read();
        }
    }
}
</code></pre><figcaption>QueryHandler.cs</figcaption></figure><p>Feature level generic repository contract.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using GenericMediatRFeatures.Entities;

namespace GenericMediatRFeatures.Features.Crud.Read;

public partial class Read
{
    public interface IRepository&lt;TEntity&gt; where TEntity : Entity
    {
        Task&lt;List&lt;TEntity&gt;&gt; Read();
    }
}</code></pre><figcaption>IRepository.cs</figcaption></figure><p>Generally you would register all the handlers with the DI container just by calling,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMediatR(Assembly.GetExecutingAssembly());</code></pre><figcaption>Program.cs</figcaption></figure><p>But in the case of our generic handlers this won&apos;t work as it needs to know the specific type for which you are writing the handler. To overcome this problem, register these hanglers explicity,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMediatR(Assembly.GetExecutingAssembly());

builder.Services.AddTransient(typeof(IRequestHandler&lt;Read.Query&lt;WeatherForecast&gt;, List&lt;WeatherForecast&gt;&gt;),
    typeof(Read.QueryHandler&lt;WeatherForecast&gt;));</code></pre><figcaption>Program.cs</figcaption></figure><p>To call this handler from a specific controller action, inject the <code>IMediator</code> interface in the constructor and call the feature as shown below,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">private readonly IMediator _mediator;

public WeatherForecastController(IMediator mediator)
{
    _mediator = mediator;
}

[HttpGet(Name = &quot;GetWeatherForecast&quot;)]
public Task&lt;List&lt;WeatherForecast&gt;&gt; Get() =&gt; _mediator.Send(new Read.Query&lt;WeatherForecast&gt;());
</code></pre><figcaption>WeatherForecastController.cs</figcaption></figure><h3 id="bonus-">Bonus:</h3><p>You might not want to expose an entity directly from a controller. In that scenario, a view-model/dto could come in handy. The mapping of the entity to a view-model could be done inside the handlers. You would want to modify your <code>Query</code> as follows,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public partial class Read
{
    public record Query&lt;TModel&gt; : IRequest&lt;List&lt;TModel&gt;&gt; where TModel : ViewModel { }
}</code></pre><figcaption>Query.cs</figcaption></figure><p>The handler should be modified as follows,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public partial class Read
{
    public class QueryHandler&lt;TEntity, TModel&gt; : IRequestHandler&lt;Query&lt;TModel&gt;, List&lt;TModel&gt;&gt; 
        where TEntity : Entity
        where TModel: ViewModel
    {
        private readonly IRepository&lt;TEntity&gt; _repository;
        private readonly IMapper _mapper;

        public QueryHandler(IRepository&lt;TEntity&gt; repository, IMapper mapper)
        {
            _repository = repository;
            _mapper = mapper;
        }
        
        public async Task&lt;List&lt;TModel&gt;&gt; Handle(Query&lt;TModel&gt; request, CancellationToken cancellationToken)
        {
            return _mapper.Map&lt;List&lt;TModel&gt;&gt;(await _repository.Read());
        }
    }
}</code></pre><figcaption>QueryHandler.cs</figcaption></figure><blockquote><a href="https://automapper.org/">AutoMapper</a> &#xA0;for mapping an entity to a view-model/dto</blockquote><p>The registration of the handler is also modified,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMediatR(Assembly.GetExecutingAssembly());

builder.Services.AddTransient(typeof(IRequestHandler&lt;Read.Query&lt;WeatherForecastModel&gt;, List&lt;WeatherForecastModel&gt;&gt;),
    typeof(Read.QueryHandler&lt;WeatherForecast, WeatherForecastModel&gt;));</code></pre><figcaption>Program.cs</figcaption></figure><p>The <code>WeatherForecastModel</code> view-model is nothing but a `POCO `exposing only the properties that are intended. To make the mapping work, <code>Automapper</code> needs a mapping configuration as follows,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public partial class Read
{
    public class MapperConfiguration : Profile
    {
        public MapperConfiguration()
        {
            CreateMap&lt;WeatherForecast, WeatherForecastModel&gt;(MemberList.Destination)
                .ForMember(d =&gt; d.TemperatureF, opts =&gt; opts.MapFrom(s =&gt; 32 + (int) (s.TemperatureC / 0.5556)));
        }
    }
}</code></pre><figcaption>MapperConfiguration.cs</figcaption></figure><blockquote>Here, <code>TemperatureF</code> is a computed property which is only available in the view-model</blockquote><p>One last thing you would want to do is to register <code>Automapper</code> services in the DI container,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());</code></pre><figcaption>Program.cs</figcaption></figure><p>Just like before, call the feature from a controller&apos;s action method, </p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">[HttpGet(Name = &quot;GetWeatherForecast&quot;)]
public Task&lt;List&lt;WeatherForecastModel&gt;&gt; Get() =&gt; _mediator.Send(new Read.Query&lt;WeatherForecastModel&gt;());</code></pre><figcaption>WeatherForecastController.cs</figcaption></figure><p>And here you have it; a generic feature to handle all crud related operations for every entiy. Browse the repository source code to find the implementation of the generic <code>Write</code> command which is pretty similar to the <code>Read</code> feature that I&apos;ve demonstrated.</p><p>Repository:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/fiyazbinhasan/GenericMediatRFeatures"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - fiyazbinhasan/GenericMediatRFeatures</div><div class="kg-bookmark-description">Contribute to fiyazbinhasan/GenericMediatRFeatures development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Generic MediatR CRUD Feature"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">fiyazbinhasan</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/60e63e562abcf9ddbe9ee6822cd9bf9110a71763258b9e90e5593a0b7e7817f1/fiyazbinhasan/GenericMediatRFeatures" alt="Generic MediatR CRUD Feature"></div></a></figure><p>Links:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://automapper.org/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">AutoMapper</div><div class="kg-bookmark-description">AutoMapper : A convention-based object-object mapper. 100% organic and gluten-free. Takes out all of the fuss of mapping one object to another.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://automapper.org/favicon.ico" alt="Generic MediatR CRUD Feature"></div></div><div class="kg-bookmark-thumbnail"><img src="https://automapper.org/images/black_logo.png" alt="Generic MediatR CRUD Feature"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/jbogard/MediatR/wiki"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Home &#xB7; jbogard/MediatR Wiki</div><div class="kg-bookmark-description">Simple, unambitious mediator implementation in .NET - Home &#xB7; jbogard/MediatR Wiki</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Generic MediatR CRUD Feature"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">jbogard</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/b171283ae3270dc79d4e7338453e8bfb8b9fbfe54260e537f376a00fde600e3f/jbogard/MediatR" alt="Generic MediatR CRUD Feature"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[MassTransit Mediator with ASP.NET Core]]></title><description><![CDATA[Mediator is one of the 23 design patterns stated inside the GOF Design Patterns book. All it does is encapsulates communication between objects, hence no coupling. It falls into the object behavior patterns category. MassTransit has a mediator implementation. Let's talk about it.]]></description><link>https://fiyazhasan.work/masstransit-mediator-with-aspnetcore/</link><guid isPermaLink="false">62fb4d11723615162418be33</guid><category><![CDATA[Notebook]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Sun, 03 Oct 2021 17:02:19 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019--1-.png" medium="image"/><content:encoded><![CDATA[<img src="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019--1-.png" alt="MassTransit Mediator with ASP.NET Core"><p><a href="https://en.wikipedia.org/wiki/Mediator_pattern">Mediator</a> is one of the 23 design patterns stated inside the <code>GOF</code> <a href="https://en.wikipedia.org/wiki/Design_Patterns">Design Patterns</a> book. All it does is encapsulates the communication between objects, hence <strong><em>no</em></strong> coupling. It falls into the object behavior patterns category.</p><p>Nevertheless, this pattern is widely used in many productions ready libraries; <a href="https://github.com/jbogard/MediatR">MediatR</a> is such as one. Recently I&apos;ve been dwelling in the world of messaging frameworks. In the land of so many awesome libraries, one caught my eyes. Ladies and gentlemen, I present to you <a href="https://masstransit-project.com/">MassTransit</a>.</p><p>Anyways, I was glad to find out that <code>MassTransit</code> has a Mediator implementation that may/may not (depends on your use case) replace the need of <code>MediatR</code>. </p><p>MassTransit Mediator runs in-process and in-memory, no transport (RabbitMQ, Service Bus, etc.) is required i.e. pretty similar to MediatR. So, why using two separate libraries when you can use one? Am I right or am I right?</p><p>No more talking. Here goes an example. </p><ul><li>Start with the default ASP.NET Core Web API project. I&apos;m using .NET 6.</li><li>Add <code><a href="https://www.nuget.org/packages/MassTransit.AspNetCore/">MassTransit.AspNetCore</a></code> package from NuGet.</li><li>Configure the MassTransit Mediator services as follows,</li></ul><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using MassTransit;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMediator();

/* Code removed for brevity */

var app = builder.Build();

/* Code removed for brevity */

app.Run();</code></pre><figcaption>Program.cs</figcaption></figure><p>MassTransit is consumer-based, unlike MediatR that uses <code>RequestHandlers</code> for handling <code>Queries</code> and <code>Commands</code>. You can write a consumer for <code>GetWeatherForecast</code> like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using MassTransit;

namespace WeatherAPI;

public class GetWeatherForecastConsumer : IConsumer&lt;GetWeatherForecasts&gt;
{
    private static readonly string[] Summaries = new[] 
    {
        &quot;Freezing&quot;, &quot;Bracing&quot;, &quot;Chilly&quot;, &quot;Cool&quot;, &quot;Mild&quot;, &quot;Warm&quot;, &quot;Balmy&quot;, &quot;Hot&quot;, &quot;Sweltering&quot;, &quot;Scorching&quot;
    };

    public async Task Consume(ConsumeContext&lt;GetWeatherForecasts&gt; context)
    {
        var forecasts = Enumerable.Range(1, 5).Select(index =&gt; new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        }).ToArray();

        await context.RespondAsync&lt;WeatherForecasts&gt;(new
        {
            Forecasts = forecasts
        });
    }
}</code></pre><figcaption>GetWeatherForecastConsumer.cs</figcaption></figure><p>Both <code>GetWeatherForecasts</code> and <code>WeatherForecasts</code> are message contracts. In MassTransit, contracts are simply interfaces,</p><pre><code class="language-csharp">public interface GetWeatherForecasts
{
}

public interface WeatherForecasts
{
    WeatherForecast[] Forecasts {  get; }
}</code></pre><p><code>GetWeatherForecasts</code> is empty cause we ain&apos;t passing any input arguments to get the forecasts.</p><p>Getting back to the <code>WeatherForecastController</code>, a request client is needed to get a response from the <code>GetWeatherForecastConsumer</code>,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using MassTransit;
using Microsoft.AspNetCore.Mvc;

namespace WeatherAPI.Controllers
{
    [ApiController]
    [Route(&quot;[controller]&quot;)]
    public class WeatherForecastController : ControllerBase
    {
        private readonly IRequestClient&lt;GetWeatherForecasts&gt; _requestClient;

        public WeatherForecastController(IRequestClient&lt;GetWeatherForecasts&gt; requestClient)
        {
            _requestClient = requestClient;
        }

        [HttpGet]
        public async Task&lt;IEnumerable&lt;WeatherForecast&gt;&gt; Get()
        {
            var response = await _requestClient.GetResponse&lt;WeatherForecasts&gt;(new { });
            return response.Message.Forecasts;
        }
    }
}</code></pre><figcaption>WeatherForecastController.cs</figcaption></figure><p>Pretty much done accept for the service registrations for <code>GetWeatherForecastConsumer</code> and <code>IRequestClient&lt;GetWeatherForecats&gt;</code>. Going back to the <code>Program.cs</code>, configure the <code>AddMediator</code> service as follows,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">builder.Services.AddMediator(cfg =&gt;
{
    cfg.AddConsumers(Assembly.GetExecutingAssembly());
    cfg.AddRequestClient&lt;GetWeatherForecasts&gt;();
});</code></pre><figcaption>Program.cs</figcaption></figure><p><code>AddConsumers(Assembly.GetExecutingAssembly())</code> will register all the consumers available in the current assembly. <code>AddRequestClient&lt;GetWeatherForecasts&gt;()</code> will register a request client for <code>GetWeatherForecasts</code>.</p><p>Done! Run the application and everything should work properly.</p><h3 id="extras-">Extras:</h3><p>Wondering whether you can register request clients on demand? Sure, you can! Inject the <code>IMediator</code> interface in the constructor of <code>WeatherForecastController</code> and use it to create a request client like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using MassTransit.Mediator;
using Microsoft.AspNetCore.Mvc;

namespace WeatherAPI.Controllers
{
    [ApiController]
    [Route(&quot;[controller]&quot;)]
    public class WeatherForecastController : ControllerBase
    {
        private readonly IMediator _mediator;

        public WeatherForecastController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        public async Task&lt;IEnumerable&lt;WeatherForecast&gt;&gt; Get()
        {
            var client = _mediator.CreateRequestClient&lt;GetWeatherForecasts&gt;();

            var response = await client.GetResponse&lt;WeatherForecasts&gt;(new { });
            return response.Message.Forecasts;
        }
    }
}</code></pre><figcaption>WeatherForecastController.cs</figcaption></figure><p>Remove the <code>AddRequestClient&lt;GetWeatherForecasts&gt;()</code> from <code>Program.cs</code> as it is no longer needed.</p><h3 id="github-repository-">Github Repository:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/fiyazbinhasan/MassTransitMediator"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - fiyazbinhasan/MassTransitMediator</div><div class="kg-bookmark-description">Contribute to fiyazbinhasan/MassTransitMediator development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="MassTransit Mediator with ASP.NET Core"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">fiyazbinhasan</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/96671f5fc94f3db2c71241c3344bd23135eaf643464fa56200fcc622cddc3c09/fiyazbinhasan/MassTransitMediator" alt="MassTransit Mediator with ASP.NET Core"></div></a></figure><h3 id="important-links-">Important Links:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://masstransit-project.com/usage/mediator.html"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Mediator | MassTransit</div><div class="kg-bookmark-description">A free, open-source distributed application framework for .NET.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://masstransit-project.com/apple-touch-icon.png" alt="MassTransit Mediator with ASP.NET Core"><span class="kg-bookmark-author">MassTransit</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://masstransit-project.com/mt-logo-small.png" alt="MassTransit Mediator with ASP.NET Core"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://masstransit-project.com/usage/requests.html"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Requests | MassTransit</div><div class="kg-bookmark-description">A free, open-source distributed application framework for .NET.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://masstransit-project.com/apple-touch-icon.png" alt="MassTransit Mediator with ASP.NET Core"><span class="kg-bookmark-author">MassTransit</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://masstransit-project.com/mt-logo-small.png" alt="MassTransit Mediator with ASP.NET Core"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://masstransit-project.com/usage/consumers.html#consumer"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Consumers | MassTransit</div><div class="kg-bookmark-description">A free, open-source distributed application framework for .NET.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://masstransit-project.com/apple-touch-icon.png" alt="MassTransit Mediator with ASP.NET Core"><span class="kg-bookmark-author">MassTransit</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://masstransit-project.com/mt-logo-small.png" alt="MassTransit Mediator with ASP.NET Core"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://masstransit-project.com/learn/analyzers.html#message-initializers"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Roslyn Analyzers | MassTransit</div><div class="kg-bookmark-description">A free, open-source distributed application framework for .NET.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://masstransit-project.com/apple-touch-icon.png" alt="MassTransit Mediator with ASP.NET Core"><span class="kg-bookmark-author">MassTransit</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://masstransit-project.com/mt-logo-small.png" alt="MassTransit Mediator with ASP.NET Core"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Cache Headers for Static Files & File Action Results]]></title><description><![CDATA[There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton

Learn how to add cache headers for static files and file action results.]]></description><link>https://fiyazhasan.work/cache-headers-for-static-files-and-file-action-results/</link><guid isPermaLink="false">62fb4d11723615162418be32</guid><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Mon, 05 Jul 2021 13:31:27 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019-1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><a href="https://github.com/fiyazbinhasan/RangeEtagsLastModified">
  <img align="center" width="100%" src="https://github-readme-stats.vercel.app/api/pin/?username=fiyazbinhasan&amp;repo=RangeEtagsLastModified" alt="Cache Headers for Static Files &amp; File Action Results">
</a>

<br>
<br><!--kg-card-end: html--><blockquote>There are only two hard things in Computer Science: cache invalidation and naming things. <em>- Phil Karlton</em></blockquote><h3 id="static-files-">Static Files:</h3><img src="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019-1.png" alt="Cache Headers for Static Files &amp; File Action Results"><p>The <code>StaticFiles</code> middleware is responsible for serving static files (CSS, javascript, image, etc.) from a <strong><em>configurable</em></strong> file location (typically from the <code>wwwroot</code> folder). A simple <code>HTTP</code> call for a static file would look something like the following,</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mdSStHqIkIfC4XaRLyl2hgycRcYXLrDh7zTMAaOR9YlDZKjy7vK9icGnXLAiPJby4jGg3olkXrqh5x48x9EMqmp0AjJnLIgZwIuhZwNQHtPt9xvYdYs3M9hQxeN0CFR-Is9ixKiB22dI2Dvn6ICbbcBkV_vGROEjvbIbjvr3edGGdJ-a6kbu9Ns7qVMfNRM2n?width=1085&amp;height=417&amp;cropmode=none" class="kg-image" alt="Cache Headers for Static Files &amp; File Action Results" loading="lazy"></figure><p>If you try the same thing from a browser, for successive requests you will get a <code>304 Not Modified</code>. That is because of the <code>ETag</code> and <code>Last-Modified</code> headers. By default, the <code>StaticFiles</code> middleware uses these headers for cache invalidation purposes.</p><p>The integrity of the cache is &#xA0;assessed by comparing the <code>If-Modified-Since</code> and <code>If-None-Match</code> header values with the previously sent <code>Etag</code> and <code>Last-Modified</code> header values respectively. If it matches; means our cache is still valid and the server sends a <code>304 Not Modified</code> status to the browser. On the browser&apos;s end, it handles the status by serving up the static file from its cache.</p><blockquote>The reason behind taking an approach like this is that you don&apos;t want your server getting worked up for handling a file that doesn&apos;t change frequently.</blockquote><p>You might be wondering, to check the integrity of the cache, we are calling the server again. So how does it benefit me? Although it&apos;s a new <code>HTTP</code> request, the request payload is much less than a full-body request. It checks if the request passes cache validation. A valid cache will make a request to opt-out of further processing.</p><p>Along the way, if you want to have total control over the caching of a static file, you can configure the <code>StaticFiles</code> middleware with <code>StaticFileOptions</code>. The following will remove the <code>ETag</code> and <code>Last-Modified</code> headers and only add a <code>Cache-Control</code> header, </p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">app.UseStaticFiles(new StaticFileOptions()
{
    OnPrepareResponse = ctx =&gt;
    {
        ctx.Context.Response.Headers.Remove(&quot;ETag&quot;);
        ctx.Context.Response.Headers.Remove(&quot;Last-Modified&quot;);
        ctx.Context.Response.Headers.Append(&quot;Cache-Control&quot;, &quot;private,max-age=100&quot;);
    }
});</code></pre><figcaption>Startup.cs</figcaption></figure><p>The cache invalidation part is now gone forever, and the file is in the <code>memory-cache</code> for <code>100</code> seconds only. After <code>100</code> seconds, the browser will again request the server to get the file like it was the first time.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mV2ZNMZsCvp4tjqGQwgs_RIKXF4tVt2LwPITLIXl0RpetH1HWDBlG3UpAdKbkaAcEFIcKf-GX3qBBf-bcSejUgJLLrT1d3fQRzcLyUBqywLZKlmxzk0IZl4ucztcNrSlJqZy-ySACKRtiY9K53emap8M1_I5nE4vmzvT-CkUyPxlqcggTPs7Odqaw90W1kIoj?width=1085&amp;height=439&amp;cropmode=none" class="kg-image" alt="Cache Headers for Static Files &amp; File Action Results" loading="lazy"></figure><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mwBI6F6t4b7YFaqXU-6ZX6hPE3QLVpeMBRs3Cc9PJaAojZl0I6QBTYsMMEafH-ZRRzpU_Vun8q7CEUQ3ohNlpF7S3s5M9Cem9vRrX2LBQSCqtq6iDzJ96jR8e38_X4NsZqVgNbHcbodU44n8Q8fIAJHYMIaNA5bbDJjiTizt1IKtid7g2FH5XLyE-nK4LXviz?width=1042&amp;height=377&amp;cropmode=none" class="kg-image" alt="Cache Headers for Static Files &amp; File Action Results" loading="lazy"></figure><h3 id="file-action-results-">File Action Results:</h3><p>You typically use one of the following <code>FileResult</code> to write a file to the response,</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Method</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>FileContentResult</td>
<td>Sends the contents of a binary file to the response.</td>
</tr>
<tr>
<td>FileStreamResult</td>
<td>Represents an ActionResult that when executed will write a file from a stream to the response.</td>
</tr>
<tr>
<td>VirtualFileResult</td>
<td>A FileResult that on execution writes the file specified using a virtual path to the response using mechanisms provided by the host.</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>All of these <code>FileResult</code> inherit from the <code>IActionResult</code> interface. So you would want to use a less derived type for the return type,</p><pre><code class="language-csharp">public IActionResult Download()
{
    return File(&quot;/assets/guitar.mp3&quot;, 
        &quot;audio/mpeg&quot;, 
        &quot;guitar.mp3&quot;);
}</code></pre><p>Using <code>FileResult</code> in earlier versions of .NET Core (<strong>1.*</strong>), you could have served files. However, there wasn&apos;t any way of configuring <em><strong>cache</strong></em> headers for the responses.</p><p>Now, <code>FileResult</code> can be overloaded with the following parameters,</p><ul><li>DateTimeOffset? lastModified</li><li>EntityTagHeaderValue entityTag</li><li>bool enableRangeProcessing</li></ul><p>With help of these parameters now you can add <em><strong>cache headers </strong></em>to any file that you want to write to the response. Here is an example of how you will do it,</p><pre><code class="language-csharp">[HttpGet(&quot;download&quot;)]
[ResponseCache(Duration = 10, Location = ResponseCacheLocation.Client)]
public IActionResult Download()
{
    return File(&quot;/assets/guitar.mp3&quot;, 
        &quot;audio/mpeg&quot;, 
        &quot;guitar.mp3&quot;,
        lastModified: DateTime.UtcNow.AddSeconds(-5),
        entityTag: new EntityTagHeaderValue($&quot;\&quot;{Convert.ToBase64String(Guid.NewGuid().ToByteArray())}\&quot;&quot;),
        enableRangeProcessing: true);
}</code></pre><p>This is just a simple example. In production, you would set <code>entityTag</code> and <code>lastModified</code> to some real-world values.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mFVWqiyGeYraMhR5L18e0I3dekrG0BvDrUr-SEe-r_ilhp2K-Y9TRgt7VdexY-wUNOY71t-ZKAtC-QUbKzA9ZyrQM0xOoDPd1oekFrT50Ul5_R7nYYHohd5VEBLYEPlqWowkAB0hRTK8tjmY0nf-vwGtyGhIo1WENn6nEVgQllsbcfF8RfsGOBdEVN5h9JQb5?width=1087&amp;height=457&amp;cropmode=none" class="kg-image" alt="Cache Headers for Static Files &amp; File Action Results" loading="lazy"></figure><blockquote>You can take a look at the <code>AvatarController</code> to see how to validate an in-memory cache entry using <code>Etag</code></blockquote><p>How do you add other <strong><em>cache headers </em></strong>like <code>Cache-Control</code> in this scenario? Well you can use the <a href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-5.0">Response Caching Middleware</a> and decorate your action with the <code>ResponseCache</code> attribute like the following,</p><pre><code class="language-csharp">[HttpGet(&quot;download&quot;)]
[ResponseCache(Duration = 10, Location = ResponseCacheLocation.Client)]
public IActionResult Download()
{
    return File(&quot;/assets/guitar.mp3&quot;,
        &quot;audio/mpeg&quot;,
        &quot;guitar.mp3&quot;,
        lastModified: DateTime.UtcNow.AddSeconds(-5),
         entityTag: new EntityTagHeaderValue($&quot;\&quot;{Convert.ToBase64String(Guid.NewGuid().ToByteArray())}\&quot;&quot;),
        enableRangeProcessing: true);
}</code></pre><p>For static files, the value for <code>entityTag</code> and <code>lastModified</code> is calculated using the following code snippet,</p><pre><code class="language-csharp">if (_fileInfo.Exists)
{
    _length = _fileInfo.Length;

    DateTimeOffset last = _fileInfo.LastModified;
            // Truncate to the second.
    _lastModified = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset).ToUniversalTime();

     long etagHash = _lastModified.ToFileTime() ^ _length;
     _etag = new EntityTagHeaderValue(&apos;\&quot;&apos; + Convert.ToString(etagHash, 16) + &apos;\&quot;&apos;);
}</code></pre><p>Anyways, you can also request partial resources by adding a <code>Range</code> header in the request. The value of the header would be a <code>bytes=to-from</code> range. Following is an example of the <code>Range</code> header in action,</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mMuoZQKZR3tdlPRnU6lNzCqk_VzPVZCNIkAuPqieN5YTWtNVUTMqFxGhrnCuBjmPQ7WnI24Z5LildssWdX_nUO00bAMZOUzb2bQDE8MnUvmwwoqutuNz2KUqHNHrSSmnl8skP56znar_BOAwj9CCv4si2LB7RfjT1Sa6DneJcTTm6olxTASA9VfcTI8ltG8aN?width=1086&amp;height=490&amp;cropmode=none" class="kg-image" alt="Cache Headers for Static Files &amp; File Action Results" loading="lazy"></figure><blockquote>Note: every resource can be partially delivered. Some partial requests can make the resource unreadable/corrupted. So, use the <code>Range</code> header wisely.</blockquote><h3 id="repository-link-">Repository Link:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/fiyazbinhasan/RangeEtagsLastModified"><div class="kg-bookmark-content"><div class="kg-bookmark-title">fiyazbinhasan/RangeEtagsLastModified</div><div class="kg-bookmark-description">Contribute to fiyazbinhasan/RangeEtagsLastModified development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Cache Headers for Static Files &amp; File Action Results"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">fiyazbinhasan</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/ee5b43c750683ab6f94b88121a14b74395d4e4ae5bbe7e135ab85da94da8d6b8/fiyazbinhasan/RangeEtagsLastModified" alt="Cache Headers for Static Files &amp; File Action Results"></div></a></figure><h3 id="important-links-">Important Links:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-5.0"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Response Caching Middleware in ASP.NET Core</div><div class="kg-bookmark-description">Learn how to configure and use Response Caching Middleware in ASP.NET Core.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">Microsoft Docs</span><span class="kg-bookmark-publisher">Rick-Anderson</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.microsoft.com/en-us/media/logos/logo-ms-social.png" alt="Cache Headers for Static Files &amp; File Action Results"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.linkedin.com/feed/update/urn:li:activity:6693501335150694400/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Fiyaz Bin Hasan (Fizz) on LinkedIn: Caching - Ins &amp; Outs - Fiyaz Hasan</div><div class="kg-bookmark-description">At #Cefalo, it&#x2019;s not always about coding! Sometimes we do take time for R&amp;D and stuff on topics we are interested in. Then we share our insights on what...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static-exp1.licdn.com/sc/h/al2o9zrvru7aqj8e1x2rzsrca" alt="Cache Headers for Static Files &amp; File Action Results"><span class="kg-bookmark-author">LinkedIn</span><span class="kg-bookmark-publisher">334 Posts</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://static-exp1.licdn.com/sc/h/c45fy346jw096z9pbphyyhdz7" alt="Cache Headers for Static Files &amp; File Action Results"></div></a></figure><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching">https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching</a></p>]]></content:encoded></item><item><title><![CDATA[Blazor Flash Cards - State Management with Fluxor]]></title><description><![CDATA[I'm a massive fan of the Redux pattern. One thing, though, it adds extra boilerplate code and may look overwhelming at first. But you get used to it and start to appreciate the power of it once your application grows to be a giant where state management becomes a nightmare.]]></description><link>https://fiyazhasan.work/state-management-with-fluxor/</link><guid isPermaLink="false">62fb4d11723615162418be31</guid><category><![CDATA[Blazor]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Sat, 03 Jul 2021 18:35:20 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019--1--1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><a href="https://github.com/fiyazbinhasan/FlashCards">
  <img align="center" width="100%" src="https://github-readme-stats.vercel.app/api/pin/?username=fiyazbinhasan&amp;repo=FlashCards" alt="Blazor Flash Cards - State Management with Fluxor">
</a>

<br>
<br><!--kg-card-end: html--><img src="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019--1--1.png" alt="Blazor Flash Cards - State Management with Fluxor"><p>I&apos;m a massive fan of the <a href="https://redux.js.org/understanding/thinking-in-redux/three-principles">Redux pattern</a>. I would say one thing, though, it&apos;s not everyone&apos;s cup of tea. It adds extra boilerplate code and may look overwhelming at first. But you get used to it and start to appreciate its power once your application grows to be a giant where state management becomes a nightmare.</p><p>Talking of sidekicks, <a href="https://reactjs.org/">React</a> has <a href="https://redux.js.org/">Redux</a>; <a href="https://angular.io/">Angular</a> has <a href="https://ngrx.io/">NGRX</a>; what does <a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor">Blazor</a> have? I&apos;m really into this library called <a href="https://github.com/mrpmorris/Fluxor">Fluxor</a> recently. You can carry over your existing knowledge of any <code>Redux</code> based state management library over here, and you are good to go.</p><p>You start by adding the following packages to your Blazor Server/WASM application,</p><pre><code>dotnet add package Fluxor.Blazor.Web

dotnet add package Fluxor.Blazor.Web.ReduxDevTools</code></pre><p>Register <code>Fluxor</code> services with the default <code>IoC</code> container. You would do that in the <code>Program.cs</code> file,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    
    /* Code removed for brevity */

    builder.Services.AddFluxor(opt =&gt;
    {
        opt.ScanAssemblies(typeof(Program).Assembly);
        opt.UseRouting();
        opt.UseReduxDevTools();
    });

    await builder.Build().RunAsync();
}</code></pre><figcaption>Program.cs</figcaption></figure><p>Inside <code>index.html</code>, add the script reference for <code>Fluxor.Blazor.Web</code>,</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;

&lt;head&gt;
    &lt;!-- Code removed for brevity --&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;!-- Code removed for brevity --&gt;
    
    &lt;script src=&quot;_framework/blazor.webassembly.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;_content/Fluxor.Blazor.Web/scripts/index.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;</code></pre><figcaption>index.html</figcaption></figure><h3 id="store-">Store:</h3><p>As the name suggests, a store persists in an application state tree, i.e., the build-out of different feature states. The <code>Store</code> has to be initialized when the application first kicks off. Hence, it is best to put out the <code>Store</code> initialization logic in the <code>App</code> component,</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;Fluxor.Blazor.Web.StoreInitializer /&gt;

&lt;Router AppAssembly=&quot;@typeof(Program).Assembly&quot;&gt;
    &lt;!-- Code remve for brevity --&gt;
&lt;/Router&gt;
</code></pre><figcaption>App.razor</figcaption></figure><h3 id="feature-">Feature:</h3><p><code>Feature</code> is an area that provides a new piece of state for the <code>Store</code>. To declare a feature, you would want to extend the abstract <code>Feature&lt;T&gt;</code> class available in <code>Fluxor</code>. <code>Feature&lt;T&gt;</code> takes a state type. The following feature class can be used to name a feature and an initial state for the <code>CounterState</code> type,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public class Feature : Feature&lt;CounterState&gt;
{
    public override string GetName()
    {
        throw new NotImplementedException();
    }

    protected override CounterState GetInitialState()
    {
        throw new NotImplementedException();
    }
}</code></pre><figcaption>Feature.cs</figcaption></figure><p>The state contains nothing but some properties. A state should be immutable in nature. You should never mutate a state directly, rather return a new state by changing its different properties. Hence, we can go for a <code>record</code> instead of a <code>class</code>,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public record CounterState(int Count);

public class Feature : Feature&lt;CounterState&gt;
{
    public override string GetName() =&gt; &quot;Counter&quot;;

    protected override CounterState GetInitialState() =&gt; new(0);
}</code></pre><figcaption>Feature.cs</figcaption></figure><p><code>CounterState</code> contains a <code>init</code> only property <code>Count</code> and it is initialized with the value <code>0</code>.</p><h3 id="action-">Action:</h3><p>A state should change based on different actions dispatched on it. An action may or may not have arguments. For example, an <code>IncrementCounterAction</code> does nothing but increment the <code>Count</code> property by <code>1</code>; but if you want to change the increment value to something more dynamic, use arguments.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public record IncrementCounterAction();
public record IncrementCounterByAction(int IncrementBy);</code></pre><figcaption>Actions.cs</figcaption></figure><h3 id="reducer-">Reducer:</h3><p>A reducer is a pure function that takes the current state and action being dispatched upon it. Depending on the action type, it produces a new state and returns it.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public class Reducers
{
    [ReducerMethod]
    public static CounterState ReduceIncrementCounterAction(CounterState state, IncrementCounterAction action) =&gt;
           state with { Count = state.Count + 1 };

    [ReducerMethod]
    public static CounterState ReduceIncrementCounterByAction(CounterState state, IncrementCounterByAction action) =&gt;
           state with { Count = state.Count + action.IncrementBy };
}</code></pre><figcaption>Reducers.cs</figcaption></figure><h3 id="effect-">Effect:</h3><p>Effects are used to handle side effects in your application. A side effect can be anything from a long-running task to an HTTP call to service. In these cases, state changes are not instantaneous. Effects perform tasks, which are synchronous/asynchronous, and dispatch different actions based on the outcome. The following uses the <code>HttpClient</code> to asynchronously get the content of the <code>sample-data/weather.json</code> file.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public class Effects
{
    private readonly HttpClient _httpClient;

    public Effects(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    [EffectMethod]
    public async Task HandleAsync(FetchDataAction action, IDispatcher dispatcher)
    {
        try
        {
            var forecasts = await _httpClient.GetFromJsonAsync&lt;WeatherForecast[]&gt;(&quot;sample-data/weather.json&quot;);
            dispatcher.Dispatch(new FetchDataSuccessAction(forecasts));
        }
        catch (Exception ex)
        {
            dispatcher.Dispatch(new FetchDataErrorAction(ex.Message));
        }
    }
}</code></pre><figcaption>Effects.cs</figcaption></figure><blockquote>Check the <em>FetchDataUseCase to get a better understanding of the related state, actions, reducers and effects.</em></blockquote><h3 id="accessing-state-">Accessing State:</h3><p>Feature states can be injected into a razor component using the <code>IState&lt;T&gt;</code> interface. The following shows how to inject the <code>Counter</code> state into the <code>Counter.razor</code> component,</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/counter&quot;

@using Fluxord
@inject IState&lt;CounterState&gt; _counterState

&lt;h1&gt;Counter&lt;/h1&gt;

&lt;p&gt;Current count: @_counterState.Value.Count&lt;/p&gt;</code></pre><figcaption>Counter.razor</figcaption></figure><blockquote>You use the <code>Value</code> property to get access to different properties of the injected state</blockquote><h3 id="dispatching-action-">Dispatching Action:</h3><p>To dispatch an action on the store depending on a UI event, inject the <code>IDispatcher</code> service and use the <code>Dispatch</code> the method passing the type of action.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/counter&quot;

@using Fluxor
@using FlashCardsWasm.Store.CounterUseCase
@inject IState&lt;CounterState&gt; _counterState
@inject IDispatcher _dispatcher

&lt;h1&gt;Counter&lt;/h1&gt;

&lt;p&gt;Current count: @_counterState.Value.Count&lt;/p&gt;

&lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;IncrementCount&quot;&gt;Click me&lt;/button&gt;

@code {
    private void IncrementCount()
    {
        _dispatcher.Dispatch(new IncrementCounterAction());
    }
}</code></pre><figcaption>Counter.razor</figcaption></figure><p>A similar approach is taken <code>FetchData</code> component. Although one thing to notice here is the inheritance of the component from <code>FluxorComponent</code>. Dispatching of the <code>FetchDataSuccessAction</code> and <code>FetchDataErrorAction</code> is happening from within the <code>Effects</code>. The UI has to be notified i.e. call <code>StateHasChanged</code> when these actions are dispatched. Using the <code>FluxorComponent</code> takes care of that.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/fetchdata&quot;

@using Fluxor
@using FlashCardsWasm.Store.FetchDataUseCase
@using static FlashCardsWasm.Store.FetchDataUseCase.Actions
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IState&lt;FetchDataState&gt; _fetchDataState


@if (_fetchDataState.Value.Error != null)
{
    &lt;MudAlert Severity=&quot;Severity.Error&quot;&gt;@_fetchDataState.Value.Error&lt;/MudAlert&gt;
}
else
{
    &lt;MudGrid Class=&quot;mt-4&quot;&gt;
        &lt;MudItem xs=&quot;12&quot; sm=&quot;12&quot; md=&quot;12&quot;&gt;
            &lt;MudText Typo=&quot;Typo.h3&quot;&gt;Weather forecast&lt;/MudText&gt;
            &lt;MudText Typo=&quot;Typo.subtitle1&quot;&gt;This component demonstrates fetching data from a service.&lt;/MudText&gt;
        &lt;/MudItem&gt;
        &lt;MudItem xs=&quot;12&quot; sm=&quot;12&quot; md=&quot;12&quot;&gt;
            &lt;MudTable Items=&quot;@_fetchDataState.Value.Forecasts&quot; Hover=&quot;true&quot; Breakpoint=&quot;Breakpoint.Md&quot; Loading=&quot;@_fetchDataState.Value.IsLoading&quot;
        LoadingProgressColor=&quot;Color.Info&quot;&gt;
                &lt;HeaderContent&gt;
                    &lt;MudTh&gt;Date&lt;/MudTh&gt;
                    &lt;MudTh&gt;Temp. (C)&lt;/MudTh&gt;
                    &lt;MudTh&gt;Temp. (F)&lt;/MudTh&gt;
                    &lt;MudTh&gt;Summary&lt;/MudTh&gt;
                &lt;/HeaderContent&gt;
                &lt;RowTemplate&gt;
                    &lt;MudTd DataLabel=&quot;Date&quot;&gt;@context.Date&lt;/MudTd&gt;
                    &lt;MudTd DataLabel=&quot;TempC&quot;&gt;@context.TemperatureC&lt;/MudTd&gt;
                    &lt;MudTd DataLabel=&quot;TempF&quot;&gt;@context.TemperatureF&lt;/MudTd&gt;
                    &lt;MudTd DataLabel=&quot;Summary&quot;&gt;@context.Summary&lt;/MudTd&gt;
                &lt;/RowTemplate&gt;
            &lt;/MudTable&gt;
        &lt;/MudItem&gt;
    &lt;/MudGrid&gt;
}



@code {
    [Inject]
    public IDispatcher Dispatcher { get; set; }

    protected override void OnInitialized()
    {
        Dispatcher.Dispatch(new FetchDataAction());
        base.OnInitialized();
    }
}</code></pre><figcaption>FetchData.razor</figcaption></figure><h3 id="redux-devtools">Redux DevTools</h3><p>To tinker around with the application state, you should have the <a href="https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en">Redux DevTools</a> extension installed in your preferred browser. Open up the <code>Developer Console</code> of your browser and go to the <code>Redux</code> tab. You will see what actions are dispatched and how your application state is modified.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4m4vfYvFQ97SuxHcJhiWyQm_WG-o73vOsjhRrG80vvvkR2No0aNS0h323xdvtF8J98_UW-FE29pDadzEFEqkDgHkcBW2D3LCP_HEqmt4oAmlcBAuxVxl-zaCFnaQFjZ588hWjdihYStNoXVwOqKlHQBJiqCXzm509_K194eBYl6QlKbFpAApZX38pBIzDaAd_U?width=1920&amp;height=1048&amp;cropmode=none" class="kg-image" alt="Blazor Flash Cards - State Management with Fluxor" loading="lazy"></figure><h3 id="repository-link-">Repository Link:</h3><p>Part II - <a href="https://github.com/fiyazbinhasan/FlashCardsWasm/tree/Part-II-State-Management-With-Fluxor">https://github.com/fiyazbinhasan/FlashCardsWasm/tree/Part-II-State-Management-With-Fluxor</a></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/fiyazbinhasan/FlashCardsWasm"><div class="kg-bookmark-content"><div class="kg-bookmark-title">fiyazbinhasan/FlashCardsWasm</div><div class="kg-bookmark-description">Contribute to fiyazbinhasan/FlashCardsWasm development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Blazor Flash Cards - State Management with Fluxor"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">fiyazbinhasan</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/ad96b7cfeae4ac5f2f5d79c092b4c57da630fe6a3f1bd5661499df199ae5cd03/fiyazbinhasan/FlashCardsWasm" alt="Blazor Flash Cards - State Management with Fluxor"></div></a></figure><h3 id="important-links-">Important Links:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/mrpmorris/Fluxor"><div class="kg-bookmark-content"><div class="kg-bookmark-title">mrpmorris/Fluxor</div><div class="kg-bookmark-description">Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor. - mrpmorris/Fluxor</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Blazor Flash Cards - State Management with Fluxor"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">mrpmorris</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/9c5e3413583b054348322f4af2cb4fb599d6939c581c34fa3416cbfb6cb46ff7/mrpmorris/Fluxor" alt="Blazor Flash Cards - State Management with Fluxor"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.nuget.org/packages/GeekHour.AspNetCore.BlazorFluxor.Templates/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GeekHour.AspNetCore.BlazorFluxor.Templates 1.0.1</div><div class="kg-bookmark-description">Project templates Blazor and Fluxor.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.nuget.org/favicon.ico" alt="Blazor Flash Cards - State Management with Fluxor"></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.nuget.org/Content/gallery/img/default-package-icon-256x256.png" alt="Blazor Flash Cards - State Management with Fluxor"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Blazor Flash Cards - Hot Reload & MudBlazor]]></title><description><![CDATA[Bootstrap is all good and graceful, but let's go for different cuisine. MudBlazor is an open-source library that you want to look at if you are into Material Design. The community is welcoming, and they are pushing new updates every once in a while.]]></description><link>https://fiyazhasan.work/blazor-flashcards-adding-mudblazor/</link><guid isPermaLink="false">62fb4d11723615162418be30</guid><category><![CDATA[Blazor]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Wed, 30 Jun 2021 09:10:33 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019--1---1-.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><a href="https://github.com/fiyazbinhasan/FlashCards">
  <img align="center" width="100%" src="https://github-readme-stats.vercel.app/api/pin/?username=fiyazbinhasan&amp;repo=FlashCards" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor">
</a>

<br>
<br><!--kg-card-end: html--><img src="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019--1---1-.png" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"><p><a href="https://getbootstrap.com/">Bootstrap</a> is all good and graceful, but let&apos;s go for different cuisine. <a href="https://mudblazor.com/">MudBlazor</a> is an open-source library that you want to look at if you are into <a href="https://material.io/design">Material Design</a>. The community is welcoming, and they are pushing new updates every once in a while.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://mudblazor.com/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">MudBlazor - Blazor Component Library</div><div class="kg-bookmark-description">Blazor Component Library based on Material Design. MudBlazor is easy to use and extend, especially for .NET devs because it uses almost no Javascript.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://mudblazor.com/_content/MudBlazor.Docs/apple-touch-icon.png" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"><span class="kg-bookmark-author">MudBlazor</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://mudblazor.com/_content/MudBlazor.Docs/images/logo.png" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"></div></a></figure><p>Consider this post as an intro to <code>MudBlazor</code>. The default template for a <code>Blazor WebAssembly</code> comes in with some layouts and pages. Although our flashcards application doesn&apos;t require these boilerplate markups, I&apos;m keeping them for the time being. I&apos;m going to try out some <code>MudBlazor</code> components on these pages and layouts.</p><p>After creating a <code>Blazor WASM</code> application, you would need to install the <code>NuGet</code> package for <code>MudBlazor</code>,</p><pre><code>dontet new blazorwasm --no-https

dotnet add package MudBlazor</code></pre><p>Inside <code>_Imports.razor</code>, add the following,</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@using MudBlazor</code></pre><figcaption>_Imports.razor</figcaption></figure><p>Add the <code>Roboto</code> font-face &amp; <code>MudBlazor</code> style references in <code>index.html</code>,</p><pre><code class="language-html">&lt;link href=&quot;https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&amp;display=swap&quot; rel=&quot;stylesheet&quot; /&gt;
&lt;link href=&quot;_content/MudBlazor/MudBlazor.min.css&quot; rel=&quot;stylesheet&quot; /&gt;</code></pre><p>While you are at it, delete the references of <code>bootstrap.min.css</code> and <code>app.css</code> files. Also, delete <code>MainLayout.razor.css</code> &amp; <code>NavMenu.razor.css</code> as we want to start from scratch.</p><blockquote>You can delete the <em>css </em>folder under <em>wwwroot </em>altogether. We will add a custom <em>css </em>file whenever we need one.</blockquote><p>Add the script file reference for <code>MudBlazor</code> inside the <code>body</code> right after <code>_framework/blazor.webassembly.js</code></p><pre><code class="language-javascript">&lt;script src=&quot;_content/MudBlazor/MudBlazor.min.js&quot;&gt;&lt;/script&gt;</code></pre><p>Register the <code>MudBlazor</code> services with the built-in <code>IoC</code> container. For <code>Blazor WASM</code>, <code>Program.cs</code> class is the place where you register your services,</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    
    /* Code removed for brevity */
    
    builder.Services.AddMudServices();
    
    await builder.Build().RunAsync();
}</code></pre><figcaption>Program.cs</figcaption></figure><p>Some <code>MudBlazor</code> components need initialization beforehand. Here are the components you need to add in the <code>MainLayout.razor</code>,</p><pre><code class="language-html">&lt;MudThemeProvider /&gt;
&lt;MudDialogProvider /&gt;
&lt;MudSnackbarProvider /&gt;</code></pre><h3 id="hot-reload">Hot Reload</h3><p>Before moving forward, I would like to point out a new feature in <code>.NET 6.0</code> called <code>Hot Reload</code>.</p><blockquote><strong>Hot reload</strong> is used to <strong>refresh</strong> only the file in which code is changed while preserving the application state</blockquote><p>In every SPA framework (React/Angular/Vue), this feature is available. It is hugely beneficial if you think from a designer&apos;s perspective. You don&apos;t have to stop and start an application every time a tiny design change is needed. </p><!--kg-card-begin: markdown--><p><s>To enable Hot Reload in a Blazor WASM app, in your launchSettings.json add the following lines under the profiles tag,</s></p>
<p><s>To try out hot reload with an existing ASP.NET Core project based on .NET 6, add the &quot;hotReloadProfile&quot;: &quot;aspnetcore&quot; property</s></p>
<!--kg-card-end: markdown--><p>You would run your application in watch mode using the following command,</p><pre><code>dotnet watch run</code></pre><p>And you will never have to stop and start your application from now on for a small change. </p><h3 id="wire-frames">Wire Frames</h3><p>There are some ready-to-use <a href="https://mudblazor.com/getting-started/wireframes">wireframes</a> from MudBlazor. These are some frequently seen layouts on the web built with different MudBlazor components like <em>MudLayout, MudAppBar, </em>MudDrawer, and MudMainContent. &#xA0;Replace the existing markup in the <code>MainLayout.razor</code> with the following,</p><p>There are some ready-to-use <a href="https://mudblazor.com/getting-started/wireframes">wireframes</a> from MudBlazor. These are frequently seen layouts on the web built with different MudBlazor components like<em> MudLayout, <a href="https://mudblazor.com/components/appbar"><a href="https://mudblazor.com/api/appbar">MudAppBar</a></a>, </em><a href="https://mudblazor.com/api/drawer">MudDrawer</a>, and <code>MudMainContent</code>. Replace the existing markup in the <code>MainLayout.razor</code> with the following,</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;MudLayout&gt;
    &lt;MudAppBar Elevation=&quot;1&quot;&gt;
        &lt;MudIconButton Icon=&quot;@Icons.Material.Filled.Menu&quot; Color=&quot;Color.Inherit&quot; Edge=&quot;Edge.Start&quot;
            OnClick=&quot;@((e) =&gt; DrawerToggle())&quot; /&gt;
        &lt;MudAppBarSpacer /&gt;
        &lt;MudIconButton Icon=&quot;@Icons.Material.Filled.MoreVert&quot; Color=&quot;Color.Inherit&quot; Edge=&quot;Edge.End&quot; /&gt;
    &lt;/MudAppBar&gt;
    &lt;MudDrawer @bind-Open=&quot;_drawerOpen&quot; Elevation=&quot;2&quot;&gt;
        &lt;MudDrawerHeader&gt;
            &lt;MudText Typo=&quot;Typo.h5&quot;&gt;Flash Cards&lt;/MudText&gt;
        &lt;/MudDrawerHeader&gt;
        &lt;MudDivider /&gt;
        &lt;NavMenu /&gt;
    &lt;/MudDrawer&gt;
    &lt;MudMainContent Class=&quot;px-8&quot; MaxWidth=&quot;MaxWidth.False&quot;&gt;
        @Body
    &lt;/MudMainContent&gt;
&lt;/MudLayout&gt;

@code {
    bool _drawerOpen = true;

    void DrawerToggle()
    {
        _drawerOpen = !_drawerOpen;
    }
}</code></pre><figcaption>MainLayout.razor/</figcaption></figure><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mX4jPRUW7Cy5oytBoksejlmp8Hnt8WGvZKqXOjhUr9MnvcCO5JIP_3xc6AzMhK2CLoL-DAl22IkGFqa0HjYvxbk3cDv-4WwQ_KCbKuubG6ZbtmmNS66UwUeln93DK9pNk6rFVTaW_eeMreGjrVx-d7Pn8NnS_sIuJg1n84l92k7ZDdJnm1DuJwFnGTGQjgo3E?width=1418&amp;height=290&amp;cropmode=none" class="kg-image" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor" loading="lazy"></figure><p>Moving on to the <code>NavMenu</code> component, I&apos;m using <a href="https://mudblazor.com/api/navmenu">MudNavMenu</a> and <code>MudNavLink</code>. <code>MudNavLink</code> automatically assigns an <code>active</code> class on a selected menu item based on the current route.</p><figure class="kg-card kg-code-card"><pre><code class="language-html"> &lt;MudNavMenu&gt;
    &lt;MudNavLink Href=&quot;&quot; Match=&quot;NavLinkMatch.All&quot; Icon=&quot;@Icons.Filled.Dashboard&quot; IconColor=&quot;Color.Inherit&quot;&gt;Home&lt;/MudNavLink&gt;
    &lt;MudNavLink Href=&quot;counter&quot; Icon=&quot;@Icons.Filled.Timer&quot; IconColor=&quot;Color.Inherit&quot;&gt;Counter&lt;/MudNavLink&gt;
    &lt;MudNavLink Href=&quot;fetchdata&quot; Icon=&quot;@Icons.Filled.FileDownload&quot; IconColor=&quot;Color.Inherit&quot;&gt;Fetch data&lt;/MudNavLink&gt;
&lt;/MudNavMenu&gt;</code></pre><figcaption>NavMenu.razor</figcaption></figure><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mGSTzgYFfzxKUJRUk22MStJsUS0meicmSelFYiaj4JZk0wm_g5O1NfiQwb3iJzp4UCSTKShw_PhtrthTNwuHlZkyAGhCQj8selsskw6DVWhDJ8f7w1znJzhQluYPU-u7p37ClvVyOqwQ3qfLTYq6nmBfc2YvR4Glt0k4JTL3d_qofBF2Wzb0GhlxjaXWCQgyh?width=1418&amp;height=290&amp;cropmode=none" class="kg-image" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor" loading="lazy"></figure><p>For structuring individual pages, try out the <a href="https://mudblazor.com/api/grid">MudGrid</a>. Starting with <code>index.razor</code> page,</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/&quot;

&lt;MudGrid Class=&quot;mt-4&quot;&gt;
    &lt;MudItem xs=&quot;12&quot; sm=&quot;12&quot; md=&quot;12&quot;&gt;
        &lt;MudText Typo=&quot;Typo.h3&quot;&gt;Hello, world!&lt;/MudText&gt;
        &lt;MudText Typo=&quot;Typo.subtitle1&quot;&gt;Welcome to your new app.&lt;/MudText&gt;
    &lt;/MudItem&gt;
&lt;/MudGrid&gt;</code></pre><figcaption>Index.razor</figcaption></figure><p>With <code>xs</code>, <code>sm</code> and <code>md</code>, you define how many units in the grid the <code>MudItem</code> will take based on different display real-estates. To stay consistent with the typography, <a href="https://mudblazor.com/api/typography">MudText</a> has been used with different <code>Typo</code> attributes.</p><p>The same thing goes for the <code>Counter.razor</code> page. Here, we have a new <a href="https://mudblazor.com/api/button">MudButton </a>component, </p><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/counter&quot;

&lt;MudGrid Class=&quot;mt-2&quot;&gt;
    &lt;MudItem xs=&quot;12&quot; sm=&quot;12&quot; md=&quot;12&quot;&gt;
        &lt;MudText Typo=&quot;Typo.h3&quot;&gt;Counter&lt;/MudText&gt;
        &lt;MudText Typo=&quot;Typo.subtitle1&quot;&gt;Current count: @currentCount&lt;/MudText&gt;
    &lt;/MudItem&gt;
    &lt;MudItem xs=&quot;12&quot; sm=&quot;12&quot; md=&quot;12&quot;&gt;
        &lt;MudButton Variant=&quot;Variant.Filled&quot; Color=&quot;Color.Primary&quot; OnClick=&quot;IncrementCount&quot;&gt;Click me&lt;/MudButton&gt;
    &lt;/MudItem&gt;
&lt;/MudGrid&gt;

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}</code></pre><figcaption>Counter.razor</figcaption></figure><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mUtpJJFqJFtngYIm7njvUR0TUqpzfUAtsoqF35_LY98ziEUp8HqZNh5ZHIpXlJ90uDY893D78aLdzdpeAKprp7L0vLZYCa48UeWkg_C1DKodbWB94QzdnM4zcQ7MrJNIl3qkuGrtN9UCzywCpkU_tzQIxa_O5pvfRB_5NIo5IdIGsd0P27Bb2bF-b022uPJW4?width=1418&amp;height=369&amp;cropmode=none" class="kg-image" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor" loading="lazy"></figure><p>For <code>FetchData.razor</code> page, I&apos;m using the <a href="https://mudblazor.com/api/table">MudTable</a>. It has cool features like binding items directly to the <code>Items</code> attribute instead of having to create a <code>foreach</code> loop and a progress bar. Also, it changes its layout based on the screen width.</p><figure class="kg-card kg-code-card"><pre><code class="language-html">@page &quot;/fetchdata&quot;
@inject HttpClient Http

&lt;MudGrid Class=&quot;mt-4&quot;&gt;
    &lt;MudItem xs=&quot;12&quot; sm=&quot;12&quot; md=&quot;12&quot;&gt;
        &lt;MudText Typo=&quot;Typo.h3&quot;&gt;Weather forecast&lt;/MudText&gt;
        &lt;MudText Typo=&quot;Typo.subtitle1&quot;&gt;This component demonstrates fetching data from a service.&lt;/MudText&gt;
    &lt;/MudItem&gt;
    &lt;MudItem xs=&quot;12&quot; sm=&quot;12&quot; md=&quot;12&quot;&gt;
        &lt;MudTable Items=&quot;@forecasts&quot; Hover=&quot;true&quot; Breakpoint=&quot;Breakpoint.Md&quot; Loading=&quot;@(forecasts == null)&quot;
            LoadingProgressColor=&quot;Color.Info&quot;&gt;
            &lt;HeaderContent&gt;
                &lt;MudTh&gt;Date&lt;/MudTh&gt;
                &lt;MudTh&gt;Temp. (C)&lt;/MudTh&gt;
                &lt;MudTh&gt;Temp. (F)&lt;/MudTh&gt;
                &lt;MudTh&gt;Summary&lt;/MudTh&gt;
            &lt;/HeaderContent&gt;
            &lt;RowTemplate&gt;
                &lt;MudTd DataLabel=&quot;Date&quot;&gt;@context.Date&lt;/MudTd&gt;
                &lt;MudTd DataLabel=&quot;TempC&quot;&gt;@context.TemperatureC&lt;/MudTd&gt;
                &lt;MudTd DataLabel=&quot;TempF&quot;&gt;@context.TemperatureF&lt;/MudTd&gt;
                &lt;MudTd DataLabel=&quot;Summary&quot;&gt;@context.Summary&lt;/MudTd&gt;
            &lt;/RowTemplate&gt;
        &lt;/MudTable&gt;
    &lt;/MudItem&gt;
&lt;/MudGrid&gt;

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync&lt;WeatherForecast[]&gt;(&quot;sample-data/weather.json&quot;);
    }

    public class WeatherForecast
    {
        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public string Summary { get; set; }

        public int TemperatureF =&gt; 32 + (int)(TemperatureC / 0.5556);
    }
}</code></pre><figcaption>FetchData.razor</figcaption></figure><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mMf5K46Pyn2-eLeMVFZUDHX-d6RuPhg_YO9kJVgBHyXlufi6Nv2IFTpIKMZ9uZ6XvhMnk4CyL-7YGOLJjiUT9YRUZmTsrwhalmu1neWbj70JY-pTDn87IOhDs1oFCQzgUHgY7zOhLsQu3BopfK4TNIXS2EVMUweEo2_icWB3IG-WotoUG1nHXfjdEpzjNkQgr?width=1401&amp;height=735&amp;cropmode=none" class="kg-image" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor" loading="lazy"></figure><p>The last bit I wanna talk about is wiring up your modified theme to the <a href="https://mudblazor.com/customization/theming/overview">&lt;MudThemeProvider/&gt;</a>. You can create an instance of <code>MudTheme</code> and add different <a href="https://mudblazor.com/customization/theming/palette">Palettes</a>, Shadows, Typography, Layout Properties, and <a href="https://mudblazor.com/customization/theming/z-index">z-index</a> for your components to use universally.</p><p>Here is how you should do it in the <code>MainLayout.razor</code>,</p><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;MudThemeProvider Theme=&quot;_customTheme&quot; /&gt;

@code {
	readonly MudTheme _customTheme = new MudTheme()
    {
        Palette = new Palette()
        {
            Primary = &quot;#de5c9d&quot;,
            Secondary = &quot;#0d6efd&quot;,
            AppbarBackground = &quot;#6f42c1&quot;,
            Error = &quot;#dc3545&quot;,
            Success = &quot;#198754&quot;,
            Info = &quot;#0d6efd&quot;
        },

        LayoutProperties = new LayoutProperties()
        {
            DrawerWidthLeft = &quot;320px&quot;
        }
    };
}</code></pre><figcaption>MainLayout.razor</figcaption></figure><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mtSc2TLtD8CVu7PP1UYlxZDU1rfSPyUGiJUnkln6NXczUWZeeW_tN9fODLuLQpjihwtx-JeB98dt5r607l8k9dxIO-54OWxvTDSSdNwcX80HIl2aE7BIwirPDo1alMSGiq82dEClwkMuZiAZWG_NkXYHCkQF4aLTFzhvf-1Oy42xJxjE12SniqfwlU8OJKMMy?width=1401&amp;height=350&amp;cropmode=none" class="kg-image" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor" loading="lazy"></figure><h3 id="repository-links-">Repository Links:</h3><p>Part I - <a href="https://github.com/fiyazbinhasan/FlashCardsWasm/tree/Part-I-Hot-Reload-And-MudBlazor">https://github.com/fiyazbinhasan/FlashCardsWasm/tree/Part-I-Hot-Reload-And-MudBlazor</a></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/fiyazbinhasan/FlashCardsWasm"><div class="kg-bookmark-content"><div class="kg-bookmark-title">fiyazbinhasan/FlashCardsWasm</div><div class="kg-bookmark-description">Contribute to fiyazbinhasan/FlashCardsWasm development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">fiyazbinhasan</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/61a520137f89b6f43df634fda5e624a5d692a7f319fd00a2d406547c39075aa2/fiyazbinhasan/FlashCardsWasm" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/fiyazbinhasan/FlashCards"><div class="kg-bookmark-content"><div class="kg-bookmark-title">fiyazbinhasan/FlashCards</div><div class="kg-bookmark-description">Learning Blazor By Creating A Flash Cards Application - fiyazbinhasan/FlashCards</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">fiyazbinhasan</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/caf6a92fc374692e9fae5f4872abb1aa9f41d9a3d6cbe380e660b88496ae7e5b/fiyazbinhasan/FlashCards" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"></div></a></figure><h3 id="useful-links-">Useful Links:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://visualstudiomagazine.com/articles/2021/04/09/net6-preview3.aspx"><div class="kg-bookmark-content"><div class="kg-bookmark-title">.NET 6 Preview 3 Furthers &#x2018;Hot Reload Everywhere&#x2019; Push for Blazor, More -- Visual Studio Magazine</div><div class="kg-bookmark-description">.NET 6 Preview 3 includes early Hot Reload support for ASP.NET Core/Blazor web apps, furthering a push to make it available across the entire tooling gamut.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://visualstudiomagazine.com/design/ECG/VisualStudioMagazine/img/vsm_apple_icon.png" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"><span class="kg-bookmark-author">Visual Studio Magazine</span><span class="kg-bookmark-publisher">David Ramel</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://visualstudiomagazine.com/-/media/ECG/redmondmag/Images/introimages/130720REDPoseyPostPC.jpg" alt="Blazor Flash Cards - Hot Reload &amp; MudBlazor"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Input & Output Formatters For .NET]]></title><description><![CDATA[What formatters do is format your response and request in your preferred data formats. For example, JSON formatters; if used, would format your request and response body in JSON. Learn how to build a custom Yaml formatted in .NET]]></description><link>https://fiyazhasan.work/dotnet-input-output-formatter/</link><guid isPermaLink="false">62fb4d11723615162418be2f</guid><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Sun, 27 Jun 2021 01:31:23 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019-1--1-.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><a href="https://github.com/fiyazbinhasan/CoreFormatters">
  <img align="center" width="100%" src="https://github-readme-stats.vercel.app/api/pin/?username=fiyazbinhasan&amp;repo=CoreFormatters" alt="Input &amp; Output Formatters For .NET">
</a>

<br>
<br><!--kg-card-end: html--><img src="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019-1--1-.png" alt="Input &amp; Output Formatters For .NET"><p>What formatters do is format your response and request in your preferred data formats. For example, <code>JSON</code> formatters; if used, would format your response (returned value from controller&apos;s action) and request (value passed as a parameter to the controller) in <code>JSON</code>. The same goes for the <code>XML</code> and other formatters. Out of the box, <code>.NET</code> provides some response data formatters. This official <a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/models/formatting">documentation</a> described them briefly.</p><p>But let&#x2019;s not talk too much about formatters; &#xA0;rather let&apos;s see how we can make one. I think that&#x2019;s what we are here for, right? Yeah! Let&#x2019;s get started.</p><p>We have two abstract classes provided by the framework, <code>InputFormmeter</code>, and <code>OutputFormatter</code>. You would want to extend from these classes to make your custom formatters. To make things a bit easier to work with responses that are simple string representations of data formats there are two other abstract formatter classes i.e. <code>TextInputFormatter</code> and <code>TextOuputFormatter</code>.</p><p>When using the <code>Yaml</code> output formatter you would get the response out of the current <code>HttpContext</code> and serialize them into raw <code>Yaml</code> response text. Pretty much the same goes for the input formatter. In this case, you would deserialize the <code>Yaml</code> content from the request and use them in a generic form. Another important thing is that you have to explicitly set a media type header for these formatters. Doing that will activate the formatters whenever a client defines an <code>Accept</code> header (for output) and <code>Content-Type</code> (for input) with that specific media type format e.g. <code>application/x-yaml</code>.</p><p>If you don&#x2019;t want to use those headers while calling your controller&#x2019;s actions you can explicitly define the type of the formatter that should be used while getting or posting content. For example, the <code>[Produces(&quot;application/x-yaml&quot;)]</code> will return the response in <code>Yaml</code> format whether you define an <code>Accept</code> header or not. Again using the <code>[Consumes(&quot;application/x-yaml&quot;)]</code> attribute would only accept Yaml content whether you define the <code>Content-Type</code> or not.</p><p>By the way, I&#x2019;m using the <code><a href="https://github.com/aaubry/YamlDotNet">YamlDotNet</a></code> library from <a href="https://twitter.com/antoineaubry">@antoineaubry</a> for Yaml&#x2019;s serializing and deserializing process.</p><h3 id="yaml-output-formatter">Yaml Output Formatter</h3><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public class YamlOutputFormatter : TextOutputFormatter
{
    private readonly Serializer _serializer;

    public YamlOutputFormatter(Serializer serializer)
    {
        _serializer = serializer;

        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
        SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationYaml);
        SupportedMediaTypes.Add(MediaTypeHeaderValues.TextYaml);
    }

    public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (selectedEncoding == null)
        {
            throw new ArgumentNullException(nameof(selectedEncoding));
        }

        var response = context.HttpContext.Response;
        using (var writer = context.WriterFactory(response.Body, selectedEncoding))
        {
            WriteObject(writer, context.Object);

            await writer.FlushAsync();
        }
    }

    private void WriteObject(TextWriter writer, object value)
    {
        if (writer == null)
        {
            throw new ArgumentNullException(nameof(writer));
        }

        _serializer.Serialize(writer, value);
    }
}</code></pre><figcaption>YamlOutputFormatter.cs</figcaption></figure><p>The code is pretty much self-explanatory. Get the <code>Yaml</code> content from the request body and deserialize them into a generic type and you are done.</p><p>The same goes for the input formatter. In this case, you would serialize the Yaml content from the client&apos;s request and use them in a generic form.</p><h3 id="yaml-input-formatter">Yaml Input Formatter</h3><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public class YamlInputFormatter : TextInputFormatter
{
    private readonly Deserializer _deserializer;

    public YamlInputFormatter(Deserializer deserializer)
    {
        _deserializer = deserializer;

        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
        SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationYaml);
        SupportedMediaTypes.Add(MediaTypeHeaderValues.TextYaml);
    }

    public override async Task&lt;InputFormatterResult&gt; ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (encoding == null)
        {
            throw new ArgumentNullException(nameof(encoding));
        }

        var request = context.HttpContext.Request;

        using (var streamReader = context.ReaderFactory(request.Body, encoding))
        {
            var type = context.ModelType;

            try
            {
                var content = await streamReader.ReadToEndAsync();
                var model = _deserializer.Deserialize(content, type);
                return await InputFormatterResult.SuccessAsync(model);
            }
            catch (Exception)
            {
                return await InputFormatterResult.FailureAsync();
            }
        }
    }
}</code></pre><figcaption>YamlInputFormatter.cs</figcaption></figure><p><code>MediaTypeHeaderValues</code> a class where I&apos;ve set up all the media type headers for my application.</p><pre><code class="language-csharp"> internal class MediaTypeHeaderValues
 {
     public static readonly MediaTypeHeaderValue ApplicationYaml
         = MediaTypeHeaderValue.Parse(&quot;application/x-yaml&quot;).CopyAsReadOnly();

     public static readonly MediaTypeHeaderValue TextYaml
         = MediaTypeHeaderValue.Parse(&quot;text/yaml&quot;).CopyAsReadOnly();
 }</code></pre><p>Notice that the <code>YamlInputFormatter</code>&#x2019;s constructor is accepting a <code>Deserializer</code> where <code>YamlOutputFormatter</code> &#xA0;is accepting a <code>Serializer</code>. You can build the serialize and deserializer with some options while configuring the formatters in the <code>Startup.cs</code>.</p><pre><code class="language-csharp">public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =&gt; {
        options.InputFormatters.Add(new YamlInputFormatter(new DeserializerBuilder().WithNamingConvention(namingConvention: new CamelCaseNamingConvention()).Build()));
        options.OutputFormatters.Add(new YamlOutputFormatter(new SerializerBuilder().WithNamingConvention(namingConvention: new CamelCaseNamingConvention()).Build()));
        options.FormatterMappings.SetMediaTypeMappingForFormat(&quot;yaml&quot;, MediaTypeHeaderValues.ApplicationYaml);
    });
}</code></pre><p>A <code>GET</code> request with <code>Accept</code> header set to <code>application/x-yaml</code></p><pre><code>Request
-------

curl -X &apos;GET&apos; \
  &apos;http://localhost:5000/api/Geeks&apos; \
  -H &apos;accept: application/x-yaml&apos;

Response
--------

- id: 1
  name: Fiyaz
  expertise: Javascript
  rating: 4.0
- id: 2
  name: Rick
  expertise: .Net
  rating: 5.0</code></pre><p>A <code>POST</code> request with <code>Content-Type</code> header set to <code>application/x-yaml</code></p><pre><code>curl -X &apos;POST&apos; \
  &apos;http://localhost:5000/api/Geeks&apos; \
  -H &apos;accept: application/x-yaml&apos; \
  -H &apos;Content-Type: application/x-yaml&apos; \
  -d &apos;id: 3
name: Jon Doe
expertise: Lorem Ipsum
rating: 5&apos;</code></pre><p><code>FormatterMappings</code> lets you call an <code>action</code> with a specific format defined in a URL segment.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">[FormatFilter]
[HttpGet]
[HttpGet(&quot;/api/[controller].{format}&quot;)]
public IEnumerable&lt;Geek&gt; Get()
{
    return _geeks;
}</code></pre><figcaption>GeeksController.cs</figcaption></figure><p>You can call the action like, </p><blockquote>http://appurl/geeks.yaml</blockquote><p>to get the response in <code>Yaml</code> </p><p>Or you can call it like, </p><blockquote>http://appurl/geeks.json</blockquote><p>to get the response in <code>JSON</code> format.</p><p>And that&#x2019;s it. This is all you need to know about building custom formatters in <code>.NET</code>. You can find a bunch of other formatters from other awesome community members scattered around the web if you want.</p><h3 id="repository-link-">Repository Link:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/fiyazbinhasan/CoreFormatters"><div class="kg-bookmark-content"><div class="kg-bookmark-title">fiyazbinhasan/CoreFormatters</div><div class="kg-bookmark-description">.NET Core Custom Formatter for Yaml. Contribute to fiyazbinhasan/CoreFormatters development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Input &amp; Output Formatters For .NET"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">fiyazbinhasan</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/3d5791a55fff2834a74ee091e7045d1e4675c22263d62e3e1428f4ffb4132914/fiyazbinhasan/CoreFormatters" alt="Input &amp; Output Formatters For .NET"></div></a></figure><h3 id="important-links-">Important Links:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-5.0"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Format response data in ASP.NET Core Web API</div><div class="kg-bookmark-description">Learn how to format response data in ASP.NET Core Web API.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">Microsoft Docs</span><span class="kg-bookmark-publisher">Rick-Anderson</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.microsoft.com/en-us/media/logos/logo-ms-social.png" alt="Input &amp; Output Formatters For .NET"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/custom-formatters?view=aspnetcore-5.0"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Custom formatters in ASP.NET Core Web API</div><div class="kg-bookmark-description">Learn how to create and use custom formatters for web APIs in ASP.NET Core.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">Microsoft Docs</span><span class="kg-bookmark-publisher">Rick-Anderson</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.microsoft.com/en-us/media/logos/logo-ms-social.png" alt="Input &amp; Output Formatters For .NET"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Custom "dotnet new" Templates for Your Team]]></title><description><![CDATA[There are a bunch of "dotnet" templates readily available for use. That's all fine and dandy! But what about creating your templates? Learn how easy it is to ship your runnable projects as templates for your team.]]></description><link>https://fiyazhasan.work/dotnet-new-custom-templates/</link><guid isPermaLink="false">62fb4d11723615162418be2e</guid><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Fiyaz Hasan]]></dc:creator><pubDate>Wed, 23 Jun 2021 16:55:05 GMT</pubDate><media:content url="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019--2-.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><a href="https://github.com/fiyazbinhasan/MudBlazorTemplates">
  <img align="center" width="100%" src="https://github-readme-stats.vercel.app/api/pin/?username=fiyazbinhasan&amp;repo=MudBlazorTemplates" alt="Custom &quot;dotnet new&quot; Templates for Your Team">
</a>

<br>
<br><!--kg-card-end: html--><img src="https://fiyazhasan.work/content/images/2022/08/UPDATED-11.21.2019--2-.png" alt="Custom &quot;dotnet new&quot; Templates for Your Team"><p>There are a bunch of dotnet templates readily available for use. To list out all the templates, open up the command prompt and type in the following command,</p><pre><code>dotnet new --list</code></pre><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mhFyfYcb8VlBrLEXKB8bis6KdVdaTsbtMstw3O9Vq1MsQC0KyyMyyFMBd4dWRA4oNItqfKufV3HJVXxMbkt9DiqZDX8DdEhPlVD8IjArWK1iavV8k9wkRTt_VTUCBJna5GypzPTTkQcuUrouTcFAAkO6IBdCl9xmqdawoiTVxlVp_cpQA-bkfF-FQA9PdwUAw?width=1267&amp;height=776&amp;cropmode=none" class="kg-image" alt="Custom &quot;dotnet new&quot; Templates for Your Team" loading="lazy"></figure><p>That&apos;s all fine and dandy! But what about creating your templates? Say, for example, you are bootstrapping a multi-project (e.g. Web, Domain, Infrastructure) microservice-based solution and find it super helpful for others to use as well. You can easily ship your template to NuGet or other sources following some easy steps. </p><p>Recently, I&apos;m working mostly with <code><a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor">Blazor</a></code> and <code><a href="https://github.com/mrpmorris/Fluxor">Fluxor</a></code> (A state management library just like Redux/Flow). I was doing some tedious work every time I wanted to start from scratch, </p><ul><li>I&apos;ve to use <code>dotnet new blazorserver</code> or <code>dotnet new blazorwasm</code> to create a new project.</li></ul><figure class="kg-card kg-code-card"><pre><code>dotnet new blazorserver -o BlazorServer
dotnet new blazorwasm -o BlazorWasm</code></pre><figcaption>Blazor Server</figcaption></figure><ul><li>Install <code><a href="https://www.nuget.org/packages/Fluxor.Blazor.Web/">Fluxor.Balzor.Web</a></code> and wire it up in the <code>Startup.cs</code> or <code>Program.cs</code></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.nuget.org/packages/Fluxor.Blazor.Web/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Fluxor.Blazor.Web 4.1.0</div><div class="kg-bookmark-description">A zero boilerplate Redux/Flux framework for Blazor</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.nuget.org/favicon.ico" alt="Custom &quot;dotnet new&quot; Templates for Your Team"></div></div><div class="kg-bookmark-thumbnail"><img src="https://api.nuget.org/v3-flatcontainer/fluxor.blazor.web/4.1.0/icon" alt="Custom &quot;dotnet new&quot; Templates for Your Team"></div></a></figure><figure class="kg-card kg-code-card"><pre><code class="language-csharp">services.AddFluxor(opt =&gt;
{
    opt.ScanAssemblies(typeof(Program).Assembly);
    opt.UseRouting();
    opt.UseReduxDevTools();
});</code></pre><figcaption>Startup.cs / Program.cs</figcaption></figure><ul><li>Add a <code>Store</code> folder and add necessary code files for <code>Actions</code>, <code>Reducers</code>, <code>State</code>, <code>Feature</code>, <code>Effects</code>, so on and so forth</li><li>Initialize the <code>Store</code> in the <code>App.razor</code></li></ul><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;Fluxor.Blazor.Web.StoreInitializer /&gt;</code></pre><figcaption>App.razor</figcaption></figure><ul><li>Import the <code>&lt;script src=&quot;_content/Fluxor.Blazor.Web/scripts/index.js&quot;&gt;&lt;/script&gt;</code> in <code>_Host.razor</code> or <code>index.html</code></li></ul><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;script src=&quot;_content/Fluxor.Blazor.Web/scripts/index.js&quot;&gt;&lt;/script&gt;</code></pre><figcaption>_Host.razor / index.html</figcaption></figure><p>I figured out that it would be easier if I just create some custom templates where all of this boilerplate stuff is already in place for me. Speaking of creating custom templates for <code>Blazor + Fluxor</code>, here&apos;s a shameless plug</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.nuget.org/packages/GeekHour.AspNetCore.BlazorFluxor.Templates/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GeekHour.AspNetCore.BlazorFluxor.Templates 1.0.1</div><div class="kg-bookmark-description">Project templates Blazor and Fluxor.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.nuget.org/favicon.ico" alt="Custom &quot;dotnet new&quot; Templates for Your Team"></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.nuget.org/Content/gallery/img/default-package-icon-256x256.png" alt="Custom &quot;dotnet new&quot; Templates for Your Team"></div></a></figure><h3 id="project-structure">Project Structure</h3><p>The project structure I&apos;m going for is following</p><figure class="kg-card kg-code-card"><pre><code>BlazorFluxorTemplates
&#x2502;   GeekHour.AspNetCore.BlazorFluxor.Templates.nuspec
&#x2502;
&#x2514;&#x2500;&#x2500;&#x2500;Content
    &#x251C;&#x2500;&#x2500;&#x2500;BlazorFluxorWasm
    &#x2502;   &#x2502;   project files
    &#x2502;   &#x2502;
    &#x2502;   &#x2514;&#x2500;&#x2500;&#x2500;.template.config
    &#x2502;           template.json
    &#x2502;
    &#x2514;&#x2500;&#x2500;&#x2500;BlazorFluxorServer
        &#x2502;   project files
        &#x2502;
        &#x2514;&#x2500;&#x2500;&#x2500;.template.config
                template.json</code></pre><figcaption>Project Structure</figcaption></figure><p>There are a couple of ways you can use to pack up a template into a <a href="https://www.nuget.org/">NuGet</a> package, </p><blockquote>The <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-pack">dotnet pack</a> command and a <em><code>.csproj</code></em> file. Alternatively, &#xA0;<a href="https://docs.microsoft.com/en-us/nuget/tools/cli-ref-pack">nuget pack</a> command along with a <em><code>.nuspec</code></em> file</blockquote><p>I&apos;m going for the second route, hence I have a <code>.nuspec</code> file at the root of the project folder. To run <a href="https://docs.microsoft.com/en-us/nuget/tools/cli-ref-pack"><code>nuget pack</code></a> command, you would need the <code>Nuget.exe</code>. No worries! Just download it from, <a href="https://www.nuget.org/downloads">nuget.org/downloads</a>.</p><p>So, I have two project folders under the <code>Content</code> folder, each one containing a different flavor of <code>Blazor</code> wired up with <code>Fluxor</code>. You can get a better look at those here,</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/GeekyHours/BlazorFluxorTemplates/tree/main/Content"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GeekyHours/BlazorFluxorTemplates</div><div class="kg-bookmark-description">Contribute to GeekyHours/BlazorFluxorTemplates development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Custom &quot;dotnet new&quot; Templates for Your Team"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">GeekyHours</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/e787050f72d89d2d75fa6d12bdc2efe30f138bc4560afb8c7a09ec0e187dee7f/GeekyHours/BlazorFluxorTemplates" alt="Custom &quot;dotnet new&quot; Templates for Your Team"></div></a></figure><p>.NET template engine expects that you have a <code>.template.config</code> folder on the root of your runnable project. To turn that runnable project into a template with some desired configurations, you better have a <code>template.json</code> file under the <code>.template.config</code> folder.</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">{
    &quot;$schema&quot;: &quot;http://json.schemastore.org/template&quot;,
    &quot;author&quot;: &quot;Geek Hour&quot;,
    &quot;classifications&quot;: [ &quot;Web&quot;, &quot;Blazor&quot;, &quot;Fluxor&quot; ],
    &quot;identity&quot;: &quot;GeekHour.Web.BlazorFluxorServer.CSharp.6.0&quot;,
    &quot;name&quot;: &quot;Blazor Fluxor Server Application&quot;,
    &quot;shortName&quot;: &quot;blazorfluxorserver&quot;,
    &quot;tags&quot;: {
        &quot;language&quot;: &quot;C#&quot;,
        &quot;type&quot;: &quot;project&quot;
    },
    &quot;sourceName&quot;: &quot;BlazorFluxorServer&quot;,
    &quot;preferNameDirectory&quot;: true,
    &quot;defaultName&quot;: &quot;BlazorFluxorServer&quot;,
    &quot;description&quot;: &quot;Blazor server template wired with Fluxor&quot;
  }</code></pre><figcaption>template.json</figcaption></figure><p>Pretty much everything is self-explanatory in the configuration file. To know more about what each of these flags does, refer to this <a href="https://docs.microsoft.com/en-us/dotnet/core/tools/custom-templates#templatejson">official doc</a>.</p><p>At this point, you are ready to install the templates locally using the following command,</p><pre><code>&gt; dotnet new --install .\BlazorFluxorTemplates\Content\

Template Name                     Short Name          Language  Tags
--------------------------------  ------------------  --------  -----------------------------
Blazor Fluxor Server Application  blazorfluxorserver  [C#]      Web/Blazor/Fluxor
Blazor Fluxor Wasm Application    blazorfluxorwasm    [C#]      Web/Blazor/Fluxor/WebAssembly</code></pre><p>Time for creating the <code>.nuspec</code> file,</p><figure class="kg-card kg-code-card"><pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;package xmlns=&quot;http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd&quot;&gt;
  &lt;metadata&gt;
    &lt;id&gt;GeekHour.AspNetCore.BlazorFluxor.Templates&lt;/id&gt;
    &lt;version&gt;1.0.1&lt;/version&gt;
    &lt;description&gt;
      Project templates Blazor and Fluxor.
    &lt;/description&gt;
    &lt;authors&gt;Geek Hour&lt;/authors&gt;
    &lt;license type=&quot;expression&quot;&gt;MIT&lt;/license&gt;
    &lt;projectUrl&gt;https://github.com/GeekyHours/BlazorFluxorTemplates&lt;/projectUrl&gt;
    &lt;repository type=&quot;git&quot; url=&quot;https://github.com/GeekyHours/BlazorFluxorTemplates&quot; branch=&quot;main&quot;/&gt;
    &lt;tags&gt;Blazor Fluxor Server WebAssembly Template&lt;/tags&gt;
    &lt;packageTypes&gt;
      &lt;packageType name=&quot;Template&quot; /&gt;
    &lt;/packageTypes&gt;
  &lt;/metadata&gt;
  &lt;files&gt;
    &lt;file src=&quot;Content\**\*.*&quot; exclude=&quot;Content\**\bin\**\*.*;Content\**\obj\**\*.*&quot; target=&quot;Content&quot; /&gt;
  &lt;/files&gt;  
&lt;/package&gt;</code></pre><figcaption>GeekHour.AspNetCore.BlazorFluxor.Templates.nuspec</figcaption></figure><p>The important thing to notice here is the <code>&lt;files&gt;</code> node; You have to include the location of the template files here.</p><p>All done! Time to run the <a href="https://docs.microsoft.com/en-us/nuget/tools/cli-ref-pack"><code>nuget pack</code></a> command to create a Nuget package (<code>.nupkg</code>). I have downloaded <code>Nuget.exe</code> in <code>C:\</code> drive, so first I&apos;ve to go inside the directory and then specify the location of the <code>.nuspec</code> file following the <code>pack</code> command.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://bl6pap003files.storage.live.com/y4mCgGtyEJ3Z3wqRSmVRytWaCIIo4PwtdssAs0M9rBdrTwu6vL8Bz03OV5gzaCfTf9nL_LQCxrNlWCh9A2VgAew_uqBBShSGgX82YQL91pwSf-tTUhVdh-NiSV2YeCJp0_hUtjr-zMw46P4IUGlELDXwLk7vIHRglCbiKDDi9GcMvjg6sgrbesD6QJUI6VVbRtF?width=1115&amp;height=187&amp;cropmode=none" class="kg-image" alt="Custom &quot;dotnet new&quot; Templates for Your Team" loading="lazy"></figure><p>Now that you have the Nuget package (<code>.nupkg</code>), you can upload it the <a href="https://www.nuget.org/packages/manage/upload">nuget.org</a> so that the rest of the world can use your template.</p><h3 id="useful-links-">Useful Links:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.microsoft.com/en-us/dotnet/core/tools/custom-templates#templatejson"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Custom templates for dotnet new - .NET CLI</div><div class="kg-bookmark-description">Learn about custom templates for any type of .NET project or files.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">Microsoft Docs</span><span class="kg-bookmark-publisher">adegeo</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.microsoft.com/en-us/media/logos/logo-ms-social.png" alt="Custom &quot;dotnet new&quot; Templates for Your Team"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/dotnet/templating/wiki/Available-templates-for-dotnet-new"><div class="kg-bookmark-content"><div class="kg-bookmark-title">dotnet/templating</div><div class="kg-bookmark-description">This repo contains the Template Engine which is used by dotnet new - dotnet/templating</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Custom &quot;dotnet new&quot; Templates for Your Team"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">dotnet</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/f839ec3cd3d0960845ee6db564abd829281da81b724f8b02f8b188f3609a3057/dotnet/templating" alt="Custom &quot;dotnet new&quot; Templates for Your Team"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/sayedihashimi/template-sample"><div class="kg-bookmark-content"><div class="kg-bookmark-title">sayedihashimi/template-sample</div><div class="kg-bookmark-description">Contribute to sayedihashimi/template-sample development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="Custom &quot;dotnet new&quot; Templates for Your Team"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">sayedihashimi</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/482a3874379c910f4122c263515efa6e2b370f6e833e6d8676a3a3f180c1699c/sayedihashimi/template-sample" alt="Custom &quot;dotnet new&quot; Templates for Your Team"></div></a></figure>]]></content:encoded></item></channel></rss>