Creating Web Apps with Blazor And .NET Aspire - Part II

Creating Web Apps with Blazor And .NET Aspire - Part II

Hello, I hope you are doing great!

Thanks for reading.

Remember to share the article with your network and invite more people to subscribe.

If you haven't yet, go check the previous articles in the series: Creating Web Apps with Blazor And .NET Aspire

In the last article we saw that we need to include validation to the UI.

First, we will use Data Annotations, some people would argue that this is not a correct approach, because it makes your models not being "clean", and they are somewhat right, this is where you need to start checking what gives you more value, and what takes longer to implement, which are your deadlines, etc.

Generally, Software Developers thinking is going to the Best Quality possible, cleaner code possible, and usually also the most complex and fulfilling code, something that gives them a challenge, I love challenges myself!, however, you also need to develop a Business Thinking, what's most effective, what's cheaper, what makes a faster delivery and what makes a faster Return of Investment, extremely high-quality code is worthless if it takes forever to implement, you would kill your own business before you even start it.

Let's continue, we want the following fields to be required

  • Firstname
  • Lastname
  • EmailAddress

The EmailAddress field also must have a valid email format

The Url fields must be valid Urls.

For the required fields we use the RequiredAttribute class, decorating the properties with [Required]

For the EmailAddress we use the EmailAddessAttribute class, decorating our properties with [EmailAddress]

So, now we will modify the class.

Note: you may have seen this warning

We are going to solve that by making our properties Nullable, you do it by suffixing the type with "?"

Replace the ContactModel class like this

public class ContactModel
{
    [Required]
    public string? Firstname { get; set; }
    [Required]
    public string? Lastname { get; set; }
    [Required]
    [EmailAddress]
    public string? EmailAddress { get; set; }
    [Url]
    public string? LinkedInProfileUrl { get; set; }
    [Url]
    public string? XformerlyTwitterUrl { get; set; }
    [Url]
    public string? IntagramProfileUrl { get; set; }
    [Url]
    public string? WebsiteUrl { get; set; }
}        

So now we have our model with the validations, however, that is not enough, we also need to tell the UI to perform the validations, to do so we use the DataAnnotationsValidator, which enabled the support for validation in the UI, and we also need to show them, we do so by using either FluentValidationSummary or FluentValidationMessage.

FluentValidationSummary will show the validation errors as a bullet list, while the FluentValidationMessage is usually placed right next to the field which is validated to the errors are shown right next to that field.

Additionally, since we are using the Fluent UI library components, we are going to use some properties the FluentTextField provides

  • InputMode: Allows a browser to display an appropriate virtual keyboard
  • TextFieldType: Sets the supported type for the field

Now, replace the Create.razor file with this

@page "/Contacts/Create"
@using BlazorAspireTutorial.Web.Models

@inject IToastService toastService

