<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ray_Dsz</title>
    <description>The latest articles on DEV Community by Ray_Dsz (@the_architect).</description>
    <link>https://dev.to/the_architect</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3205455%2F3a2526c7-8a59-4756-8509-bfe41a74417e.webp</url>
      <title>DEV Community: Ray_Dsz</title>
      <link>https://dev.to/the_architect</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/the_architect"/>
    <language>en</language>
    <item>
      <title>Top Ingredients for Blazor Server Recipe</title>
      <dc:creator>Ray_Dsz</dc:creator>
      <pubDate>Sat, 14 Jun 2025 13:34:31 +0000</pubDate>
      <link>https://dev.to/the_architect/top-ingredients-for-blazor-server-recipe-287m</link>
      <guid>https://dev.to/the_architect/top-ingredients-for-blazor-server-recipe-287m</guid>
      <description>&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Project Structure&lt;/li&gt;
&lt;li&gt;The Ingredients&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Blazor is a powerful front-end framework, especially loved by C# developers who want to build modern web applications without switching to JavaScript-based stacks. It comes in two main flavors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Blazor Server – uses server-side rendering, where the UI interactions are processed on the server via SignalR.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blazor WebAssembly – runs directly in the browser (client-side) using WebAssembly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of these are now part of the Blazor Web App model introduced in .NET 8, giving you the flexibility to mix and match rendering modes.&lt;/p&gt;

&lt;p&gt;In this article, I’ll be focusing on Blazor Server and walk you through the key components and configurations that make your front-end secure, reliable, and production-ready—especially when integrating with Azure Entra Authentication.&lt;/p&gt;

&lt;p&gt;So let’s dive in! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you have seen my previous posts, I had created a template with clean architecture. You can view it &lt;a href="https://dev.to/rayson_lawrencedsouza_54/clean-architecture-for-enterprise-applications-a-practical-guide-from-the-trenches-3ii0"&gt;here&lt;/a&gt;. Iam just going to continue the frontend part on the same project. Below is the project structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsoiodc12ptz4797nrx56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsoiodc12ptz4797nrx56.png" alt="Blazor Template" width="454" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the code in this project is generated out of the box, and we won’t be focusing on the UI part in this blog. (For UI, I personally recommend using &lt;a href="https://blazor.radzen.com/?theme=material3" rel="noopener noreferrer"&gt;Radzen&lt;/a&gt;—a great component library that works seamlessly with Blazor.)&lt;/p&gt;

&lt;p&gt;Our main focus will be everything except the Components folder.&lt;/p&gt;

&lt;p&gt;Here’s a quick breakdown of the important folders and their roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;BasicDataModels/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This folder contains the models which are necessary to retrieve the json values from appSettings. Like jwt parameters, Api hosts etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;Extensions/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Contains HttpClientExtension to handle success, failure from the api.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;Middleware/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I wanted to generate an id that would be common across entire transaction. So that i could check the logs how the request has flown and where the error occured. Using this Id, I would be able to get the entire path of the request from UI -&amp;gt; Api -&amp;gt; Send Mail Api etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;Models/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This folder contains the models with structure matching to the model structure comming from api.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;Services/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This folder contains all the api calls. I used interface implementation pattern here. Each api host has its own  folder like request, azure graph, workflow etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, We will go ahead to the most important part, Setting up the blazor server in a neat way..&lt;/p&gt;

&lt;h3&gt;
  
  
  The Ingredients &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We will basically going through each ingredient and its set up.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Setting Up Azure Entra Authentication&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Authentication plays a crucial role. Since, we had Azure user management. We can use Azure Entra Authentication into our application's. You can check my post &lt;a href="https://dev.to/the_architect/set-up-azure-entra-authentication-with-blazor-server-24mg"&gt;here&lt;/a&gt; to set it up.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;Logging for Blazor Server&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Logging is a crucial process in web application development. This plays a crucial role for the developers to debug. Thanks to Serilog, We can now log in a seamless way. &lt;/p&gt;

