Динамическая маршрутизация в BaseUrl в Asp.Net Core OData 4.0

2

В настоящее время я разрабатываю OData Api для приложения С# Asp.Net Core.

Чтобы соответствовать спецификациям нашего API, URL должен соответствовать нашей мультитенантной архитектуре: https://website.com/api/tenants/{tenantId}/odata/

Поскольку в OData 4.0 нет спецификации для реализации динамического базового URL-адреса, я реализовал следующий обходной путь: используйте промежуточное программное обеспечение для замены динамического tenantId в контексте HTTP статической строкой "tenantId". Теперь мне нужно найти способ изменить/манипулировать метаданными OData, чтобы отменить этот обходной путь в ответе.

Пример реализации

Starup.cs:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    private IConfiguration Configuration { get; }


    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDependencies(Configuration);
        services.AddDbContext<DBContext>();
        services.AddOData();
        services.AddODataQueryFilter();
        services.AddAutoMapper();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }


    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // Custom Workaround Middleware
        app.Use(async (context, next) =>
        {
            // TGis Method parses the tenant id from the Request.Path, replaces it and wries it to the context.Items to maintain the information for later
            (Microsoft.AspNetCore.Http.HttpContext contextwTid, System.Guid tenantGuid) = ODataHelper.ParseTenantIDToContext(context);
            context = contextwTid;
            await next.Invoke();
        });

        app.UseMvc(b =>
        {
            b.Select().Filter().OrderBy().MaxTop(100).Count();
            b.MapODataServiceRoute(
                routeName: "odata",
                routePrefix: "api/tenants/tenantId/odata",
                model: ODataHelper.GetEdmModel());
        });

    }

ODataHelper:

  ...
  public static (Microsoft.AspNetCore.Http.HttpContext, Guid) ParseTenantIDToContext(Microsoft.AspNetCore.Http.HttpContext context)
    {
        System.Guid tenantGuid = System.Guid.Empty;
        if (context.Request.Path.ToString().Split('/').Length > 3 && context.Request.Path.ToString().ToLower().Contains("odata"))
        {
            bool isValidGUID = System.Guid.TryParse(context.Request.Path.ToString().Split('/')[3], result: out tenantGuid);
            if (isValidGUID)
                context.Request.Path = context.Request.Path.Value.Replace(context.Request.Path.ToString().Split('/')[3], "tenantId");
            context.Items["tenantId"] = tenantGuid.ToString();
        }
        return (context, tenantGuid);
    }
  ...

Пример контроллера:

public class ClientsController : ODataController
{
    private readonly DBService<Client> _service;

    public ClientsController(DBService<Client> service)
    {
        _service = service;
    }

    [HttpGet]
    [EnableQuery]
    [ODataRoute("Clients")]
    public async Task<IEnumerable<Client>> Get(
        ODataQueryOptions<Client> options)
    {
        System.Guid tenantId = ODataHelper.GetTenantIDFromContext(this.HttpContext);
        IQueryable res = await _service.Get(
            tenantId, 
            AuthorizationHelper.GetSubjectId(tenantId, User), 
            AuthorizationHelper.GetAllowedUserRoles(RoleType.Reporting), 
            options, 
            null);
        return new List<Client>(res.Cast<Client>());
    }
}

Вопросы:

  1. Есть ли лучший способ реализовать динамическую базовую маршрутизацию в OData с Asp.Net Core?
  2. Есть ли способ манипулировать запросом или метаданными OData. Подробно ответ должен отображать исходный URL с динамическим tenantId в "@OData.context" и (на будущее) в метаданных подкачки OData.

Исследования/поиск в Google до сих пор:

Теги:
asp.net-mvc

1 ответ

0
Лучший ответ

РЕДАКТИРОВАТЬ 2: Иногда вы думаете, что так сложно, что вы упускаете очевидное. Решение для динамической маршрутизации с OData:

Startup.cs