<FluentEditForm Model="@this.contactModel" OnValidSubmit="OnValidSubmit">
    <div>
        <DataAnnotationsValidator></DataAnnotationsValidator>
        <FluentValidationSummary></FluentValidationSummary>
    </div>
    <div>
        <FluentLabel Typo="Typography.Body">@nameof(ContactModel.Firstname)</FluentLabel>
        <FluentTextField @bind-Value="@this.contactModel.Firstname"></FluentTextField>
        <FluentValidationMessage For="(()=>this.contactModel.Firstname)"></FluentValidationMessage>
    </div>
    <div>
        <FluentLabel Typo="Typography.Body">@nameof(ContactModel.Lastname)</FluentLabel>
        <FluentTextField @bind-Value="@this.contactModel.Lastname"></FluentTextField>
        <FluentValidationMessage For="(()=>this.contactModel.Lastname)"></FluentValidationMessage>
    </div>
    <div>
        <FluentLabel Typo="Typography.Body">@nameof(ContactModel.EmailAddress)</FluentLabel>
        <FluentTextField @bind-Value="@this.contactModel.EmailAddress" InputMode="InputMode.Email" TextFieldType="TextFieldType.Email"></FluentTextField>
        <FluentValidationMessage For="(()=>this.contactModel.EmailAddress)"></FluentValidationMessage>
    </div>
    <div>
        <FluentLabel Typo="Typography.Body">@nameof(ContactModel.LinkedInProfileUrl)</FluentLabel>
        <FluentTextField @bind-Value="@this.contactModel.LinkedInProfileUrl" InputMode="InputMode.Url" TextFieldType="TextFieldType.Url"></FluentTextField>
        <FluentValidationMessage For="(()=>this.contactModel.LinkedInProfileUrl)"></FluentValidationMessage>
    </div>
    <div>
        <FluentLabel Typo="Typography.Body">@nameof(ContactModel.IntagramProfileUrl)</FluentLabel>
        <FluentTextField @bind-Value="@this.contactModel.IntagramProfileUrl" InputMode="InputMode.Url" TextFieldType="TextFieldType.Url"></FluentTextField>
        <FluentValidationMessage For="(()=>this.contactModel.IntagramProfileUrl)"></FluentValidationMessage>
    </div>
    <div>
        <FluentLabel Typo="Typography.Body">@nameof(ContactModel.XformerlyTwitterUrl)</FluentLabel>
        <FluentTextField @bind-Value="@this.contactModel.XformerlyTwitterUrl" InputMode="InputMode.Url" TextFieldType="TextFieldType.Url"></FluentTextField>
        <FluentValidationMessage For="(()=>this.contactModel.XformerlyTwitterUrl)"></FluentValidationMessage>
    </div>
    <div>
        <FluentLabel Typo="Typography.Body">@nameof(ContactModel.WebsiteUrl)</FluentLabel>
        <FluentTextField @bind-Value="@this.contactModel.WebsiteUrl" InputMode="InputMode.Url" TextFieldType="TextFieldType.Url"></FluentTextField>
        <FluentValidationMessage For="(()=>this.contactModel.WebsiteUrl)"></FluentValidationMessage>
    </div>
    <div>
        <FluentButton Type="ButtonType.Submit">Create</FluentButton>
    </div>
</FluentEditForm>

@code
{
    [SupplyParameterFromForm]
    public ContactModel contactModel { get; set; } = new();

    private void OnValidSubmit()
    {
        this.toastService.ShowSuccess("Model is valid!");
    }
}        

Run the application and try to create a contact, now the OnValidSubmit code will not be executed, and you will get the validations error messages

Ok, so that's enough with the validation, now we want to persist the data, how do we do it? We are going to use an SQL Server database.

Right click the solution to add a new project, search for SQL Server, and select SQL Server Database project, and type a name, mine is BlazorAspireTutorialDatabase


In the project create a "dbo" folder, and inside, create a "Tables" folder, then right click the Tables folder, and select Add -> Table


The table name is Contact.

Ok, so now, we need to design the table, we need a Primary Key, for that we will use a column named "ContactId", this will be marked as a Primary Key, with a custom name for the constraint, and we also want the database engine to automatically generate the id value on inserts, so we will mark it as an IDENTITY column.

Then we need to add the columns corresponding to the ContactModel class.

Replace the Contact.sql file with the following

CREATE TABLE [dbo].[Contact]
(
    [ContactId] BIGINT NOT NULL CONSTRAINT PK_Contact PRIMARY KEY IDENTITY, 
    [EmailAddress] NVARCHAR(150) NOT NULL, 
    [Firstname] NVARCHAR(50) NOT NULL, 
    [Lastname] NVARCHAR(50) NOT NULL, 
    [LinkedInProfileUrl] NVARCHAR(150) NULL, 
    [XformerlyTwitterUrl] NVARCHAR(150) NULL, 
    [IntagramProfileUrl] NVARCHAR(150) NULL, 
    [WebsiteUrl] NVARCHAR(150) NULL
)        

That's it with the database for the moment, now, we need to create our Data Access.

Right click the solution, and choose to add a new project, search for Class Library.

Type a name for the project, I used BlazorAspireTutorial.DataAccess

Delete the file Class1.cs

Now, we are going to automatically generate our Data Access code, for that, we will use EF Core Power Tools.

In Visual Studio, go to Extensions -> Manage Extensions

If you already have them, you will see them in the Installed Tab, if you do not have them, go to the Browse tab and install them

When you have the extension installed, you will see a new option when doing right click over a project.

Go to your Data Access project, right click and select EF Core Power Tools -> Reverse Engineer

