Динамическая маршрутизация в BaseUrl в Asp.Net Core OData 4.0 - PullRequest
0 голосов
/ 25 февраля 2019

В настоящее время я разрабатываю OData Api для приложения C # 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 до сих пор:

1 Ответ

0 голосов
/ 26 февраля 2019

РЕДАКТИРОВАТЬ 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.

* 1028Основное решение - предоставить специальную функцию 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;
     }
});

My 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 довольно небезопасен, посколькуЗаголовки запроса также могут быть созданы конечным пользователем.В конце концов, я решил получить ID из наших требований авторизации, которые его предоставляют.Поэтому пользователь не может атаковать этот обходной путь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...