I need to map two variables that could contain slashes, to a controller, in my ASP MVC application. Let's see this with an example.
我需要将可能包含斜杠的两个变量映射到我的ASP MVC应用程序中的控制器。我们来看一个例子。
- Repository and Path will be URL-encoded parameters.
- 存储库和路径将是url编码的参数。
- Repository can have 0 slashes or 1 slash as a maximum (rep or rep/module)
- 存储库可以有0斜杠或1斜杠作为最大值(rep或rep/module)
- Path can have an arbitrary number of slashes.
- 路径可以有任意数量的斜杠。
For example these are valid URLs:
例如,这些是有效的url:
http://mysite/rep/Items
http://mysite/rep/module/Items/foo/bar/file.c
Someone could give some suggestions about how to define this route?
有人能就如何定义这条路线提出一些建议吗?
4 个解决方案
#1
2
Looks like a custom route might cut the mustard:
看起来定制路线可能会符合要求:
public class MyRoute: Route
{
public MyRoute()
: base("{*catchall}", new MvcRouteHandler())
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
// we do not have a match for {*catchall}, although this is very
// unlikely to ever happen :-)
return null;
}
var segments = httpContext.Request.Url.AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (segments.Length < 4)
{
// we do not have the minimum number of segments
// in the url to have a match
return null;
}
if (!string.Equals("items", segments[1], StringComparison.InvariantCultureIgnoreCase) &&
!string.Equals("items", segments[2], StringComparison.InvariantCultureIgnoreCase))
{
// we couldn't find "items" at the expected position in the url
return null;
}
// at this stage we know that we have a match and can start processing
// Feel free to find a faster and more readable split here
string repository = string.Join("/", segments.TakeWhile(segment => !string.Equals("items", segment, StringComparison.InvariantCultureIgnoreCase)));
string path = string.Join("/", segments.Reverse().TakeWhile(segment => !string.Equals("items", segment, StringComparison.InvariantCultureIgnoreCase)).Reverse());
rd.Values["controller"] = "items";
rd.Values["action"] = "index";
rd.Values["repository"] = repository;
rd.Values["path"] = path;
return rd;
}
}
which could be registered before the standard routes:
可在标准路线前登记:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("myRoute", new MyRoute());
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
And if you intend to put arbitrary strings in the path portion of your urls I hope you are aware of the Zombie Operating Systems
which might surprise you.
如果您打算在url的路径部分中放置任意字符串,我希望您已经注意到了可能令您吃惊的僵尸操作系统。
#2
2
Finally, based in the answer of Darin Dimitrov, I implemented the following custom route, that solves my problem:
最后,基于Darin Dimitrov的回答,我实现了以下自定义路径,解决了我的问题:
public class RepositoryRoute : Route
{
public RepositoryRoute(string name, string url, object defaults)
: base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
string moduleUrl = url.Replace(
REPOSITORY_PARAMETER, REPOSITORY_PARAMETER + MODULE_PARAMETER);
mModuleRoute = new Route(
moduleUrl, new RouteValueDictionary(defaults), new MvcRouteHandler());
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData rd = mModuleRoute.GetRouteData(httpContext);
if (rd == null)
return base.GetRouteData(httpContext);
if (!rd.Values.ContainsKey(MODULE))
return rd;
// set repository as repo/submodule format
// if a submodule is present in the URL
string repository = string.Format("{0}/{1}",
rd.Values[REPOSITORY],
rd.Values[MODULE]);
rd.Values.Remove(MODULE);
rd.Values[REPOSITORY] = repository;
return rd;
}
Route mModuleRoute;
const string REPOSITORY = "repository";
const string MODULE = "module";
const string REPOSITORY_PARAMETER = "{" + REPOSITORY + "}/"; // {repository}/
const string MODULE_PARAMETER = "{" + MODULE + "}/"; // {module}/
}
Which is registered in the following way:
以下列方式登记:
routes.Add(new RepositoryRoute(
"Items",
"{repository}/Items/{*path}",
new { controller = "Items", action = "Index", path = "/" }
));
The route uses an internal route, that defines a module parameter, and if it's found, I concat it to the repository, and remove it. So mapping repository or repository/module is transparent.
该路由使用一个内部路由,该路由定义了一个模块参数,如果找到了,我将它保存到存储库,并删除它。因此,映射存储库或存储库/模块是透明的。
#3
0
If you cannot live with "old fashioned" style parameters and URL encoding, I think the only way you can achieve it is like this. Note that this is not tested but should basically work. Also I've put the controller name at the start and the Items
separator is now essentially meaningless apart from acting as a delimiter.
如果您不能接受“老式”样式参数和URL编码,我认为实现它的唯一方法是这样的。请注意,这并没有经过测试,但应该基本有效。我还把控制器名放在开头,项目分隔符除了作为分隔符之外,现在基本上没有任何意义。
Controller
控制器
Create a controller with a single, parameterless method:
使用单一的无参数方法创建控制器:
public class GetRepo : Controller
{
public ActionResult Index()
{
//TBC
return View();
}
}
Routing
路由
Ensure routing is set up to allow http://www.example.com/GetRepo/anything to route to your index method.
确保设置了路由以允许http://www.example.com/GetRepo/anything路由到索引方法。
Note that the GetRepo
part is important as otherwise what happens if your URL is www.example.com/blah/repo/items/other/stuff
and you happen to have a controller called blah
?
注意GetRepo部分很重要,否则,如果你的URL是www.example.com/blah/repo/items/other/stuff你碰巧有一个名为blah的控制器会发生什么?
The Magic
神奇的
Now you deconstruct the Url manually by using Request.Url.AbsolutePath
.
现在,您可以使用Request.Url.AbsolutePath手动解构Url。
var urlPath = Request.Url.AbsolutePath;
//Split the path by '/', drop the first entry as it's the action method
var parts = urlPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Skip(1).ToList();
//Find where "Items" separator appears:
var posOfItems = parts.IndexOf("Items");
//Everything before separator is the repo:
var repo = string.Join("/", parts.Take(posOfItems));
//Everything after separator is the path:
var path = string.Join("/", parts.Skip(posOfItems + 1));
//Now do something with repo/path variables
#4
-1
You cannot do this mapping correctly, because of nature of this problem. Try to get a pencil and map following URL to repository and path:
由于这个问题的性质,您不能正确地进行映射。试着用铅笔把下面的URL映射到存储库和路径:
http://mysite/rep/Items/Items/Items/Items/Items/Items/Items/Items/Items
There are multiple mappings:
有多个映射:
1) Repository = Items Path = Items/Items/Items/Items/Items/Items/Items
1) Repository = Items Path = Items/Items/Items/Items/Items/Items/Items /Items
2) Repository = Items/Items Path = Items/Items/Items/Items/Items/Items
2) Repository = Items/Items Path = Items/Items/Items/Items/Items/Items /Items
and so on....
等等....
So either you should
所以你应该
- Pass parameters as query string
- 将参数作为查询字符串传递
- Define multiple routes for each of format of repository (and add parts to full repository name in controller method)
- 为每个存储库格式定义多个路由(并在controller方法中向完整存储库名称添加部分)
#1
2
Looks like a custom route might cut the mustard:
看起来定制路线可能会符合要求:
public class MyRoute: Route
{
public MyRoute()
: base("{*catchall}", new MvcRouteHandler())
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
// we do not have a match for {*catchall}, although this is very
// unlikely to ever happen :-)
return null;
}
var segments = httpContext.Request.Url.AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (segments.Length < 4)
{
// we do not have the minimum number of segments
// in the url to have a match
return null;
}
if (!string.Equals("items", segments[1], StringComparison.InvariantCultureIgnoreCase) &&
!string.Equals("items", segments[2], StringComparison.InvariantCultureIgnoreCase))
{
// we couldn't find "items" at the expected position in the url
return null;
}
// at this stage we know that we have a match and can start processing
// Feel free to find a faster and more readable split here
string repository = string.Join("/", segments.TakeWhile(segment => !string.Equals("items", segment, StringComparison.InvariantCultureIgnoreCase)));
string path = string.Join("/", segments.Reverse().TakeWhile(segment => !string.Equals("items", segment, StringComparison.InvariantCultureIgnoreCase)).Reverse());
rd.Values["controller"] = "items";
rd.Values["action"] = "index";
rd.Values["repository"] = repository;
rd.Values["path"] = path;
return rd;
}
}
which could be registered before the standard routes:
可在标准路线前登记:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("myRoute", new MyRoute());
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
And if you intend to put arbitrary strings in the path portion of your urls I hope you are aware of the Zombie Operating Systems
which might surprise you.
如果您打算在url的路径部分中放置任意字符串,我希望您已经注意到了可能令您吃惊的僵尸操作系统。
#2
2
Finally, based in the answer of Darin Dimitrov, I implemented the following custom route, that solves my problem:
最后,基于Darin Dimitrov的回答,我实现了以下自定义路径,解决了我的问题:
public class RepositoryRoute : Route
{
public RepositoryRoute(string name, string url, object defaults)
: base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
string moduleUrl = url.Replace(
REPOSITORY_PARAMETER, REPOSITORY_PARAMETER + MODULE_PARAMETER);
mModuleRoute = new Route(
moduleUrl, new RouteValueDictionary(defaults), new MvcRouteHandler());
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData rd = mModuleRoute.GetRouteData(httpContext);
if (rd == null)
return base.GetRouteData(httpContext);
if (!rd.Values.ContainsKey(MODULE))
return rd;
// set repository as repo/submodule format
// if a submodule is present in the URL
string repository = string.Format("{0}/{1}",
rd.Values[REPOSITORY],
rd.Values[MODULE]);
rd.Values.Remove(MODULE);
rd.Values[REPOSITORY] = repository;
return rd;
}
Route mModuleRoute;
const string REPOSITORY = "repository";
const string MODULE = "module";
const string REPOSITORY_PARAMETER = "{" + REPOSITORY + "}/"; // {repository}/
const string MODULE_PARAMETER = "{" + MODULE + "}/"; // {module}/
}
Which is registered in the following way:
以下列方式登记:
routes.Add(new RepositoryRoute(
"Items",
"{repository}/Items/{*path}",
new { controller = "Items", action = "Index", path = "/" }
));
The route uses an internal route, that defines a module parameter, and if it's found, I concat it to the repository, and remove it. So mapping repository or repository/module is transparent.
该路由使用一个内部路由,该路由定义了一个模块参数,如果找到了,我将它保存到存储库,并删除它。因此,映射存储库或存储库/模块是透明的。
#3
0
If you cannot live with "old fashioned" style parameters and URL encoding, I think the only way you can achieve it is like this. Note that this is not tested but should basically work. Also I've put the controller name at the start and the Items
separator is now essentially meaningless apart from acting as a delimiter.
如果您不能接受“老式”样式参数和URL编码,我认为实现它的唯一方法是这样的。请注意,这并没有经过测试,但应该基本有效。我还把控制器名放在开头,项目分隔符除了作为分隔符之外,现在基本上没有任何意义。
Controller
控制器
Create a controller with a single, parameterless method:
使用单一的无参数方法创建控制器:
public class GetRepo : Controller
{
public ActionResult Index()
{
//TBC
return View();
}
}
Routing
路由
Ensure routing is set up to allow http://www.example.com/GetRepo/anything to route to your index method.
确保设置了路由以允许http://www.example.com/GetRepo/anything路由到索引方法。
Note that the GetRepo
part is important as otherwise what happens if your URL is www.example.com/blah/repo/items/other/stuff
and you happen to have a controller called blah
?
注意GetRepo部分很重要,否则,如果你的URL是www.example.com/blah/repo/items/other/stuff你碰巧有一个名为blah的控制器会发生什么?
The Magic
神奇的
Now you deconstruct the Url manually by using Request.Url.AbsolutePath
.
现在,您可以使用Request.Url.AbsolutePath手动解构Url。
var urlPath = Request.Url.AbsolutePath;
//Split the path by '/', drop the first entry as it's the action method
var parts = urlPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Skip(1).ToList();
//Find where "Items" separator appears:
var posOfItems = parts.IndexOf("Items");
//Everything before separator is the repo:
var repo = string.Join("/", parts.Take(posOfItems));
//Everything after separator is the path:
var path = string.Join("/", parts.Skip(posOfItems + 1));
//Now do something with repo/path variables
#4
-1
You cannot do this mapping correctly, because of nature of this problem. Try to get a pencil and map following URL to repository and path:
由于这个问题的性质,您不能正确地进行映射。试着用铅笔把下面的URL映射到存储库和路径:
http://mysite/rep/Items/Items/Items/Items/Items/Items/Items/Items/Items
There are multiple mappings:
有多个映射:
1) Repository = Items Path = Items/Items/Items/Items/Items/Items/Items
1) Repository = Items Path = Items/Items/Items/Items/Items/Items/Items /Items
2) Repository = Items/Items Path = Items/Items/Items/Items/Items/Items
2) Repository = Items/Items Path = Items/Items/Items/Items/Items/Items /Items
and so on....
等等....
So either you should
所以你应该
- Pass parameters as query string
- 将参数作为查询字符串传递
- Define multiple routes for each of format of repository (and add parts to full repository name in controller method)
- 为每个存储库格式定义多个路由(并在controller方法中向完整存储库名称添加部分)