在Blazor页面中以同步模式访问cookie

huangapple go评论81阅读模式
英文:

Access cookie in Blazor page in sync mode

问题

我正在使用.NET 7.0和Blazor构建Web应用程序。

退一步 - 基本要求

在得到一些提示后,我认为我选择了错误的方式来实现我的需求。

因此,我想更详细地解释一下基本情况和要求 - 也许这会有助于澄清。

与数据源的整体连接由IMoxClientConnection的实例管理。
由于这些对象包含用户的身份验证信息,每个访问网页的用户都需要一个新的实例(但不仅仅是“scoped”,因为用户不应该在每次打开页面时都需要登录)。

因此,我实现了一个ConnectionHandler,它基本上是MoxSessionId和相应的IMoxClientConnection实例的字典。
(在内部检查是否已经存在或是否需要创建新的实例)
这个ConnectionHandler被添加为SingletonService,因此它在应用程序运行时保持持久化。

所有服务都需要这样一个IMoxClientConnection来访问数据源。

ConnectionHandler提供了一个函数GetClientConnectorForSession(string moxSessionId),供服务检索要使用的正确IMoxClientConnection

也许这个解释有助于理解主要思想。

需求

我需要为每个访问Web应用程序的客户端获取任何类型的会话ID。只要浏览器选项卡打开并在使用中,这个会话ID就需要持久存在。目前,当浏览器/选项卡关闭或不再使用时,数据是否持久存在并不重要。

思路/迄今为止尝试过的

我的基本想法是将我的自定义会话ID(作为字符串)存储在Cookie或类似的位置。
据我所了解,这在Blazor中不再像在“旧”的asp.net中那么容易。

我需要从我的基本服务类内部访问这个会话ID。
因此,我尝试使用SessionStorage。

我在Program.cs中添加了以下代码:
builder.Services.AddScoped<ProtectedSessionStorage>();
并使用DI使对象在我的服务类中可用。
在我的基本服务类中,有以下函数来访问我的会话ID:

private string CreateOrGetSessionId()
{
  var sessionId = _sessionStorage.GetAsync<string>("MoxSessionId").Result.Value;

  if (string.IsNullOrWhiteSpace(sessionId))
  {
    sessionId = Guid.NewGuid().ToString();
    _sessionStorage.SetAsync("MoxSessionId", sessionId);
  }

  return sessionId;
}

问题

但当我运行应用程序时,在执行第一行(GetAsync)之后什么都不会发生。
在一些教程中,我找到了一些提示,似乎还应该有一个像GetItem之类的函数,可以同步运行 - 但是这些示例似乎对我不起作用。

问题

是否有人可以为我提供一个可行的示例,演示如何实现我的初始需求。我使用了ProtectedSessionStorage,因为我在一个示例中看到了它 - 但基本上不需要保护/加密。

提前感谢。

英文:

I'm building a web app using .net 7.0 and Blazor.


Step back - basic requirement

After a couple of hints and tips, I asume, that I'm choosen the wrong way to achive my requirements.

Therefore I want to explain a little more details about the basic situation and requirement - maybe this helps a little to clarify.

The overall connection to data source is managed by instances of IMoxClientConnection.
As these objects contains the authentication information of the user, there needs to be a new instance for every user accessing the web page (but not just "scoped" as the user should not have to login on every page he opens).

So I implemented a ConnectionHandler which basicaly is a Dictionary of the MoxSessionId and the corresponding IMoxClientConnection instance.
(Which internaly checks if its already present or a new one needs to be created)
This ConnectionHandler is added as a SingletonService so it persists along application runtime.

All the services requires such a IMoxClientConnection to access the data source.

The ConnectionHandler provides a function GetClientConnectorForSession(string moxSessionId) for the services to retrieve the correct IMoxClientConnection to use.

Maybe this explaination helps a littel to understand the main idea behind.


Requirement

I need to get any kind of session id for every client accessing the web app. This session id needs to be persistent as long as the browser tab is open and in use. At the moment it doesn't matter if the data persists when the browser/the tab is closed or not.

Idea/Tried so far

My basic idea was to store my own session id (as a string) in a cookie or similar.
As far as I found out this is not that easy in Blazor anymore as it was on "old" asp.net.

I need to access this session id from within my base-service class.
So I tried to use SessionStorage.

I added
builder.Services.AddScoped<ProtectedSessionStorage>();
to the Program.cs and used DI to make the object available in my service class.
In my base-service class there is the following function to access my session id:

