IHttpContextAccessor.HttpContext 在执行脱离 UseODataBatching 中间件并导致 IdentityServer 中出现 NullReferenceExp 后为 null
IHttpContextAccessor.HttpContext is null after execution falls out of the UseODataBatching middleware and causing NullReferenceExp in IdentityServer
我有一个简单的 .NET6 OData API,默认配置启用了批处理。
API 使用默认 VS 模板中的 IdentityServer 配置。
Program.cs
var builder = WebApplication.CreateBuilder(args);
if (builder.Configuration.GetSection("ConnectionStrings:Active").Value == "Postgres")
{
var connectionString = builder.Configuration.GetConnectionString("Postgres");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(connectionString));
}
else
{
var connectionString = builder.Configuration.GetConnectionString("SQLServer");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
}
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = "/login";
options.UserInteraction.LogoutUrl = "/logout";
})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
builder.Services.AddLocalization();
builder.Services.AddControllersWithViews(options =>
{
options.ModelBinderProviders.Insert(0, new CustomModelBinderProvider());
})
.AddOData(opt =>
{
var batchHandler = new DefaultODataBatchHandler();
batchHandler.MessageQuotas.MaxNestingDepth = 2;
batchHandler.MessageQuotas.MaxReceivedMessageSize = 100;
batchHandler.MessageQuotas.MaxOperationsPerChangeset = 10;
opt.AddRouteComponents("oapi",
new OdataModelBuilder().GetEDM(),
services => services.AddSingleton<ISearchBinder, ODataSearch>());
opt.AddRouteComponents("oapi_b",
new OdataModelBuilder().GetEDM(),
batchHandler);
opt.EnableQueryFeatures();
})
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
factory.Create(typeof(SharedResources));
})
.AddRazorRuntimeCompilation();
builder.Services.AddRazorPages();
builder.Services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());
builder.Services.AddHttpContextAccessor();
if (builder.Environment.IsDevelopment())
{
builder.Services.AddScoped<DummySeedService>();
}
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
app.UseWebAssemblyDebugging();
app.UseODataRouteDebug();
using (var scope = app.Services.CreateScope())
{
var seedService = scope.ServiceProvider.GetRequiredService<DummySeedService>();
await seedService.Seed();
}
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseODataBatching();
app.UseRouting();
app.UseMiddleware<ODataTestMiddleware>();
var supportedCultures = new[] { "en-US", "ar-SY" };
var localizationOptions = new RequestLocalizationOptions().SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.Run();
问题
发出$batch
请求后,当执行脱离UseODataBatching
中间件时,IHttpContextAccessor
中的HttpContext
属性变为null,并且这会在 IdentityServer
中间件中触发一个 NullReferenceException
,从而为所有请求返回 500
Requests
(来自邮递员):
{
"requests": [
{
"id": "{{$guid}}",
"url": "Patients(1)",
"method": "GET",
"headers": {
"content-type": "application/json"
}
},
{
"id": "{{$guid}}",
"url": "Patients(2)",
"method": "GET",
"headers": {
"content-type": "application/json"
}
}
]
}
Responses
:
{
"responses": [
{
"id": "8a4dac81-2662-472b-bef9-273401a53cfb",
"status": 500,
"headers": {}
},
{
"id": "933c6bbe-db67-4526-8199-2eedc176dc7b",
"status": 500,
"headers": {}
}
]
}
当删除 IdentityServer
中间件时,批处理请求通过并且 returns 200
两个请求都没有问题。
作为测试我写了一个测试中间件ODataTestMiddleware
public class ODataTestMiddleware
{
private readonly RequestDelegate requestDelegate;
public ODataTestMiddleware(RequestDelegate requestDelegate)
{
this.requestDelegate = requestDelegate;
}
public async Task InvokeAsync(HttpContext httpContext, IHttpContextAccessor contextAccessor)
{
await requestDelegate(httpContext);
}
}
而IHttpContextAccessor.HttpContext
在这里也是空的
我在 OData repo IHttpContextAccessor.HttpContext returns null when executing within an odata batch call, but i am using the ASP.NET Core 版本上看到了这个问题,所以我不知道两者在实现方面的区别。
有没有我可以尝试的解决方法?
谢谢你的时间。
我最终添加了一个手动重新填充 IHttpContextAccessor
的中间件。
public class ODataHttpContextFixer
{
private readonly RequestDelegate requestDelegate;
public ODataHttpContextFixer(RequestDelegate requestDelegate)
{
this.requestDelegate = requestDelegate;
}
public async Task InvokeAsync(HttpContext httpContext, IHttpContextAccessor contextAccessor)
{
contextAccessor.HttpContext ??= httpContext;
await requestDelegate(httpContext);
}
}
中间件应该放在ODataBatching
中间件之后。
我有一个简单的 .NET6 OData API,默认配置启用了批处理。 API 使用默认 VS 模板中的 IdentityServer 配置。
Program.cs
var builder = WebApplication.CreateBuilder(args);
if (builder.Configuration.GetSection("ConnectionStrings:Active").Value == "Postgres")
{
var connectionString = builder.Configuration.GetConnectionString("Postgres");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(connectionString));
}
else
{
var connectionString = builder.Configuration.GetConnectionString("SQLServer");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
}
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = "/login";
options.UserInteraction.LogoutUrl = "/logout";
})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
builder.Services.AddLocalization();
builder.Services.AddControllersWithViews(options =>
{
options.ModelBinderProviders.Insert(0, new CustomModelBinderProvider());
})
.AddOData(opt =>
{
var batchHandler = new DefaultODataBatchHandler();
batchHandler.MessageQuotas.MaxNestingDepth = 2;
batchHandler.MessageQuotas.MaxReceivedMessageSize = 100;
batchHandler.MessageQuotas.MaxOperationsPerChangeset = 10;
opt.AddRouteComponents("oapi",
new OdataModelBuilder().GetEDM(),
services => services.AddSingleton<ISearchBinder, ODataSearch>());
opt.AddRouteComponents("oapi_b",
new OdataModelBuilder().GetEDM(),
batchHandler);
opt.EnableQueryFeatures();
})
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
factory.Create(typeof(SharedResources));
})
.AddRazorRuntimeCompilation();
builder.Services.AddRazorPages();
builder.Services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());
builder.Services.AddHttpContextAccessor();
if (builder.Environment.IsDevelopment())
{
builder.Services.AddScoped<DummySeedService>();
}
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
app.UseWebAssemblyDebugging();
app.UseODataRouteDebug();
using (var scope = app.Services.CreateScope())
{
var seedService = scope.ServiceProvider.GetRequiredService<DummySeedService>();
await seedService.Seed();
}
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseODataBatching();
app.UseRouting();
app.UseMiddleware<ODataTestMiddleware>();
var supportedCultures = new[] { "en-US", "ar-SY" };
var localizationOptions = new RequestLocalizationOptions().SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.Run();
问题
发出$batch
请求后,当执行脱离UseODataBatching
中间件时,IHttpContextAccessor
中的HttpContext
属性变为null,并且这会在 IdentityServer
中间件中触发一个 NullReferenceException
,从而为所有请求返回 500
Requests
(来自邮递员):
{
"requests": [
{
"id": "{{$guid}}",
"url": "Patients(1)",
"method": "GET",
"headers": {
"content-type": "application/json"
}
},
{
"id": "{{$guid}}",
"url": "Patients(2)",
"method": "GET",
"headers": {
"content-type": "application/json"
}
}
]
}
Responses
:
{
"responses": [
{
"id": "8a4dac81-2662-472b-bef9-273401a53cfb",
"status": 500,
"headers": {}
},
{
"id": "933c6bbe-db67-4526-8199-2eedc176dc7b",
"status": 500,
"headers": {}
}
]
}
当删除 IdentityServer
中间件时,批处理请求通过并且 returns 200
两个请求都没有问题。
作为测试我写了一个测试中间件ODataTestMiddleware
public class ODataTestMiddleware
{
private readonly RequestDelegate requestDelegate;
public ODataTestMiddleware(RequestDelegate requestDelegate)
{
this.requestDelegate = requestDelegate;
}
public async Task InvokeAsync(HttpContext httpContext, IHttpContextAccessor contextAccessor)
{
await requestDelegate(httpContext);
}
}
而IHttpContextAccessor.HttpContext
在这里也是空的
我在 OData repo IHttpContextAccessor.HttpContext returns null when executing within an odata batch call, but i am using the ASP.NET Core 版本上看到了这个问题,所以我不知道两者在实现方面的区别。
有没有我可以尝试的解决方法? 谢谢你的时间。
我最终添加了一个手动重新填充 IHttpContextAccessor
的中间件。
public class ODataHttpContextFixer
{
private readonly RequestDelegate requestDelegate;
public ODataHttpContextFixer(RequestDelegate requestDelegate)
{
this.requestDelegate = requestDelegate;
}
public async Task InvokeAsync(HttpContext httpContext, IHttpContextAccessor contextAccessor)
{
contextAccessor.HttpContext ??= httpContext;
await requestDelegate(httpContext);
}
}
中间件应该放在ODataBatching
中间件之后。