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/

Restores Dependencies and Tools of a Project in .NET

Restore dependencies and tools for the project in the current directory:

dotnet restore

Restore dependencies and tools for the app1 project found in the given path:

dotnet restore ./projects/app1/app1.csproj

Restore the dependencies and tools for the project in the current directory using the file path provided as the source:

dotnet restore -s c:\packages\mypackages

Restore the dependencies and tools for the project in the current directory using the two file paths provided as sources:

dotnet restore -s c:\packages\mypackages -s c:\packages\myotherpackages

Restore dependencies and tools for the project in the current directory showing detailed output:

dotnet restore --verbosity detailed

References
https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-restore

SpacerElement in Virtualize component in Blazor 7.0

If the Virtualize component is placed inside an element that requires a specific child tag name, SpacerElement allows you to obtain or set the virtualization spacer tag name. The default value is div. For the following example, the Virtualize component renders inside a table body element (tbody), so the appropriate child element for a table row (tr) is set as the spacer.

@page "/virtualized-table"

<HeadContent>
    <style>
        html, body { overflow-y: scroll }
    </style>
</HeadContent>

<h1>Virtualized Table Example</h1>

<table id="virtualized-table">
    <thead style="position: sticky; top: 0; background-color: silver">
        <tr>
            <th>Item</th>
            <th>Another column</th>
        </tr>
    </thead>
    <tbody>
        <Virtualize Items="@fixedItems" ItemSize="30" SpacerElement="tr">
            <tr @key="context" style="height: 30px;" id="[email protected]">
                <td>Item @context</td>
                <td>Another value</td>
            </tr>
        </Virtualize>
    </tbody>
</table>

@code {
    private List<int> fixedItems = Enumerable.Range(0, 1000).ToList();
}

References
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/virtualization?view=aspnetcore-7.0
https://youtu.be/oNZnYNUpu54

Raw string literals in C# 11

Raw string literals can contain arbitrary text, including whitespace, new lines, embedded quotes, and other special characters without requiring escape sequences.

string longMessage = """
    This is a long message.
    It has several lines.
        Some are indented
                more than others.
    Some should start at the first column.
    Some have "quoted text" in them.
    """;

Raw string literals can be combined with string interpolation to include braces in the output text. Multiple $ characters denote how many consecutive braces start and end the interpolation:

var location = $$"""
   You are at {{{Longitude}}, {{Latitude}}}
   """;

References
https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#raw-string-literals

required modifier in C# 11

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName) =>
        (FirstName, LastName) = (firstName, lastName);

    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    public int? Age { get; set; }
}

public class Student : Person
{
    public Student() : base()
    {
    }

    [SetsRequiredMembers]
    public Student(string firstName, string lastName) :
        base(firstName, lastName)
    {
    }

    public double GPA { get; set; }
}

The SetsRequiredMembers disables the compiler’s checks that all required members are initialized when an object is created. Use it with caution.

References
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required
https://youtu.be/9CDgPgWF9IY