private string CreateOrGetSessionId()
{
  var sessionId = _sessionStorage.GetAsync<string>("MoxSessionId").Result.Value;

  if (string.IsNullOrWhiteSpace(sessionId))
  {
    sessionId = Guid.NewGuid().ToString();
    _sessionStorage.SetAsync("MoxSessionId", sessionId);
  }

  return sessionId;
}

Problem

But when I run the app nothing happens after the first row is executed (GetAsync).
On some tutorials I found hints, that there also should be a function like GetItem or so, which runs synchronusly - but none of these examples seems to work for me.

Question

Can anybody help me out with a working example how I could achieve my initial requirement.
I used the ProtectedSessionStorage as I saw in an example - but basicaly there is no need for protection/encryption.

Thanks in advance

答案1

得分: 0

我会将创建/获取逻辑放入OnInitializedAsyncOnAfterRenderAsync(取决于服务器模式)方法内的异步函数中,并从那里设置/读取该项。

英文:

What I would do is put this create/get logic into an async function within your OnInitializedAsync or OnAfterRenderAsync (depending on your server mode) method and set /read the item from there.

答案2

得分: 0

正如你所说,情况并不像看起来那么简单,因为你需要处理以下问题:
 - 只能在应用程序/页面/组件第一次呈现后恢复该值。
 - 组件的异步行为。

基于上述原因,你也不能以同步方式执行它。

你的代码块:

