我目前正在为 C# Asp.Net Core 应用程序开发 OData Api。
为了符合我们 API 的规范,URL 需要遵循我们的 Multi-Tenancy 架构:
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);
}
...
示例 Controller :
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>());
}
}
问题:
- 有没有更好的方法来使用 Asp.Net Core 在 OData 中实现动态基本路由?
- 有什么方法可以操纵请求或 OData 元数据。详细地说,响应需要在“@OData.context”和(将来)OData 分页元数据中显示带有动态租户 ID 的原始 url。
到目前为止的研究/谷歌搜索:
- ODataMediaTypeFormatter in WebApi但我没有找到 .net Core 的实现。
- Solution for WebApi 2.2但是 .net Core 中没有 UrlHelper。
- Offical OData WebApi tutorial但这并不能证明是一种比 workaorund 更简单的方法。
最佳答案
编辑 2: 有时您会想得太复杂,以至于错过了显而易见的事情。使用 OData 进行动态路由的解决方案:
启动.cs
app.UseMvc(b =>
{
b.Select().Filter().OrderBy().MaxTop(100).Count();
b.MapODataServiceRoute(
routeName: "odata",
routePrefix: "api/tenants/{tenantId}/odata",
model: ODataHelper.GetEdmModel());
});
Controller :
[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 中的特殊 header 。
主要的解决方案是提供一个特殊的 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
的方式非常不安全,因为最终用户也可以创建请求 header 。最后,我决定从提供它的授权声明中接收 ID。因此,用户无法攻击此解决方法。
关于c# - Asp.Net Core OData 4.0 中 BaseUrl 中的动态路由,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54868782/