有没有办法处理asp.net核心odata错误

时间:2018-07-24 05:23:52

标签: asp.net-core odata

有没有办法处理asp.net核心odata错误?

我有一个具有一个属性,主键为WITH message AS ( SELECT 'ITEM1' AS item, 'Dear ${username}, you have changed your address to ${address}' AS content FROM dual UNION ALL SELECT 'ITEM2', 'Hi ${username}, thank you for attending this event.' FROM dual ) SELECT item,LISTAGG(content,',') WITHIN GROUP ( ORDER BY lvl ) FROM ( SELECT item, regexp_substr(content,'(\$\{.+?\})',1,level,NULL,1) as content, level as lvl FROM message CONNECT BY level <= regexp_count(content,'\$\{.+?\}') AND PRIOR item = item AND PRIOR sys_guid() IS NOT NULL ) GROUP BY item; 的模型类DimDateAvailable,并且像int DateId一样进行了呼叫。

其他调用按预期方式工作并返回我所需要的内容-这是故意生成错误的调用,并且失败,因为模型上没有名为test的属性。响应会按预期返回,例如:/data/DimDateAvailable?$select=test,后跟堆栈跟踪。

{"error":{"code":"","message":"The query specified in the URI is not valid. Could not find a property named 'test' on type 'DimDateAvailable'...env.IsDevelopment()时此响应很好,但我不想在未开发时公开堆栈跟踪。

我已经看过了将代码包装在控制器的true方法中的try-catch中,但是我认为有一个操作过滤器运行在结果上,因此永远不会被调用。另一方面,我看不到在哪里注入任何中间件和/或添加任何过滤器来捕获错误。我怀疑可能有一种方法可以覆盖输出格式化程序以实现我想要的功能,但是我看不到。

这是我目前的状态:

在Startup.cs中:

get

在TelemetryDbContext.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();
}

在DimDateAvailable.cs

public virtual DbSet<DimDateAvailable> DimDateAvailable { get; set; }

我的控制器:

public class DimDateAvailable
{
  [Key]
  public int DateId { get; set; }
}

这是在带有Microsoft.AspNetCoreOData v7.0.1和EntityFramework 6.2.0软件包的asp.net core 2 Web应用程序中。

3 个答案:

答案 0 :(得分:1)

如果要自定义响应,包括自定义错误响应,请尝试使用ODataQueryOptions而不是

[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]

https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#invoking-query-options-directly

检查一些样本

它将允许您缓存验证错误并建立自定义响应。

答案 1 :(得分:1)

研究Ihar的建议使我陷入困境,最终我在MVC选项中插入了import React from 'react'; const PhotoItem = ({photo}) => { return ( <div className='item item-`${this.key}`'> <div className='photo'> <img src={photo} /> </div> </div> ) }; export default PhotoItem; ,以拦截ODataOutputFormatter响应并重新格式化。

有趣的是,ODataPayloadKind.Errorcontext.Features中拥有IExceptionHandlerFeature的实例,但在app.UseExceptionHandler()中却没有。这种缺乏几乎是促使我首先提出这个问题的原因,但是通过在ODataOutputFormatter中翻译context.Object得以解决,这也是我在OData源代码中看到的。我不知道下面的更改是在asp.net内核中还是在使用AspNetCoreOData软件包时的良好做法,但它们现在可以满足我的要求。

更改为Startup.cs

ODataOutputFormatter

新类CustomODataOutputFormatter.cs和CommonExtensions.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();
  });
}

更改为控制器:

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;
  }
}

答案 2 :(得分:0)

我过去曾遇到过这个问题,而无需编写中间件就可以正常工作的唯一方法是:

尝试一下:

    catch (ODataException ex)
    {
        HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;//This line is important, if not it will return 500 Internal Server Error.
        return BadRequest(ex.Message);//Just respond back the actual error which is 100% correct.
    }

然后错误将显示为:

{
    "@odata.context": "http://yourendpoint.com$metadata#Edm.String",
    "value": "The property 'test' cannot be used in the $select query option."
}

希望这会有所帮助。

谢谢