英文:
ASP.NET, Dependency Injection, Blazor and Async - SendAsync issue
问题
Update: 似乎与在ASP.NET中使用自己的HttpClient和SendAsync直接相关。我猜测您在使用HttpClient/Async进行自己的Web请求时,在ASP.NET "content"中需要考虑什么特殊情况。目前,我已将我正在使用的两个调用切换到非异步,以便今天完成我的工作,但明天将重新审查正确的DI。只是好奇为什么使用自己的HttpClient并进行异步调用会产生不同的行为?
First Question: 我试图逐步查找这个代码中的问题。但它总是跳到PageTitle标签上断点,因为@Person在此时为空。它进入了第一个异步调用,但从未返回。我追踪了一下,似乎是在HttpClient异步调用中获取后端服务的令牌。我可以在Fiddler中看到它获得了成功的响应。但它不会从那个异步调用中返回...只是"跳过去"。
在服务端Blazor中使用异步有什么好的经验法则吗?您是否使用ConfigureAwait(false)?在任何调用上使用它吗?对Blazor的渲染模型还很陌生。但从我所阅读的内容来看,它似乎会等待或应该等待非null的事物?将服务注册为Scoped是一个好的默认规则吗?创建一个新的HttpClient而不是注入它是否有问题?我一上午都在尽力排除问题。已卸载Mediatr,现在只使用Handler作为服务类。
以下是在我遇到问题的代码示例。
@page "/person/{personId:int}/{viewMode}"
@using Laz.Core.Blazor.Components
@using Laz.Project.Core.Domain.Models
@using Laz.Core.Domain.Handlers
@inject GetPersonHandler GetPersonHandler
@inject ILogger
@code {
[Parameter]
public int PersonId { get; set; } = 0;
[Parameter]
public string ViewMode { get; set; }
public ApiPerson Person { get; set; } = null;
protected override async Task OnInitializedAsync()
{
Logger.LogInformation("OnInitializedAsync - Person Page");
var req = new GetPersonRequest { PersonId = PersonId };
Logger.LogInformation("1"); // 在此之后,它似乎在异步调用中迷失了,然后跳到渲染,导致错误。这是我第一次遇到这个问题。
GetPersonResponse response = await GetPersonHandler.Handle(req, CancellationToken.None);
Logger.LogInformation("2");
Person = response.Person;
Logger.LogInformation("3");
await base.OnInitializedAsync();
}
}
英文:
Update: Seemed to be directly related to using my own HttpClient with SendAsync in ASP.NET I'm guessing. What special considerations do you have to take with your own web requests via HttpClient/Async while being in an ASP.NET "content". For now, I've switched the two calls I'm using to the non-async so I can finish my work for the day, but will revisit the correct DI tomorrow. Just curious why using your own HttpClient and making an Async call behaves differently?
First Question: I was trying to step through this code to detect the problem. But it always just skips to breaking on the PageTitle tag because @Person is null at the moment.
It goes into the first async call and never returns. Traced it a little further and it seems to make an HttpClient Async call get to get a token from backend service. And I can see in fiddler it gets a successful response. But doesn't come back from that async... just "jumps ahead".
Any good rules of thumb around using async in service-side Blazor?
Do you use ConfigureAwait(false) ? use it on any calls? New to Blazor's rendering model. But from the reading I did it seems like it will wait or its supposed to wait on things being non-null ?? Is registering services Scoped a good default rule of thumb? Is creating a fresh HttpClient instead of injecting it an issue? I've been trying to rule things out all morning. Uninstalled Mediatr and am just using the Handler as Service classe now.
Here is code sample of where things start going awry for me.
@page "/person/{personId:int}/{viewMode}"
@using Laz.Core.Blazor.Components
@using Laz.Project.Core.Domain.Models
@using Laz.Core.Domain.Handlers
@inject GetPersonHandler GetPersonHandler
@inject ILogger<PrintPerson> Logger
@code {
[Parameter]
public int PersonId { get; set; } = 0;
[Parameter]
public string ViewMode { get; set; }
public ApiPerson Person { get; set; } = null;
protected override async Task OnInitializedAsync()
{
Logger.LogInformation("OnInitializedAsync - Person Page");
var req = new GetPersonRequest {PersonId = PersonId};
Logger.LogInformation("1"); // after this it seems to get lost in async calls and jump to rendering which throws error. This is my first encounter with this issue.
GetPersonResponse response = await GetPersonHandler.Handle(req, CancellationToken.None);
Logger.LogInformation("2");
Person = response.Person;
Logger.LogInformation("3");
await base.OnInitializedAsync();
}
}
<PageTitle>{@Person.Name}</PageTitle>
答案1
得分: 2
如果我正确阅读了你的代码,你似乎还没有完全掌握组件生命周期,并且存在一个空值处理的问题。请检查一下为什么在 FetchData
的渲染代码中有一个空值检查。
在你的代码中,这行代码(我认为是你的更新中的一个 HttpClient 请求)将控制返回给组件代码中 OnInitializedAsync
的调用者。然后,首次渲染该组件,在这一点上:
<PageTitle>{@Person.Name}</PageTitle>
Person
为 null。
你可以在一个简单的 MRE 中复现你的问题。
@page "/"
<PageTitle>@_model.Title</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
@code {
private Model? _model = null;
protected override async Task OnInitializedAsync()
{
await Task.Yield();
_model = new Model() { Title="Hello" };
}
public class Model
{
public string? Title { get; set; }
}
}
如果你想将变量初始化为 null,解决方案是要优雅地处理 null 值:
<PageTitle>@(Person?.Name ?? "Not Set")</PageTitle>
PS 你是否看到 Person.Name
上可能出现的空引用警告?
英文:
If I've read your code correctly, you haven't yet fully grasped the Component lifecycle, and have a null handling problem. Review why there's a null check in the render code in FetchData
.
In your code this line [which I believe from your update is a HttpClient request] yields control back to the caller of OnInitializedAsync
in the component code. This then renders the component for the first time, at which point:
<PageTitle>{@Person.Name}</PageTitle>
Person
is null.
You can reproduce your problem in a simple MRE.
@page "/"
<PageTitle>@_model.Title</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
@code {
private Model? _model = null;
protected override async Task OnInitializedAsync()
{
await Task.Yield();
_model = new Model() { Title="Hello" };
}
public class Model
{
public string? Title { get; set; }
}
}
If you want to initialize variables as null, the solution is to handle nulls gracefully:
<PageTitle>@(Person?.Name ?? "Not Set")</PageTitle>
PS Did you see a possible null reference warning on Person.Name
?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论