Blazor 应用作为 WASM 或服务器运行。无法在服务器模式下使用依赖注入

时间:2021-07-13 12:35:16

标签: dependency-injection server blazor webassembly

我阅读了大量关于在单个应用程序中切换 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

我意识到这是一个“大”问题,但希望这可以将其提炼为关键要素。我怀疑我忽略的架构中缺少一些基本的东西。

1 个答案:

答案 0 :(得分:0)

在查看您的存储库后更新

您需要在 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 处查看我对合并网站的看法,