天天看點

建構Blazor WASM和伺服器一體化解決方案代碼庫解決方案和項目Blazor項目變更Blazor.Web運作應用程式添加資料服務Blazor項目Program.cs建構并運作項目它是如何工作的?總結

目錄

代碼庫

解決方案和項目

Blazor項目變更

MainLayout

導航菜單

Blazor.Web

Index.cshtml

Startup.cs

運作應用程式

添加資料服務

WeatherForecast.cs

IWeatherForecastService.cs

WeatherForecastServerService.cs

WeatherForecastAPIService.cs

WeatherForecastController.cs

Blazor項目Program.cs

Blazor.Web Startup.cs

建構并運作項目

它是如何工作的?

總結

​​​​​​​

建構Blazor WASM和伺服器一體化解決方案代碼庫解決方案和項目Blazor項目變更Blazor.Web運作應用程式添加資料服務Blazor項目Program.cs建構并運作項目它是如何工作的?總結

代碼庫

文章的代碼庫在這裡 - https://github.com/ShaunCurtis/AllinOne

解決方案和項目

使用Blazor WebAssembly模闆建立一個名為Blazor的新解決方案。不要選擇在Aspnetcore上托管它。您将獲得一個名為Blazor 的項目。

現在使用ASP.NET Core Web App模闆向解決方案添加第二個項目。稱之為Blazor.Web。将其設定為啟動項目。

解決方案現在應如下所示:

建構Blazor WASM和伺服器一體化解決方案代碼庫解決方案和項目Blazor項目變更Blazor.Web運作應用程式添加資料服務Blazor項目Program.cs建構并運作項目它是如何工作的?總結

Blazor項目變更

該解決方案在網站的子目錄中運作WASM上下文。要使其正常工作,需要對Blazor項目進行一些修改。

  1. 将wwwroot的内容移動到Blazor.Web并删除wwwroot中的所有内容。
  2. 向項目檔案中添加一個StaticWebAssetBasePath條目,設定為wasm 。這在使用它的上下文中區分大小寫,是以請堅持使用小寫字母。
  3. 添加必要的包。

項目檔案應如下所示:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <StaticWebAssetBasePath>wasm</StaticWebAssetBasePath>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.4" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.4" PrivateAssets="all" />
    <PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
  </ItemGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

</Project>
           

MainLayout

MainLayout需要修改以處理這兩種情況。該解決方案更改了每個上下文的配色方案。WASM Teal和Server Steel。

@inherits LayoutComponentBase
<div class="page">
    @*change class*@
    <div class="@_sidebarCss">
        <NavMenu />
    </div>
    <div class="main">
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank" rel="external nofollow"  target="_blank">About</a>
        </div>
        <div class="content px-4">
            @Body
        </div>
    </div>
</div>

@code {
    [Inject] NavigationManager NavManager { get; set; }
    private bool _isWasm => NavManager?.Uri.Contains("wasm", StringComparison.CurrentCultureIgnoreCase) ?? false;
    private string _sidebarCss => _isWasm ? "sidebar sidebar-teal" : "sidebar sidebar-steel";
}
           

将以下Css樣式添加到下面的元件Css檔案.sidebar中。

.sidebar {
    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}

/* Added Styles*/
.sidebar-teal {
    background-image: linear-gradient(180deg, rgb(0, 64, 128) 0%, rgb(0,96,192) 70%);
}

