【问题标题】:Dynamic Routing in BaseUrl in Asp.Net Core OData 4.0Asp.Net Core OData 4.0 中 BaseUrl 中的动态路由
【发布时间】:2019-02-28 20:54:02
【问题描述】:

我目前正在为 C# Asp.Net Core 应用程序开发 OData Api。

为了符合我们 API 的规范,URL 需要遵循我们的多租户架构: https://website.com/api/tenants/{tenantId}/odata/

由于 OData 4.0 没有规范如何实现动态基本 url,我实现了以下解决方法:使用中间件将 HTTP 上下文中的动态tenantId 替换为静态字符串“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. 有没有更好的方法来使用 Asp.Net Core 在 OData 中实现动态基础路由?
  2. 有什么方法可以操作请求或 OData 元数据。具体来说,响应需要在“@OData.context”和(用于将来)OData 分页元数据中显示带有动态tenantId 的原始 url。

到目前为止的研究/谷歌搜索:

【问题讨论】:

    标签: c# asp.net asp.net-mvc odata dynamic-routing


    【解决方案1】:

    编辑 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 Implementation 进行大量研究后,我终于发现,我提供的第一个链接“ODataMediaTypeFormatter in 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 用来构建元数据的基地址。作为实现,我在通过services.AddOData() 添加 OData 后在ConfigureServices 中添加以下代码:

    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 的方式非常不安全,因为最终用户也可以创建请求标头。最后,我决定从提供它的授权声明中接收 ID。因此,用户无法攻击此变通方法。

    【讨论】:

      猜你喜欢
      • 2022-01-12
      • 2016-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多