Solution architecture
A Lagoon solution follows a layered organization. Each layer has a defined role, which makes the application wiring predictable between the Blazor WebAssembly UI, the ASP.NET Core server, the EF Core model, and batch processing.
Project overview
The typical structure separates the following projects:
| Project | Role |
|---|---|
APP.Client | Blazor WebAssembly UI: Razor pages, client components, and client HTTP calls. |
APP.Server | Backend API, Blazor WebAssembly application host, authentication, and ASP.NET Core middleware. |
APP.Model | ApplicationDbContext, EF Core entities, migrations, and seeders. |
APP.Shared | Shared data: ViewModels, constants, enumerations, roles, and policies. |
APP.Services | Business logic and DI registration via Registry.cs. |
APP.Batch | Console application for batch processing, jobs, and commands. |
Dependencies follow the application structure: APP.Shared feeds the client and model; APP.Model and APP.Services feed the server and batch.
Entry points
Lagoon applications expose concise entry points. The Program class delegates startup to the framework, and the Main class describes the application behavior.
Server
The server calls LagoonWebApplication.Run<Main>(args). The general pattern is Main : LgAuthApplication<TDbContext, TUser>; in the demo application, Main inherits from LgAuthApplication<ApplicationDbContext, ApplicationUser>.
namespace Demo.Server;
/// <summary>
/// The entry point of the application.
/// </summary>
public class Program
{
/// <summary>
/// Application startup.
/// </summary>
/// <param name="args">The arguments.</param>
public static void Main(string[] args)
{
// Delegates the creation and startup of the ASP.NET Core server to Lagoon.
LagoonWebApplication.Run<Main>(args);
}
}
using Lagoon.Server.Application;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Demo.Model.Models;
using Demo.Model.Seeds;
using Lagoon.Server.Application.IdentitySources;
using Demo.Server.Technical;
namespace Demo.Server;
/// <summary>
/// The web server application's configuration and event manager.
/// </summary>
public class Main : LgAuthApplication<ApplicationDbContext, ApplicationUser>
{
protected override void OnConfigureDbContext(DbContextOptionsBuilder builder)
{
// Configures the EF Core provider and the application DbContext options.
ApplicationDbContext.Configure(builder, Configuration, ApplicationInformation.IsDevelopment);
}
protected override void OnConfigureServices(IServiceCollection services)
{
// Registers the application's business services.
Services.Registry.RegisterServices(services);
services.AddNotification<NotificationManager>();
}
protected override async Task OnStartAsync(IServiceProvider scopedServiceProvider, CancellationToken cancellationToken)
{
if (Configuration.GetValue("AutoUpdateDatabase", true))
{
// Applies migrations then calls OnUpdateDatabaseAsync.
await UpdateDatabaseAsync(scopedServiceProvider, cancellationToken);
}
}
protected override async Task OnUpdateDatabaseAsync(IServiceProvider scopedServiceProvider, ApplicationDbContext db, CancellationToken cancellationToken)
{
// Initializes reference data.
UserManager<ApplicationUser> userManager = scopedServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
await DataSeeder.SeedAsync(db, userManager, cancellationToken);
if (ApplicationInformation.IsDevelopment)
{
// Adds demo data in development.
await FakeDataSeeder.SeedAsync(db, cancellationToken);
}
}
protected override void OnConfigure(IApplicationBuilder app)
{
if (ApplicationInformation.IsDevelopment)
{
// Enables Blazor WebAssembly debugging in development.
app.UseWebAssemblyDebugging();
}
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapLagoonFallback();
});
}
protected override void OnConfigureAuthentification(AuthenticationOptions options)
{
// Allows unauthenticated access to the index.html page.
options.AllowAnonymousIndex = true;
// Enables form authentication.
options.AllowFormAuthentication = true;
}
protected override void OnConfigureAuthorization(AuthorizationOptions options)
{
// Registers the application's shared policies.
Policies.ConfigureAuthorization(options);
}
}
The common overrides are OnConfigureServices, OnConfigureDbContext, OnStartAsync, OnUpdateDatabaseAsync, OnConfigure, OnConfigureAuthentification, and OnConfigureAuthorization.
Client
The Blazor WebAssembly client calls LagoonWebAssemblyApplication.RunAsync<Main>(args). The Main class inherits from LgCustomApplication.
namespace Demo.Client;
/// <summary>
/// The entry point of the application.
/// </summary>
public class Program
{
/// <summary>
/// Application startup.
/// </summary>
/// <param name="args">The arguments.</param>
public static Task Main(string[] args)
{
// Starts the Lagoon Blazor WebAssembly application.
return LagoonWebAssemblyApplication.RunAsync<Main>(args);
}
}
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
namespace Demo.Client;
/// <summary>
/// The web assembly application's configuration and event manager.
/// </summary>
public class Main : LgCustomApplication
{
protected override void OnConfigureBehavior(ApplicationBehavior app)
{
#if RELEASE
// Configures EULA acceptance in Release builds.
app.RequireEulaConsent = true;
app.EulaFromServer = true;
#endif
app.DevelopedByLogoUri = new List<string> { LOGO_URI_DZ };
app.GridView(options =>
{
// Enables summary filters on GridViews.
options.ShowSummaryFilters = true;
});
}
protected override void OnInitialize(WebAssemblyHostBuilder builder)
{
// Declares the root component in the target DOM element.
builder.RootComponents.Add<Shared.App>("#app");
// Registers the application's client services.
Services.Registry.RegisterServices(builder.Services);
}
protected override void OnConfigureAuthorization(AuthorizationOptions options)
{
// Mirrors the shared policies with the server.
Policies.ConfigureAuthorization(options);
}
}
The common overrides are OnConfigureBehavior, OnInitialize, and OnConfigureAuthorization.
Batch
The batch calls LagoonConsoleApplication.Run<Main>(args). The Main class inherits from LgApplication<ApplicationDbContext> and commands are declared with attributes like [Subcommand] or [Command].
namespace Demo.Batch;
/// <summary>
/// The entry point of the application.
/// </summary>
internal class Program
{
/// <summary>
/// Application startup.
/// </summary>
/// <param name="args">The arguments.</param>
public static int Main(string[] args)
{
// Starts the Lagoon console application and returns the exit code.
return LagoonConsoleApplication.Run<Main>(args);
}
}
using Lagoon.Console.Application;
using Microsoft.EntityFrameworkCore;
namespace Demo.Batch;
/// <summary>
/// The console application's configuration and event manager.
/// </summary>
[Subcommand(typeof(UpdateDbCommand))]
public class Main : LgApplication<ApplicationDbContext>
{
public Main() : base() { }
protected override void OnConfigureDbContext(DbContextOptionsBuilder builder)
{
// Reuses the application's EF Core configuration.
ApplicationDbContext.Configure(builder, Configuration, ApplicationInformation.IsDevelopment);
}
protected override void OnConfigureServices(IServiceCollection services)
{
// Registers services available for batch commands.
Services.Registry.RegisterServices(services);
}
protected override Task OnShowStatusAsync(ShowStatusEventArgs e)
{
// Extension point to add custom application status.
return Task.CompletedTask;
}
}
In batch commands, services are injected via method parameters of OnExecute or OnExecuteAsync.
Module build pipeline
The files Modules/LagoonPreBuild.targets and Modules/LagoonPreBuild.UI.targets consume two MSBuild properties defined in module projects:
| Property | Role |
|---|---|
<LagoonPriority> | Defines the load order at runtime: 0 for Core, 2 for UI or Server, 4 by default. |
<HasRootableComponents> | Set to True when the module exposes rootable Blazor components. |
The Modules folder can also switch to NuGet packaging mode with _IsPacking=true, configured in Directory.Build.props.
Resource generation (Lagoon.Build)
Module and application resources are generated by MSBuild tasks from the Lagoon.Build package, executed automatically before each build. No external tool is required: the global lgn tool is no longer invoked by any build (it remains useful for its other commands, like lgn update).
For an application, these tasks:
- aggregate SCSS sources from
Lagoon.UI, other referencedLagoon.UI*modules, and the client project, then compile them to CSS; - group JavaScript scripts and icons;
- merge
Dico.xmldictionaries.
The generated files are written to the application's Client/wwwroot/_vroot folder: this is build output, not to be edited by hand or versioned. See Themes and styles for style customization.
In Release, JavaScript is minified with Terser, which requires node.js and npm on the build machine: the Terser package is installed automatically on first use in ~/.lagoon/_prebuild (or in the folder designated by the LAGOON_PREBUILD_CACHE environment variable). In Debug, scripts are only concatenated and nothing is required.
Learn more
Also see Project structure, First project, and Database update.