英文:
OnInitializedAsync() is being called twice
问题
以下是您要翻译的内容:
For some reason when bringing up the page CampaignPage.razor, `OnInitializedAsync()` is being called twice.
In addition, the property `ReturnUrl` is null each time (should be the first time) and is null again when `HandleValidSubmitAsync()` is called.
The first time it hits it, this is the call stack:
LouisHowe.web.dll!LouisHowe.web.Pages.Manager.CampaignPage.OnInitializedAsync() Line 143 C#
[External Code]
LouisHowe.web.dll!LouisHowe.web.Pages.Pages__Host.ExecuteAsync.AnonymousMethod__23_1() Line 40 C#
[External Code]
And the second time, this is it:
LouisHowe.web.dll!LouisHowe.web.Pages.Manager.CampaignPage.OnInitializedAsync() Line 143 C#
[External Code]
In addition it's 5+ seconds between the two hits and there's very little the system is doing to initialize the page. It hits the database but that's Sql Server Express sitting on my computer doing nothing else.
This only happens for case of mode == read or update. And in that case, in `OnInitializedAsync()` it is calling the following code. So it's something in this code that is causing the issue.
Note that it calls the same dbContext to get `Countries.All()` for the case of mode == create and that does not have the problem. So it's something in the call to `Campaigns.FindByNames()` that's doing it - but how?
await using (var dbContext = await NoTrackingDbFactory.CreateDbContextAsync())
{
// 1. if no uniqueId then create mode (because what would we Read/Update)?
if (string.IsNullOrEmpty(UniqueId))
ModeState = Mode.Create;
else
{
// 2. if have a uniqueId then it can't be create.
if (ModeState == Mode.Create)
ModeState = Mode.Read;
// 3. Read it in from the database.
_campaign = await dbContext.Campaigns.FindByNames(string.Empty,
UniqueId, CampaignIncludes.All).FirstOrDefaultAsync();
if (_campaign == null)
{
_badUniqueId = true;
ModeState = Mode.Read;
return;
}
Model.Campaign = _campaign;
}
var list = new List<IOrganization>();
list.AddRange(await dbContext.Countries.All().ToListAsync());
list.Add(Country.Dashes);
list.AddRange(await dbContext.States.All().ToListAsync());
AllRegions = list;
}
And here's `FindByNames`
public static IQueryable<Campaign> FindByNames(this IQueryable<Campaign> source, string searchName,
string searchUniqueId, params Expression<Func<Campaign, object?>>[] includes)
{
var query = source;
if (searchName.Length != 0)
query = query.Where(campaign => campaign.Name.Contains(searchName));
if (searchUniqueId.Length != 0)
query = query.Where(campaign => campaign.UniqueId.Contains(searchUniqueId));
foreach (var include in includes)
query = query.Include(include);
return query.OrderBy(i => i.Name);
}
So, what would cause the long delay and the multiple calls?
So I did as @MrCakaShaunCurtis suggested and put a debug entering and leaving `OnInitializedAsync()` and here's what I get.
Only once case (mode == create):
enter OnInitializedAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
... 2 expected DB calls
leave OnInitializedAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
enter OnAfterRenderAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
leave OnAfterRenderAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
enter OnAfterRenderAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
leave OnAfterRenderAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
And for the calls twice case:
enter OnInitializedAsync() CampaignPage Instance 427cd0b9-bf07-41d7-924f-0355da16b441
... 4 (FindByNames query becomes 2 calls) expected DB calls
leave OnInitializedAsync() CampaignPage Instance 427cd0b9-bf07-41d7-924f-0355da16b441
Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (2ms) [Parameters=[@__userid_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [a].[Id], [a].[AccessFailedCount], [a].[ConcurrencyStamp], [a].[Email], [a].[EmailConfirmed], [a].[LockoutEnabled], [a].[LockoutEnd], [a].[NormalizedEmail], [a].[NormalizedUserName], [a].[PasswordHash], [a].[PhoneNumber], [a].[PhoneNumberConfirmed], [a].[SecurityStamp], [a].[TwoFactorEnabled], [a].[UserName]
FROM [AspNetUsers] AS [a]
WHERE [a].[Id] = @__userid_0
enter OnInitializedAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
... 4 calls again
leave OnInitializedAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
'LouisHowe.web.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\System.Web.HttpUtility.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
enter OnAfterRenderAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
leave OnAfterRenderAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
'LouisHowe.web.exe' (CoreCLR: clrhost): Loaded 'C:\git\LouisHowe\LouisHowe.web\bin\Debug\net7.0\Newtonsoft.Json.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
enter OnAfterRenderAsync() CampaignPage Instance 2
<details>
<summary>英文:</summary>
For some reason when bringing up the page CampaignPage.razor, `OnInitializedAsync()` is being called twice.
In addition, the property `ReturnUrl` is null each time (should be the first time) and is null again when `HandleValidSubmitAsync()` is called.
The first time it hits it, this is the call stack:
LouisHowe.web.dll!LouisHowe.web.Pages.Manager.CampaignPage.OnInitializedAsync() Line 143 C#
[External Code]
LouisHowe.web.dll!LouisHowe.web.Pages.Pages__Host.ExecuteAsync.AnonymousMethod__23_1() Line 40 C#
[External Code]
And the second time, this is it:
LouisHowe.web.dll!LouisHowe.web.Pages.Manager.CampaignPage.OnInitializedAsync() Line 143 C#
[External Code]
In addition it's 5+ seconds between the two hits and there's very little the system is doing to initialize the page. It hits the database but that's Sql Server Express sitting on my computer doing nothing else.
This only happens for case of mode == read or update. And in that case, in `OnInitializedAsync()` it is calling the following code. So it's something in this code that is causing the issue.
Note that it calls the same dbContext to get `Countries.All()` for the case of mode == create and that does not have the problem. So it's something in the call to `Campaigns.FindByNames()` that's doing it - but how?
await using (var dbContext = await NoTrackingDbFactory.CreateDbContextAsync())
{
// 1. if no uniqueId then create mode (because what would we Read/Update)?
if (string.IsNullOrEmpty(UniqueId))
ModeState = Mode.Create;
else
{
// 2. if have a uniqueId then it can't be create.
if (ModeState == Mode.Create)
ModeState = Mode.Read;
// 3. Read it in from the database.
_campaign = await dbContext.Campaigns.FindByNames(string.Empty,
UniqueId, CampaignIncludes.All).FirstOrDefaultAsync();
if (_campaign == null)
{
_badUniqueId = true;
ModeState = Mode.Read;
return;
}
Model.Campaign = _campaign;
}
var list = new List<IOrganization>();
list.AddRange(await dbContext.Countries.All().ToListAsync());
list.Add(Country.Dashes);
list.AddRange(await dbContext.States.All().ToListAsync());
AllRegions = list;
}
And here's `FindByNames`
public static IQueryable<Campaign> FindByNames(this IQueryable<Campaign> source, string searchName,
string searchUniqueId, params Expression<Func<Campaign, object?>>[] includes)
{
var query = source;
if (searchName.Length != 0)
query = query.Where(campaign => campaign.Name.Contains(searchName));
if (searchUniqueId.Length != 0)
query = query.Where(campaign => campaign.UniqueId.Contains(searchUniqueId));
foreach (var include in includes)
query = query.Include(include);
return query.OrderBy(i => i.Name);
}
So, what would cause the long delay and the multiple calls?
----------
So I did as @MrCakaShaunCurtis suggested and put a debug entering and leaving `OnInitializedAsync()` and here's what I get.
Only once case (mode == create):
enter OnInitializedAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
... 2 expected DB calls
leave OnInitializedAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
enter OnAfterRenderAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
leave OnAfterRenderAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
enter OnAfterRenderAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
leave OnAfterRenderAsync() CampaignPage Instance a69f0a2d-e36a-4297-b8a1-d8d9cd12bbd1
And for the calls twice case:
enter OnInitializedAsync() CampaignPage Instance 427cd0b9-bf07-41d7-924f-0355da16b441
... 4 (FindByNames query becomes 2 calls) expected DB calls
leave OnInitializedAsync() CampaignPage Instance 427cd0b9-bf07-41d7-924f-0355da16b441
Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (2ms) [Parameters=[@__userid_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [a].[Id], [a].[AccessFailedCount], [a].[ConcurrencyStamp], [a].[Email], [a].[EmailConfirmed], [a].[LockoutEnabled], [a].[LockoutEnd], [a].[NormalizedEmail], [a].[NormalizedUserName], [a].[PasswordHash], [a].[PhoneNumber], [a].[PhoneNumberConfirmed], [a].[SecurityStamp], [a].[TwoFactorEnabled], [a].[UserName]
FROM [AspNetUsers] AS [a]
WHERE [a].[Id] = @__userid_0
enter OnInitializedAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
... 4 calls again
leave OnInitializedAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
'LouisHowe.web.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\System.Web.HttpUtility.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
enter OnAfterRenderAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
leave OnAfterRenderAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
'LouisHowe.web.exe' (CoreCLR: clrhost): Loaded 'C:\git\LouisHowe\LouisHowe.web\bin\Debug\net7.0\Newtonsoft.Json.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
enter OnAfterRenderAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
leave OnAfterRenderAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
enter OnAfterRenderAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
leave OnAfterRenderAsync() CampaignPage Instance 2b5a7537-85cc-4bca-9b29-403f8cdba668
So it's calling the Identity DB to get the `AspNetUsers` user record. It generally does that before each call to `OnInitializedAsync()`, I assume to initialize the value in the `Task<AuthenticationState>` cascading parameter.
So it's re-initializing, before ever getting to `OnAfterRenderAsync()`.
I made the change @MarvinKlein suggested (2 places) - **that fixed it.** I now have:
@page "/"
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using LouisHowe.web
@namespace LouisHowe.web.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Copyright (c) 2023 by David Thielen - ALL RIGHTS RESERVED -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="~/"/>
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css"/>
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet"
asp-append-version="true"/>
<link href="css/site.css" rel="stylesheet" asp-append-version="true"/>
<link href="css/menu.css" rel="stylesheet" asp-append-version="true"/>
<link href="css/header.css" rel="stylesheet" asp-append-version="true"/>
<link rel="stylesheet" href="css/dx-demo.css" asp-append-version="true">
<link rel="stylesheet" href="css/dx-demo-pages.css" asp-append-version="true">
<link href="LouisHowe.web.styles.css" rel="stylesheet"/>
<link rel="icon" type="image/png" href="favicon.png"/>
<script src="~/js/Interop.js"></script>
<component type="typeof(HeadOutlet)" render-mode="Server"/>
</head>
<body>
@{
var initialTokenState = new Services.InitialApplicationState
{
XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken,
Cookie = HttpContext.Request.Cookies[".AspNetCore.Cookies"]
};
}
<component type="typeof(App)" render-mode="Server" param-InitialState="initialTokenState" />
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></script>
</body>
</html>
[And this is explained here.][1] Which leads to another question [that I'll post separately.][2]
[1]: https://stackoverflow.com/questions/58229732/whats-the-difference-between-rendermode-server-and-rendermode-serverprerendered
[2]: https://stackoverflow.com/questions/76452794/what-is-the-best-way-in-blazor-to-initialize-a-page-that-hits-the-db-to-initiali
</details>
# 答案1
**得分**: 0
以下是翻译好的内容:
不可能的。`OnInitialized{Async}` 不会被调用两次,你正在创建两个组件实例(或手动调用它)。 你需要找出为什么会创建两个实例。 你的代码对你来说可能很明显,但对我来说没有太多的上下文,所以我觉得它相当难理解。
为了证明这一点,为组件实例创建一个唯一的ID,并将信息记录到输出。 以下是使用 `Index` 来实现的方式:
```csharp
@page "/"
@using System.Diagnostics;
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
欢迎来到你的新应用程序。
<SurveyPrompt Title="Blazor对你来说如何工作?" />
@code {
public Guid Uid { get; init; } = Guid.NewGuid();
protected override Task OnInitializedAsync()
{
Debug.WriteLine($"初始化组件 {this.GetType().Name} 实例 {this.Uid.ToString()}");
return base.OnInitializedAsync();
}
}
在调试时,进行大量的调试输出以观察发生的事情和事件的顺序。当你运行异步代码时,断点通常无法提供足够的信息。人类太慢了!
英文:
Not possible. OnInitialized{Async}
isn't called twice, your creating two instances of the component (or manually calling it). You need to figure out why you're creating two instances. Your code may be obvious to you, but to me it's pretty opaque without a lot of context.
To prove it, create a Unique ID for the component instance and log information to Output. Here's how to do it with Index
@page "/"
@using System.Diagnostics;
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
@code {
public Guid Uid { get; init; } = Guid.NewGuid();
protected override Task OnInitializedAsync()
{
Debug.WriteLine($"Initialized Component {this.GetType().Name} Instance {this.Uid.ToString()}");
return base.OnInitializedAsync();
}
}
When debugging do lots of debug writes to watch what happens and the sequence of events. Break points often tell you nothing when you have async code running. Humans are just toooo slow!
答案2
得分: 0
这个答案是由@MarvinKlein在评论中提供的。
在 _Host.cshtml
中设置 render-mode="Server"
(两个位置),然后 OnInitializedAsync()
只会被调用一次。
英文:
This answer was provided by @MarvinKlein above in a comment.
In _Host.cshtml
set render-mode="Server"
(two locations) and then OnInitializedAsync()
will only be called once.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论