Blazor 应用程序作为 WASM 或服务器运行。无法让依赖注入在服务器模式下工作
Posted
技术标签:
【中文标题】Blazor 应用程序作为 WASM 或服务器运行。无法让依赖注入在服务器模式下工作【英文标题】:Blazor App Run as WASM or Server. Cannot get Dependency Injection working in Server Mode 【发布时间】:2021-09-22 12:21:05 【问题描述】:我已经阅读了大量关于在单个应用程序中切换 WASM 和服务器模式的文章。最符合我要求的是:https://itnext.io/blazor-switching-server-and-webassembly-at-runtime-d65c25fd4d8
我正在尝试设置我的项目,以便由于更好的调试等而可以在服务器模式下开发,并使用 WebAssembly 进行部署。为了隔离我一直面临的问题,我基于标准的 WebAssembly(托管)模板创建了这个 github 存储库。 https://github.com/gwruck/Blazor_WASM_Server。有关详细信息,请参阅自述文件。
该解决方案在 WebAssembly 模式下运行良好,但在服务器模式下,我收到错误消息:“InvalidOperationException:无法为类型 'Blazor_WASM_Server.Client.Pages.Index' 的属性 'api' 提供值。没有注册服务'Blazor_WASM_Server.Client.WebApiClient' 类型。
我在客户端应用程序 (WebApiClient) 中创建了一个 Typed Http 客户端并将其注入到索引页面中。我也尝试过注入许多其他服务,但我似乎无法让它们中的任何一个在服务器模式下工作。
根据这篇文章,我认为这种方法可行。 https://www.pragimtech.com/blog/blazor/call-rest-api-from-blazor/
我知道 WASM 和 Server 对 DI 的处理是不同的,但在这种情况下我根本无法让它工作。这是尝试组合两种 Blazor 的基本限制,还是有人能找到解决方法?如果您需要更多信息来描述问题,请告诉我。
以下是代码的关键元素:
Blazor_WASM_Server.Server
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.0-preview.5.21301.17" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-preview.5.21301.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\Blazor_WASM_Server.Client.csproj" />
<ProjectReference Include="..\Shared\Blazor_WASM_Server.Shared.csproj" />
</ItemGroup>
</Project>
namespace Blazor_WASM_Server.Server
public class Program
public static void Main(string[] args)
CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.UseStartup<Startup>();
);
public enum HybridType
ServerSide,
WebAssembly//,
public class HybridOptions
//Choose WebAssembly or ServerSide
public HybridType HybridType get; set; = HybridType.ServerSide;
public class Startup
public Startup(IConfiguration configuration)
Configuration = configuration;
public HybridOptions hybridOptions = new HybridOptions();
public IConfiguration Configuration get;
public void ConfigureServices(IServiceCollection services)
services.AddControllersWithViews();
services.AddRazorPages();
if (hybridOptions.HybridType == HybridType.ServerSide)
//Conditionally add ServerSide
services.AddServerSideBlazor();//new
services.Configure<HybridOptions>(Configuration);
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
else
app.UseExceptionHandler("/Error");
app.UseHsts();
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host");
);
_Host.cshtml
@page "_Host"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Blazor_WASM_Server.Server
@using Microsoft.Extensions.Options
@using Blazor_WASM_Server.Client
@inject IOptions<HybridOptions> HybridOptions
@
@*Retrieve the running mode*@
var hybridType = HybridOptions?.Value?.HybridType ?? HybridType.WebAssembly;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Sabre PM</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="css/site.css" rel="stylesheet" />
<link href="_content/Syncfusion.Blazor.Themes/bootstrap4.css" rel="stylesheet" />
</head>
<body>
<div id="app">
**@*Conditionally apply Server/Webassembly. This is where the magic happens*@**
@if (hybridType == HybridType.ServerSide)
<component type="typeof(App)" render-mode="ServerPrerendered"/>
<persist-component-state/>
<script src="_framework/blazor.server.js"></script>
else if (hybridType == HybridType.WebAssembly)
<div id="app">Loading...</div>
<script src="_framework/blazor.webassembly.js"></script>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">????</a>
</div>
</body>
</html>
Blazor_WASM_Server.Client
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0-preview.5.21301.17" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0-preview.5.21301.17" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-preview.5.21301.5" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0-preview.5.21301.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Blazor_WASM_Server.Shared.csproj" />
</ItemGroup>
</Project>
public class Program
public static async Task Main(string[] args)
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<Client.App>("#app");
//Added a typed Http client
builder.Services.AddHttpClient<WebApiClient>(client =>
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
);
builder.Services.AddScoped(sp => new HttpClient BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) );
await builder.Build().RunAsync();
public class WebApiClient
public HttpClient Client get;
public WebApiClient(HttpClient client)
Client = client;
@* Index.razor *@
@page "/"
@inject WebApiClient api
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
@code
//Inject the WebApiClient and verify it has been set
protected override void OnInitialized()
**//For some reason, whatever I try to inject here will work in WASM Mode, but not in SeverSide mode**
if (api == null) throw new NullReferenceException("WebApiClient not set by Dependency Injection");
base.OnInitialized();
我知道这经常混合和匹配范例,但如果您将其设置为读取 Web API,则非常相似的代码将在“纯”服务器端应用程序中运行。我只是无法弄清楚为什么它在这个混合版本中不起作用。我怀疑这与项目类型有关(Microsoft.NET.Sdk.BlazorWebAssembly vs Microsoft.NET.Sdk.Web
我意识到这是一个“大”问题,但希望这能将其提炼为关键要素。我怀疑我忽略的架构中缺少一些基本的东西。
【问题讨论】:
当服务器模式出现问题时,发布该代码更有意义。 Startup.cs 和 Program.cs 开始。 【参考方案1】:在查看您的回购后更新
您需要在 Blazor 服务器启动中注册一个 HttpClient
Blazor_WASM_Server.Server 中的 startup.cs 应如下所示:
using Blazor_WASM_Server.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Linq;
using System.Net.Http;
namespace Blazor_WASM_Server.Server
public enum HybridType
ServerSide,
WebAssembly//,
//HybridManual,
//HybridOnNavigation,
//HybridOnReady
public class HybridOptions
public HybridType HybridType get; set; = HybridType.ServerSide;
public class Startup
public Startup(IConfiguration configuration)
Configuration = configuration;
public HybridOptions hybridOptions = new HybridOptions();
public IConfiguration Configuration get;
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
services.AddControllersWithViews();
services.AddRazorPages();
if (hybridOptions.HybridType == HybridType.ServerSide)
services.AddServerSideBlazor();
// Server Side Blazor doesn't register HttpClient by default
// Thanks to Robin Sue - Suchiman https://github.com/Suchiman/BlazorDualMode
if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
// Setup HttpClient for server side in a client side compatible fashion
services.AddScoped<HttpClient>(s =>
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var uriHelper = s.GetRequiredService<NavigationManager>();
return new HttpClient
BaseAddress = new Uri(uriHelper.BaseUri)
;
);
services.AddScoped<WebApiClient>();
services.Configure<HybridOptions>(Configuration);
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
else
app.UseExceptionHandler("/Error");
// 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.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host");
);
您可以在 https://github.com/ShaunCurtis/AllinOne 上查看我对合并网站的看法,
【讨论】:
哇 - 那是我错过的金块。我看过 Robin Sue 的作品,但必须承认我错过了那部分。我以前没见过你的 - 现在会研究它。希望这个帖子可以帮助其他人避免我刚刚落下的兔子洞!!! NP。 :-) 进入黑洞并朝自己的脚开枪是编码中的常见做法! 再次感谢您。我“意识到” WASM 在浏览器中运行,而 Server 在服务器上运行,但我忘记了在何处注册服务的微妙之处——即使经过数小时的研究。我可以从 MS 获得的最接近的是在这个链接上,但它并没有真正深入到它的底部,恕我直言。 link.以上是关于Blazor 应用程序作为 WASM 或服务器运行。无法让依赖注入在服务器模式下工作的主要内容,如果未能解决你的问题,请参考以下文章
使用身份验证托管的 Blazor Wasm 上出现 Azure 500 错误