app.UseMvc(b =>
        {
            b.Select().Filter().OrderBy().MaxTop(100).Count();
            b.MapODataServiceRoute(
                routeName: "odata",
                routePrefix: "api/tenants/{tenantId}/odata",
                model: ODataHelper.GetEdmModel());
        });

контроллер:

    [HttpGet]
    [EnableQuery]
    [ODataRoute("Clients")]
    public async Task<IEnumerable<Client>> Get(
        ODataQueryOptions<Client> options,
        [FromRoute] Guid tenantId)
    {
        IQueryable res = await _service.Get(
            tenantId,
            AuthorizationHelper.GetSubjectId(tenantId, User),
            AuthorizationHelper.GetAllowedUserRoles(RoleType.Reporting),
            options,
            null);
        return new List<Client>(res.Cast<Client>());
    }

Я оставляю здесь свой обходной путь для случая, когда кто-то может использовать его:

После значительного исследования в реализации OData.Net Core я, наконец, обнаружил, что моя первая предоставленная ссылка " ODataMediaTypeFormatter в WebApi " уже обеспечила решение моего обходного пути.

Во-первых, BaseAddressFactory может только данный HTTP-запрос. Поэтому мне нужно было изменить следующий код:

 public static (Microsoft.AspNetCore.Http.HttpContext, Guid) ParseTenantIDToContext(Microsoft.AspNetCore.Http.HttpContext context)
    {
        System.Guid tenantGuid = System.Guid.Empty;
        if (context.Request.Path.ToString().Split('/').Length > 3 && context.Request.Path.ToString().ToLower().Contains("odata"))
        {
            bool isValidGUID = System.Guid.TryParse(context.Request.Path.ToString().Split('/')[3], result: out tenantGuid);
            if (isValidGUID)
            {
                context.Request.Path = context.Request.Path.Value.Replace(context.Request.Path.ToString().Split('/')[3], "tenantId");
                context.Items["tenantId"] = tenantGuid.ToString();
                context.Request.Headers.Remove("tenantId");
                context.Request.Headers.Append("tenantId", tenantGuid.ToString());
            }
        }
        return (context, tenantGuid);
    }

В этом разделе я сохраняю требуемый tenantId не только в HTTPContext, но и в качестве специального заголовка в HTTPRequest.

Основное решение - предоставить специальную функцию BaseAddressFactory которая управляет базовым адресом, который OData использует для создания метаданных. В качестве реализации я добавляю следующий код в ConfigureServices после добавления OData через services.AddOData():

services.AddMvc(op =>
{
     foreach (var formatter in op.OutputFormatters
          .OfType<ODataOutputFormatter>())
     {
          formatter.BaseAddressFactory = ODataHelper.CustomBaseAddressFactory;
     }
     foreach (var formatter in op.InputFormatters
          .OfType<ODataInputFormatter>())
     {
          formatter.BaseAddressFactory = ODataHelper.CustomBaseAddressFactory;
     }
});

Мой ODataHelper.CustomBaseAddressFactory выглядит так:

public static Uri CustomBaseAddressFactory (HttpRequest request)
{
    Guid tenantGuid = GetTenantIDFromRequest(request);
    request.Headers.Remove("tenantId");
    Uri std = ODataInputFormatter.GetDefaultBaseAddress(request);
    string ret = replaceTentantIdInURL(std.ToString(), tenantGuid);
    return ret[ret.Length - 1] != '/' ? new Uri(ret + '/') : new Uri(ret);
}

Чтобы обеспечить максимально возможную совместимость, я использую стандартный ODataInputFormatter.GetDefaultBaseAddress а затем снова заменяет мой статический заполнитель.

РЕДАКТИРОВАТЬ

Этот способ сохранения tenantId довольно небезопасен, поскольку заголовки запросов также могут быть созданы tenantId пользователем. В конце концов, я решил получить ID из наших требований авторизации, которые его предоставляют. Поэтому пользователь не может атаковать этот обходной путь.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню