[英]Dynamic Routing in BaseUrl in Asp.Net Core OData 4.0
我目前正在為 C# Asp.Net Core 應用程序開發 OData Api。
為了符合我們 API 的規范,URL 需要遵循我們的多租戶架構: https://website.com/api/tenants/{tenantId}/odata/
: https://website.com/api/tenants/{tenantId}/odata/
{tenantId}/ https://website.com/api/tenants/{tenantId}/odata/
由於 OData 4.0 沒有規范如何實現動態基本 url,我實現了以下解決方法:使用中間件將 HTTP 上下文中的動態租戶 ID 替換為靜態字符串“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>());
}
}
問題:
到目前為止的研究/谷歌搜索:
編輯 2:有時您認為如此復雜以至於您錯過了顯而易見的事情。 使用 OData 進行動態路由的解決方案:
啟動文件
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進行了大量研究后,我終於發現,我提供的第一個鏈接“ WebApi 中的 ODataMediaTypeFormatter ”已經為我的解決方法提供了解決方案。
首先, 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
保存在tenantId
中,而且還將其保存為 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。 因此,用戶無法攻擊此變通方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.