Use HttpContext Object in Blazor Server to Retrieve Information About the User or User Agent

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // . . .
    services.AddHttpContextAccessor();
}

Index.razor

@page "/"
@using Microsoft.AspNetCore.Http
@inject IHttpContextAccessor httpContextAccessor

<p>UserAgent = @UserAgent</p>
<p>IPAddress = @IPAddress</p>

@code {
    public string UserAgent { get; set; }
    public string IPAddress { get; set; }

    protected override void OnInitialized()
    {
        UserAgent = httpContextAccessor.HttpContext.Request.Headers["User-Agent"];
        IPAddress = httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
    }
}

Access HttpContext in Service

Don’t use IHttpContextAccessor/HttpContext directly or indirectly in the Razor components of Blazor Server apps. Blazor apps run outside of the ASP.NET Core pipeline context. The HttpContext isn’t guaranteed to be available within the IHttpContextAccessor, and HttpContext isn’t guaranteed to hold the context that started the Blazor app.

The recommended approach for passing request state to the Blazor app is through root component parameters during the app’s initial rendering. Alternatively, the app can copy the data into a scoped service in the root component’s initialization lifecycle event for use across the app.

namespace Get_HttpContext_ASP.NET_Core
{
    using Microsoft.AspNetCore.Http;

    public class UserService : IUserService
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public UserService(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public string GetLoginUserName()
        {
            return _httpContextAccessor.HttpContext.User.Identity.Name;
        }
    }
}

References
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-7.0
https://www.syncfusion.com/faq/blazor/tips-and-tricks/how-do-you-use-the-httpcontext-object-in-blazor-server-side-to-retrieve-information-about-the-user-or-user-agent
https://www.telerik.com/blogs/how-to-get-httpcontext-asp-net-core
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/use-http-context?view=aspnetcore-7.0

Install MongoDB Community Edition on Ubuntu 22.04

wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo service mongod start
systemctl enable mongod.service

References
https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/

Enrichment in Serilog

Log events can be enriched with properties in various ways. A number of pre-built enrichers are provided through NuGet:

Install-Package Serilog.Enrichers.Process
Install-Package Serilog.Enrichers.Environment
Install-Package Serilog.Enrichers.Thread

Configuration for enrichment is done via the Enrich configuration object:

var log = new LoggerConfiguration()
    .Enrich.WithMachineName()   
    .Enrich.WithThreadId()
    .Enrich.WithThreadName()
    .Enrich.WithProcessId()
    .Enrich.WithProcessName()
    .WriteTo.Console()
    .CreateLogger();

The LogContext

Serilog.Context.LogContext can be used to dynamically add and remove properties from the ambient “execution context”; for example, all messages written during a transaction might carry the id of that transaction, and so-on.

This feature must be added to the logger at configuration-time using .FromLogContext():

var log = new LoggerConfiguration()
    .Enrich.FromLogContext()

Then, properties can be added and removed from the context using LogContext.PushProperty():

log.Information("No contextual properties");

using (LogContext.PushProperty("A", 1))
{
    log.Information("Carries property A = 1");

    using (LogContext.PushProperty("A", 2))
    using (LogContext.PushProperty("B", 1))
    {
        log.Information("Carries A = 2 and B = 1");
    }

    log.Information("Carries property A = 1, again");
}

References
https://github.com/serilog/serilog/wiki/Enrichment

Form Validation Using Validator Component in ASP.NET Blazor

public class CustomValidator : ComponentBase
{
    private ValidationMessageStore messageStore;
    [CascadingParameter] public EditContext CurrentEditContext { get; set; }

    protected override void OnInitialized()
    {
        if (CurrentEditContext == null)
        {
            throw new InvalidOperationException(
                "To use validator component your razor page should have the edit component");
        }

        messageStore = new ValidationMessageStore(CurrentEditContext);
        
        // Clear Error Message On Raise Of OnValidationRequested Form Event
        CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
        
        // Clear Error Message On Input Field Change Event
        CurrentEditContext.OnFieldChanged += (s, e) => messageStore.Clear(e.FieldIdentifier);
    }