&lt;h4&gt;
  
  
  Packages Required
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Serilog.AspNetCore&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  In &lt;code&gt;appSettings.json&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        "Serilog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Serilog.AspNetCore.RequestLoggingMiddleware": "Warning",
        "Microsoft.AspNetCore.Components.RenderTree.Renderer": "Error"
      }
    },
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "D:\\ApplicationLogs\\templateUILogs.txt",
          "rollingInterval": "Month",
          "rollOnFileSizeLimit": true,
          "outputTemplate": "[CorrId:{CorrelationId}] [{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}"
        }
      }
    ],
    "Enrich": [ "WithProperty" ],
    "Properties": {
      "ApplicationName": "ISWebAppTemplate"
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  In &lt;code&gt;Program.cs&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        builder.Host.UseSerilog((context, loggerConfig) =&amp;gt;
        {
            loggerConfig.ReadFrom.Configuration(context.Configuration)
            .Enrich.FromLogContext();
        });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;Setting up CorrelationId for Transaction&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Now consider a situation, Where you have blazor server app, Web Api and a Mail Api which is called within Web Api. What if something fails in the middle?. Even though, We set up serilog in each of the project. How do we track it?.&lt;br&gt;
For ex: If you submit an request in &lt;/p&gt;

&lt;p&gt;&lt;code&gt;blazor form -&amp;gt; It calls post request in Api -&amp;gt; Sends an mail to user via mail api that request is submitted&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If any failure happens there is no way we can track the log as there is nothing common in them. To resolve this, We create a correlation Id in blazor and pass it via api calls throughout the transaction till it completes. We maintain a single Correlation Id for one complete transaction .i.e. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Generate CorrelationId in Blazor -&amp;gt; Pass it to Web Api -&amp;gt; Pass it to mail api&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Let's see how we can set it up.&lt;/p&gt;
&lt;h4&gt;
  
  
  In &lt;code&gt;Services Folder&lt;/code&gt;:
&lt;/h4&gt;

&lt;p&gt;Create an interface, implementation classes like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ICorrelationService.cs
public interface ICorrelationService
{
    string? CorrelationId { get; set; }
}

//CorrelationServiceState.cs
public class CorrelationServiceState : ICorrelationService
{

    public string? CorrelationId
    {
        get;
        set;
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  In &lt;code&gt;Middleware Folder&lt;/code&gt; :
&lt;/h4&gt;

&lt;p&gt;Create a CorrelationCircuitHandler File&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class CorrelationCircuitHandler : CircuitHandler
{
    private readonly ILogger&amp;lt;CorrelationCircuitHandler&amp;gt; _logger;
    private readonly ICorrelationService _correlationService;

    public CorrelationCircuitHandler(ILogger&amp;lt;CorrelationCircuitHandler&amp;gt; logger, ICorrelationService correlationService)
    {
        _logger = logger;
        _correlationService = correlationService;
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        _correlationService.CorrelationId = circuit.Id;
        LogContext.PushProperty("CorrelationId", _correlationService.CorrelationId);
        _logger.LogInformation("Circuit connected");
        // Store or use circuit.Id as a unique connection/session ID
        return Task.CompletedTask;
    }

    public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        LogContext.PushProperty("CorrelationId", _correlationService.CorrelationId);
        _logger.LogInformation("Circuit disconnected");
        return Task.CompletedTask;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  In &lt;code&gt;DependecyInjection.cs&lt;/code&gt; :
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        services.AddScoped&amp;lt;ICorrelationService, CorrelationServiceState&amp;gt;();
        services.AddScoped&amp;lt;CircuitHandler, CorrelationCircuitHandler&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;Exception Handling in Blazor&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Blazor by default catches the server rendering errors. But, it doesn't catch the errors that are thrown from Api like 403, 404, 401 etc. In order to catch these, We need to set up Custom Exception Logging Service. So Let's start...&lt;/p&gt;

&lt;h4&gt;
  
  
  In &lt;code&gt;Services Folder&lt;/code&gt;:
&lt;/h4&gt;

&lt;p&gt;Create an interface, implementation classes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// IExceptionLoggingService.cs
public interface IExceptionLoggingService
{
    ProblemDetails Log(Exception ex);
}

// ExceptionLoggingService.cs
public class ExceptionLoggingService(ILogger&amp;lt;ExceptionLoggingService&amp;gt; logger, ICorrelationService correlationService) : DelegatingHandler, IExceptionLoggingService
{
    public ProblemDetails Log(Exception exception)
    {

        var stackTrace = new StackTrace(exception, true);
        var frame = stackTrace.GetFrame(0);
        var problemDetails = new ProblemDetails
        {
            StatusCode = StatusCodes.Status500InternalServerError,
            FileName = exception.TargetSite?.DeclaringType?.FullName,
            LineNumber = frame?.GetFileLineNumber(),
            MethodName = exception.TargetSite?.Name,
            Type = exception.GetType().Name,
            Title = exception.Message,
            Description = exception.InnerException?.Message,
            StackTrace = exception.StackTrace
        };

        LogContext.PushProperty("CorrelationId", correlationService.CorrelationId);

        logger.LogError(
    $"StatusCode: {problemDetails.StatusCode}, " +
    $"File : {problemDetails.FileName}, " +
    $"Line Number {problemDetails.LineNumber}, " +
    $"Method Name {problemDetails.MethodName}, " +
    $"Type: {problemDetails.Type}, " +
    $"Title: {problemDetails.Title}, " +
    $"Description: {problemDetails.Description}, " +
    $"StackTrace: {problemDetails.StackTrace}, " +
    $"TimeStamp: {DateTime.Now}, ");
        return problemDetails;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Problem Details class in BasicDataModels Folder
public class ProblemDetails
{
    public int? StatusCode { get; set; }

    public string? FileName { get; set; }

    public int? LineNumber { get; set; }

    public string? MethodName { get; set; }
    public string? Type { get; set; }
    public string? Title { get; set; }
    public string? Description { get; set; }
    public string? StackTrace { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  In &lt;code&gt;DependencyInjection.cs&lt;/code&gt;:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        services.AddScoped&amp;lt;IExceptionLoggingService, ExceptionLoggingService&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;Retry Policy for Api in Blazor&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;What if the api fails or the app pool is failing and the Blazor app is not able to connect to api?. What if the api is temporary down. During these times, It's crucial that we retry the request to api for around 2 to 3 times and gracefully handle it in blazor. So hence, Retry Policy. Let's see how to set it up..&lt;/p&gt;

&lt;h4&gt;
  
  
  Package Required
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Polly
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  In &lt;code&gt;DependencyInjection.cs&lt;/code&gt;:
&lt;/h4&gt;

&lt;p&gt;According to below code, The httpClient will retry 5 times with each time multiplying twice the seconds to previous request hit. Ex: If the first retry was done. It will retry after 2 seconds, then after 4 seconds and it will retry 5 times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        services.AddHttpClient("TemplateClient", client =&amp;gt;
        {
            client.BaseAddress = new Uri(baseUrls.TemplateApiBaseUrl);
        })
                    .AddTransientHttpErrorPolicy(policyBuilder =&amp;gt;
            policyBuilder.WaitAndRetryAsync(5,
                retryAttempt =&amp;gt; TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;Rate limiting in Blazor Application&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Rate limiting is necessary if too many requests are hitting to a blazor at once. Usually as per my experience. IIS server can handle 5 million transaction a minute. But, if you want to limit it like 15 requests for 5 seconds. The requests comming after this will be kept in queue and the requests after will be rejected. So let's see how to set it up...&lt;/p&gt;

&lt;h4&gt;
  
  
  In &lt;code&gt;DependencyInjection.cs&lt;/code&gt;:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; // In AddUIServices method
        services.AddRateLimiter(options =&amp;gt;
        {
            options.AddFixedWindowLimiter("fixed", opt =&amp;gt;
            {
                opt.PermitLimit = 12;
                opt.Window = TimeSpan.FromSeconds(24);
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
                opt.QueueLimit = 2;
            });
        });

//In UseUIServices method
        app.MapRazorComponents&amp;lt;App&amp;gt;()
            .AddInteractiveServerRenderMode()
            .RequireRateLimiting("policy");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;Content Security Policy (CSP)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Content Security Policy is crucial and one of the most important ingredient of Blazor. It allows only the requests from the domain we set up in CSP of our blazor app. This works similar to CORS in ASP .NET Core Web Api. It just blocks the requests comming from the other domain. Note that, If you want to block requests from particular set of machines, CSP doesnt help. Let's see how we can implement this..&lt;/p&gt;

&lt;h4&gt;
  
  
  In &lt;code&gt;DependencyInjection.cs&lt;/code&gt;:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// In UseUIServices method
        app.Use(async (context, next) =&amp;gt;
        {

            // Add the CSP header
            context.Response.Headers.Add("Content-Security-Policy",
                $"base-uri 'self'; " +
                $"default-src 'self' https://localhost:* https://&amp;lt;domain&amp;gt;.com; " +
                $"img-src data: https://&amp;lt;domain&amp;gt;.com; " +
                $"object-src 'none'; " +
                $"script-src 'self' https://&amp;lt;domain&amp;gt;.com 'unsafe-eval'; " +
                $"style-src 'self' https://&amp;lt;domain&amp;gt;.com 'sha256-eLbuM5dktsItN/wyd3rBMDvH9/MlAz4tA5/eNpxjhsQ=' 'sha256-eLbuM5dktsItN/wyd3rBMDvH9/MlAz4tA5/eNpxjhsQ=' 'unsafe-hashes'; " +
                $"upgrade-insecure-requests;");

            await next();
        });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;Error handling in Api Call from Blazor&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I spent a lot of time on how to handle the validation errors from api and api erros from api. So i came up approach that suits my situation. Of course, There might be easier and more reliable approach. But, This is the one i went with, So let's dig deep...&lt;/p&gt;

&lt;h4&gt;
  
  
  In &lt;code&gt;MainLayout.razor&lt;/code&gt;:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; @if (hasError)
 {
     &amp;lt;Error problemDetails="@problemDetails" /&amp;gt;
 }
 else
 {
     &amp;lt;CascadingValue Value="userToken" Name="Token"&amp;gt;
         &amp;lt;Header @bind-empDetails="empDetails" /&amp;gt;
     &amp;lt;/CascadingValue&amp;gt;

     &amp;lt;RadzenBody class="carrental-body"&amp;gt;
         @Body
     &amp;lt;/RadzenBody&amp;gt;
 }

@code { 
    private bool hasError = false;

    private ProblemDetails? problemDetails;
                        var requestsCreatedByEmp = await requestService.GetAllRequestsByEmpId(samAccount);
                        if (!requestsCreatedByEmp.isApiFailure)
                        {
                            var data = requestsCreatedByEmp.Data;
                        }
                        else
                        {
                            hasError = true;
                            problemDetails = requestsCreatedByEmp.ApiFailureDetails;
                        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  In &lt;code&gt;Error.razor&lt;/code&gt;:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page "/Error"
@inject ICorrelationService correlationService
@inject NavigationManager NavigationManager
@inject IExceptionLoggingService ExceptionLogging
&amp;lt;PageTitle&amp;gt;Error&amp;lt;/PageTitle&amp;gt;
&amp;lt;RadzenCard Variant="Variant.Outlined" class="rz-my-12 rz-mx-auto "&amp;gt;
        &amp;lt;RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.Start" Gap="1rem" class="rz-p-4"&amp;gt;
        &amp;lt;RadzenIcon Icon="dangerous" IconColor="@Colors.Danger" /&amp;gt;
            &amp;lt;RadzenStack Gap="0"&amp;gt;
            &amp;lt;RadzenLabel&amp;gt;Something went wrong!! :(&amp;lt;/RadzenLabel&amp;gt;
            &amp;lt;RadzenLabel&amp;gt;Correlation Id: @CorrelationId&amp;lt;/RadzenLabel&amp;gt;
            &amp;lt;/RadzenStack&amp;gt;
        &amp;lt;/RadzenStack&amp;gt;
    &amp;lt;/RadzenCard&amp;gt;
@code {
    [Parameter]
    public Exception? ErrorDetails { get; set; }

    [Parameter]
    public ProblemDetails? problemDetails { get; set; }
    private string CorrelationId;
    protected override void OnInitialized()
    {
        CorrelationId = correlationService.CorrelationId;
        //if the errors are not api errors
        if(ErrorDetails != null)
        {
           ExceptionLogging.Log(ErrorDetails);
        }

    }

    private void goToHome()
    {
        NavigationManager.NavigateTo("home", TemplateConstants.s_yes);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  In &lt;code&gt;RequestService.cs&lt;/code&gt;:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This get's the custom token generated from MainLayout
    public async Task GetCustomToken()
    {

        var token = await _userToken.WaitForTokenAsync();
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _userToken.Token);
    }

//Adds correlation id to api header to pass it to upcomming api
    private HttpRequestMessage AppendCorrelationId(HttpRequestMessage? request)
    {
        request.Headers.Add("X-Correlation-ID", _correlationService.CorrelationId ?? "unknown");
        return request;
    }

//This method is called from Mainlayout.razor
    public async Task&amp;lt;Result&amp;lt;IEnumerable&amp;lt;CreateRequest&amp;gt;&amp;gt;&amp;gt; GetAllRequestsByEmpId(string userId)
    {
        try
        {
            int empId = 0;
            await GetCustomToken();
            var request = new HttpRequestMessage(HttpMethod.Get, $"{basicApiUrls.TemplateApiBaseUrl}/requests/requestByEmployee/{empId}");
            request = AppendCorrelationId(request);
            var response = await _httpClient.SendAsync(request);
            return await response.HandleApiResponse&amp;lt;IEnumerable&amp;lt;CreateRequest&amp;gt;&amp;gt;(logger);
        }
        catch (HttpRequestException exception)
        {
            var problemDetails = exceptionLogger.Log(exception);

            // Handle network-related errors
            return Result&amp;lt;IEnumerable&amp;lt;CreateRequest&amp;gt;&amp;gt;.FailureStatus(problemDetails);
        }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;There’s still a lot more that can be added to improve and expand this setup. What I’ve shared here is based on what I’ve learned and implemented so far.&lt;/p&gt;

&lt;p&gt;Feel free to build on it, customize it to your needs, and make it even better.&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>radzen</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Set up Azure Entra Authentication with Blazor</title>
      <dc:creator>Ray_Dsz</dc:creator>
      <pubDate>Fri, 13 Jun 2025 17:19:59 +0000</pubDate>
      <link>https://dev.to/the_architect/set-up-azure-entra-authentication-with-blazor-server-24mg</link>
      <guid>https://dev.to/the_architect/set-up-azure-entra-authentication-with-blazor-server-24mg</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;As more enterprises embrace the Microsoft ecosystem, user management, file sharing, and productivity tools have become more seamlessly integrated than ever. With Microsoft now offering its own ERP solution—Dynamics 365 (D365)—many organizations are exploring hybrid environments that combine on-premises systems with cloud-based services.&lt;/p&gt;

&lt;p&gt;In our case, we adopted a hybrid setup: SAP as our ERP system, and Microsoft Azure for user management, file storage, and more. Most of our critical company data remained on-premises, but we had a growing need to modernize our authentication approach.&lt;/p&gt;

&lt;p&gt;Our primary requirement was to move from Windows Authentication to Multi-Factor Authentication (MFA) for improved security. To achieve this, we implemented Azure Entra Authentication.&lt;/p&gt;

&lt;p&gt;In this blog, I’ll walk you through the step-by-step process of integrating Azure Entra Authentication into a Blazor Server application&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Azure app registration
&lt;/h3&gt;

&lt;p&gt;Before your Blazor Server application can authenticate users or communicate securely with Azure services, it must first establish trust with Azure Active Directory (Azure AD). This is done through a process known as App Registration.&lt;/p&gt;

&lt;p&gt;Think of this as the initial handshake—a formal agreement where Azure recognizes your application, grants it an identity, and provides the necessary credentials (like Client ID and Secret) to authenticate and authorize securely.&lt;/p&gt;

&lt;p&gt;In this step, we’ll register the application in Azure to enable secure, machine-to-machine communication using Azure Entra ID. Once registered, we’ll receive the necessary configuration values (such as Tenant ID, Client ID, and optionally, Client Secret) which we’ll use in our Blazor Server app. Below is the step-by-step approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First register the azure app, Go to &lt;a href="https://portal.azure.com/" rel="noopener noreferrer"&gt;https://portal.azure.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Search for App Registrations and Click on new Registration&lt;/li&gt;
&lt;li&gt;Type your app name, Select account type. You can add Redirect    URI later.&lt;/li&gt;
&lt;li&gt;Once you register, You will get a screen like this..&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4yoz8wdjhk0y9lf191y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4yoz8wdjhk0y9lf191y.png" alt="App Registration Screen" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Copy Application (client) ID and Directory (tenant) ID. You need to add it in Blazor app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once done, Click on Certificates &amp;amp; secrets and Create a new client secret and copy the value. Remember, You will not be able to copy later. You need to add it in Blazor app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Please keep in mind, Always Choose secret value for development. For production, I always suggest client certificate as it is much secure and doesn't expire for nearly 3 years.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can even auto rotate the certificates by keeping in KeyVault. So that it never expires.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The client secret expires after 1 year. This leads to your app failure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It's a best practice to register a separate Azure App for each Blazor Server application, rather than using a single shared app registration across multiple apps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If something goes wrong—such as a client secret expiration, authentication failure—it will only affect that specific application, not every app relying on a shared registration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once done, click on Overview -&amp;gt; Select Redirect URIs. Add a redirect uri.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If your blazor app is running locally, It would be something like this 
https://localhost:7039/signin-oidc

For Postman,
https://oauth.pstmn.io/v1/callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;(Optional) After this, Go to Token Configuration. Select add option claim -&amp;gt; Access -&amp;gt; Select whichever claims you want to receive in app and click Add.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Api Permissions -&amp;gt; Click on Add a Permission -&amp;gt; Microsoft Graph -&amp;gt; Delegated -&amp;gt; Select User.Read.All and Profile and select Add&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The following steps are only necessary if you want to bring roles from azure. This means you are maintaining roles and user mapping in Azure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Expose an Api -&amp;gt; Add a scope -&amp;gt; Add required Fields and click create.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The scope will be similar to below&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; api://c60dde-e4af-47-8d-7c170b/User.All
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Go to App roles, Add your application roles here&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6sua1em6fskglck85nw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6sua1em6fskglck85nw.png" alt="App roles Screen" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To Assign the roles to user, Click on How do I assign App roles and click on Enterprise application. You will get a screen like below&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbv6i085i14vr0yun220.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbv6i085i14vr0yun220.png" alt="Enterprise app screen" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Then, Under Manage -&amp;gt; Users and groups -&amp;gt; Add user/group -&amp;gt; map role to user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Voila, You azure part is done. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting up Azure Entra authentication in Blazor.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Now, Comming to step 2. I already assume you have created a blazor server project.&lt;/li&gt;
&lt;li&gt;Once done, We will start to set it up. Create a DependencyInjection class for blazor with two methods
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public static IServiceCollection AddUIServices(this IServiceCollection services, IConfiguration configuration, IConfigurationBuilder builder)
{
        return services;

}
    public static WebApplication UseUIServices(this WebApplication app, IConfiguration configuration)
{
        return app;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In Program.cs
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        var builder = WebApplication.CreateBuilder(args);
        builder.Services.AddUIServices(builder.Configuration, builder.Configuration);
        var app = builder.Build();

        app.UseUIServices(builder.Configuration);

        app.Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Packages Required:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microsoft.Identity.Web.UI
Microsoft.Identity.Web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In DependencyInjection.cs
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//In AddUIServices method       services.AddMicrosoftIdentityWebAppAuthentication(configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches(options =&amp;gt;
{
    options.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15);
});
        services.AddControllersWithViews(options =&amp;gt;
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        }).AddMicrosoftIdentityUI();

// In UseUIServices method, Make sure you have these
        app.UseHttpsRedirection();

        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthentication();
        app.MapControllers();
        app.UseAntiforgery();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In appSettings.json
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "&amp;lt;YOUR_TENANT_ID&amp;gt;",
    "ClientId": "&amp;lt;YOUR_CLIENT_ID&amp;gt;",
    "ClientSecret": "&amp;lt;YOUR_CLIENT_SECRET_VALUE&amp;gt;",
    "Domain": "&amp;lt;YOUR_DOMAIN&amp;gt; usually your_company.com",
    "CallbackPath": "/signin-oidc"
  },
  "AppScopes": {
    "scopeBaseUrl": "api://c60dde-e4af-47-8d-7c170b/User.All"
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Done, You basic configuration is done. Now we move to MainLayout.razor&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I use Radzen as component library, So my MainLayout would look like this:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@inherits LayoutComponentBase
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject IRequestService requestService

&amp;lt;RadzenLayout class="carrental-layout"&amp;gt;
    &amp;lt;CascadingAuthenticationState&amp;gt;
        &amp;lt;AuthorizeView&amp;gt;
            &amp;lt;NotAuthorized&amp;gt;
                &amp;lt;Login /&amp;gt;
            &amp;lt;/NotAuthorized&amp;gt;
            &amp;lt;Authorized&amp;gt;
                &amp;lt;CascadingValue Value="empDetails" Name="EmployeeDetails"&amp;gt;
                    @if (hasError)
                    {
                        &amp;lt;Error problemDetails="@problemDetails" /&amp;gt;
                    }
                    else
                    {
                        &amp;lt;CascadingValue Value="userToken" Name="Token"&amp;gt;
                            &amp;lt;Header @bind-empDetails="empDetails" /&amp;gt;
                        &amp;lt;/CascadingValue&amp;gt;

                        &amp;lt;RadzenBody class="carrental-body"&amp;gt;
                            @Body
                        &amp;lt;/RadzenBody&amp;gt;
                    }


                &amp;lt;/CascadingValue&amp;gt;
            &amp;lt;/Authorized&amp;gt;
        &amp;lt;/AuthorizeView&amp;gt;
    &amp;lt;/CascadingAuthenticationState&amp;gt;
    &amp;lt;RadzenFooter class="carrental-footer"&amp;gt;
        &amp;lt;Footer /&amp;gt;
    &amp;lt;/RadzenFooter&amp;gt;
&amp;lt;/RadzenLayout&amp;gt;

&amp;lt;RadzenComponents @rendermode="@RenderMode.InteractiveServer" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the OnInitializedAsync method of MainLayout, We need to add this code
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    protected async override Task OnInitializedAsync()
    {
        var auth = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        var user = auth.User;
        if (auth.User.Identity.IsAuthenticated)
        {
            string userId = user.Identity.Name.Replace(TemplateConstants.s_samaccount, string.Empty);
                var result = await requestService.GetMyDetailsAsync(userId);

        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the above code, Request Service is my custom api. This api is used to show you how to get the access token from azure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the Request Service Api class,&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    private readonly HttpClient _httpClient;
    private readonly AppScopes appScopes;
    private readonly BasicApiUrls basicApiUrls;
    private readonly ITokenAcquisition tokenAcquisition;

    public RequestService(BasicApiUrls basicApiUrls,
        ITokenAcquisition tokenAcquisition,
        AppScopes appScopes,
        IHttpClientFactory _httpClientFactory)
    {
        this.basicApiUrls = basicApiUrls;
        this.tokenAcquisition = tokenAcquisition;

        _httpClient = _httpClientFactory.CreateClient("RequestClient");
        this.appScopes = appScopes;
    }
    public string Token { get; set; } = "";

    public async Task GetAzureToken()
    {

        try
        {
            Token = await tokenAcquisition.GetAccessTokenForUserAsync(new string[] { appScopes.ScopeBaseUrl! });
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token);
        }
        catch (Exception ex)
        {
            exceptionLogger.Log(ex);
        }
    }

    public async Task&amp;lt;Result&amp;lt;EmployeeDetails&amp;gt;&amp;gt; GetMyDetailsAsync(string employeeId)
    {
        try
        {
            await GetAzureToken();
            var request = new HttpRequestMessage(HttpMethod.Get, $"{basicApiUrls.WorkflowApiBaseUrl}/employee/{employeeId}");
            request = AppendCorrelationId(request);
            var response = await _httpClient.SendAsync(request);
            return await response.HandleApiResponse&amp;lt;EmployeeDetails&amp;gt;(logger);
        }
        catch (HttpRequestException exception)
        {
            var problemDetails = exceptionLogger.Log(exception);
            // Handle network-related errors
            return Result&amp;lt;EmployeeDetails&amp;gt;.FailureStatus(problemDetails);
        }

    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This is how you pass the token to the respective api&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Validating the token passed from Blazor in Api
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Finally, Comming to the last part that is validating the token in api.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Similar to blazor, I have created DependencyInjection class with two methods and i have called them in Program.cs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Packages required:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microsoft.Identity.Web;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Similar to blazor, Please add Azure Configuration in appSettings of Api as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add below lines in AddApiService method&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services.AddAuthentication().AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

            services.AddAuthorization(options =&amp;gt;
            {
                // Azure AD-based policy
                options.AddPolicy("AzureAuth", policy =&amp;gt;
                {
                    policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme); // "Bearer"
                    policy.RequireAuthenticatedUser();
                    policy.RequireClaim("http://schemas.microsoft.com/identity/claims/scope", "User.All");
                    policy.RequireRole("Template.User");
                });
            });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In UseApiServices method
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            app.UseAuthentication();
            app.UseAuthorization();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, in Minimal Api
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        public void AddRoutes(IEndpointRouteBuilder app)
        {
            var group = app.MapGroup("api/request/v{version:apiVersion}/employee")
                                .WithApiVersionSet()
                                .HasApiVersion(new ApiVersion(1));
            group.MapGet("{id}", UserDetails).CacheOutput().RequireAuthorization("AzureAuth");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;That's all it takes, This is a complete set up of azure entra from Azure app registration -&amp;gt; Blazor -&amp;gt; Api.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;There are multiple ways to validate tokens and handle authentication flows—it ultimately depends on your architecture, security requirements, and preferred implementation style.&lt;/p&gt;

&lt;p&gt;In this blog, I used .NET 8, Blazor Server, and ASP.NET Core Web API to demonstrate how to integrate Azure Entra Authentication in a clean and effective way.&lt;/p&gt;

&lt;p&gt;I hope this guide not only helps you get started but also works seamlessly in your setup.&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>azure</category>
      <category>webdev</category>
      <category>blazor</category>
      <category>csharp</category>
    </item>
    <item>
      <title>GitLab for Enterprise</title>
      <dc:creator>Ray_Dsz</dc:creator>
      <pubDate>Sun, 01 Jun 2025 16:20:27 +0000</pubDate>
      <link>https://dev.to/the_architect/gitlab-for-enterprise-28pp</link>
      <guid>https://dev.to/the_architect/gitlab-for-enterprise-28pp</guid>
      <description>&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;GitLab terminology for project tracking tools&lt;/li&gt;
&lt;li&gt;How To Get Started&lt;/li&gt;
&lt;li&gt;Gitlab CI/CD&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Introduction &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Every enterprise or every project needs to have a codebase. Without it, It's chaos. Developers working on project will have a hard time picking up on updates. As each of them will have their own copy. If local computer is somehow crashed, The entire data is deleted. So many problems. In our case, We have our own on-prem GitLab Server. &lt;/p&gt;

&lt;p&gt;Let's delve into what we are going to learn here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The systematic approach of how a project should handle repository code. Some guidelines and stuff.&lt;/li&gt;
&lt;li&gt;How Clean Architecture helps in GitLab to avoid Merge Conflicts etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitLab terminology for project tracking tools &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Some of the content here is from ChatGPT as iam lazy&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Issues – Track tasks, bugs, or feature requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Issue boards – Visualize and manage issues using customizable kanban boards.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Milestones – Group issues and merge requests by project goals or deadlines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wiki – Collaborative documentation space for your project.&lt;br&gt;
Merge requests – Propose, review, and merge code changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repository – Central storage for your project’s codebase and version history.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Branches – Independent lines of development within the same repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commits – Snapshots of changes made to the codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tags – Named points in history used to mark releases or important commits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Labels - Customizable tags used to categorize, prioritize, and filter issues and merge requests for better project organization and workflow management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pipelines – Automated CI/CD workflows triggered by code changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Artifacts – Output files generated by pipeline jobs (e.g., build results).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Package Registry – Built-in system to publish and share packages (e.g., NuGet).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How To Get Started &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;When it comes to Enterprise applications, Everybody just dumps the code into the codebase without any use of core concepts. It's okay, But still it looks alot more useless. You will achieve the prime responsibility of the codebase .i.e your code is safe and the recent code is pushed. But, What if you want to categorise it. Where you went wrong and stuff.&lt;/p&gt;

&lt;p&gt;Let me show you the approach i use.&lt;/p&gt;

&lt;p&gt;At first, Iam assuming you already have a project in gitlab. Now, There are two scenarios in Enterprise applications. Either you develop or you maintain.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Milestone
&lt;/h4&gt;

&lt;p&gt;So entire process starts with the Milestone. You can find it &lt;code&gt;Project Repo&lt;/code&gt; / &lt;code&gt;Plan&lt;/code&gt; / &lt;code&gt;Milestones&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ruv4m45kp2ass3vhvky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ruv4m45kp2ass3vhvky.png" alt="Image description" width="359" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our case, We have maintained two milestones&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development : As we are developing some applications&lt;/li&gt;
&lt;li&gt;Maintenance : As we are maintaining some applications as employees raise issues, a change in application workflow etc..&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step2: Labels
&lt;/h4&gt;

&lt;p&gt;Labels are prime differentiators in your repo. You can find it&lt;br&gt;
&lt;code&gt;Project Repo&lt;/code&gt; / &lt;code&gt;Manage&lt;/code&gt; / &lt;code&gt;Labels&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fks2q6brxktcwddhp9zr2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fks2q6brxktcwddhp9zr2.png" alt="labels" width="368" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For both, Development and Maintenance we have some common labels&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Feature&lt;/code&gt;: When a new change is introduced by app owner or by developer. This flag is used.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Bugs&lt;/code&gt;: If employee or tester has raised an issue. This flag is used&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Priority-low&lt;/code&gt;: When priority is low&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Priority-Med&lt;/code&gt;: When priority is medium&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Priority-High&lt;/code&gt;: When priority is high&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Update&lt;/code&gt;: When you have fixed an issue or you are working on feature that has already deployed. This flag is used&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Documentation&lt;/code&gt;: Used when you are updating the document related to app. And Yes, Its better to keep the documents here as well.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 3: Issues
&lt;/h4&gt;

&lt;p&gt;Whenever something happens, It's always best practice to create an issue. Whether it is a new feature or update or bug. Each commit has to have atleast one issue.&lt;/p&gt;

&lt;p&gt;You can find it &lt;br&gt;
&lt;code&gt;Project Repo&lt;/code&gt; / &lt;code&gt;Plan&lt;/code&gt; / &lt;code&gt;Issues&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ruv4m45kp2ass3vhvky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ruv4m45kp2ass3vhvky.png" alt="Image description" width="359" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While creating a new Issue, Details matter. Write Precisely what you are expecting. This includes files that are going to change. It's better you have someone who assigns the issues or you can do it yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxf5qx957bekhqd8zwca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxf5qx957bekhqd8zwca.png" alt="issues" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where Labels and Milestone comes into picture. Once created, An issue id is generated. Usually it would be like #3, #120 etc. It's incremental.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 4: Branches
&lt;/h4&gt;

&lt;p&gt;You can create branches within VS 2022. Like below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50059ns6laz9lz5tkbt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50059ns6laz9lz5tkbt1.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or in GitLab: &lt;br&gt;
&lt;code&gt;Project Repo&lt;/code&gt; / &lt;code&gt;Code&lt;/code&gt; / &lt;code&gt;Branches&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnne5cev1jz9tuju3or23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnne5cev1jz9tuju3or23.png" alt="branches" width="196" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The format for Branch name i follow is if its an&lt;/p&gt;

&lt;p&gt;&lt;code&gt;feature&lt;/code&gt;: feature/EmployeeId/IssueId&lt;br&gt;
&lt;code&gt;update&lt;/code&gt;: update/EmployeeId/IssueId&lt;br&gt;
&lt;code&gt;bug&lt;/code&gt;: bug/EmployeeId/IssueId&lt;/p&gt;

&lt;p&gt;This will help me make it easier to understand, which branches and issues are mine. Please make sure one branch for one deployment.&lt;br&gt;
There should be two more branches: main (default, prod), integration (test env).&lt;br&gt;
*&lt;em&gt;Please maintain an approval system for the merge request to these branches. *&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 5: Merge Requests
&lt;/h4&gt;

&lt;p&gt;Once you create the branch and push into it. Then there will be only two merge requests created for the entire feature deployment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your branch -&amp;gt; integration&lt;/li&gt;
&lt;li&gt;integration -&amp;gt; main&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find it here:&lt;br&gt;
&lt;code&gt;Project Repo&lt;/code&gt; / &lt;code&gt;Code&lt;/code&gt; / &lt;code&gt;Merge Requests&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnne5cev1jz9tuju3or23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnne5cev1jz9tuju3or23.png" alt="merge requests" width="196" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, Keep in mind. You have issue open and a branch created. When you create a merge request from your branch -&amp;gt; integration and is approved by your senior. The issue should be automatically closed and branch should be automatically deleted. Because, the recent code will be merged into integration and there is no need of branches getting piled up. To achieve this,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltt6c3b80c99qbkkb0os.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltt6c3b80c99qbkkb0os.png" alt="Image description" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inorder to close the issue: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Title, Issue closes  or closes  or closes Issue . This makes GitLab understand that once merge request is approved, This issue should be closed. &lt;/li&gt;
&lt;li&gt;Give a description. We can create a template as well, We will check it later.&lt;/li&gt;
&lt;li&gt;Select milestone, Label, Reviewer, Assigner etc.&lt;/li&gt;
&lt;li&gt;Under Merge Options, 

&lt;ul&gt;
&lt;li&gt;Select Delete source branch when merge request is accepted to delete the branch you created.&lt;/li&gt;
&lt;li&gt;Select Squash commits when merge request is accepted. If you have multiple commits under same branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  GitLab CI/CD &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;GitLab CI/CD is an integrated toolset that automates the build, test, and deployment phases of your software development lifecycle, directly within GitLab.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.gitlab-ci.yml&lt;/code&gt;: A YAML file in your repo that defines CI/CD pipelines, jobs, and stages.&lt;br&gt;
&lt;code&gt;Runners&lt;/code&gt;: Agents that execute the jobs; can be shared or project-specific. We have set up our gitlab runner in our server. It runs as windows service. &lt;/p&gt;
&lt;h4&gt;
  
  
  How to set up runner
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To install Gitlab runner in your environment or server. You can    &lt;a href="https://docs.gitlab.com/runner/install/windows/" rel="noopener noreferrer"&gt;click here&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once done, Go to your repo project and go to &lt;code&gt;Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;CI/CD Settings&lt;/code&gt;-&amp;gt; &lt;code&gt;Runners&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rpfv2d4wy1t6cndhltl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rpfv2d4wy1t6cndhltl.png" alt="runners" width="204" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp9uo76qp72oe7krl07c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp9uo76qp72oe7krl07c.png" alt="gitlab" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on new project runner. You can select the platform, tags. Tags help in differentiating the jobs that needs to be run. In your yaml file, if you set the build to specific tag, Then the build will run on runner which has that tag. You specify that tag here&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyjbdytz5xywj2e5vow1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyjbdytz5xywj2e5vow1.png" alt="runner tag" width="800" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you dont have any tags. Check the run untagged jobs.&lt;/li&gt;
&lt;li&gt;Once you click on create runner. You will redirected here&lt;/li&gt;
&lt;li&gt;Go to the server or environment where gitlab runner is installed and run the command provided in gitlab to register the runner. You can register multiple runners as well.&lt;/li&gt;
&lt;li&gt;Then you run the command gitlab-runner run. If it succeeds, The runner is working fine.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  CI/CD GitLab yaml file
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stages:
  - build
  - test
  - deploy

build-job:
  stage: build
  script:
    - echo "build started"
    - 'dotnet build Test.sln'
    - 'dotnet publish Test.WebApi\Test.WebApi.csproj -c Release  -p:PublishProfile=StageProfile -o publish-api'
    - 'dotnet publish Test.WebUI\Test.WebUI.csproj -c Release  -p:PublishProfile=StageProfile -o publish-webui'
    - echo "build complete."
  artifacts:
    paths:
      - publish-api
      - publish-webui

unit-test-job:
  stage: test
  script:
    - echo "Started Testing"
    - 'dotnet test Test.Unit.Tests\Test.Unit.Tests.csproj'
    - echo "Testing Completed"


deploy-staging:
  stage: deploy
  environment: staging
  script:
    - echo "Deploying application to Staging server..."
    - .\deploy\deploy_staging.ps1
    - echo "Application successfully deployed on staging"
  only:
    - integration

deploy-prod:
  stage: deploy
  environment: production
  script:
    - echo "Deploying application to production server..."
    - .\deploy\deploy_prod.ps1
    - echo "Application successfully deployed on production"
  only:
    - main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The above is the content in yaml file. &lt;br&gt;
There are 3 stages&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each stage has job&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;build-job -&amp;gt; This job builds two projects webapi, webui. Once build succeeds, It pushes the release to the server path of our dev server. This is maintained in Stage Profile. This is not a cleaner approach and i wont reccomend it. The code is pushed to respective artifact folders in gitlab runner server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unit-Test-Job -&amp;gt; This checks all the testcases written in Test project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Both of the above jobs run of each commit push to the repo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;deploy-staging -&amp;gt; This job runs the powershell thats present in file deploy_staging.ps1. This only runs when the merge request is approved to integration branch. This stops the app pool and pushes the release code from artifacts to dev server web app folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;deploy-prod -&amp;gt; This job runs the powershell thats present in file deploy_prod.ps1. This only runs when the merge request is approved to main branch. This stops the app pool and pushes the release code from artifacts to prod server web app folder.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Test Cases integration
&lt;/h4&gt;

&lt;p&gt;If you want to check the test case report to check how many test cases are covered. This can also be integrated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stages:
  - build
  - test
  - deploy

build-job:
  stage: build
  script:
    - 'dotnet build Test.sln'
    - echo "build complete."

unit-test-job:
  stage: test
  only:
    - pushes
  script:
    - echo "Started Testing"
    - 'dotnet test Test.Unit.Tests\Test.Unit.Tests.csproj --results-directory $CI_PROJECT_DIR/cobertura --collect:"XPlat Code Coverage" --test-adapter-path:. --logger:"junit;LogFilePath=..\artifacts\{assembly}-test.xml;MethodFormat=Class;FailureBodyFormat=Verbose"'
    - 'Copy-Item -Path "$CI_PROJECT_DIR\cobertura\*\coverage.cobertura.xml" -Destination "$CI_PROJECT_DIR\cobertura\coverage.cobertura.xml"'
    - dotnet tool install -g dotnet-reportgenerator-globaltool #uncomment it if you are using gitlab runner for the very 1st time
    - dotnet tool install -g CodeCoverageExtractor #uncomment it if you are using gitlab runner for the very 1st time
    - reportgenerator -reports:"$CI_PROJECT_DIR/cobertura/coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:"Html"
    - codecoverageextractor $CI_PROJECT_DIR/cobertura/coverage.cobertura.xml
  artifacts:
    when: always
    expire_in: 1 week
    paths:
      - ./**/*test.xml
      - $CI_PROJECT_DIR/cobertura/coverage.cobertura.xml
      - coveragereport
    reports:
      junit:
        - ./**/*test.xml
      coverage_report:
        coverage_format: cobertura
        path: $CI_PROJECT_DIR/cobertura/coverage.cobertura.xml
  dependencies:
    - build-job
  coverage: /total_coverage=(\d+.?\d?)/

deploy-dev:
  stage: deploy
  environment: Development
  script:
    - echo "Deploying application to Staging server..."
    - 'dotnet publish Test.WebApi\Test.WebApi.csproj -p:PublishProfile=StageProfile'
    - 'dotnet publish Test.WebUI\Test.WebUI.csproj -p:PublishProfile=StageProfile'
    - echo "Application successfully deployed on staging"
  only:
    - integration

deploy-staging:
  stage: deploy
  environment: Staging
  script:
    - echo "Deploying application to Staging server..."
    - 'dotnet publish Test.WebApi\Test.WebApi.csproj -p:PublishProfile=StageProfile'
    - 'dotnet publish Test.WebUI\Test.WebUI.csproj -p:PublishProfile=StageProfile'
    - echo "Application successfully deployed on staging"
  only:
    - integration

deploy-prod:
  stage: deploy
  environment: Production
  script:
    - echo "Deploying application to production server..."
    - 'dotnet publish Test.WebApi\Test.WebApi.csproj -p:PublishProfile=ProdProfile'
    - 'dotnet publish Test.WebUI\Test.WebUI.csproj -p:PublishProfile=ProdProfile'
    - echo "Application successfully deployed on production"
  only:
    - main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, We have used cobertura for test cases count and to generate xml. We have set it to expire in 1 week as well. This is used by junit to show the xml file. Once its generated, The Gitlab will fetch it automatically and display the report from xml file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is the entire process we follow. From the configuration to standardization to Deployming automatically. This is the magic that runs behind CI/CD.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>dotnet</category>
      <category>enterprise</category>
    </item>
    <item>
      <title>Create and Publish a Custom .NET Project Template and Private NuGet to GitLab Package Registry</title>
      <dc:creator>Ray_Dsz</dc:creator>
      <pubDate>Thu, 29 May 2025 04:02:11 +0000</pubDate>
      <link>https://dev.to/the_architect/create-and-publish-a-custom-net-project-template-and-private-nuget-to-gitlab-package-registry-1a6h</link>
      <guid>https://dev.to/the_architect/create-and-publish-a-custom-net-project-template-and-private-nuget-to-gitlab-package-registry-1a6h</guid>
      <description>&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Creating a Project Template Package&lt;/li&gt;
&lt;li&gt;How to get started&lt;/li&gt;
&lt;li&gt;Creating a Nuget Package&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Introduction &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In my previous &lt;a href="https://dev.to/rayson_lawrencedsouza_54/clean-architecture-for-enterprise-applications-a-practical-guide-from-the-trenches-3ii0"&gt;article&lt;/a&gt;, I shared a practical overview of Clean Architecture and the structure we implemented across our applications.&lt;/p&gt;

&lt;p&gt;In this follow-up, I’ll walk you through how we turned that structure into a reusable project template. With over 20+ internal applications and a team of 8 developers, we simply can’t afford to build each app from scratch every time. That’s where a project template comes in — speeding up development, enforcing consistency, and avoiding repetitive boilerplate.&lt;/p&gt;

&lt;p&gt;Imagine a scenario where team members download NuGet packages independently — possibly pulling in licensed, unvetted, or even unwanted packages. This can lead to compliance issues and unnecessary bloat.&lt;/p&gt;

&lt;p&gt;So the next logical step?&lt;/p&gt;

&lt;p&gt;Centralize NuGet package management using a private registry, and embed that configuration directly into the project template!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Project Template Package &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;If you are new here, I had created a .NET Clean Architecture Template previously. You can check &lt;a href="https://dev.to/rayson_lawrencedsouza_54/clean-architecture-for-enterprise-applications-a-practical-guide-from-the-trenches-3ii0"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Structure is same as below. Done? Let's start=&amp;gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3p7s3pdpa9glw5fk5t1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3p7s3pdpa9glw5fk5t1v.png" alt="IS Web Template" width="546" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create a folder structure like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Template&lt;/code&gt; / &lt;code&gt;Content&lt;/code&gt;/ &lt;code&gt;is-template&lt;/code&gt; / your Clean Architecture Solution here&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Within &lt;code&gt;is-template&lt;/code&gt; folder, Create &lt;code&gt;.template.config&lt;/code&gt; folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After this, Create json file within the &lt;code&gt;.template.config&lt;/code&gt; called &lt;code&gt;template.json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can modify the template.json content from below&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
      "$schema": "http://json.schemastore.org/template",
      "author": "Rayson",
      "classifications": [
        "blazor",
    "WebAPI"
      ],
      "name": "Team Architecture project template",
      "description": "Project template to create starter project for .NET Team project",
      "identity": "IS.project.starter",
      "shortName": "is-template",
      "sourceName": "ISWebAppTemplate",
      "tags": {
        "language": "C#",
        "type": "project"
      },
      "symbols": {
        "Framework": {
          "type": "parameter",
          "description": "The target framework for the project.",
          "datatype": "choice",
          "choices": [
            {
              "choice": "net8.0"
            },
            {
              "choice": "net9.0"
          }
          ],
          "defaultValue": "net8.0",
          "replaces": "{TargetFramework}"
        }
      }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Now run the below command in cmd (Path should be at root of your .Net Clean Architecture Solution):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new -i .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Now, you can run the below command in cmd to create a project based on template. Below, &lt;code&gt;is-template&lt;/code&gt; is the shortName you provided in &lt;code&gt;template.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new is-template -n YourNewProject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Optional: &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Now, if you want this package to be publicly available to all your team member, You can follow the below approach.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites:&lt;/li&gt;
&lt;li&gt;GitLab / GitHub - Contributor Access required.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;I will be using gitlabs, As i have expertise on this. But, Iam pretty sure the same process would be used for github as well.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;First let's make it secure. We will create a access token for the project. You can create it by going to &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Gitlab&lt;/code&gt; -&amp;gt; &lt;code&gt;Your Project Repo&lt;/code&gt; -&amp;gt; &lt;code&gt;Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Access token&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Configure the token as below and create it&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgwuqdmbk0ol3990e07j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgwuqdmbk0ol3990e07j.png" alt="gitlab token" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Copy the token and keep it secure. We need it in next step&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open cmd and run the below command:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet nuget add source "https://&amp;lt;gitlab-domain&amp;gt;/api/v4/projects/&amp;lt;project-id&amp;gt;/packages/nuget/index.json" --name is_template_package --username &amp;lt;gitlab-username&amp;gt; --password &amp;lt;access-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Note: 
&amp;lt;gitlab-username&amp;gt; - your gitlab username
&amp;lt;project-id&amp;gt; - You can find it in your gitlab project like below
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg47gk6yltj5rgahrggg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg47gk6yltj5rgahrggg9.png" alt="project id" width="800" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now go back to gitlab repo and got to Deploy -&amp;gt; Package Registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvyo4r1x1il1c9nv86de.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvyo4r1x1il1c9nv86de.png" alt="deploy" width="691" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You should see a is_template_package here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, Run the below commands:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet build -c Release

dotnet pack -c Release -o ./nuget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You should see a &lt;code&gt;nuget&lt;/code&gt; folder created in root, Which contains a &lt;code&gt;&amp;lt;solution-name&amp;gt;.nupkg&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once done, Run the below command in cmd:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet nuget push ./nuget/&amp;lt;solution-name&amp;gt;.nupkg --api-key &amp;lt;access-token&amp;gt; --source "https://&amp;lt;gitlab domain&amp;gt;/api/v4/projects/&amp;lt;project-id&amp;gt;/packages/nuget/index.json"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The approach what i would suggest is using CI/CD pipeline. This is a yaml file where you add the stages. This will be explained in separate post later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to get started &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Now that we have set up the private Nuget which is available to team members, How to team members install it?. Follow the below process...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add below nuget resource on your local environment using dotnet command. This will add IS team private nuget repository as one of the source for nuget packages.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet nuget add source "https://&amp;lt;gitlab-domain&amp;gt;/api/v4/projects/&amp;lt;project-id&amp;gt;/packages/nuget/index.json"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install the IS starter project template using below command
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new install &amp;lt;nupkg-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create new project using below command. Replace  with the choice of your project name.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new is-template -n &amp;lt;solution name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Example: dotnet new is-starter-web -n TravelDesk this will create solution as TravelDesk.sln and replace the projects name with the given name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Update template&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the below command to update the template
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;If you wish to check if there are any updates to the template run the below command
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new update --check-only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Uninstall/Remove template&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you want to remove the template from your machine run the below command.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new uninstall &amp;lt;nuget-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a Nuget Package &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In order to create a centralised Nuget Package manager, We have to create a console application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open VS 2022, Select &lt;code&gt;Create a new Project&lt;/code&gt; -&amp;gt; &lt;code&gt;Console App&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove the Default Class created.&lt;/li&gt;
&lt;li&gt;Install all necessary packages that are required for all the projects here. This will be the single point of access for all packages.&lt;/li&gt;
&lt;li&gt;Your final solution should look like this&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvueopbucht6tiv6bizx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvueopbucht6tiv6bizx.png" alt="nuget solution" width="345" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note, In the above image, The yml file was created by me and the nuget folder will be visible, Once you finish the below process.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once done, Follow the same process from this section.&lt;/li&gt;
&lt;li&gt;Once you got the source gitlab url from the process. The source url would be in this format:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;gitlab-domain&amp;gt;/api/v4/projects/&amp;lt;project-id&amp;gt;/packages/nuget/index.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Go to the default template that we created before this section. Open the template solution in vs 2022. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Right Click Solution&lt;/code&gt; -&amp;gt; &lt;code&gt;Manage NuGet Packages for Solution&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click on the &lt;code&gt;Settings (Gear Icon)&lt;/code&gt; -&amp;gt; &lt;code&gt;Add new item (+ icon)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9sv845roofa296c3sls0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9sv845roofa296c3sls0.png" alt="Nuget" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give package source name and Source as the gitlab url in format:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;gitlab-domain&amp;gt;/api/v4/projects/&amp;lt;project-id&amp;gt;/packages/nuget/index.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Remove everything except this.&lt;/li&gt;
&lt;li&gt;You can follow the process of packaging the template again by checking here and this time change the version from &lt;code&gt;1.0.0&lt;/code&gt; to &lt;code&gt;1.0.2&lt;/code&gt;. You can set it in &lt;code&gt;.csproj&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Once done, In your template, You will get an update to IS.Nuget package. Once updated,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is what will be present in &lt;code&gt;Directory.Packages.props&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PackageVersion Include="IS.Nuget" Version="1.0.2" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in all projects except domain within template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PackageReference Include="IS.Nuget" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note that: The gitlab project repo for both Template and Nuget are maintained separately. They should not be placed under single repo. So, The project id for Nuget and Template would be different. Same goes for Access token as well.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Creating a project template isn’t just about saving time — it’s about enforcing consistency, reducing errors, and scaling development across multiple applications and teams.&lt;/p&gt;

&lt;p&gt;By turning your Clean Architecture setup into a reusable .NET template and combining it with a centralized NuGet package registry, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Accelerate onboarding for new team members&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prevent unauthorized or accidental package usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure all apps follow the same architectural and dependency standards&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The template now contains a Private Nuget which will have an update everytime you add a new Nuget package into Console app and push it to gitlab private registry.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach has helped our team manage 20+ enterprise applications with fewer surprises and better control.&lt;/p&gt;

&lt;p&gt;Wait!! There's more..&lt;/p&gt;

&lt;p&gt;Have you ever wondered:&lt;br&gt;
"Rayson, can't this process be even cleaner?"&lt;/p&gt;

&lt;p&gt;Well... you're absolutely right — I hate running a bunch of manual commands in cmd too.&lt;/p&gt;

&lt;p&gt;The good news? We can simplify it further using CI/CD pipelines and environment variables to automate most of these steps.&lt;/p&gt;

&lt;p&gt;Stay tuned — in the next post, I’ll show how to streamline this entire workflow with automation, so you can focus more on development and less on setup.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>template</category>
      <category>dotnet</category>
      <category>nuget</category>
    </item>
    <item>
      <title>Clean Architecture for Enterprise Applications: A Practical Guide from the Trenches</title>
      <dc:creator>Ray_Dsz</dc:creator>
      <pubDate>Tue, 27 May 2025 15:59:18 +0000</pubDate>
      <link>https://dev.to/the_architect/clean-architecture-for-enterprise-applications-a-practical-guide-from-the-trenches-3ii0</link>
      <guid>https://dev.to/the_architect/clean-architecture-for-enterprise-applications-a-practical-guide-from-the-trenches-3ii0</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;The Question, What?&lt;/li&gt;
&lt;li&gt;The Question, Why?&lt;/li&gt;
&lt;li&gt;The Question, How?&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Have you ever faced a situation where you wanted to separate concerns like logging, database operations, and business logic? &lt;/p&gt;

&lt;p&gt;While this can be partially achieved by tweaking a traditional monolithic architecture, there's a better way.&lt;/p&gt;

&lt;p&gt;What if I told you there's an architectural style designed to separate these concerns cleanly and effectively?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Yes — that’s Clean Architecture. Let’s dive in and explore why it works so well.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Clean Architecture in Nutshell&lt;/strong&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Clean Architecture is a layered architecture that enforces separation of concerns through strict boundaries. Each layer has a clear role and direction of dependency. &lt;/p&gt;

&lt;p&gt;Typically, There are four Main Clean Architecture Layers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj5uwjpxd750pi719pk9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj5uwjpxd750pi719pk9.png" alt="Clean Architecture Model" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Domain: &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;This is the core and innermost layer of the architecture. It has no dependencies on other layers and contains the pure business model of your application. You won't find any NuGet packages here. &lt;/p&gt;

&lt;p&gt;It typically includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enums&lt;/li&gt;
&lt;li&gt;Models&lt;/li&gt;
&lt;li&gt;Constants&lt;/li&gt;
&lt;li&gt;Value Objects&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Application: &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;The Application layer depends only on the Domain layer. It contains use cases and business logic that orchestrate operations using domain entities. &lt;/p&gt;

&lt;p&gt;Here you define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application services (use cases)&lt;/li&gt;
&lt;li&gt;Interfaces for repositories or external services&lt;/li&gt;
&lt;li&gt;Validation rules and constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: Logic for how a request should be created, or when a workflow should be triggered.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Infrastructure: &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;This layer provides implementations for interfaces defined in the Application layer. It depends on the Application. But, As the Application depends on Domain layer. Infrastructure layer also depends on Domain layer. &lt;/p&gt;

&lt;p&gt;Typical components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database context (e.g., AppDbContext in EF Core)&lt;/li&gt;
&lt;li&gt;Repository implementations&lt;/li&gt;
&lt;li&gt;External services (e.g., mail, file storage)&lt;/li&gt;
&lt;li&gt;Data migrations and configurations&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Presentation Layer: &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;The outermost layer of the system. It provides the user interface or API that external clients interact with. It depends on the Application and Infrastructure layers. &lt;/p&gt;

&lt;p&gt;It usually contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API endpoints (e.g., Minimal APIs or Controllers)&lt;/li&gt;
&lt;li&gt;Middleware (e.g., global exception handling)&lt;/li&gt;
&lt;li&gt;Authentication &amp;amp; Authorization logic&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;The Question: Why?&lt;/strong&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;I used to wonder whether Clean Architecture was the right fit for our use case — modernizing a suite of homegrown applications.&lt;/p&gt;

&lt;p&gt;The company I currently work for maintains over 20 internal applications, each built over time with different frameworks and architectural styles. Some were in Angular, others in .NET, and a few in React. As the system grew, maintaining expertise across so many tech stacks became difficult and inefficient. We didn’t want to depend on hiring developers with specific skills for each application.&lt;/p&gt;

&lt;p&gt;So, we made a strategic decision: standardize all new and modernized apps using a single stack — .NET with Clean Architecture. It offered better scalability, clear separation of concerns, and most importantly, consistency across all applications.&lt;/p&gt;

&lt;p&gt;Initially, this approach felt like over-engineering — especially for our smaller apps. Clean Architecture introduces more layers and structure, which might feel heavy when your app has just a few features or a limited user base (ours was around 2,000 users). But let's be honest, &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Architecture isn't only about scale of users — it's about scale of complexity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even if an app starts small, business logic can grow rapidly. With monolithic structures, you risk dumping everything into one massive "business layer" over time. Clean Architecture helps you avoid that by enforcing boundaries from the beginning.&lt;/p&gt;

&lt;p&gt;Yes, it’s more complex upfront. New developers might need time to get used to the structure. But once they do, they’ll appreciate how easy it is to navigate, maintain, and extend the codebase — layer by layer, feature by feature.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;The Question, How?&lt;/strong&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;How do you actually build a Clean Architecture solution?&lt;/p&gt;

&lt;p&gt;The most important thing for us, when modernizing over 20 internal applications, was to establish a common boilerplate — a reusable template that could serve as a starting point for every application we build going forward.&lt;/p&gt;

&lt;p&gt;Here’s an overview of that structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqmth4j5d9g8coful1cf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqmth4j5d9g8coful1cf.png" alt="Overall Template Structure" width="553" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this post, I’ll explain the structure at a high level. In future posts, I’ll dive deeper into each component. Now, Let's start with creating a blank solution in VS 2022. Then follow up:&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  Solution Items:
&lt;/h4&gt;

&lt;p&gt;These files apply settings across the entire solution, not &lt;br&gt;
   just individual projects&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.editorconfig&lt;/code&gt;: Contains code formatting rules. For 
   example, in Visual Studio 2022, this can enforce naming 
   conventions (e.g., readonly variables must start with &lt;code&gt;_&lt;/code&gt;, 
   static variables with &lt;code&gt;s_&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Create it with: &lt;code&gt;Add → New Item → Editor Config (.NET)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Directory.Build.props&lt;/code&gt;: Stores common metadata for all 
  projects (e.g., name, description, Git repo URL).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Create it with: &lt;code&gt;Add → New Item → XML File&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Project&amp;gt;
  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;Title&amp;gt;IS Web Application Template&amp;lt;/Title&amp;gt;
    &amp;lt;Authors&amp;gt;Rayson Dsouza&amp;lt;/Authors&amp;gt;
    &amp;lt;Description&amp;gt;This template will be the base for all the applications that are going to be built under IS. These include API, Web Blazor Applications.&amp;lt;/Description&amp;gt;
    &amp;lt;RepositoryType&amp;gt;gitlabs&amp;lt;/RepositoryType&amp;gt;
    &amp;lt;PackageTags&amp;gt;api project, api endpoints, response, web ui&amp;lt;/PackageTags&amp;gt;
    &amp;lt;RepositoryUrl&amp;gt;{YOUR_GITLAB_REPO_URL}&amp;lt;/RepositoryUrl&amp;gt;
    &amp;lt;LangVersion&amp;gt;12&amp;lt;/LangVersion&amp;gt;
    &amp;lt;RootNamespace&amp;gt;ISWebAppTemplate&amp;lt;/RootNamespace&amp;gt;
    &amp;lt;TargetFramework&amp;gt;net8.0&amp;lt;/TargetFramework&amp;gt;
    &amp;lt;Nullable&amp;gt;enable&amp;lt;/Nullable&amp;gt;
    &amp;lt;ImplicitUsings&amp;gt;enable&amp;lt;/ImplicitUsings&amp;gt;
    &amp;lt;InvariantGlobalization&amp;gt;false&amp;lt;/InvariantGlobalization&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;
&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Directory.Packages.props&lt;/code&gt;: Centralizes NuGet package 
  references across the solution, avoiding redundancy.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Create it with: &lt;code&gt;Add → New Item → XML File&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Project&amp;gt;
  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;ManagePackageVersionsCentrally&amp;gt;true&amp;lt;/ManagePackageVersionsCentrally&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;
  &amp;lt;ItemGroup&amp;gt;
    &amp;lt;PackageVersion Include="Carter" Version="8.2.1" /&amp;gt;
    &amp;lt;PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.16" /&amp;gt;
    &amp;lt;PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4" /&amp;gt;
    &amp;lt;PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.4" /&amp;gt;
    &amp;lt;PackageVersion Include="Swashbuckle.AspNetCore" Version="8.1.1" /&amp;gt;
    &amp;lt;PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.10.0" /&amp;gt;
  &amp;lt;/ItemGroup&amp;gt;
&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  src/Backend: Clean Architecture Backend
&lt;/h4&gt;

&lt;p&gt;This folder holds the backend solution based on Clean &lt;br&gt;
   Architecture.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ISWebAppTemplate.Api&lt;/code&gt;: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zwqouyc8l79hj1ubcwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zwqouyc8l79hj1ubcwf.png" alt="Template Presentation Layer" width="428" height="303"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Project Sdk="Microsoft.NET.Sdk.Web"&amp;gt;
  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;GenerateDocumentationFile&amp;gt;true&amp;lt;/GenerateDocumentationFile&amp;gt;
    &amp;lt;RootNamespace&amp;gt;ISWebAppTemplate.Api&amp;lt;/RootNamespace&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;
  &amp;lt;ItemGroup&amp;gt;
    &amp;lt;PackageReference Include="Carter" /&amp;gt;
    &amp;lt;PackageReference Include="Microsoft.AspNetCore.OpenApi" /&amp;gt;
    &amp;lt;PackageReference Include="Microsoft.EntityFrameworkCore.Design"&amp;gt;
      &amp;lt;PrivateAssets&amp;gt;all&amp;lt;/PrivateAssets&amp;gt;
      &amp;lt;IncludeAssets&amp;gt;runtime; build; native; contentfiles; analyzers; buildtransitive&amp;lt;/IncludeAssets&amp;gt;
    &amp;lt;/PackageReference&amp;gt;
    &amp;lt;PackageReference Include="Swashbuckle.AspNetCore" /&amp;gt;
  &amp;lt;/ItemGroup&amp;gt;

  &amp;lt;ItemGroup&amp;gt;
    &amp;lt;ProjectReference Include="..\ISWebAppTemplate.Application\ISWebAppTemplate.Application.csproj" /&amp;gt;
    &amp;lt;ProjectReference Include="..\ISWebAppTemplate.Infrastructure\ISWebAppTemplate.Infrastructure.csproj" /&amp;gt;
  &amp;lt;/ItemGroup&amp;gt;
&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Presentation Layer (outermost layer). An ASP.NET Core &lt;br&gt;
  Web API project. You can refer to Presentation layer for more details.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ISWebAppTemplate.Application&lt;/code&gt;: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65qiidb6nowpabds5hj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65qiidb6nowpabds5hj3.png" alt="Template Application Layer" width="422" height="335"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Project Sdk="Microsoft.NET.Sdk"&amp;gt;
  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;RootNamespace&amp;gt;ISWebAppTemplate&amp;lt;/RootNamespace&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;
  &amp;lt;ItemGroup&amp;gt;
    &amp;lt;ProjectReference Include="..\ISWebAppTemplate.Domain\ISWebAppTemplate.Domain.csproj" /&amp;gt;
  &amp;lt;/ItemGroup&amp;gt;
&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Application Layer. A class library focused on business &lt;br&gt;
  logic. You can refer to Application layer for more &lt;br&gt;
  details.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ISWebAppTemplate.Infrastructure&lt;/code&gt;: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxkob73gsbeeiyo3ziry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxkob73gsbeeiyo3ziry.png" alt="Template Infrastructure Layer" width="426" height="240"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Project Sdk="Microsoft.NET.Sdk"&amp;gt;

  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;RootNamespace&amp;gt;ISWebAppTemplate.Infrastructure&amp;lt;/RootNamespace&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;

  &amp;lt;ItemGroup&amp;gt;
    &amp;lt;PackageReference Include="Microsoft.EntityFrameworkCore.Tools"&amp;gt;
      &amp;lt;PrivateAssets&amp;gt;all&amp;lt;/PrivateAssets&amp;gt;
      &amp;lt;IncludeAssets&amp;gt;runtime; build; native; contentfiles; analyzers; buildtransitive&amp;lt;/IncludeAssets&amp;gt;
    &amp;lt;/PackageReference&amp;gt;
  &amp;lt;/ItemGroup&amp;gt;

  &amp;lt;ItemGroup&amp;gt;
    &amp;lt;ProjectReference Include="..\ISWebAppTemplate.Application\ISWebAppTemplate.Application.csproj" /&amp;gt;
  &amp;lt;/ItemGroup&amp;gt;

&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Infrastructure Layer.  A class library focused on Handling &lt;br&gt;
 database and external service interactions. You can refer to &lt;br&gt;
 Infrastructure layer for more details.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ISWebAppTemplate.Domain&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vyw4w7iz6uwldm7lfu2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vyw4w7iz6uwldm7lfu2.png" alt="Template Domain Layer" width="428" height="340"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Project Sdk="Microsoft.NET.Sdk"&amp;gt;
  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;RootNamespace&amp;gt;ISWebAppTemplate.Domain&amp;lt;/RootNamespace&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;
&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Domain Layer (innermost/core layer). Contains domain &lt;br&gt;
  entities, value objects, and core rules. You can refer to &lt;br&gt;
  Domain layer for more details.&lt;/p&gt;




&lt;h4&gt;
  
  
  src/Frontend: Blazor Web UI
&lt;/h4&gt;

&lt;p&gt;This folder contains the frontend application. I have &lt;br&gt;
   created Blazor Web App and selected Blazor server as &lt;br&gt;
   renderer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ISWebAppTemplate.WebUI&lt;/code&gt;: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0k02k5mh5t9c3tb3dbx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0k02k5mh5t9c3tb3dbx.png" alt="Template UI Structure" width="497" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Blazor Server application with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Radzen for UI components&lt;/li&gt;
&lt;li&gt;Azure Entra MFA Authentication&lt;/li&gt;
&lt;li&gt;Correlation ID generation for tracing requests across 
  downstream APIs — helpful in debugging and log analysis.&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;code&gt;/tests:&lt;/code&gt; Unit and Integration Testing
&lt;/h4&gt;

&lt;p&gt;We separate tests into two clear layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ISWebAppTemplate.Integration.Tests&lt;/code&gt;: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1v76tuh1zcfpbx7bt6q3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1v76tuh1zcfpbx7bt6q3.png" alt="Template Integration Test" width="468" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus: Full transaction and behavior testing&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: If a requester is in Band X, no approval is &lt;br&gt;
 required; otherwise, admin approval is triggered. &lt;br&gt;
 Integration tests verify this business logic &lt;br&gt;
 holistically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ISWebAppTemplate.Unit.Tests&lt;/code&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05kx2dswdshd82dxomrq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05kx2dswdshd82dxomrq.png" alt="Template Unit Test" width="422" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focus: Validations and logic at the entity level&lt;/li&gt;
&lt;li&gt;Example: The request title must be at least 50 
 characters long&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Key Difference:&lt;/strong&gt;&lt;br&gt;
   Unit tests validate isolated components like a field or &lt;br&gt;
        rule.&lt;br&gt;
   Integration tests validate end-to-end behavior and rule &lt;br&gt;
   application.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;I know this has been a lot to take in — especially since I’ve only scratched the surface. But don’t worry, I’ll be diving deeper in upcoming posts. Each of the techniques and patterns used in this project will be covered individually to give you a clearer and more practical understanding of how they work in real scenarios.&lt;/p&gt;

&lt;p&gt;So why Clean Architecture over a traditional monolith?&lt;/p&gt;

&lt;p&gt;It all comes down to separation of concerns, scalability, and long-term maintainability. Clean Architecture gave us a structure that helps manage complex business logic across 20+ applications — something that would have become unmanageable with a typical monolithic setup.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note: We are not using Docker or containerization in this setup. Since our applications serve around 2,000 users and are hosted on a stable on-premise server, the performance needs are easily met. The main reason for adopting Clean Architecture was to better manage complex business logic, not to meet scale or deployment flexibility.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next Up: &lt;a href="https://dev.to/rayson_lawrencedsouza_54/create-and-publish-a-custom-net-project-template-and-private-nuget-to-gitlab-package-registry-1a6h"&gt;Create and Publish a Custom .NET Project Template and Private NuGet to GitLab Package Registry&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cleanarchitecture</category>
      <category>dotnet</category>
      <category>enterprise</category>
      <category>blazor</category>
    </item>
  </channel>
</rss>
