In Microsoft’s words, .NET Aspire is a set of tools, templates, and packages for building observable, production ready apps. In case you are not familiar with Aspire yet, please read the content of Microsoft’s website to get up to speed with the basic ideas of Aspire. You may also find this YouTube interview with David Fowler, the creator of .NET Aspire at Microsoft, helpful - Why Everyone Will be Using .NET Aspire /w David Fowler. David and Nick Chapsas talk about the evolution of .NET Aspire over the last two years, its recent "pivot" and ambitious plans for the future.
Our XAF team has spent some time considering the functionality of Aspire, attempting to find the best integration points which will allow XAF developers to take advantage of the orchestration features Aspire offers out of the box. The team sees Aspire as a potential "1-click solution", a more modern alternative to some of the templates in the XAF Solution Wizard, which could achieve some of these goals:
- Simplify long and error-prone setup processes like those we describe in our documentation for Azure App Service and Nginx support on Linux, as well as other popular web server configurations.
- Provision required databases and other application resources for both development and deployment environments (for instance, using a Docker setup).
The team hopes that we will be able to save a lot of time for our customers, whether they use XAF or not! Right now, much of this is a moving target, since Microsoft continues to develop Aspire rapidly. At the same time, some functionality is already commonly used and can be considered stable, so for now we decided to publish some details that will help you make use of the Aspire tools in your own XAF projects.
See also our related article at .NET Aspire Integration in DevExpress Reports.
Learn More about DevExpress Application Framework for .NET Developers (XAF)
If you have not heard of XAF (Cross-Platform .NET App UI & Web API) or are new to the DevExpress product line, XAF or is a developer-centric .NET application framework designed to build feature-complete Office-inspired line-of-business apps (leveraging DevExpress Blazor and WinForms UI components via Entity Framework or XPO ORM) and powerful Web API Services.
- XAF Benefits for ASP.NET Core Blazor Developers
- XAF Benefits for Windows Forms Developers
- XAF Benefits for WPF Developers
- XAF Benefits for JS Developers
Feel free to review our XAF demos and compare our implementation with other software development methodologies. You can also find many screenshots/examples of typical XAF apps in our App Gallery or community sites like this one.
Add Aspire to an XAF Blazor Project
To illustrate the exact steps required to make Aspire work with XAF, I started from a new XAF solution created in Visual Studio 2022 by the Solution Wizard. I configured the project with a Web UI (ASP.NET Core Blazor), and I used all the standard options selected by the Wizard by default. If you prefer Visual Studio Code, you can create a new XAF project using the newest DevExpress Template Kit. As always, detailed instructions are available in the online docs at XAF - Getting Started.
I changed the target framework for both projects in the solution to .NET 9.0. This may not be strictly necessary, but since .NET Aspire moves quickly at this time, and the latest release actually has version 9.1 already, it made sense to me to bring the demo solution up to date as well.
Finally, I added a Business Object called
DataItem
, just so the UI has something to show and
I can verify that the application does what it should.
With the test solution prepared, the job of adding .NET Aspire support seems simple: there’s a context menu item called .NET Aspire Orchestrator Support… on the Blazor project.
I clicked that entry, confirmed all the standard settings in the dialog that followed, Visual Studio went away for a short while to think about it… and then an error came up:
This is a list of the fixes I had to apply now for XAF v24.2 project templates:
-
Modify the file
Extensions.cs
in the newly added projectServiceDefaults
to take the old-style startup structure of the ASP.NET Core application into account. -
Remove the extra copy of
Extensions.cs
which the wizard inexplicably dropped into theModule
project. The .NET Aspire wizard for .NET 8.0 did not do this, but the .NET 9 one did. - Modify the startup logic of the Blazor app project, since the wizard has not added any of the necessary calls automatically.
- Work around the lack of automatic endpoint detection, which is a consequence of the startup code structure.
Additionally, I also updated all packages to the latest versions.
And now, please read on for all the details!
3rd-Party Package Updates
As I explained before, since Aspire is cutting edge technology, it makes sense to stick to the latest versions (btw, in the video above David Fowler clearly explained the difference of .NET vs Aspire release cadences and support policies). For consistency, I recommend using the latest versions of everything! I recommend doing this before you start making other changes.
You can bring up the NuGet Package Manager for each project and
install updates that way, but personally I prefer using the
Package Manager Console window (you can bring it up from the
Tools menu in Visual Studio). The command
Get-Package -updates
displays all available
updates:
PM> Get-Package -updates
Id Versions Description ProjectName
-- -------- ----------- -----------
Microsoft.EntityFrameworkCore.Sq... {9.0.3} Microsoft SQL Server database provider for... XafAspireDemo.Module
Microsoft.Data.SqlClient {6.0.1} The current data provider for SQL Server a... XafAspireDemo.Module
System.IdentityModel.Tokens.Jwt {8.6.1} Includes types that provide support for cr... XafAspireDemo.Module
Microsoft.EntityFrameworkCore.In... {9.0.3} In-memory database provider for Entity Fra... XafAspireDemo.Module
Microsoft.EntityFrameworkCore.Pr... {9.0.3} Lazy loading proxies for Entity Framework ... XafAspireDemo.Module
No package updates are available from the current package source for project 'XafAspireDemo.Blazor.Server'.
Aspire.Hosting.AppHost {9.1.0} Core library and MSBuild logic for .NET As... XafAspireDemo.AppHost
OpenTelemetry.Instrumentation.As... {1.11.1} ASP.NET Core instrumentation for OpenTelem... XafAspireDemo.ServiceDefaults
OpenTelemetry.Exporter.OpenTelem... {1.11.2} OpenTelemetry protocol exporter for OpenTe... XafAspireDemo.ServiceDefaults
OpenTelemetry.Instrumentation.Ru... {1.11.1} .NET runtime instrumentation for OpenTelem... XafAspireDemo.ServiceDefaults
Microsoft.Extensions.Http.Resili... {9.3.0} Resilience mechanisms for HttpClient. XafAspireDemo.ServiceDefaults
Microsoft.Extensions.ServiceDisc... {9.1.0} Provides extensions to HttpClient that ena... XafAspireDemo.ServiceDefaults
OpenTelemetry.Extensions.Hosting {1.11.2} Contains extensions to start OpenTelemetry... XafAspireDemo.ServiceDefaults
OpenTelemetry.Instrumentation.Http {1.11.1} Http instrumentation for OpenTelemetry .NET. XafAspireDemo.ServiceDefaults
Now I ran the command Update-Package
and waited
for all the packages to be updated to their latest versions.
One small issue remained after this: the
Sdk
reference in the project file of the
AppHost
project still referred to version
9.0.0
:
<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />
I fixed this manually to version 9.1.0
, since that
is the latest available version.
<Sdk Name="Aspire.AppHost.Sdk" Version="9.1.0" />
Service Default Extensions
First, the extra copy of Extensions.cs
can simply
be deleted from the Module
project. It is not
needed.
The class Extensions
implemented in
Extensions.cs
in the
ServiceDefaults
project can be replaced entirely
by the code below. Note this implementation contains exactly
the same default code that the original file uses. The
differences are how the code is applied. The standard file uses
the IHostApplicationBuilder
interface for most
purposes, while my replacement works directly with the
IServiceCollection
. This makes it a lot easier to
interface with the startup code of the Blazor app.
public static class Extensions
{
public static void AddAspireServiceDefaults(this IServiceCollection services)
{
services.AddMetrics();
services.AddOpenTelemetry();
services.AddHealthChecks().AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
services.AddServiceDiscovery();
services.ConfigureHttpClientDefaults(http =>
{
http.AddStandardResilienceHandler();
http.AddServiceDiscovery();
});
}
public static IServiceCollection ConfigureOpenTelemetry(
this IServiceCollection services,
IConfiguration configuration,
IWebHostEnvironment environment
)
{
services
.AddLogging(logging =>
{
logging.AddOpenTelemetry(options =>
{
options.IncludeFormattedMessage = true;
options.IncludeScopes = true;
});
})
.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing
.AddSource(environment.ApplicationName)
.AddSource("Microsoft.AspNetCore.SignalR.Server")
.AddAspNetCoreInstrumentation()
// Uncomment the following line to enable gRPC instrumentation
//.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation();
});
AddOpenTelemetryExporters(services, configuration);
return services;
}
private static void AddOpenTelemetryExporters(
IServiceCollection services,
IConfiguration configuration
)
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(
configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]
);
if (useOtlpExporter)
{
services.AddOpenTelemetry().UseOtlpExporter();
}
}
public static void MapDefaultAspireDevEndpoints(this IEndpointRouteBuilder endpoints)
{
endpoints.MapHealthChecks("/health");
endpoints.MapHealthChecks(
"/alive",
new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") }
);
}
}
Like the original file the wizard created, you should regard
this as a starting point and adjust the code of the extension
methods as needed for your own application system. One change I
have applied in comparison to the standard code is the addition
of the extra tracing source
Microsoft.AspNetCore.SignalR.Server
, which makes
telemetry information from SignalR available in the Aspire
Dashboard. This is an example for changes that you need to make
depending on your own requirements.
Modify Startup Code
All these changes apply to the file Startup.cs
in
the Blazor.Server
project. I began by extending
the constructor to include a reference to the environment
object in addition to the configuration:
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
Right at the start of the
ConfigureServices
method, I added calls to the new
extension methods. The two new lines have a
-->
prefix here:
public void ConfigureServices(IServiceCollection services) {
--> services.AddAspireServiceDefaults();
--> services.ConfigureOpenTelemetry(Configuration, WebHostEnvironment);
services.AddSingleton(
typeof(Microsoft.AspNetCore.SignalR.HubConnectionHandler<>),
typeof(ProxyHubConnectionHandler<>));
...
I also applied an optional extension at this
point. I added the NuGet package
OpenTelemetry.Instrumentation.EntityFrameworkCore
(this is currently a pre-release version!) to
the project, and then enabled instrumentation for EF Core with
these extra code lines:
public void ConfigureServices(IServiceCollection services) {
services.AddAspireServiceDefaults();
services.ConfigureOpenTelemetry(Configuration, WebHostEnvironment);
--> services.ConfigureOpenTelemetryTracerProvider(builder =>
--> {
--> builder.AddEntityFrameworkCoreInstrumentation();
--> });
services.AddSingleton(
typeof(Microsoft.AspNetCore.SignalR.HubConnectionHandler<>),
typeof(ProxyHubConnectionHandler<>));
...
Finally, I made one change to the
Configure
method. Right at the end of it, I added
a block which conditionally adds endpoint mappings using the
standard extension method created by the Aspire wizard:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
endpoints.MapXafEndpoints();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
endpoints.MapControllers();
});
--> if (env.IsDevelopment())
--> {
--> app.UseEndpoints(endpoints =>
--> {
--> endpoints.MapDefaultAspireDevEndpoints();
--> });
--> }
}
At this point it became possible to run the application, and the Aspire Dashboard came up correctly in the browser. However, the resource for the Blazor project did not display any endpoints, which rendered it practically inaccessible.
Add the Blazor App Endpoint
The code in Program.cs
in the
AppHost
project is now the entry point of the
application system. This is where Aspire configures its
orchestration, even if right now there’s just one project to
configure. It is not unusual to modify this code based your own
requirements, and in this case I found that an explicit call
was required to help Aspire understand that the Blazor app has
an HTTPS endpoint:
var builder = DistributedApplication.CreateBuilder(args);
builder
.AddProject<Projects.XafAspireDemo_Blazor_Server>("xafaspiredemo-blazor-server")
--> .WithHttpsEndpoint();
builder.Build().Run();
Now the demo app has an endpoint entry through which the application can be accessed as before.
After running a few operations in the Blazor frontend, the Aspire Dashboard also begins displaying entries in the Traces and Metrics panels. You can dig in here and analyze the application behavior in some detail.
Coming Up
Now that the basic steps are out of the way and the XAF application is running within the Aspire system, I will publish a new post soon to talk about a few features that are easily accessible now. This is what I’m planning to describe:
- Utilize the Open Telemetry support for custom metrics and traces.
- Take advantage of Aspire orchestration features by making SQL Server a container dependency.
- Consider steps required for a cloud deployment, and check out how Aspire simplifies this process.
Your Feedback Matters!
You can also download our GitHub example and play with different configurations on your own. Please send your feedback about Aspire, our plans or your existing and future uses of Aspire — thanks to all of you who have already done that! — and any questions or ideas, we will attempt to consider everything!