    public void DisplayErrors(Dictionary<string, List<string>> errors)
    {
        foreach (var error in errors)
        {
            messageStore.Add(CurrentEditContext.Field(error.Key), error.Value);
        }

        CurrentEditContext.NotifyValidationStateChanged();
    }

    public void DisplayErrors(string fieldName, List<string> errors)
    {
        foreach (var error in errors)
        {
            messageStore.Add(CurrentEditContext.Field(fieldName), error);
        }

        CurrentEditContext.NotifyValidationStateChanged();
    }
}

App.razor

<EditForm Model="@_model" OnValidSubmit="ValidSubmit">
        <DataAnnotationsValidator/>
        <ValidationSummary/>
        <CustomValidator @ref="_customValidator"></CustomValidator>
...
@code {

    private CustomValidator _customValidator;

...
private async Task ValidSubmit(EditContext obj)
   {
       if (await CheckValidations())
       {
           if (OnSubmit.HasDelegate)
           {
               Customer.Customerr = _model.Customer.TrimText();
               Customer.ProvinceUUID = _model.ProvinceUUID;
               Customer.CityUUID = _model.CityUUID;

               await OnSubmit.InvokeAsync(new NewCustomerInfoEventArgs()
               {
                   Customer = Customer,
               });
           }
       }
   }

   private async Task<bool> CheckValidations()
   {
       int errors = 0;
       ApplicationDbContext context = await DbFactory.CreateDbContextAsync();
       var customer = _model.Customer.TrimText();
       var isExist = await context.DcCustomers.AnyAsync(x => x.Customerr == customer);
       await context.DisposeAsync();
       if (isExist)
       {
           _customValidator.DisplayErrors(nameof(_model.Customer), new List<string> { "نام مشتری تکراری است" });
           errors += 1;
       }

       if (errors == 0)
       {
           return true;
       }
       
       return false;
   }

References
https://www.learmoreseekmore.com/2021/01/blazor-server-forms-validator-component.html

Route Constraints in ASP.NET Core Blazor

@page "/route-parameter/{text?}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}
@page "/user/{Id:int}/{Option:bool?}"

<p>
    Id: @Id
</p>

<p>
    Option: @Option
</p>

@code {
    [Parameter]
    public int Id { get; set; }

    [Parameter]
    public bool Option { get; set; }
}
Constraint Example Example Matches Invariant
culture
matching
bool {active:bool} trueFALSE No
datetime {dob:datetime} 2016-12-312016-12-31 7:32pm Yes
decimal {price:decimal} 49.99-1,000.01 Yes
double {weight:double} 1.234-1,001.01e8 Yes
float {weight:float} 1.234-1,001.01e8 Yes
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638{CD2C1638-1638-72D5-1638-DEADBEEF1638} No
int {id:int} 123456789-123456789 Yes
long {ticks:long} 123456789-123456789 Yes

References
https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-7.0#route-constraints

Blazor Custom Form Validation

If we have a validation requirement that cannot be implemented using the built-in attributes, we can create a custom validation attribute.

  1. Create a class that derives from the built-in abstract ValidationAttribute class and override IsValid() method.
  2. IsValid() method returns null if there are no validation errors, otherwise a ValidationResult object.
  3. ValidationResult accepts 2 parameters – Validation error message and the property name with which this validation error message must be associated with.
using System.ComponentModel.DataAnnotations;

namespace EmployeeManagement.Models.CustomValidators
{
    public class EmailDomainValidator : ValidationAttribute
    {
        public string AllowedDomain { get; set; }

        protected override ValidationResult IsValid(object value, 
            ValidationContext validationContext)
        {
            string[] strings = value.ToString().Split('@');
            if (strings[1].ToUpper() == AllowedDomain.ToUpper())
            {
                return null;
            }

            return new ValidationResult($"Domain must be {AllowedDomain}",
            new[] { validationContext.MemberName });
        }
    }
}
public class Employee
{
    [EmailDomainValidator(AllowedDomain = "pragimtech.com")]
    public string Email { get; set; }
}

References
https://www.pragimtech.com/blog/blazor/blazor-custom-form-validation/