这是我从星期五开始撕裂头发的问题。
我有一个MVC应用程序,为单个客户端提供多个不同的子组。对于品牌和某些样式元素,Url需要格式化为: www.site.com/Login www.site.com/Client1/Login www.site.com/Client2/Login www.site.com/Client3/Login 等等。
我们还希望保持这种结构,转移到www.site.com/Client1/News等。
静态路由不在桌面上。甚至是生成它们的工具。该网站将拥有X页面,为Y客户提供了一条独特的路线,我不禁想到性能。而且由于动态性,在飞行中创建虚拟目标并不是我想要旅行的路线。
起初,解决方案似乎微不足道。我尝试了两种测试解决方案。
第一个派生自CustomRouteBase并且能够在重写的GetRouteData方法中确定正确的路由,然后使用GetVirtualPath生成正确的Url。第二种解决方案使用约束来查看客户端是否在模式中并相应地路由。两者都会击中正确的控制器并生成正确的链接。
然后我添加了区域(这只是一个原型,但真实的网站使用了区域)。
两种解决方案都失败了。这些区域已经正确注册并按照通常应该的方式工作。但是使用第一个解决方案,我找不到覆盖GetVirtualPath进行区域注册的方法。我知道Area类有一个扩展方法,但这不符合我的需要。
我也尝试过使用约束,但是Url的“客户端”部分没有被添加到区域的任何动作链接中,并尝试使用约束路由到控制器,这给了我找不到好的旧视图错误(即使我在路线中指定了区域,也从根搜索)。
所以我的第一个问题是我是否采取了错误的方式?如果有更好的方法来实现这一点,我就是时代。如果没有,那我该如何管理区域呢?
我可以在这里放一些代码,但这一切都适用于我想要它做的事情。我有点迷失在如何处理区域问题上。但不幸的是,我一直在发布前3个月继承了这个项目,而我的团队根本没有资源重新构建没有它们的网站。
@Max ...我尝试了类似的东西,但在www.site.com/Client1中,这些区域仍然无法正确显示链接...这是来自原型6,我尝试了几种不同的方式。
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//throws view engine can't find view error. If I use www.website.com/Client1, hvering over area links does not give the correct path
routes.MapRoute("Area",
"{folder}/{area}/{controller}/{action}/{id}",
new { area = "Authentication", controller = "Authentication", action = "Index", id = UrlParameter.Optional },
new { folder = new IsFolderContsraint(), area = new IsArea() }
);
//works fine.. if I use www.website.com/Client1, hovering over regular (non area) links works fine
routes.MapRoute("Branded",
"{folder}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { folder = new IsFolderContsraint() }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
这是我尝试解决它的另一种方式。请不要破解实施,它只是一个POC。这里的问题是Area不是RouteBase的一部分,所以我无法修改它们的虚拟路径。因此,只要动作不在某个区域内,每个动作链接等都会正确呈现并且一切正常。
public class Folders
{
private static Dictionary<string, string> _folders= new Dictionary<string, string>()
{ {"test1", "style1"},
{"test2", "style2"},
{"test3", "style3"}
};
public static Dictionary<string, string> FolderNames { get { return _folders; } }
}
public class AreaDefinitions
{
private static Dictionary<string, string> _areas = new Dictionary<string, string>()
{ {"Authentication", "Authentication"} };
public static Dictionary<string, string> AreaDefinition { get { return _areas; } }
}
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new CustomRouteBase());
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
public class CustomRouteBase : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
//Creates routes based on current app execution pass
//a bit like doing my own routing constraints, but is more dynamic.
//get route data (and CreateVirtualPath) will be called for any action needing to be rendered in the current view.
//But not for areas :( Ugly but just a prototype
string url = httpContext.Request.AppRelativeCurrentExecutionFilePath;
url = url.StartsWith("~/") ? url.Substring(2, url.Length - 2) : url;
string[] urlParts = url.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
RouteData rd = new RouteData(this, new MvcRouteHandler());
if (urlParts.Length == 0)
{
rd.Values.Add("controller", urlParts.Length > 0 ? urlParts[0] : "Home");
rd.Values.Add("action", urlParts.Length > 1 ? urlParts[1] : "Index");
return rd;
}
if (Folders.FolderNames.ContainsKey(urlParts[0]))
{
if (urlParts.Length > 1 && AreaDefinitions.AreaDefinition.ContainsKey(urlParts[1]))
{
rd.DataTokens["area"] = urlParts[1];
rd.Values.Add("controller", urlParts.Length > 2 ? urlParts[2] : "Home");
rd.Values.Add("action", urlParts.Length > 3 ? urlParts[3] : "Index");
}
else
{
rd.Values.Add("controller", urlParts.Length > 1 ? urlParts[1] : "Home");
rd.Values.Add("action", urlParts.Length > 2 ? urlParts[2] : "Index");
}
rd.DataTokens.Add("folder", urlParts[0]);
}
else
{
rd.Values.Add("controller", urlParts.Length > 0 ? urlParts[0] : "Home");
rd.Values.Add("action", urlParts.Length > 1 ? urlParts[1] : "Index");
}
return rd;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Assembled virtial path here using route and values.
//Worked (ugly but functioned, but was never called for areas.)
string url = "";
if (requestContext.RouteData.DataTokens.ContainsKey("folder"))
url += requestContext.RouteData.DataTokens["folder"] + "/";
if (values.ContainsKey("controller"))
url += values["controller"] + "/";
if (values.ContainsKey("action"))
url += values["action"];
var vpd = new VirtualPathData(requestContext.RouteData.Route, url);
return vpd;
}
}
感谢Liam ...这与我们的视图引擎的定制方式类似,首先从“plugins”文件夹中读取视图覆盖。但这里的问题有点不同。这是一个已经拥有一个具有视图覆盖的站点的客户端,但是它拥有自己的多个客户端。这里的行为只是为了控制样式表,徽标等。用户登录后,我可以确定样式和品牌应该是什么,但对于登录页面,客户端想要使用像(www)这样的网址。 site.com/Client1“来识别这个。我已经放弃并编写了一个处理程序,只需将请求转换为www.site.com/?client=client1,这样我就可以处理登陆页面样式,但是将URL保留为www.ste.com会更好。 / Client1 / login等。这是一个经典的asp应用程序的转换,它使用vdirs来托管不同的目录。由于潜在客户端数量(100个),静态路由会变得很重。我的解决方案都有所作为......这是导致所有问题的领域。如果我能找到一种方法来动态地重新映射他们的虚拟路径,就像我在RouteBase中创建的路由一样,我会在商业中......我想。