MVC 3 AREAS - Hierarchial workflows - c#

For the project that I am working on, we have companies. Companies have contacts and facilities. Based on the business rules, the flow is you select a company to access the contacts or facilities.
EDIT:
Entities are companies, facilities and contacts.
As each entity has it's own workflows, so they all have their one AREA in the code. What would be a clean way to make sure tha the routing url would be something like below:
/Company/1234/contact/456
/Company/1234/facility/679
If there was a way to next areas that would seem like a good way, but could make the code messy.

I don't think you need to use areas
To do what you want could be done by defining routes in the global.asax for each "subcontroller" to help the engine. (I have assumed that Contact and Facility are separate controllers?)
It does mean being very specific about what pattern goes to what route, but I think the below will do what you need.
Add these 2 new routes in the global asax (above the default route):
routes.MapRoute(
"ContactRoute", // Route name
"Company/{id}/Contact/{action}/{contactId}", // URL with parameters
new { controller = "Contact", action = "Index"
} // Parameter defaults
);
routes.MapRoute(
"FacilityRoute", // Route name
"Company/{id}/Facility/{action}/{facilityId}", // URL with parameters
new { controller = "Facility", action = "Index"
} // Parameter defaults
);
I'm not 100% on the code, you might need to define the contactId and facilityId as optional, but I hope it gives you an idea?

Related

determine routename, controller, action area in net core controller

