Blazor Server App 中的 Hangfire 作业中未注入服务
Service not being injected within Hangfire job in Blazor Server App
免责声明:我是 C#、ASP.NET 核心和依赖注入领域的新手。我从默认模板创建了一个简单的 Blazor 服务器应用程序,它搭建了一个模拟天气服务并在 table 中显示从中获取的数据。现在我希望 table 每五秒自动更新一次,为此我使用了 Hangfire.AspNetCore
和 Hangfire.MemoryStorage
包。所以我稍微修改了 FetchData.razor
组件,使其看起来像这样:
@page "/fetchdata"
@using WeatherTest.Data
@using Hangfire
@inject WeatherForecastService ForecastService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[] forecasts;
public async Task UpdateForecasts()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
BackgroundJob.Schedule(() => UpdateForecasts(), TimeSpan.FromSeconds(5));
}
protected override async Task OnInitializedAsync()
{
await UpdateForecasts();
}
}
这是天气服务:
using System;
using System.Linq;
using System.Threading.Tasks;
namespace WeatherTest.Data
{
public class WeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
var rng = new Random();
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray());
}
}
}
以及服务配置:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddHangfire(c => c.UseMemoryStorage());
services.AddHangfireServer();
}
问题是 UpdateForecasts()
在 OnInitializedAsync()
调用时成功,但在 Hangfire 调用时失败,在 forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
处抛出以下异常:
System.NullReferenceException: 'Object reference not set to an
instance of an object.'
WeatherTest.Pages.FetchData.ForecastService.get returned null.
在我看来,由于 Hangfire worker 在另一个线程中运行,因此 WeatherForecastService
不会被注入。我对吗?是否可以从多个线程使用单例,或者每个线程都应该有自己的服务实例?最后,我该如何解决?
这是使用标准 Timer
的 FetchData
组件。
几点:
- 我添加了一个
Task.Delay
来模拟慢速连接并显示页面正在刷新。
StateHasChanged
像这样包装 await this.InvokeAsync(StateHasChanged)
以确保它在 UI 上下文线程上获得 运行。
- 实施
IDisposable
因为我们有定时器事件处理程序要断开连接。
@page "/fetchdata"
@implements IDisposable
<PageTitle>Weather forecast</PageTitle>
@using Whosebug.Server.Data
@inject WeatherForecastService ForecastService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
<div class="p-2">
<button class="btn @this.btnCss" @onclick="ToggleTimer">@this.btnText</button>
</div>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private System.Timers.Timer aTimer = new System.Timers.Timer(2000);
private WeatherForecast[]? forecasts;
private string btnCss => aTimer.Enabled ? "btn-danger" : "btn-success";
private string btnText => aTimer.Enabled ? "Stop" : "Start";
protected override async Task OnInitializedAsync()
{
aTimer.Elapsed += TimerElapsed;
aTimer.AutoReset = true;
aTimer.Enabled = true;
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
public async void TimerElapsed(Object? sender, System.Timers.ElapsedEventArgs e)
{
forecasts = null;
await this.InvokeAsync(StateHasChanged);
await Task.Delay(500);
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
await this.InvokeAsync(StateHasChanged);
}
public void ToggleTimer()
{
aTimer.Enabled = !aTimer.Enabled;
this.InvokeAsync(StateHasChanged);
}
public void Dispose()
=> aTimer!.Elapsed -= TimerElapsed;
}
免责声明:我是 C#、ASP.NET 核心和依赖注入领域的新手。我从默认模板创建了一个简单的 Blazor 服务器应用程序,它搭建了一个模拟天气服务并在 table 中显示从中获取的数据。现在我希望 table 每五秒自动更新一次,为此我使用了 Hangfire.AspNetCore
和 Hangfire.MemoryStorage
包。所以我稍微修改了 FetchData.razor
组件,使其看起来像这样:
@page "/fetchdata"
@using WeatherTest.Data
@using Hangfire
@inject WeatherForecastService ForecastService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[] forecasts;
public async Task UpdateForecasts()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
BackgroundJob.Schedule(() => UpdateForecasts(), TimeSpan.FromSeconds(5));
}
protected override async Task OnInitializedAsync()
{
await UpdateForecasts();
}
}
这是天气服务:
using System;
using System.Linq;
using System.Threading.Tasks;
namespace WeatherTest.Data
{
public class WeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
var rng = new Random();
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray());
}
}
}
以及服务配置:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddHangfire(c => c.UseMemoryStorage());
services.AddHangfireServer();
}
问题是 UpdateForecasts()
在 OnInitializedAsync()
调用时成功,但在 Hangfire 调用时失败,在 forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
处抛出以下异常:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
WeatherTest.Pages.FetchData.ForecastService.get returned null.
在我看来,由于 Hangfire worker 在另一个线程中运行,因此 WeatherForecastService
不会被注入。我对吗?是否可以从多个线程使用单例,或者每个线程都应该有自己的服务实例?最后,我该如何解决?
这是使用标准 Timer
的 FetchData
组件。
几点:
- 我添加了一个
Task.Delay
来模拟慢速连接并显示页面正在刷新。 StateHasChanged
像这样包装await this.InvokeAsync(StateHasChanged)
以确保它在 UI 上下文线程上获得 运行。- 实施
IDisposable
因为我们有定时器事件处理程序要断开连接。
@page "/fetchdata"
@implements IDisposable
<PageTitle>Weather forecast</PageTitle>
@using Whosebug.Server.Data
@inject WeatherForecastService ForecastService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
<div class="p-2">
<button class="btn @this.btnCss" @onclick="ToggleTimer">@this.btnText</button>
</div>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private System.Timers.Timer aTimer = new System.Timers.Timer(2000);
private WeatherForecast[]? forecasts;
private string btnCss => aTimer.Enabled ? "btn-danger" : "btn-success";
private string btnText => aTimer.Enabled ? "Stop" : "Start";
protected override async Task OnInitializedAsync()
{
aTimer.Elapsed += TimerElapsed;
aTimer.AutoReset = true;
aTimer.Enabled = true;
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
public async void TimerElapsed(Object? sender, System.Timers.ElapsedEventArgs e)
{
forecasts = null;
await this.InvokeAsync(StateHasChanged);
await Task.Delay(500);
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
await this.InvokeAsync(StateHasChanged);
}
public void ToggleTimer()
{
aTimer.Enabled = !aTimer.Enabled;
this.InvokeAsync(StateHasChanged);
}
public void Dispose()
=> aTimer!.Elapsed -= TimerElapsed;
}