【ASP.NET Core】在Blazor中獲取 HTTP 上下文信息

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章