今天咱們來扯一下 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 引數。就得到傳遞的資料。