You will see a window like the next one, select the dacpac corresponding to the database project in the current Visual Studio Solution

Click Ok, a new window will show up, mark everything for now, we only have one table in the project anyway

Click Ok, select to use DataAnnotation attributes


Click Ok, after a while our Entity Framework code is generated, and we can start using it from our application.

For this tutorial, we are going to use a containerized database, which will be initialized by our .NET Aspire AppHost.

Now, right click on the AppHost project, and select Add -> .NET Aspire package, install the one for "Aspire.Hosting.SqlServer"

Now, in the Program.cs of the AppHost, add the marked lines

Now, we need the webapp to reference the sql database, add the marked lines


This is the complete code for the AppHost so far

var builder = DistributedApplication.CreateBuilder(args);

var sql = builder.AddSqlServer("sql")
                 .WithDataVolume()
                 .AddDatabase("sqldb");

builder.AddProject<Projects.BlazorAspireTutorial_Web>("blazoraspiretutorial-web")
    .WithReference(sql)
    .WaitFor(sql);

builder.Build().Run();        

Now, let's go to the Blazor Web app, right click the project, and then select Add -> .NET Aspire package, install "Aspire.Microsoft.EntityFrameworkCore.SqlServer"

Go to the Program.cs and include the marked line


This is the updated file so far

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using BlazorAspireTutorial.Web.Components;
using BlazorAspireTutorial.Web.Components.Account;
using BlazorAspireTutorial.Web.Data;
using Microsoft.FluentUI.AspNetCore.Components;
using BlazorAspireTutorial.DataAccess.Models;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();
builder.Services.AddFluentUIComponents();

builder.AddSqlServerDbContext<BlazorAspireTutorialDatabaseContext>("sqldb");

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();

builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddIdentityCookies();

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddSignInManager()
    .AddDefaultTokenProviders();

builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();

var app = builder.Build();

app.MapDefaultEndpoints();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();


app.UseAntiforgery();

app.MapStaticAssets();
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

// Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints();

app.Run();        

Run your application, the console may show an error like this

Since we are now using a containerized database, we need our machine to support containers, the quickest way to do so is to install Docker Desktop, you download it from here:

Docker Desktop: The #1 Containerization Tool for Developers | Docker

After you have installed and run docker, try again to run your application, the console error would have disappeared, and the .NET Aspire Dashboard will look somewhat like this

The process will take a while, eventually, the State should be "Running" for all the projects


Now, we need to modify our UI code to invoke the logic to create a new contact in the database. We will talk about that on the next article.

That will be everything for today, wait for the next article of this series.

I hope you find this article useful.

We are currently working with "FairPlayCombined GitHub repository", part of the "FairPlay" Open-Source Software initiaitve, go to the GitHub repository today and give it a star!

Help the success of this initiative: Become a GitHub Sponsor Today!

Hire My Services: https://www.dhirubhai.net/smart-links/AQEEZMHSFFnX1Q

要查看或添加评论,请登录

Eduardo Fonseca的更多文章

  • Blazor How-To: Display Toast Notifications

    Blazor How-To: Display Toast Notifications

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

    3 条评论
  • How to use Instagram APIs with C# and .NET - Part I

    How to use Instagram APIs with C# and .NET - Part I

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

    3 条评论
  • Blazor How-To: Dynamically Set Page Render Mode

    Blazor How-To: Dynamically Set Page Render Mode

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

  • Features in the FairPlay platform

    Features in the FairPlay platform

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

  • Blazor How-To: Creating a Blog platform - Part 1

    Blazor How-To: Creating a Blog platform - Part 1

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

  • Progress Update on The FairPlay Platform

    Progress Update on The FairPlay Platform

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

  • How can Software Developers fight boredom

    How can Software Developers fight boredom

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

  • Using Artificial Intelligence to Improve Data Validations

    Using Artificial Intelligence to Improve Data Validations

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

  • How to Create Image Shares for LinkedIn Using C#

    How to Create Image Shares for LinkedIn Using C#

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

  • How to overcome legacy-fatigue?

    How to overcome legacy-fatigue?

    Hello, I hope you are doing great! Thanks for reading. Remember to share the article with your network and invite more…

社区洞察

其他会员也浏览了