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 或服务器运行。无法让依赖注入在服务器模式下工作的主要内容,如果未能解决你的问题,请参考以下文章