Czy istnieje sposób na obsługę błędów ODATA Cov.NET?

Mam model modelu DimDateAvailable za pomocą jednej właściwości, głównym kluczem int DateId i wykonuję połączenie jak /data/DimDateAvailable?$select=test.

Inne połączenia Pracują zgodnie z oczekiwaniami i zwrotu, co jestem po - jest to celowe połączenie, aby wygenerować błąd, a to nie powiedzie się, ponieważ nie ma własności o nazwie Test na modelu. Odpowiedź wraca zgodnie z oczekiwaniami, jak więc: {"error":{"code":"","message":"The query specified in the URI is not valid. Could not find a property named 'test' on type 'DimDateAvailable'..., a następnie ślad stosu.

Ta odpowiedź jest w porządku, gdy env.IsDevelopment() jest true, ale nie chcę ujawniać śladu stosu, gdy nie jest w rozwoju.

Spojrzałem na zawijanie kodu w metodę regulatorów "{X0}} w trybie próbnym, ale myślę, że jest filtr akcji działa nad wynikami, dzięki czemu nigdy się nie zostaje wywołane. Z drugiej strony nie widzę, gdzie należy wstrzyknąć dowolne oprogramowanie pośrednie i / lub dodać wszystkie filtry, aby złapać błędy. Podejrzewam, że może zastąpić formated wyjściowy, aby osiągnąć to, czego chcę, ale nie widzę, jak.

Oto, co mam w tej chwili:

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

W telemetrii dbcontext.cs:

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

W Dimdateavailable.cs.

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

Mój kontroler:

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

Jest to w aplikacji internetowej ASP.NET 2 z Microsoft.aspnetCoreodata V7.0.1 i EntityFramework 6.2.0.

2
meataxe 24 lipiec 2018, 08:23

3 odpowiedzi

Najlepsza odpowiedź

Badanie sugestii Ihara prowadzi mnie w dół dziury królika i trafiłem do wkładania ODataOutputFormatter do opcji MVC, aby przechwycić {X1}} odpowiedzi i sformatować je.

Było interesujące, że context.Features trzymano instancję IExceptionHandlerFeature w app.UseExceptionHandler(), ale nie w ODataOutputFormatter. To nie było dość, co skłoniło mnie do stwarzania tego pytania w pierwszej kolejności, ale został rozwiązany przez tłumaczenie context.Object w ODataOutputFormatter, co jest coś, co widziałem, jak również w źródle Odata. Nie wiem, czy poniższe zmiany są dobrą praktyką w rdzeniu ASP.NET lub podczas korzystania z pakietu ASPNetCoreodata, ale robią to teraz, czego chcę.

Zmiany na starcie.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();
  });
}

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

Zmiany w kontrolerze:

[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
public IQueryable<DimDateAvailable> Get()
{
  return this.data.DimDateAvailable.AsQueryable();
}
1
meataxe 26 lipiec 2018, 23:38

Jeśli chcesz dostosować odpowiedzi, w tym dostosowanie odpowiedzi błędów, spróbuj użyć odataqueryoptions zamiast używać

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

Sprawdź kilka próbek w https://docs.Microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-Spnet-Web-Pi/ Pomocnicze-ODATA-Query-Options # Invoking-Query-Opcje - bezpośrednio

Pozwoliłoby Ci podbić błędy walidacyjne i zbudować odpowiedź niestandardową.

1
Ihar Yakimush 26 lipiec 2018, 13:18

Miałem ten problem w przeszłości i jedynym sposobem, w jaki dostałem tę pracę bez konieczności pisania oprogramowania pośredniego było jak:

Spróbuj tego:

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

Następnie błąd będzie wyglądał:

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

Mam nadzieję że to pomoże.

Dzięki

0
Ernest 29 sierpień 2019, 14:55