我正在嘗試配置Razor 頁面路由(不是 Razor 視圖)并通過目錄結構使其支持多租戶...
所以,我會為不同的租戶提供一堆頁面,例如:
/Pages/1/About.cshtml
/Pages/2/About.cshtml
/Pages/2/Other.cshtml
... plus many many more ...
以及tenantID 查找的來源,即:
"example.com" : 1,
"site.tld" : 2,
...
然后,當有人請求“example.com/About”時,它會正確映射到租戶#1 子檔案夾中的頁面(因為“example.com”映射到上面示例中的#1),而不是不同的租戶“About”頁面。
廢棄的解決方案...
- 有很多 Razor View 解決方案,但這不是我想要的(我正在使用 Razor PAGES)。
- 我還看到一個人使用 url-rewriting,但這有點蠻力和不優雅,我想要一個適當的路由解決方案。
- 硬編碼路由顯然會起作用(在映射或頁面指令中),但這不可擴展且容易出錯。
可能的解決方案?
使用 IPageRouteModelConvention 似乎是配置 Razor Pages 路由的“正確”方式?
似乎我可以修改路由選擇器以剝離租戶 ID 子目錄,從而使頁面在根路徑中可用。但是,我還需要確保請求了適當的租戶頁面,而不是其他租戶的頁面......
One way (I think) this could be done is using an ActionConstraint (which can also be configured in IPageRouteModelConvention). If the origin:tenantId dictionary was hard-coded then I think that would be easy... but my tenant lookup data needs to be pulled from the DB (I actually have a TenantCollection service added as a singleton in the .NET Core service collection already).
The problem is that I don't have access to the ServiceProvider (to get my TenantCollection) at builder.Services.Configure(...) call. So I can't create the ActionConstraint to restrict access to certain pages for certain origins since I don't have the tenant mapping data.
Here is some example code in-case it helps to illustrate...
builder.Services.AddSingleton<TenantCollection>();
builder.Services.AddRazorPages();
builder.Services.Configure<RazorPagesOptions>(options =>
{
var tenantCollection = GET_MY_TENANT_COLLECTION; // Cant do?
options.Conventions.Add(new MultiTenantPageRouteModelConvention(tenantCollection));
});
I feel like I'm missing something obvious, or attacking the problem from the wrong direction?
uj5u.com熱心網友回復:
所以,最后我“錯過了一些明顯的東西”。在 ActionConstraint 中,可以通過 ActionConstraintContext 的 RouteContext.HttpContext.RequestServices 參考訪問 ServiceProvider。這使我能夠獲得我需要的服務來做我需要的事情。簡單的。
與其把它留在那里,我想我還不如讓這篇文章更有價值..所以我將對我正在做的事情進行精簡的實作,以防將來有人發現它有用。
程式.cs
...
builder.Services.AddSingleton<MyTenantCollection>();
builder.Services.AddScoped(MyTenant.ImplementationFactoryBasedOnRequestOrigin);
builder.Services.Configure<RazorPagesOptions>(options =>
{
options.Conventions.Add(new MyPageRouteModelConvention());
});
...
MyPageRouteModelConvention.cs
...
public class MyPageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
// Only modify pages in the tenants folder.
if (!model.ViewEnginePath.StartsWith("/Tenants/"))
return;
// Tenants/<num>/<page>...
if (!validateAndParseTenantFolderNumFromPath(model.ViewEnginePath, out int tenantFolderNum))
return;
var constraint = new MyTenantActionConstraint(tenantFolderNum);
foreach (var selector in model.Selectors)
{
// Change the selector route template so the page is
// accessible as if it was in the root path.
// Eg "Tenants/123/SomePage" changes to "SomePage"
selector.AttributeRouteModel.Template =
stripOffTheTenantPath(selector.AttributeRouteModel.Template);
// Note that this is directly modifying this selector's route template,
// so it will no longer be accessible from the tenant sub folder path.
// Alternatively one could create a new selector based on this
// one, modify the template in the same way, and add it as a new
// selector to the model.Selectors collection.
// Add the constraint which will restrict the page from being
// chosen unless the request's origin matches the tenant
// (ie: folderNum == tenantId).
selector.ActionConstraints.Add(constraint);
}
}
}
...
MyTenantActionConstraint.cs
...
public class MyTenantActionConstraint : IActionConstraint
{
public int Order => 0;
private readonly int _tenantID;
public MyTenantActionConstraint(int tenantID)
{
_tenantID = tenantID;
}
public bool Accept(ActionConstraintContext context)
{
// Get the MyTenant that matches the current requests origin
// using the MyTenant.ImplementationFactoryBasedOnRequestOrigin.
// This is a 'scoped' service so it only needs to do it once per request.
// Alternatively one could just get the MyTenantCollection and find the
// tenant by _tenantID and then check that your tenant.ExpectedOrigin matches
// the current HttpContext.Request.Host, but that would run
// every time MyTenantActionConstraint.Accept is invoked.
var tenant =
context.RouteContext.HttpContext.RequestServices.GetService(
typeof(MyTenant)) as MyTenant;
// Return whether or not this ActionConstraint and more importantly
// the Page/Route this ActionConstraint is attached to
// is within the tenant folder (eg Pages/Tenants/123/About.cshtml)
// which has the same number (eg 123) as the tenant Id that
// corresponds to the tenant that matches the current request's
// origin (ie tenantWithId123.DomainName == currentRequest.Host),
// meaning.. true/false this page-route is for this tenant.
return tenant?.Id == _tenantID;
}
}
...
uj5u.com熱心網友回復:
using Microsoft.AspNetCore.Mvc.RazorPages;
using WebAppRazor.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<TestService>();
// Add services to the container.
builder.Services.AddRazorPages();
ServiceProvider serviceProvider = builder.Services.BuildServiceProvider();
var userRepository = serviceProvider.GetService<TestService>();
var a = userRepository.getString();
我有一個測驗服務,它將回傳一個字串。那么這段代碼對我有用,我可以通過上面的代碼呼叫這個服務。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/440804.html
