有没有一种方法来处理asp.net核心odata错误 [英] Is there a way to handle asp.net core odata errors
问题描述
有没有办法处理asp.net核心odata错误?
Is there a way to handle asp.net core odata errors?
我有一个具有一个属性的模型类DimDateAvailable
,它的主键为int DateId
,并且我像/data/DimDateAvailable?$select=test
那样进行了呼叫.
I have a model class DimDateAvailable
with one property, a primary key of int DateId
, and I make a call like /data/DimDateAvailable?$select=test
.
其他调用按预期方式工作并返回我所需要的内容-这是故意生成错误的调用,但失败的原因是模型上没有名为test的属性.响应将按预期返回,如下所示:{"error":{"code":"","message":"The query specified in the URI is not valid. Could not find a property named 'test' on type 'DimDateAvailable'...
,然后是堆栈跟踪.
Other calls work as expected and return what I'm after - this is a deliberate call to generate a fault, and it fails because there is no property named test on the model. The response comes back as expected, like so: {"error":{"code":"","message":"The query specified in the URI is not valid. Could not find a property named 'test' on type 'DimDateAvailable'...
followed by a stack trace.
当env.IsDevelopment()
为true
时,此响应很好,但我不想在未开发时公开堆栈跟踪.
This response is fine when env.IsDevelopment()
is true
but I don't want to expose the stack trace when not in development.
我已经看过将代码包装在控制器的get
方法中的try-catch中,但是我认为有一个动作过滤器运行在结果上,因此它永远不会被调用.另一方面,我看不到在哪里注入任何中间件和/或添加任何过滤器来捕获错误.我怀疑可能有一种方法可以覆盖输出格式化程序以实现我想要的功能,但我看不到.
I've looked at wrapping the code in the controllers' get
method in a try-catch, but I think there's an action filter running over the results so it never gets called. On the other hand, I can't see where to inject any middleware and/or add any filters to catch errors. I suspect there might be a way to override an output formatter to achieve what I want but I can't see how.
这是我现在拥有的:
在Startup.cs中:
In Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TelemetryDbContext>();
services.AddOData();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routeBuilder =>
{
routeBuilder.MapODataServiceRoute("odata", "data", GetEdmModel());
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
// insert special bits for e.g. custom MLE here
routeBuilder.EnableDependencyInjection();
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<DimDateAvailable>("DimDateAvailable");
return builder.GetEdmModel();
}
在TelemetryDbContext.cs中:
In TelemetryDbContext.cs:
public virtual DbSet<DimDateAvailable> DimDateAvailable { get; set; }
在DimDateAvailable.cs
In DimDateAvailable.cs
public class DimDateAvailable
{
[Key]
public int DateId { get; set; }
}
我的控制器:
public class DimDateAvailableController : ODataController
{
private readonly TelemetryDbContext data;
public DimDateAvailableController(TelemetryDbContext data)
{
this.data = data;
}
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
public IActionResult Get()
{
return Ok(this.data.DimDateAvailable.AsQueryable());
}
}
这是在带有Microsoft.AspNetCoreOData v7.0.1和EntityFramework 6.2.0软件包的asp.net core 2 Web应用程序中.
This is in an asp.net core 2 web app with the Microsoft.AspNetCoreOData v7.0.1 and EntityFramework 6.2.0 packages.
推荐答案
研究Ihar的建议使我无所适从,最后我在MVC选项中插入了ODataOutputFormatter
来拦截ODataPayloadKind.Error
响应并重新格式化.
Investigating Ihar's suggestion lead me down the rabbit hole, and I ended up inserting an ODataOutputFormatter
into the MVC options to intercept ODataPayloadKind.Error
responses and reformat them.
有趣的是,看到context.Features
在app.UseExceptionHandler()
中保存了IExceptionHandlerFeature
的实例,但在ODataOutputFormatter
中却没有.这种缺乏几乎是促使我首先提出这个问题的原因,但通过在ODataOutputFormatter
中翻译context.Object
得以解决,这也是我在OData源代码中看到的.我不知道下面的更改是在asp.net内核中还是在使用AspNetCoreOData软件包时的良好做法,但它们现在可以满足我的要求.
It was interesting to see that context.Features
held an instance of IExceptionHandlerFeature
in app.UseExceptionHandler()
but not in the ODataOutputFormatter
. That lack was pretty much what prompted me to pose this question in the first place, but was solved by translating the context.Object
in the ODataOutputFormatter
which is something I saw done in the OData source as well. I don't know if the changes below are good practice in asp.net core or when using the AspNetCoreOData package, but they do what I want for now.
更改为Startup.cs
Changes to Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TelemetryDbContext>();
services.AddOData();
services.AddMvc(options =>
{
options.OutputFormatters.Insert(0, new CustomODataOutputFormatter(this.Environment.IsDevelopment()));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Added this to catch errors in my own code and return them to the client as ODataErrors
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Use(async (context, next) =>
{
var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
if (error?.Error != null)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var response = error.Error.CreateODataError(!env.IsDevelopment());
await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
}
// when no error, do next.
else await next();
});
});
app.UseMvc(routeBuilder =>
{
routeBuilder.MapODataServiceRoute("odata", "data", GetEdmModel());
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
// insert special bits for e.g. custom MLE here
routeBuilder.EnableDependencyInjection();
});
}
新类CustomODataOutputFormatter.cs和CommonExtensions.cs
New classes CustomODataOutputFormatter.cs and CommonExtensions.cs
public class CustomODataOutputFormatter : ODataOutputFormatter
{
private readonly JsonSerializer serializer;
private readonly bool isDevelopment;
public CustomODataOutputFormatter(bool isDevelopment)
: base(new[] { ODataPayloadKind.Error })
{
this.serializer = new JsonSerializer { ContractResolver = new CamelCasePropertyNamesContractResolver() };
this.isDevelopment = isDevelopment;
this.SupportedMediaTypes.Add("application/json");
this.SupportedEncodings.Add(new UTF8Encoding());
}
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
if (!(context.Object is SerializableError serializableError))
{
return base.WriteResponseBodyAsync(context, selectedEncoding);
}
var error = serializableError.CreateODataError(this.isDevelopment);
using (var writer = new StreamWriter(context.HttpContext.Response.Body))
{
this.serializer.Serialize(writer, error);
return writer.FlushAsync();
}
}
}
public static class CommonExtensions
{
public const string DefaultODataErrorMessage = "A server error occurred.";
public static ODataError CreateODataError(this SerializableError serializableError, bool isDevelopment)
{
// ReSharper disable once InvokeAsExtensionMethod
var convertedError = SerializableErrorExtensions.CreateODataError(serializableError);
var error = new ODataError();
if (isDevelopment)
{
error = convertedError;
}
else
{
// Sanitise the exposed data when in release mode.
// We do not want to give the public access to stack traces, etc!
error.Message = DefaultODataErrorMessage;
error.Details = new[] { new ODataErrorDetail { Message = convertedError.Message } };
}
return error;
}
public static ODataError CreateODataError(this Exception ex, bool isDevelopment)
{
var error = new ODataError();
if (isDevelopment)
{
error.Message = ex.Message;
error.InnerError = new ODataInnerError(ex);
}
else
{
error.Message = DefaultODataErrorMessage;
error.Details = new[] { new ODataErrorDetail { Message = ex.Message } };
}
return error;
}
}
更改为控制器:
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
public IQueryable<DimDateAvailable> Get()
{
return this.data.DimDateAvailable.AsQueryable();
}
这篇关于有没有一种方法来处理asp.net核心odata错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!