今天咱們來扯一下 Blazor 應用程序怎麼訪問 HttpContext。其實這句話有坑,爲了避免大夥伴們掉茅坑,老周直接說明:Blazor 是不能訪問 HttpContext 的。哪怕你在服務容器中註冊了 IHttpContextAccessor 也不行,無法返回有效的上下文。
爲啥?這得從 Blazor 的運行方式說起。銀河系周知,Blazor 允許用 .NET 代碼編寫客戶端邏輯,在某種程度(注意,是某種程度)上替代“奸商”……哦不,是 JS。Javascript 雖然語法上很像C某某,但和C某某比還是差得很遠,代碼閱讀起來總感覺XYZ。儘管不能完全替換,但能實現一部分也是很好的。Blazor 主要是照顧像老周這種屌絲程序猴,一旦來項目了,一個人承擔後臺前臺,連美工、圖片處理都要一手包。所以用寫後臺代碼的方式寫前臺更習慣。
上面一段全是廢話!Blazor 只是在第一次運行的時候(不管是 Server 版還是 WASM 版)纔會產生 HTTP 請求,之後的交互都由 Web Socket 來擔任。往上說一層就是 SignalR。說直白一點就是,它只有第一次訪問服務器纔有 HttpContext,因爲 Blazor 應用還沒加載。一旦它加載成功了,後面就沒 HttpContext 的事了。
結論:要取得 HttpContext 中需要的數據,只能在第一次請求時獲得,然後你想盡辦法讓這些數據傳給 Blazor 應用程序。本文老周將演示兩種方法——基本能對付過去。
方法一:級聯參數
Blazor 需要一個HTML頁面來承載,然後 Page 之間的切換都在這個HTML頁面上進行。項目模板默認使用 Razor Pages—— 生成一個名爲 _Host 的頁面(當然你改用 MVC 的視圖來加載也可以,一樣的原理)。然後使用 component 標記幫助器來加載 Blazor 應用程序。Blazor 應用是通過組件來構建的,一般會有一個名爲 App 的組件,充當根組件。
<component type="typeof(App)" render-mode="ServerPrerendered" />
App 組件放個 Router,作用無非就是能找到 Blazor 應用頁面就呈現,找不到就顯示反饋。
<Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> </Found> <NotFound> <PageTitle>Not found</PageTitle> <LayoutView Layout="@typeof(MainLayout)"> <p role="alert">Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router>
其實,Blazor 頁面元素就是顯示在 RouteView 組件下面的。所以,要使用級聯參數組件,就可以把 CascadingValue 組件作爲 RouteView 父級。
咱們先看一下數據的傳遞順序:
1、要讀 HttpContext 對象裏的東西,獲取數據的代碼得在 _Host 頁面上寫;
2、在用 component 組件加載 App 組件時通過參數把數據傳遞給 App 內部;
3、App 組件內將數據傳遞給 CascadingValue;
4、各個 Blazor 頁面組件都能夠從 CascadingValue 組件中獲取到數據。
HttpContext --> _Host.cshtml --> App.razor --> CascadingValue --> XXX
下面是實現過程:(假設我們要獲取 URL 查詢參數)
1、在 App 組件中定義名爲 Version 的屬性,字符串類型。要應用 Parameter 特性,說明它是一個組件參數,在 component 標記幫助器中可以傳參。
2、把 RouteView 組件放到 CascadingValue 組件下面,並讓它的值(Value)引用 Version 屬性的值。
// App.razor 文件 <Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <CascadingValue Name="ver" TValue="string" Value="@Version"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> </CascadingValue> </Found> <NotFound> <PageTitle>Not found</PageTitle> <LayoutView Layout="@typeof(MainLayout)"> <p role="alert">Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> @code { [Parameter] public string? Version { get; set; } }
3、在 _Host.cshtml 文件中(或你自己定義的 MVC 視圖文件)中,讀取 URL 查詢中的 “v” 字段的值,然後通過 component 組件把值傳遞 App 組件。
…… @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @{ Layout = "_Layout"; // 從URL參數中讀參數 if(!HttpContext.Request.Query.TryGetValue("v",out var value)) { value = "0.0.0"; } } <component type="typeof(App)" render-mode="ServerPrerendered" param-Version="@value.ToString()" />
用 component 組件傳遞參數,可以用 param-* 特性,後面的 * 表示 App 組件接收參數的成員名稱,這裏是我們前面定義的 Version 屬性,直接寫成 param-Version 即可。如果屬性名爲 Name,那就寫成 param-Name。
4、假設現在 Index.razor 組件要使用數據。需要在 Index 組件中定義一個屬性成員,一定要應用 CascadingParameter 特性。注意這裏 Name = "ver"。這個名字和剛纔 App 組件中 CascadingValue 的 Name 是匹配的。
<p>接收到的數據:@Data</p> @code { [CascadingParameter(Name = "ver")] public string? Data{ get; set; } }
要呈現 Data 屬性的內容,只需在 HTML 中引用即可。
在運行程序後,訪問時加上 v=5.0.3,這個字段的值就能傳到 Index 組件中。
二、單實例服務
這個方案是運用了依賴注入的功能,咱們定義一個類,它的屬性用於存儲參數。
public class PassDataService { public int Key1 { get; set; } public string? Key2 { get; set; } }
然後把它註冊爲單實例服務。
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); …… builder.Services.AddSingleton<PassDataService>(); var app = builder.Build();
在 _Host.cshtml 文件或你自定義的 MVC 視圖文件中,讀出 HttpContext 中數據,然後設置到 PassDataService 實例的屬性上。
@page "/" @namespace TestApp.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @inject PassDataService _datasv; @{ Layout = "_Layout"; // 讀取URL查詢 if(!HttpContext.Request.Query.TryGetValue("key1", out var kVal1)) { kVal1 = "0"; } if(!HttpContext.Request.Query.TryGetValue("key2", out var kVal2)) { kVal2 = string.Empty; } // 賦值 _datasv.Key1 = int.TryParse(kVal1, out int k1) ? k1 : 0; _datasv.Key2 = kVal2; } <component type="typeof(App)" render-mode="ServerPrerendered" />
@inject 指令可以注入需要的服務實例。
@inject <類型> <變量名>
在 Index 組件中,只要注入 PassDataService 服務實例,就能獲取到數據。
@page "/" @inject PassDataService _datasv <PageTitle>Index</PageTitle> <h1>Hello, world!</h1> <p>獲取到的數據:</p> <div>Key1 = @_datasv.Key1</div> <div>Key2 = @_datasv.Key2</div>
運行應用程序,在 URL 後面加上 key1 和 key2 參數。就得到傳遞的數據。