.sidebar-steel {
    background-image: linear-gradient(180deg, #2a3f4f 0%, #446680 70%);
}
/* End Added Styles*/
           

導航菜單

添加代碼和标記——它添加了一個連結以在上下文之間切換。

<div class="top-row pl-4 navbar navbar-dark">
    @*Change title*@
    <a class="navbar-brand" href="" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >Blazor</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon">
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        @*Add links between contexts*@
        <li class="nav-item px-3">
                <NavLink class="nav-link" href="@_otherContextUrl" target="_blank" rel="external nofollow"  Match="NavLinkMatch.All">
                    <span class="oi oi-home" aria-hidden="true"> @_otherContextLinkName
                </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter" target="_blank" rel="external nofollow" >
                <span class="oi oi-plus" aria-hidden="true"> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata" target="_blank" rel="external nofollow" >
                <span class="oi oi-list-rich" aria-hidden="true"> Fetch data
            </NavLink>
        </li>
    </ul>
</div>

@code {
    [Inject] NavigationManager NavManager { get; set; }
    private bool _isWasm => NavManager?.Uri.Contains("wasm", StringComparison.CurrentCultureIgnoreCase) ?? false;
    private string _otherContextUrl => _isWasm ? "/" : "/wasm";
    private string _otherContextLinkName => _isWasm ? "Server Home" : "WASM Home";
    private string _title => _isWasm ? "AllinOne WASM" : "AllinOne Server";
    private bool collapseNavMenu = true;
    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}
           

FetchData.razor

通過在開頭添加/來更新用于擷取預測的URL ,該檔案現在位于根目錄中而不是wasm。

protected override async Task OnInitializedAsync()
{
    forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("/sample-data/weather.json");
}
           

Blazor.Web

更新項目檔案:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.3" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\Blazor\Blazor.csproj" />
  </ItemGroup>
</Project>
           

添加Razor網頁的頁面叫做WASM.cshtml——為WASM SPA啟動頁。

@page "/wasm"
@{
    Layout = null;
}

<!DOCTYPE html<span class="pl-kos">>
<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>Blazor</title>
    @*Change base*@
    <base href="/wasm/" target="_blank" rel="external nofollow"  />
    @*Update Link hrefs*@
    <link href="/css/bootstrap/bootstrap.min.css" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  rel="stylesheet" />
    <link href="/css/app.css" target="_blank" rel="external nofollow"  rel="stylesheet" />
    <link href="/wasm/Blazor.styles.css" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  rel="stylesheet" />
</head>
<body>
    <div id="app">Loading...</div>
    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    @*Update js sources *@
    <script src="/wasm/_framework/blazor.webassembly.js"></script>
</body>
</html>
           

添加第二個Razor網頁的頁面叫做Server.cshtml——為Servr SPA啟動頁。

@page "/"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!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>Blazor</title>
    <base href="/" target="_blank" rel="external nofollow"  />
    <link href="/css/bootstrap/bootstrap.min.css" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  rel="stylesheet" />
    <link href="/css/site.css" target="_blank" rel="external nofollow"  rel="stylesheet" />
    <link href="/wasm/Blazor.styles.css" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  rel="stylesheet" />
</head>

<body>
    <component type="typeof(Blazor.App)" render-mode="ServerPrerendered" />

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>
           

Index.cshtml

将@page指令更新為@page "/index".

Startup.cs

更新Startup以處理WASM和伺服器中間件路徑。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        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)
                };
            });
        }
    }

    // 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();
        }
        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.UseStaticFiles();

        app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/wasm"), app1 =>
        {
            app1.UseBlazorFrameworkFiles("/wasm");
            app1.UseRouting();
            app1.UseEndpoints(endpoints =>
            {
                endpoints.MapFallbackToPage("/wasm/{*path:nonfile}", "/wasm");
            });
        });

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapBlazorHub();
            endpoints.MapRazorPages();
            endpoints.MapFallbackToPage("/Server");
        });
    }
}
           

運作應用程式

應用程式現在應該可以運作了。它将在伺服器上下文中啟動。通過左側菜單中的連結切換到WASM上下文。當您在上下文之間切換時,您應該會看到顔色的變化。

添加資料服務

雖然上述配置有效,但它需要一些示範代碼來展示它如何處理更傳統的資料服務。我們将修改解決方案以使用非常基本的資料服務來展示應該使用的DI和接口概念。

将資料和服務檔案夾添加到Blazor項目。

WeatherForecast.cs

向Data添加一個WeatherForecast類。

public class WeatherForecast
{
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public string Summary { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
           

IWeatherForecastService.cs

為Services添加一個IWeatherForecastService接口。

public interface IWeatherForecastService
{
    public Task<List<WeatherForecast>> GetRecordsAsync();
}
           

WeatherForecastServerService.cs

向Services添加一個WeatherForecastServerService類。通常這會連接配接到資料庫,但這裡我們隻是建立了一組虛拟記錄。

public class WeatherForecastServerService : IWeatherForecastService
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private List<WeatherForecast> records = new List<WeatherForecast>();

    public WeatherForecastServerService()
        => this.GetForecasts();

    public void GetForecasts()
    {
        var rng = new Random();
        records = Enumerable.Range(1, 10).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        }).ToList();
    }

    public Task<List<WeatherForecast>> GetRecordsAsync()
        => Task.FromResult(this.records);
}
           

WeatherForecastAPIService.cs

向Services添加一個WeatherForecastAPIService類。

public class WeatherForecastAPIService : IWeatherForecastService
{
    protected HttpClient HttpClient { get; set; }

