Aspire를 지속적으로 가지고 놀면서 이것저것 다루던 와중, 기묘한 현상에 부딪치게 되어 우회할 수 있는 방법이 발견되어 공유드립니다.
클라우드 내 인프라를 On-Demand로 제어하면서도 복잡하지 않게 만들고 싶은 어려운 결정 때문에 여러 도구를 PoC 해보다가 어느 정도 윤곽을 잡는데 도움을 주는 조합을 찾게 되어서 간단한 사용기를 공유해봅니다. 바로 .NET Aspire 이야기인데요,
프로토타이핑해보고 있는 프로젝트 구성은 이렇습니다.
[image]
AppHost: .NET Aspire의 뼈대가 되는 프로젝트. IDE에서는 시작 프로젝트로 활용하는 부분. 캐싱 서버로는 MS에서 런칭한 Redis 호환 구현체이자 순수 .NET으로 동작하는 Garnet을, DB는 PostgreSQL 컨테이너를 사용하도록 설정했습니다.
Data: EF Core 모델과 컨텍스트만 담되, 특정 DB에 관련된 설정은 여기에 포함시키지 않았습니다.
ServiceDefaults: Aspire 프로젝트를 만들면 자동으로 따라붙는 클래스 라이브러리 프로젝트로, 앱 호스트 타입의 닷넷 프로젝트 (ASP.NET 포함)에 서비스 디스커버리 …
해당글과 몇몇 유명한 닷넷 유튜버 MS를 보고 따라하던 와중 예상치도 못하게 Blazor WASM은 Aspire 리소스에서 검색하도록 지원을 하지 않는다 였습니다.
(이걸 진작 파악하지 못한 이유는 Rider에서 디버깅모드가 아닌 Run 상태로 주로 쓰다보니 구동이 되고 실제 통신이 되다가, 무언가 막히게 되어 디버깅모드를 키는순간 안켜지면서 아차차… 이거뭔가 이상하다)
그러면서 찾아본결과
Allows you to add Aspire service discovery to Blazor WebAssembly (client) apps, storing service discovery information in appSettings.json or another means you choose.
해당 레포의 도움을 받아서 해결하게 되었습니다.
var builder = DistributedApplication.CreateBuilder(args);
var inventoryApi = builder.AddProject<Projects.AspNetCoreWebApi>("inventoryapi");
var billingApi = builder.AddProject<Projects.SomeOtherWebApi>("billingapi");
builder.AddProject<Projects.Blazor>("blazorServer")
.AddWebAssemblyClient<Projects.Blazor_Client>("blazorWasmClient")
.WithReference(inventoryApi)
.WithReference(billingApi);
builder.Build().Run();
샘플로 작성한 Blazor라는 프로젝트가 뭐지하고는 한참 찾아본결과, 그냥
추가해서 해결했습니다.
이게 어찌 가능한가 했더니, 해당 레포에 설명이 있더군요.
.NET Aspire doesn’t currently (as of early 2025) facilitate a Blazor > WebAssembly (client) app discovering Aspire resources, even if the app has been added to the distributed application, because Blazor WebAssembly apps run in the browser and are “standalone”. This has been commented on here:
The expectation is that these apps will need to be aware of the web APIs they’re supposed to call without relying on Aspire, and that they will store these in appsettings.json
or appsettings.{environmentName}.json
. This works fine, but if the endpoint changes, or if it differs in your development and production environments, you have to remember to manage those changes in your client app as well as your other resources. This is precisely the problem Aspire is intended to solve.
My little library Aspire4Wasm solves the problem by writing the service discovery information to the appsettings.{environmentName}.json
file of your client app for you.
결국 WASM이 브라우저에서 실행되고 독립형 이기때문에 라는게 주된이유고.
그에 따른 파생문제가 도메인의 변경등이 생겼을때, 문제가 되고 자기의 라이브러리를 통해 문제가 해결이 가능하다!
일단 해당 프로젝트가 개인이 만든 프로젝트라서 걱정이 많을 수는 있지만
Blazor wasm을 다루는 유저들의 토론이 진행되고 있으며
열림 03:39AM - 11 Feb 25 UTC
area-app-model
This is related to my pull request here: https://github.com/dotnet/aspire/pull/7… 162
## Background and Motivation
.NET Aspire doesn't currently (as of early 2025) facilitate a Blazor WebAssembly (client) app discovering Aspire resources, even if the app has been added to the distributed application, because Blazor WebAssembly apps run in the browser and are "standalone". This has been commented on here:
- https://github.com/dotnet/aspire/issues/4785
- https://github.com/dotnet/aspire/issues/1536
- https://github.com/dotnet/aspire/issues/6781
I suppose Microsoft's expectation was that client apps (including Blazor WASM clients and any SPAs) will need to be aware of the web APIs they're supposed to call and that they will store these in `appsettings.json` or `appsettings.{environmentName}.json` without relying on Aspire. This works fine, but if the endpoint changes, or if it differs in your development and production environments, you have to manage those changes in your client app as well as your other resources. This is the kind of problem Aspire is intended to solve (and does so exceedingly well, apart from Blazor). These changes to Aspire will achieve that.
This proposal would integrate my [Nuget package Aspire4Wasm](https://www.nuget.org/packages/Aspire4Wasm/) ([source code here](https://github.com/BenjaminCharlton/Aspire4Wasm)) into Aspire (with the addition of only 5 new classes and 2 interfaces, and changing none of the existing ones).
The changes allow an Aspire `AppHost` to define web APIs that a Blazor app can access, and pass that service discovery information to the app by writing to its `appsettings.{environment}.json` files.
## Proposed API
You can see the diffs I'm proposing here: https://github.com/dotnet/aspire/pull/7162/files
## Usage Examples
In your Aspire AppHost project's Program.cs file:
1. Add the Web Api projects you want your client to be able to call with the existing `AddProject` method
2. Add your Blazor Server app with `AddProject` method then chain a call to the new `AddWebAssemblyClient` to add your client app.
3. Then chain a call to `WithReference` to point the client to each web API (you can repeat this for as many Web APIs as you need)
In your WebAssembly client's `Program.cs` file:
1. Call `AddServiceDiscovery`
2. Configure your `HttpClient`s either globally or one at a time. In each client's `BaseAddress` property, use the name you gave to the resource in your AppHost and Aspire's `https+http://` syntax.
See the example below:
### Example Program.cs in AppHost
```
var builder = DistributedApplication.CreateBuilder(args);
var inventoryApi = builder.AddProject<Projects.AspNetCoreWebApi>("inventoryapi");
var billingApi = builder.AddProject<Projects.SomeOtherWebApi>("billingapi");
builder.AddProject<Projects.Blazor>("blazorServer")
.AddWebAssemblyClient<Projects.Blazor_Client>("blazorWasmClient")
.WithReference(inventoryApi)
.WithReference(billingApi);
builder.Build().Run();
```
### Example Program.cs in your Blazor WebAssembly Client
Install (on the WebAssembly client) the `Microsoft.Extensions.ServiceDiscovery` Nuget package to get the official Aspire service discovery functionality that is going to read your resource information from your app settings. Then add the code below:
```
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(static http =>
{
http.AddServiceDiscovery();
});
builder.Services.AddHttpClient<IInventoryService, InventoryService>(
client =>
{
client.BaseAddress = new Uri("https+http://inventoryapi"); // The syntax for service discovery in Aspire will now work!
});
builder.Services.AddHttpClient<IBillingService, BillingService>(
client =>
{
client.BaseAddress = new Uri("https+http://billingapi"); // The syntax for service discovery in Aspire will now work!
});
```
## Default behaviour
Using the default behaviour (in the example) your AppHost will write the service discovery information for all the referenced resources into the `appsettings.{environmentName}.json` file of your client app for you.
It uses the following structure. The structure is important because it allows Aspire to "discover" the information on the client.
```
{
"Services": {
"inventoryapi": {
"https": [
"https://localhost:1234"
],
"http": [
"http://localhost:4321"
]
},
"billingapi": {
"https": [
"https://localhost:9876"
],
"http": [
"http://localhost:6789"
]
}
}
}
```
## Custom behaviours (optional)
If you want to serialize the service discovery information some other way in your WebAssembly application (for example, in a different JSON file, or in an XML file) you can do so in the AppHost `Program.cs` by creating a custom implementation of `IServiceDiscoveryInfoSerializer` and passing it to the call to `AddWebAssemblyClient` via the `WebAssemblyProjectBuilderOptions` class, like this:
```
var builder = DistributedApplication.CreateBuilder(args);
var inventoryApi = builder.AddProject<Projects.AspNetCoreWebApi>("inventoryapi");
var billingApi = builder.AddProject<Projects.SomeOtherWebApi>("billingapi");
builder.AddProject<Projects.Blazor>("blazorServer")
.AddWebAssemblyClient<Projects.Blazor_Client>("blazorWasmClient" options => {
options.ServiceDiscoveryInfoSerializer = yourImplementation;
})
.WithReference(inventoryApi)
.WithReference(billingApi);
builder.Build().Run();
```
If you choose to make a custom implementation of `IServiceDiscoveryInfoSerializer`, you only need to override one method:
```
public void SerializeServiceDiscoveryInfo(IResourceWithServiceDiscovery resource) { }
```
Note: If you choose to override the default behaviour with an output format that Aspire can't read from your WebAssembly client app, you'll also need to override the discovery behaviour on the client, which is outside the scope of what I've developed here.
## Using service discovery to configure CORS in your web API (optional)
You can also reference one or more Blazor apps from a web API. One use case would be to configure Cross Origin Resource Sharing (CORS) in the web API to grant access to your clients to submit HTTP requests.
(Note: None of this section below demonstrates my changes to the existing API of Aspire; it's just a use case demonstrating how the existing functionality can be leveraged to make use of my proposed changes.)
### Example updated Program.cs in AppHost project
```
var builder = DistributedApplication.CreateBuilder(args);
var blazorServer = builder.AddProject<Projects.InMyCountry_UI_Server>("blazorServer");
var webApi = builder.AddProject<Projects.InMyCountry_WebApi>("inventoryApi")
.WithReference(blazorServer) // This will pass the endpoint URL of the Blazor app to the web API so that it can be added as a trusted origin in CORS.
.WaitFor(blazorServer);
blazorServer.AddWebAssemblyClient<Projects.InMyCountry_UI_Client>("blazorWasmClient") // Now we can add the Blazor WebAssembly (client) app in the Aspire4Wasm package.
.WithReference(webApi); // And pass the Blazor client a reference to the web API
builder.Build().Run();
```
### The example above will add the following to the appsettings{.Environment}.json file of the web API project
```
{
"Clients": [
"https://{url of your blazor app should be here}"
]
}
```
It should add as many clients as you configured in the AppHost.
### Example continued in Program.cs in the web API project
Now that the web API has a reference to the Blazor app in appsettings, we can configure CORS like this:
```
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
var clients = builder.Configuration.GetSection("Clients").Get<string[]>() ?? []; // Get the clients from the list in appsettings.
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins(clients); // Add the clients as allowed origins for cross origin resource sharing.
policy.AllowAnyMethod();
policy.WithHeaders("X-Requested-With");
policy.AllowCredentials();
});
});
// Etc.
```
## Troubleshooting
These are just a few things that I noticed helped me and I hope they help you too.
* You don't need a `launchsettings.json` in your webassembly client project. The one in your Blazor server project will do.
* In the `launchsettings.json` of your blazor server project, I recommend that you set `launchBrowser` to `false`. This means that when the Aspire dashboard opens up, you'll need to click the link to open up your Blazor client. This is good! If you don't do this, your Blazor client is going to launch on a random port chosen by Aspire. When launched on a random port, your web API might reject the requests of your Blazor client because it doesn't have the expected origin to comply with the API's CORS policy. I tried to stop this happening but couldn't, so this is my workaround.
## Alternative Designs
I considered various names for the method. I preferred simply `AddProject` like the others, but that is taken. I considered `AddClientApp` and `AddBlazorWasm` and `AddBlazorWebAssemblyClient` but I settled on `AddWebAssemblyClient`.
I considered other names for `IServiceDiscoveryInfoSerializer` and its method `SerializeServiceDiscoveryInfo`. For example `AppSettingsWriter` and `WriteServiceJson`. I chose those names because they weren't wedded to JSON, XML or any other means of writing the service discovery information to the client app.
## Risks
These changes will not make the Aspire developer experience with Blazor totally seamless. For example, although it's been great for my hosted Blazor WebAssembly app, I haven't got it to work with stand-alone Blazor WebAssembly apps yet. I haven't figured out how to stop the app launching on a random port as well as the one specified in `launchsettings.json` but I get around this by setting `launchBrowser` to false in the Blazor server app's `launchSettings.json`.
However, this is a step in the right direction and will help make Aspire a bit more Blazor-friendly. If you want to see what it does and doesn't do before accepting the changes, try the Nuget package Aspire4Wasm (version 3.0.0 at the time of writing).
해당 유저가 git issue에도 등록할 정도로 열정적이라서 빠른시일내에 WASM을 지원하는 혹은 해당 레포가 Aspire에 포함될 수도 있을 것 같습니다!