服务器控制器中的 Blazor 客户端托管构造函数依赖注入
Blazor client hosted constructor dependency injection in server controller
我在 Blazor 客户端托管的应用程序中执行 API 请求时收到 HTTP 500。如果我省略控制器参数,请求就会成功,但是,一旦我在控制器中设置了一个参数,我就会得到一个 HTTP 500。
我已经检查并测试了容器,并且接口已注册。只要我不在服务器控制器构造函数中提供参数,客户端就可以工作。
我需要做什么才能在 Blazor 应用程序的服务器控制器内进行构造函数注入?
在撰写本文时,我正在使用 Visual Studio 2019 16.2 预览版 4 & .NET Core 3 预览版。
Startup.cs
public class Startup
{
private readonly IDependency _dependency;
public Startup()
{
_dependency = new Dependency();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
_dependency
.SetServiceCollection(services)
.SetServiceProvider(services.BuildServiceProvider());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
});
_dependency
.SetWebHostEnvironment(env)
.ConfigureDependencies()
.ConfigureDatabases();
}
}
Dependency.cs
public class Dependency : IDependency
{
private IServiceCollection _services;
private IServiceProvider _provider;
private IWebHostEnvironment _environment;
public IDependency SetServiceCollection(IServiceCollection services)
{
if (_services != null)
throw new AlreadyConfiguredException($"{nameof(services)} is already configured.");
_services = services;
return this;
}
public IDependency SetServiceProvider(IServiceProvider provider)
{
if (_provider != null)
throw new AlreadyConfiguredException($"{nameof(provider)} is already configured.");
_provider = provider;
return this;
}
public IDependency SetWebHostEnvironment(IWebHostEnvironment environment)
{
if (_environment != null)
throw new AlreadyConfiguredException($"{nameof(environment)} is already configured.");
_environment = environment;
return this;
}
public IDependency ConfigureDatabases()
{
var builder = new ConfigurationBuilder()
.SetBasePath(_environment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{_environment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
var config = builder.Build();
var database = config.GetConnectionString("MenuDatabase");
MenuStartup.Configure(_services, database);
OpeningTimeStartup.Configure(_services, database);
return this;
}
public IDependency ConfigureDependencies()
{
// Bounded Contexts
// Domain Models
_services.AddTransient<IMenu, Menu.Menu>();
// Infrastructure
_services.AddTransient(typeof(IRepository<,>), typeof(Repository<,>));
_services.AddTransient<IFactory, Factory>();
_services.AddSingleton<ICompositeCache, MemoryCompositeCache>();
_services.AddTransient<IDataContextFactory, DataContextFactory>();
_services.AddTransient<IMenuRepository, MenuRepository>();
return this;
}
}
我Dependency.cs
public interface IDependency
{
IDependency SetServiceCollection(IServiceCollection services);
IDependency SetServiceProvider(IServiceProvider buildServiceProvider);
IDependency ConfigureDependencies();
IDependency SetWebHostEnvironment(IWebHostEnvironment environment);
IDependency ConfigureDatabases();
}
MenuDataController.cs
[Route("api/[controller]")]
public class MenuDataController : Controller
{
private readonly IMenu _menu;
public MenuDataController(IMenu menu)
{
_menu = menu;
}
[HttpGet("[action]")]
public IEnumerable<SpiceViewModel> Spices()
{
return new Spice[] { };
}
}
Spices.razor
@inject HttpClient Http
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2093904">brief survey</a>
</span>
and tell us what you think.
@if (spices == null)
{
<p><em>Loading...</em></p>
}
else
{
<div class="form-group">
<label for="spicesSelect">Select list:</label>
<select class="form-control" id="spicesSelect">
<option>None</option>
@foreach (var spice in spices)
{
<option value="@spice.Id">@spice.Name</option>
}
</select>
</div>
}
</div>
@code {
[Parameter] string Title { get; set; }
SpiceViewModel[] spices;
protected override async Task OnInitAsync()
{
spices = await Http.GetJsonAsync<SpiceViewModel[]>("api/MenuData/Spices");
}
}
我戴眼镜升级了眼睛,原来Controller里面有多个构造函数。按照 Microsoft 的建议打开服务器日志记录很有帮助。
- 我展示的示例 Microsoft 有多个构造函数和一个参数。 DI不知道选哪个
- DI 不会自动激活未在容器中注册的实例类型。
对于像我这样搜索“blazor controller dependency injection”并发现这个问题的人,这就是我得到的。
原始 Blazor client-hosted 项目模板会在您完成新项目创建后显示以下代码 (WeatherForecastController.cs)。
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
this.logger = logger;
}
为了使用您自己创建的数据库上下文(或任何其他依赖注入),您只需要修改原始构造函数而不是创建一个新构造函数(只保留一个构造函数)。
public WeatherForecastController(ILogger<WeatherForecastController> logger, MyDbContext context)
{
this._context = context;
this.logger = logger;
}
我在 Blazor 客户端托管的应用程序中执行 API 请求时收到 HTTP 500。如果我省略控制器参数,请求就会成功,但是,一旦我在控制器中设置了一个参数,我就会得到一个 HTTP 500。
我已经检查并测试了容器,并且接口已注册。只要我不在服务器控制器构造函数中提供参数,客户端就可以工作。
我需要做什么才能在 Blazor 应用程序的服务器控制器内进行构造函数注入?
在撰写本文时,我正在使用 Visual Studio 2019 16.2 预览版 4 & .NET Core 3 预览版。
Startup.cs
public class Startup
{
private readonly IDependency _dependency;
public Startup()
{
_dependency = new Dependency();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
_dependency
.SetServiceCollection(services)
.SetServiceProvider(services.BuildServiceProvider());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
});
_dependency
.SetWebHostEnvironment(env)
.ConfigureDependencies()
.ConfigureDatabases();
}
}
Dependency.cs
public class Dependency : IDependency
{
private IServiceCollection _services;
private IServiceProvider _provider;
private IWebHostEnvironment _environment;
public IDependency SetServiceCollection(IServiceCollection services)
{
if (_services != null)
throw new AlreadyConfiguredException($"{nameof(services)} is already configured.");
_services = services;
return this;
}
public IDependency SetServiceProvider(IServiceProvider provider)
{
if (_provider != null)
throw new AlreadyConfiguredException($"{nameof(provider)} is already configured.");
_provider = provider;
return this;
}
public IDependency SetWebHostEnvironment(IWebHostEnvironment environment)
{
if (_environment != null)
throw new AlreadyConfiguredException($"{nameof(environment)} is already configured.");
_environment = environment;
return this;
}
public IDependency ConfigureDatabases()
{
var builder = new ConfigurationBuilder()
.SetBasePath(_environment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{_environment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
var config = builder.Build();
var database = config.GetConnectionString("MenuDatabase");
MenuStartup.Configure(_services, database);
OpeningTimeStartup.Configure(_services, database);
return this;
}
public IDependency ConfigureDependencies()
{
// Bounded Contexts
// Domain Models
_services.AddTransient<IMenu, Menu.Menu>();
// Infrastructure
_services.AddTransient(typeof(IRepository<,>), typeof(Repository<,>));
_services.AddTransient<IFactory, Factory>();
_services.AddSingleton<ICompositeCache, MemoryCompositeCache>();
_services.AddTransient<IDataContextFactory, DataContextFactory>();
_services.AddTransient<IMenuRepository, MenuRepository>();
return this;
}
}
我Dependency.cs
public interface IDependency
{
IDependency SetServiceCollection(IServiceCollection services);
IDependency SetServiceProvider(IServiceProvider buildServiceProvider);
IDependency ConfigureDependencies();
IDependency SetWebHostEnvironment(IWebHostEnvironment environment);
IDependency ConfigureDatabases();
}
MenuDataController.cs
[Route("api/[controller]")]
public class MenuDataController : Controller
{
private readonly IMenu _menu;
public MenuDataController(IMenu menu)
{
_menu = menu;
}
[HttpGet("[action]")]
public IEnumerable<SpiceViewModel> Spices()
{
return new Spice[] { };
}
}
Spices.razor
@inject HttpClient Http
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2093904">brief survey</a>
</span>
and tell us what you think.
@if (spices == null)
{
<p><em>Loading...</em></p>
}
else
{
<div class="form-group">
<label for="spicesSelect">Select list:</label>
<select class="form-control" id="spicesSelect">
<option>None</option>
@foreach (var spice in spices)
{
<option value="@spice.Id">@spice.Name</option>
}
</select>
</div>
}
</div>
@code {
[Parameter] string Title { get; set; }
SpiceViewModel[] spices;
protected override async Task OnInitAsync()
{
spices = await Http.GetJsonAsync<SpiceViewModel[]>("api/MenuData/Spices");
}
}
我戴眼镜升级了眼睛,原来Controller里面有多个构造函数。按照 Microsoft 的建议打开服务器日志记录很有帮助。
- 我展示的示例 Microsoft 有多个构造函数和一个参数。 DI不知道选哪个
- DI 不会自动激活未在容器中注册的实例类型。
对于像我这样搜索“blazor controller dependency injection”并发现这个问题的人,这就是我得到的。
原始 Blazor client-hosted 项目模板会在您完成新项目创建后显示以下代码 (WeatherForecastController.cs)。
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
this.logger = logger;
}
为了使用您自己创建的数据库上下文(或任何其他依赖注入),您只需要修改原始构造函数而不是创建一个新构造函数(只保留一个构造函数)。
public WeatherForecastController(ILogger<WeatherForecastController> logger, MyDbContext context)
{
this._context = context;
this.logger = logger;
}