    public WeatherForecastAPIService(HttpClient httpClient)
        => this.HttpClient = httpClient;

    public async Task<List<WeatherForecast>> GetRecordsAsync()
        => await this.HttpClient.GetFromJsonAsync<List<WeatherForecast>>($"/api/weatherforecast/list");
}
           

WeatherForecastController.cs

最後将一個WeatherForecastController類添加到控制器檔案夾中的Blazor.Web項目。

using System.Collections.Generic;
using System.Threading.Tasks;
using Blazor.Data;
using Microsoft.AspNetCore.Mvc;
using <span class="pl-en">MVC = Microsoft.AspNetCore.Mvc;
using Blazor.Services;

namespace Blazor.Web.APIControllers
{
    [ApiController]
    public class WeatherForecastController : ControllerBase
    {
        protected IWeatherForecastService DataService { get; set; }

        public WeatherForecastController(IWeatherForecastService dataService)
            => this.DataService = dataService;

        [MVC.Route("/api/weatherforecast/list")]
        [HttpGet]
        public async Task<List<WeatherForecast>> GetList() => await DataService.GetRecordsAsync();
    }
}
           

Blazor項目Program.cs

通過它的.IWeatherForecastService将API服務添加到Blazor項目中的Program.cs。

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("#app");

        builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
        builder.Services.AddScoped<IWeatherForecastService, WeatherForecastAPIService>();

        await builder.Build().RunAsync();
    }
}
           

Blazor.Web Startup.cs

在Blazor.Web項目中将伺服器服務添加到Startup.cs中,再通過它的IWeatherForecastService。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddScoped<IWeatherForecastService, WeatherForecastServerService>();
    .....
}
           

建構并運作項目

解決方案現在應該建構并運作。

建構Blazor WASM和伺服器一體化解決方案代碼庫解決方案和項目Blazor項目變更Blazor.Web運作應用程式添加資料服務Blazor項目Program.cs建構并運作項目它是如何工作的?總結
建構Blazor WASM和伺服器一體化解決方案代碼庫解決方案和項目Blazor項目變更Blazor.Web運作應用程式添加資料服務Blazor項目Program.cs建構并運作項目它是如何工作的?總結

它是如何工作的?

從根本上說,Blazor伺服器和Blazor WASM應用程式之間的差別在于它運作的上下文。在解決方案中,所有SPA代碼都建構在Web Assembly項目中,并由WASM和伺服器上下文使用。沒有“共享”代碼庫代碼,因為它是完全相同的前端代碼,具有相同的入口點——App.razor。兩個上下文之間的不同之處在于後端服務的提供者。

已聲明Web程式集項目<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">。它建構标準Blazor.dll檔案和WASM特定代碼,包括Web程式集“啟動配置檔案”blazor.boot.json。

在Web程式集上下文中,初始頁面加載blazor.webassembly.js。這将加載blazor.boot.json,它告訴blazor.webassembly.js如何在浏覽器中“啟動”Web程式集代碼。它運作Program建構WebAssemblyHost,加載定義的服務,并啟動Renderer将應用html元素替換為Program中指定的根元件。這将加載路由器,它讀取Url,擷取适當的元件,将其加載到指定的布局中,然後開始渲染過程。SPA啟動并運作。

在伺服器上下文中,伺服器端代碼在初始加載頁面中選取元件引用并靜态呈現它。它将呈現的頁面傳遞給用戶端。這會加載并運作blazor.server.js,它會回調伺服器SignalR Hub并擷取動态呈現的應用根元件。SPA啟動并運作。服務容器和渲染器位于Blazor Hub 中——通過services.AddServerSideBlazor()在Web伺服器啟動時調用Startup來啟動。

我們實作的資料服務展示了依賴注入和接口。UI元件——在我們的例子中FetchData使用IWeatherForcastService在Services中注冊的服務。在 WASM上下文中,服務容器啟動WeatherForecastAPIService,而在伺服器上下文中,服務容器啟動WeatherForecastServerService。兩個不同的服務,遵循相同的接口,由使用該接口的UI元件消費。UI元件并不關心它們使用哪個服務,它隻需要實作IWeatherForcastService。

總結

希望本文能夠深入了解Blazor SPA的工作原理以及伺服器和WASM Blazor SPA之間的真正差別。

https://www.codeproject.com/Articles/5299017/Building-a-Blazor-WASM-and-Server-All-In-One-Solut

繼續閱讀