Маршрут атрибутов OData не работает должным образом в ASP.NET Core 5 с Microsoft.AspNetCore.OData v8.0.0-preview2
Я работаю над API ASP.NET Core 5 с Entity Framework Core 5, и я использую Microsoft.AspNetCore.Odata v8.0.0-preview2
Я просмотрел блоги Сэма Сюй; Маршрутизация в ASP.NET Core OData 8.0 Preview и ASP.NET Core OData 8.0 Preview для.NET 5
Я выполняю интеграцию с существующей базой данных SQL Server, схему которой я не контролирую, поэтому мне приходится работать с тем, что предоставляется.
В базе данных есть таблица "Организации" и таблица "Местоположение".
Я установил OrganizationsController и OrganizationLocationsController.
Маршрутизация моего OrganizationsController, похоже, работает должным образом, так как я могу выполнить GET, чтобы...
https://{{url}}/odata-v1/organizations
и получить список всех организаций в таблице, и я могу ПОЛУЧИТЬ, чтобы...
https://{{url}}/odata-v1/organizations/'1000'
и вернуть указанную запись организации.
Теперь таблица OrganizationLocations может иметь несколько расположений для данного OrganizationID. Итак, что я хочу сделать, так это получить...
https://{{url}}/odata-v1/organizations('1000')/locations
или, желательно...
https://{{url}}/odata-v1/organizations/'1000'/locations
и вернуть список всех записей OrganizationLocation для указанного OrganizationID.
Однако, когда я пытаюсь выполнить этот вызов в Postman, он возвращает 404 NOT FOUND.
Если я установил точку останова в Startup.cs -> Configure method, в разделе app.Use(next => context => ... в строке
if (endpoint == null)
и проверьте переменную конечной точки, она равна нулю.
Почему значение конечной точки равно нулю? Что мне не хватает?
Вот мой Startup.cs
using System;
using System.Collections;
using System.Collections.Generic;
using CDR.API.Azure.Extensions;
using CDR.API.Data;
using CDR.API.Middleware;
using EII.CDR.Models.ERP.M1;
using MediatR;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.OData;
using Microsoft.AspNetCore.OData.Routing;
using Microsoft.AspNetCore.OData.Routing.Template;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using Microsoft.OpenApi.Models;
namespace CDR
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Add Azure AD OAUTH2.0 Authentication Services
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
// Set up database access
ConfigureDatabase(services);
services.AddHttpContextAccessor();
services.AddRouting();
services.AddControllers();
services.AddOData(
option => option.AddModel("odata-v1", GetV1EdmModel())
.Select()
.Expand()
.Filter()
.OrderBy()
.Count());
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(UserIdPipe<,>));
services.AddMediatR(typeof(Startup).Assembly);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting(); // UseRouting should appear before any other middleware.
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
// Middleware for testing endpoint routes
app.Use(next => context =>
{
var endpoint = context.GetEndpoint();
if (endpoint == null)
{
return next(context);
}
IEnumerable templates;
IODataRoutingMetadata metadata = endpoint.Metadata.GetMetadata<IODataRoutingMetadata>();
if (metadata != null)
{
templates = metadata.Template.GetTemplates();
}
return next(context); // put a breakpoint here when testing.
});
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
protected virtual void ConfigureDatabase(IServiceCollection services)
{
var sqlTransientErrors = new List<int>() { 10928, 10929, 10053, 10054, 10060, 40197, 40540, 40613, 40143, 64 };
services.AddDbContext<M1Context>(options =>
{
options.UseSqlServer(
Configuration.GetConnectionString("M1DbConnectionString"),
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), sqlTransientErrors);
sqlOptions.CommandTimeout(Convert.ToInt32(Configuration["DbSettings:M1DbCommandTimeout"]));
});
});
}
private static IEdmModel GetV1EdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Organization>("Organizations");
builder.EntitySet<OrganizationLocation>("OrganizationLocations");
builder.EntitySet<OrganizationContact>("OrganizationContacts");
return builder.GetEdmModel();
}
}
}
Вот мой OrganizationsController;
using System;
using System.Linq;
using CDR.API.Data;
using CDR.Models.ERP.M1;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Results;
using Microsoft.AspNetCore.OData.Routing.Attributes;
using Microsoft.AspNetCore.OData.Routing.Controllers;
namespace CDR.API.Controllers.V1.ODataControllers
{
[Authorize]
[ODataRoutePrefix("organizations")]
public class OrganizationsController : ODataController
{
private readonly M1Context context;
public OrganizationsController(M1Context context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
public IQueryable<Organization> GetOrganizations()
{
return context.Organizations;
}
[ODataRoute("{id}")]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Select)]
public SingleResult<Organization> GetOrganizationById([FromRoute] string id)
{
return new SingleResult<Organization>(context.Organizations.Where(o => o.OrganizationId == id));
}
}
}
Вот мой контроллер OrganizationLocations;
using System;
using System.Linq;
using CDR.API.Data;
using CDR.Models.ERP.M1;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Results;
using Microsoft.AspNetCore.OData.Routing.Attributes;
using Microsoft.AspNetCore.OData.Routing.Controllers;
namespace CDR.API.Controllers.V1.ODataControllers
{
[Authorize]
[ODataRoutePrefix("organizations({id})")]
public class OrganizationLocationsController : ODataController
{
private readonly M1Context context;
public OrganizationLocationsController(M1Context context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
[ODataRoute("locations")]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
public IQueryable<OrganizationLocation> GetOrganizationLocations(string id)
{
return context.OrganizationLocations.Where(ol => ol.OrganizationId == id);
}
[ODataRoute("locations({locationId})")]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Select)]
public SingleResult<OrganizationLocation> GetOrganizationLocationById(string id, string locationId)
{
return new SingleResult<OrganizationLocation>(context.OrganizationLocations.Where(ol => ol.OrganizationId == id && ol.LocationId == locationId));
}
}
}