【问题标题】:Is there a way to handle asp.net core odata errors有没有办法处理 asp.net core odata 错误
【发布时间】:2018-12-31 15:02:44
【问题描述】:

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

我有一个模型类DimDateAvailable,它有一个属性,主键int DateId,我拨打/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'...,后跟堆栈跟踪。

env.IsDevelopment()true 时,此响应很好,但我不想在未开发时公开堆栈跟踪。

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

这是我目前拥有的:

在 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 中:

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

在 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 应用程序。

【问题讨论】:

    标签: asp.net-core odata


    【解决方案1】:

    调查 Ihar 的建议让我陷入了困境,我最终在 MVC 选项中插入了一个 ODataOutputFormatter 以拦截 ODataPayloadKind.Error 响应并重新格式化它们。

    有趣的是,context.Featuresapp.UseExceptionHandler() 中拥有IExceptionHandlerFeature 的实例,但在ODataOutputFormatter 中却没有。这种缺乏几乎是促使我首先提出这个问题的原因,但通过在ODataOutputFormatter 中翻译context.Object 来解决,这也是我在OData 源中看到的。我不知道下面的更改是在 asp.net core 中还是在使用 AspNetCoreOData 包时的好习惯,但它们现在可以满足我的需求。

    对 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

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

    【讨论】:

      【解决方案2】:

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

      试试这个:

          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."
      }
      

      希望这会有所帮助。

      谢谢

      【讨论】:

      • 这看起来很有希望,但当我把它放在我的 ASP.NET Core 操作方法中时,最终并没有发现错误。看起来好像错误发生在操作执行之前。我认为由于对IQueryable 的评估延迟,它可能会在执行操作之后发生,但它似乎发生在路由之前。
      • 嗨@flipdoubt,您可以尝试在自定义中间件中使用此逻辑,然后尝试使用中间件的调用顺序来查看您得到的结果。
      • 我把它放在一个自定义的EnableQueryAttribute 中以达到预期的效果。详情stackoverflow.com/a/63176502/470
      • 您会将这个自定义中间件放在配置中间件列表的哪个位置?在身份验证和路由之间?
      • 在身份验证后试一试,这样您就可以防止自定义中间件在身份验证发生之前处理逻辑。
      【解决方案3】:

      如果您想要自定义响应,包括自定义错误响应,请尝试使用 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查看一些样本

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

      【讨论】:

        猜你喜欢
        • 2019-09-22
        • 1970-01-01
        • 2021-02-14
        • 2013-10-23
        • 2015-09-19
        • 1970-01-01
        • 2020-08-23
        • 2015-04-12
        • 1970-01-01
        相关资源
        最近更新 更多