```csharp
var sessionId = _sessionStorage.GetAsync<string>("MoxSessionId").Result.Value;

会阻塞线程。你试图将一段异步代码嵌入同步方法中。

下面是一个提供者服务,用于管理该值。请注意,只能通过调用 GetSessionIDAsync 来获取ID。没有读取属性,因为属性的getter只能是同步的,而获取值是异步的。

public class MoxSessionProvider
{
    private ProtectedSessionStorage _protectedSessionStorage;
    private Guid _sessionUid = Guid.Empty;

    public MoxSessionProvider(ProtectedSessionStorage protectedSessionStorage)
        => _protectedSessionStorage = protectedSessionStorage;

    public const string MoxSessionId = "MoxSessionId";

    public async Task<Guid> GetSessionIDAsync()
    {
        if (_sessionUid != Guid.Empty)
            return _sessionUid;

        await GetAsync();
        return _sessionUid;
    }

    private async Task GetAsync()
    {
        try
        {
            var result = await _protectedSessionStorage.GetAsync<Guid>(MoxSessionProvider.MoxSessionId);
            if (result.Success)
                _sessionUid = result.Value;

            if (!result.Success)
            {
                _sessionUid = Guid.NewGuid();
                await _protectedSessionStorage.SetAsync(MoxSessionProvider.MoxSessionId, _sessionUid);
            }
        }
        catch
        {
            _sessionUid = Guid.Empty;

        }
    }
}

这与 HttpContextAccessor 一起注册:

builder.Services.AddScoped<MoxSessionProvider>();
builder.Services.AddHttpContextAccessor();

现在我们可以构建一个 SessionUidCascade 组件来传播该值。

@inject ProtectedSessionStorage _storage
@inject MoxSessionProvider _moxSessionState
@inject IHttpContextAccessor HttpContextAccessor

<CascadingValue Name="SessionUid" Value="this.GetSessionUidTask" IsFixed=true>
    @ChildContent
</CascadingValue>

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }

    private bool _isClientRender;
    private TaskCompletionSource<Guid?> _taskCompletionSource = new();

    public Task<Guid?> GetSessionUidTask => _taskCompletionSource.Task;

    protected override async Task OnInitializedAsync()
    {
        // 检查是否实际渲染,而不是服务器端预渲染
        _isClientRender = HttpContextAccessor.HttpContext is not null && HttpContextAccessor.HttpContext.Response.HasStarted;

        // 使用Yield确保组件第一次呈现
        await Task.Yield();

        // 预渲染,因此返回null
        if (!_isClientRender)
            _taskCompletionSource.SetResult(null);
        
        // 渲染,因此获取值
        if (_isClientRender)
        {
            var uid = await _moxSessionState.GetSessionIDAsync();
            _taskCompletionSource.SetResult(uid);
        }
    }
}

将其添加到 App 中,以便所有人都可以捕获级联。

<SessionUidCascade>
<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, theres nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
</SessionUidCascade>

你可以在一个组件中像这样使用它 - SessionUidComponent

        <div class="bg-dark text-white p-2 m-2">
            <pre>Session ID: @(_sessionUid is null ? "Retrieving" : _sessionUid) </pre>
        </div>

@code {
    [CascadingParameter(Name = "SessionUid")] public Task<Guid?> SessionUidTask { get; set; } = default!;

    private Guid? _sessionUid = Guid.Empty;

    protected override async Task OnInitializedAsync()
    {
        ArgumentNullException.ThrowIfNull(SessionUidTask);
        _sessionUid = await SessionUidTask;
    }
}
@page "/"
<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<SessionUidComponent />
英文:

As you say, it isn't quite as simple as it may seem because you need to deal with:

  • Only being able to recover the value after the application/page/component has rendered for the first time.
  • Async behaviour of components.

You also can't do it synchronously for the reasons above.

Your code block:

var sessionId = _sessionStorage.GetAsync<string>("MoxSessionId").Result.Value;

blocks the thread. You're trying to shoehorn a piece of async code into a synchronous method.

Here's a Provider service to manage the value. Note you can only get the ID by calling GetSessionIDAsync. There no read property as property getters can only be sync and getting the value is async.

public class MoxSessionProvider
{
    private ProtectedSessionStorage _protectedSessionStorage;
    private Guid _sessionUid = Guid.Empty;

    public MoxSessionProvider(ProtectedSessionStorage protectedSessionStorage)
        => _protectedSessionStorage = protectedSessionStorage;

    public const string MoxSessionId = "MoxSessionId";

    public async Task<Guid> GetSessionIDAsync()
    {
        if (_sessionUid != Guid.Empty)
            return _sessionUid;

        await GetAsync();
        return _sessionUid;
    }

    private async Task GetAsync()
    {
        try
        {
            var result = await _protectedSessionStorage.GetAsync<Guid>(MoxSessionProvider.MoxSessionId);
            if (result.Success)
                _sessionUid = result.Value;

            if (!result.Success)
            {
                _sessionUid = Guid.NewGuid();
                await _protectedSessionStorage.SetAsync(MoxSessionProvider.MoxSessionId, _sessionUid);
            }
        }
        catch
        {
            _sessionUid = Guid.Empty;

        }
    }
}

This is registered along with the HttpContextAccessor :

builder.Services.AddScoped<MoxSessionProvider>();
builder.Services.AddHttpContextAccessor();

We can now build a SessionUidCascade component to cascade the value.

@inject ProtectedSessionStorage _storage
@inject MoxSessionProvider _moxSessionState
@inject IHttpContextAccessor HttpContextAccessor

<CascadingValue Name="SessionUid" Value="this.GetSessionUidTask" IsFixed=true>
    @ChildContent
</CascadingValue>

@code {
    [Parameter] public RenderFragment? ChildContent { get; set; }

    private bool _isClientRender;
    private TaskCompletionSource<Guid?> _taskCompletionSource = new();

    public Task<Guid?> GetSessionUidTask => _taskCompletionSource.Task;

    protected override async Task OnInitializedAsync()
    {
        // Check if we are actually rendering not server side pre-rendering
        _isClientRender = HttpContextAccessor.HttpContext is not null && HttpContextAccessor.HttpContext.Response.HasStarted;

        // Yields which will ensure the component is rendered for the first time
        await Task.Yield();

        // Pre Rendering so return null
        if (!_isClientRender)
            _taskCompletionSource.SetResult(null);
        
        // Rendering so get the value
        if (_isClientRender)
        {
            var uid = await _moxSessionState.GetSessionIDAsync();
            _taskCompletionSource.SetResult(uid);
        }
    }
}

Add it to App so everyone can capture the cascade.

<SessionUidCascade>
<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, theres nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
</SessionUidCascade>

You can consume it like this in an component - SessionUidComponent.


        <div class="bg-dark text-white p-2 m-2">
            <pre>Session ID: @(_sessionUid is null ? "Retrieving" : _sessionUid) </pre>
        </div>

@code {
    [CascadingParameter(Name = "SessionUid")] public Task<Guid?> SessionUidTask { get; set; } = default!;

    private Guid? _sessionUid = Guid.Empty;

    protected override async Task OnInitializedAsync()
    {
        ArgumentNullException.ThrowIfNull(SessionUidTask);
        _sessionUid = await SessionUidTask;
    }
}
@page "/"
<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<SessionUidComponent />

huangapple
  • 本文由 发表于 2023年6月19日 15:27:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76504472.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定