I am trying to write a class to determine the next step when navigating through an online journey in a net core mvc app.
I would like this journey to be configurable - allowing the step order to be altered based on some configuration.
I figured the easiest way to do this would be to hold a list of 'steps' which would have the controller and action (possibly area and routename), locate the current step in this, and simply generating a redirect based on whatever is next in the list.
I am having trouble getting all of this. I can get the current controller and action fine, using the ControllerContext.RouteData.Values["controller"]/["action"] with no problem, but getting the routename appears to be a bit trickier.
the RouteData also hold a list of Routers, but it is not clear which one of these is the one used for the current request.
is it possible to get the route name? or am I barking up the wrong tree..? or do i need to effectively run the current url back through the MVC routing and grab what I need from that.
we are currently using conventional routing set in the UseMvc(routes=>{... method in the startup.. with a number of routes in, currently split between different 'products'.
For instance:
UseMvc(routes =>{
routes.MapRoute(
name: "prod1",
template: "product1/{controller=AboutYou}/{action=Index}/{id?}");
routes.MapRoute(
name: "prod2",
template: "product2/{controller=AboutYou}/{action=Index}/{id?}");
});

WebAPI Controller Methods that Differ By Value Types

I have an existing API I'm moving over to WebAPI, so I'm not free to change the URL. Breaking existing clients is not an option for me.
Knowing that, the original API would accept either a Guid (an ID) or a string (a name) for a given action method. The old API handler would decipher the URL parameter and send the request to a controller action designed to accept the given parameter type.
As an example:
Get(Guid id)
versus
Get(string name)
With WebAPI, the parameter binding is greedy across value types, so depending on which is first in the controller source file, that action is the one invoked. For my needs, that's not working. I was hoping the binder would realize the conversion to a Guid would fail for a name and then select the more generic string-based action. No dice. The Guid simply comes across as a null value (interestingly since it's a value type, but that's what I'm getting in the debugger at a certain point in the processing).
So my question is how best to handle this? Do I need to go as far as implementing a custom IHttpActionSelector? I tried the attribute routing approach (with constraints), but that isn't working quite right (bummer as it looks cool). Is there a mechanism in WebAPI that accounts for this I don't (yet) know about? (I know I can hack it in by testing the string for Guid-ness and invoking the other controller method, but I'm hoping for a more elegant, WebAPI-based solution...)
I'd spent a lot of time trying to fit attribute-based routing in, but I've not got that to work. However, I did solve my particular issue using route constraints. If you register the more-constrained route first, WebAPI (like MVC) will apply the constraints and skip over more-constrained routes until it finds one that it can select, if any.
So, using my example, I'd set up routes like so:
_config.Routes.MapHttpRoute(name: "ById",
routeTemplate: "product/{id}",
defaults: new { controller = "ProductDetails" },
constraints: new { id = #"^\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?$" });
_config.Routes.MapHttpRoute(name: "ByName",
routeTemplate: "product/{name}",
defaults: new { controller = "ProductDetails" });
The first route accepts a constraint in the form of a regular expression for a Guid. The second accepts all other values, and the controller will deal with non-product names (returns a 404). I tested this in the self-hosted WebAPI server and it works fantastically.
I am sure attribute-based routing would be more elegant, but until I get that to work, it's routing the old way for me. At least I found a reasonable WebAPI-based solution.

Possible to create catch all route for a controller?

For a specific controller, is it possible to route any action that does not exist to the index?
For example if I have
fashionController/
fashionController/shoes/
fashionController/bags/
fashionController/otherStuff/
I want to be able to only setup the Index view & action and that anything else will just use the Index automatically without having to create separate views/actions for anything else.
Yes.
There is no requirement that controller or action name are part of Url. For example you can route all "fashion/*" to the same action with following route.
routes.MapRoute(
"AllToIndex",
"fashion/{argument1}",
new { controller = "fashion", action = "Index", argument1 = "" }
);
Note that routes matched in order they are added, so if you register this route after default "{controller}/{action}" one it will never be matched. Generally more specific routes should go before more generic once and last should be optional "cath'em all" one with "{*path}" math.

MVC URL path extensions

I was just wondering whether its possible to have something like this: I have an Area named Admin and a Controller named 'Edit'. Within this controller I have my Index() which simply lists a bunch of hyperlinks that is treated by the 'Brand' action.
Therefore my url so far is: Admin/Edit/{Brand}.
My question is whether it is possible to have for example: Admin/Edit/{Brand}/Create (as well as edit and delete). This isn't to delete brands, its just to create things within those brands?
I approach that my approach may be misguided and this may necessitate being split into multiple controllers or whatever so don't think that I would like a workaround to make it work this way.
You could define the following route in your area registration:
context.MapRoute(
"Admin_default",
"Admin/{controller}/{brand}/{action}",
new { action = "Index" }
);
And if you wanted to have other controllers than Edit in this area which have the default route, you could register 2 routes but you will have to define a constraint for the {brand} token or the routing engine won't be able to disambiguate between a brand and a controller action name.

Why does RedirectToRoute("Default") not redirect to the root?

Given these routes:
routes.MapRoute("Test", "test", new { controller = "Test", action = "Index" });
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
If I call RedirectToRoute("Default") from the Index action of the TestController it redirects to /test but I expected it to redirect to /
I checked the result of calling RedirectToRoute("Default") before returning it during a debugging session.
RedirectToRouteResult result = RedirectToRoute("Default");
It has a property RouteName with a value "Default" and a property RouteValues with no elements (Count = 0). I checked using Reflector, and null is passed internally as the RouteValueDictionary.
Again, I would expect that given the defaults for the route defined in my application, it would redirect to Index view on the HomeController.
Why doesn't it redirect to /?
The RouteValueDictionary filled in to the current action is being used to fill in the Controller, Action, ID. This is usually the functionality you want and lets you do things like <%:Html.ActionLink("MyAction")%> without needing to specify your controller in a view.
To force it to the complete fall back default just use:
RedirectToRoute("default", null");
The second argument is for your routevalues and by specifying it you override the preexisting values.
However I would say the preferred way would be to have a Home route which will not take any parameters. This means your redirects will not need a load of nulls floating around and a call to RedirectToRoute("Home") is also nice and explicit.
Edit
This is a really old answer and as a couple of people have mentioned doesn't seem to have been working for them. For what it's worth I now don't use this pattern and explicitly override controller and area when I need to break out. Not only does it apparently work better but when you come back to the code it's good to see explicitly that you're coming out of this context and mean to blank out certain routevalues.
For the record I have also tended more towards named route based URL generation over convention based generation.
I don't think that "null" parameter Chao mentions has ever worked for me. Perhaps it did in prior MVC versions, but now in MVC 5.2 this is the only way I can get it to work:
RedirectToRoute("Default", new { controller = "", action = "" });
Without looking at the actual code to see what is going on it can be a bit tricky to know why, but this forum post does shed some light. The answerer says that under the covers it might be creating a RouteValueDictionary which would contain the controller and action you are currently in, which in your case would be a controller of Test and an action of Index. Then, when you call RedirectToRoute("Default"), the controller and the action in the RouteValueDictionary will be used when matching the default route and you will be taken to the Index action in your Test controller.
Now you could always do a Redirect("/") to take you to the main page of your site.

Categories