Imported from the CodePlex archive for reference purposes. Support for MvcCodeRouting has ended.
Here was a quick hacked up way to make what I need work:
public class PersonnelController : Controller
{
[CustomRoute("~/Agents/Registration/{registrationId}/Personnel/{personnelId}")]
public ActionResult Detail([FromRoute]int registrationId, [FromRoute]int personnelId)
{
return Content("registrationId:" + registrationId.ToString() + " and personnel Id:" + personnelId.ToString());
}
}
--------------
public static ICollection<Route> MapCodeRoutes(this RouteCollection routes, string baseRoute, Type rootController, CodeRoutingSettings settings) {
if (routes == null) throw new ArgumentNullException("routes");
if (rootController == null) throw new ArgumentNullException("rootController");
var registerInfo = new RegisterInfo(null, rootController) {
BaseRoute = baseRoute,
Settings = settings
};
Route[] newRoutes = RouteFactory.CreateRoutes(registerInfo);
foreach (var route in newRoutes)
{
// TODO: JSG Hack remove
if (route.Url.Contains("~/"))
{
var fixedUpUrl = route.Url.Substring(
route.Url.IndexOf("~/") + 2);
route.Url = fixedUpUrl;
}
routes.Add(route);
}
Routes.axd shows:
routes.MapRoute(null, "Agents/Registration/{registrationId}/Personnel/{personnelId}", new { controller = @"Personnel", action = @"Detail" }, new { registrationId = @"0|-?[1-9]\d*", personnelId = @"0|-?[1-9]\d*" }, new[] { "Modules.Agents.Controllers.Registration.Personnel" }); // MvcCodeRouting.RouteContext: "Agents/Registration"
I like this feature, and dislike my implementation :)
If you tell me a better place to achieve this, I will submit a patch.
I kind of do like the ~/ for the mechanism, though maybe an "IsAbsolute" property on CustomRoute would be better? (Or maybe both should be supported?)
Josh
Me again.
Here is where I believe a better place would be for the above functionality:
What do you think? Or, am I just missing a way to do this already? :)
RouteFactory.cs:
static CodeRoute CreateRoute(IEnumerable<ActionInfo> actions) {
........
bool actionFormat = actionMapping.Any(p => !String.Equals(p.Key, p.Value, StringComparison.Ordinal));
bool requiresActionMapping = actionFormat && includeActionToken;
string url = string.Empty;
if (first.CustomRoute != null) {
if (first.CustomRoute.Contains("~/"))
{
var absoluteUrl = first.CustomRoute.Substring(
first.CustomRoute.IndexOf("~/") + 2);
url = absoluteUrl;
}
else
{
segments.Add(first.CustomRoute);
}
} else {
segments.Add(!includeActionToken ? first.ActionSegment : String.Concat("{action}"));
segments.AddRange(first.RouteParameters.Select(r => r.RouteSegment));
}
if (url == string.Empty)
{
url = String.Join("/", segments.Where(s => !String.IsNullOrEmpty(s)));
}
Thanks for the idea. Using a custom route that starts with ~/ is a very elegant solution to the problem, and makes MvcCodeRouting an alternative to other attribute-based routing libraries.
This is now implemented in the latest revision, it only required a small modification. I will continue to test it and see how it affects the rest of the features, I'm confident this will make the next release.
To answer the original question, the only way to do it is to put the action in the Registration controller and use a custom route, like this:
namespace App.Modules.Agents.Controllers.Registration { public class RegistrationController : Controller { [CustomRoute("{id}/{action}/{personnelId}")] public ActionResult Personnel([FromRoute]int id, [FromRoute]int personnelId) { ... } } }
Ok, thank you, I think I did try something to the effect successfully, but really wanted it in the nested controller instead, hence the tilde solution.
The thing I dislike about the tilde solution is the need to hard code the "parent" paths...so I imagine with the segments approach you have I could figure a "relative path" way, perhaps something like:
CustomRoute("../{registrationId}/Personnel/{personnelId}")
In this, it would just build route all the way to it's parent, then tack on the rest.
But, I could even generalize this:
I am thinking an attribute like:
[Inherit]
ActionResult Personnell(int registrationId, int personnelId)
Then, given that the parent has:
/Root/Parent/{registrationId}
(and that maps to some action)
When [Inherit] is there, it will
Thus build a route that is, by convention:
/Agent/Registration/{registrationId}/Personnel/{personnelId}
Now......if such a mechanism could be multiply nested to:
/Agents/Registration/45/Personnel/2/ContactInfo/Home/Email
Where "Home" would actually be an "id" of sorts, as would "Email" ......
well..I am not sure about that, but I will give the simple case a shot :)
From: maxtoroq
To answer the original question, the only way to do it is to put the action in the Registration controller and use a custom route, like this:
namespace App.Modules.Agents.Controllers.Registration { public class RegistrationController : Controller { [CustomRoute("{id}/{action}/{personnelId}")] public ActionResult Personnel([FromRoute]int id, [FromRoute]int personnelId) { ... } } }Read the full discussion online.
To add a post to this discussion, reply to this email (mvccoderouting@discussions.codeplex.com)
To start a new discussion for this project, email mvccoderouting@discussions.codeplex.com
You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.
Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com
I tried the "InheritRoute" idea, and it works...but is very limited in scope (But kind of works for what I need at the moment :) )
namespace Nsar.Modules.SelectAgentData.Controllers.Registration.Personnel
{
public class PersonnelController : Controller
{
[InheritRoute]
public ActionResult Detail(int registrationId, int personnelId)
{
return Content("registrationId:" + registrationId.ToString() + " and personnel Id:" + personnelId.ToString());
}
}
}
Produces this route:
http://localhost:12795/SelectAgentData/Registration/55/Personnel/Detail/44
And int RouteFactory above the previous bit of code:
if (first.GetCustomAttributes(typeof(InheritRouteAttribute), false).Length > 0)
{
var rootUrl = segments[0].Substring(0, segments[0].LastIndexOf("/"));
var controllerName = segments[0].Substring(segments[0].LastIndexOf("/") + 1);
var paramIndex = 0;
if (first.Parameters.Count > 1)
{
url += string.Format("/{0}", "{" + first.Parameters[0].Name + "}");
url += string.Format("/{0}", controllerName);
url += string.Format("/{0}", first.ActionSegment);
paramIndex = 1;
}
url += string.Format("/{0}", "{" + first.Parameters[paramIndex].Name + "}");
url = rootUrl + url;
}
else if (first.CustomRoute != null) {
if (first.CustomRoute.Contains("~/"))
{
Your InheritRoute solution isn't general, since your are asuming the first action parameter goes before the controller token.
Yeah...any ideas for making it general?
CustomRoute("../{registrationId}/{controller}/{personnelId}") doesn't look bad, but I'm afraid to overcomplicate things. Should be easy to implement though.
Another option, that is consistent to how CustomRoute works for actions methods, is to use CustomRoute on the controller, like this:
namespace Nsar.Modules.SelectAgentData.Controllers.Registration.Personnel { [CustomRoute("{registrationId}/{controller}")] public class PersonnelController : Controller { [FromRoute] public int registrationId { get; set; } protected override void Initialize(RequestContext requestContext) { this.BindRouteProperties(); } [CustomRoute("{id}")] public ActionResult Details([FromRoute]int id) { ... } } }
I've implemented the CustomRoute on controllers, it works very well. Give it a try.
Thanks! I downloaded, and tried, but I'm not sure how to use it properly...
I get exception here:
ControllerContext is null:
bindingContext.ValueProvider = (hasCustomValues) ?
new DictionaryValueProvider<object>(values, CultureInfo.InvariantCulture)
: new RouteDataValueProvider(controller.ControllerContext);
----
Controller:
[CustomRoute("{registrationId}/{controller}")]
public class PersonnelController : NsarControllerBase
{
[FromRoute]
public int RegistrationId { get; set; }
protected override void Initialize(RequestContext requestContext)
{
this.BindRouteProperties();
}
[CustomRoute("{id}")]
public ActionResult Details([FromRoute] int id)
{
return Content("registrationId:" + RegistrationId.ToString() + " and personnel Id:" + id.ToString());
}
}
Routes.axd
routes.MapRoute(null, "SelectAgentData/Registration/{registrationId}/{controller}/{id}", new { action = @"Details" }, new { controller = @"Personnel", RegistrationId = @"0|-?[1-9]\d*", id = @"0|-?[1-9]\d*" }, new[] { "Nsar.Modules.SelectAgentData.Controllers.Registration.Personnel" }); // MvcCodeRouting.RouteContext: "SelectAgentData/Registration"
You have to call the base Initialize method which sets ControllerContext:
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
this.BindRouteProperties();
}
Ok, I will try, thanks!
I think this should do what I need, but am wondering, will it work with deeper nesting?
Suppose I have:
/SelectAgentData/Registrations/55/Labs/5/Agents/22
Or:
/SelectAgentData/Registrations/55/Labs/5/Personnel/12/Clearances/5
Josh
No, the only way to go deeper, without using an absolute custom route, is to implement them on the Labs controller and use literal segments instead of namespaces:
[CustomRoute("{registrationId}/{controller}")] public class LabsController : Controller { [FromRoute] public int RegistrationId { get; set; } [CustomRoute("{id}/Personnel/{personnelId}/Clearances/{clearanceId}")] public ActionResult Clearance(...) { ... } }
I don't know what kind of app you are working on, but I've never had the need for such a deep URL hierarchy, personally I would just go back to the root at some point, e.g. /Personnel/{personnelId}/Clearances/{clearanceId}, assuming those are unique identifiers.