我正在制作一个asp.net-mvc网站。该网站旨在作为多个客户的基础,所有客户都有自己独特的业务需求。对于任何给定的控制器方法,我可能会或可能不会根据客户端ClientId
为客户端设置自定义视图。
现在我如何处理这个问题是通过ResourceSelectorObject
这样的:
public class ClientResourceSelector
{
public ClientResourceSelector(int clientId)
{
this.ClientId = clientId;
}
public int ClientId { get; set; }
public readonly List<ViewProfile> ViewProfiles = new List<ViewProfile>()
{
new ViewProfile { ClientId = 8, Controller = "Contact", Action = "NewContact", View = "C008/NewContact" }
};
public string ViewName(string controller, string action)
{
var Profile = ViewProfiles.FirstOrDefault(X => X.Controller.Equals(controller) && X.Action.Equals(action) && X.ClientId == ClientId);
if (Profile == null) return string.Empty;
return Profile.View;
}
}
然后在代码中,我以这种方式使用该对象:
// GET: Contact/NewContact
public ActionResult NewContact()
{
var selector = new ClientResourceSelector(ClientId);
string alternate_view = selector.ViewName("Contact", "NewContact");
if (String.IsNullOrEmpty(alternate_view))
return View(NewContactViewModel.Instance(ClientId));
else
return View(alternate_view, NewContactViewModel.Instance(ClientId));
}
问题,这绝对是编程等同于“第一世界问题”,但我仍然可以只调用View(viewModel)
并让它选择适当的视图以编程方式显示而无需我记得在选择器中注册每个视图。
显然,我想覆盖所有控制器继承的抽象控制器中的View()
方法。但我不确定代码的外观。任何的意见都将会有帮助。
答案 0 :(得分:1)
以下是我过去创作的方式。我构建的大多数租户系统都使用某种类型的路由/请求参数(可以轻松更新以使用DNS或wahtever,您有很多选项)来确定特定的租户。我使用在任何控制器(或路由)之前执行的动作过滤器来填充路径数据(对于特定于租户的路由也很有用)。
public class TenantActionFilterAttribute : ActionFilterAttribute
{
internal const string _Tenant = "tenant";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Do this how ever you want, right now I'm using querystring
// Could be changed to use DNS name or whatever
var tenant = filterContext.HttpContext.Request.QueryString[_Tenant] as string;
if (tenant != null)
{
filterContext.RouteData.Values[Tenant] = tenant;
}
}
}
全局注册动作过滤器:
RegisterGlobalFilters(GlobalFilters.Filters);
(或使用依赖注入框架)
然后是自定义ViewEngine:
public class TenantViewEngine : RazorViewEngine
{
private string GetPrefix(ControllerContext controllerContext)
{
var result = string.Empty;
var tenant = controllerContext.RouteData.Values[TenantActionFilterAttribute.Tenant] as string;
if (!string.IsNullOrEmpty(tenant))
{
result = "Tenants/" + tenant + "/";
}
return result;
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
var prefix = GetPrefix(controllerContext);
if (partialPath.StartsWith("~/"))
{
partialPath = partialPath.Insert(2, prefix);
}
else if (partialPath.StartsWith("~") || partialPath.StartsWith("/"))
{
partialPath = partialPath.Insert(1, prefix);
}
else if (string.IsNullOrEmpty(partialPath))
{
partialPath = prefix + partialPath;
}
return base.CreatePartialView(controllerContext, partialPath);
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var prefix = GetPrefix(controllerContext);
if (viewPath.StartsWith("~/"))
{
viewPath = viewPath.Insert(2, prefix);
}
else if (viewPath.StartsWith("~") || viewPath.StartsWith("/"))
{
viewPath = viewPath.Insert(1, prefix);
}
else if (!string.IsNullOrEmpty(viewPath))
{
viewPath = prefix + viewPath;
}
if (masterPath.StartsWith("~/"))
{
masterPath = masterPath.Insert(2, prefix);
}
else if (masterPath.StartsWith("~") || masterPath.StartsWith("/"))
{
masterPath = masterPath.Insert(1, prefix);
}
else if (!string.IsNullOrEmpty(masterPath))
{
masterPath = prefix + masterPath;
}
return base.CreateView(controllerContext, viewPath, masterPath);
}
}
我无法完全记住这是如何工作的,但搜索路径从默认值变为非常接近的位置:
"~/Tenants/<TenantName>/Areas/{3}/Views/{1}/{0}.cshtml",
"~/Areas/{3}/Views/{1}/{0}.cshtml",
"~/Tenants/<TenantName>//Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Tenants/<TenantName>//Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
其中1:Controller,2:View / Action,3:AreaName