Multiple URL routes to a single action method - c#

This is probably something very simple, but Googling feebly isn't getting me anywhere.
In the web application we're building, we've got Projects, and Templates. Templates are really just Projects, with the IsTemplate flag set to true. So naturally, we've got a single Project controller that handles (or will handle) both cases.
We've got one route to a New action method on the controller:
Project/New
That's just the standard {controller}/{action}/{id} route handling that one. Now, the New action method has an IsTemplate parameter. I'd like to have one route where that's passed in as false (the one above), and a second one where it's passed in as true:
Templates/New
What's the proper way to mask an arbitrary action method parameter with varying URLs like that? I tried the following, but it just confused the routing (Html.ActionLink ends up pointing at Templates/New):
routes.MapRoute(
null,
"Template/New",
new { controller = "Project", action = "New", IsTemplate = true }
);
Or would it be a lot simpler for me to just split this into two action methods, and have those call a single private controller method with a hard-coded parameter value?

Seems like this might be the simplest option. I've split the single New action method into two action methods, both of which have different routes defined, and a third private method that does all the work. I'm still open to recommendations if there's a prettier way to do it.
[HttpGet]
//Action method for a new template
public ActionResult NewTemplate() {
return New(true, null);
}
[HttpGet]
//Action method for a new project
public ActionResult New(int? TemplateProjectID) {
return New(false, TemplateProjectID);
}
private ActionResult New(bool IsTemplate, int? TemplateProjectID) {
//Assorted code follows

Perhaps RouteUrl can help you?
If you named the route to TemplateNew for example, you should be able to create an url for that:
New Template

Related

How to call a controller that is in another assembly when using Html.Action()?

I want to use method Html.Action() in my project.
I have two projects.
project 1 - area - HomeController - IndexAction
project 2 - i write a function helper to use in my layout.
public static IHtmlString RenderTest(this HtmlHelper htmlHelper)
{
string mhs = "";
mhs += htmlHelper.Action("Index", "Home", new { area = "area" });
return new MvcHtmlString(mhs);
}
for project 1, I write a route map:
context.MapRoute("area_default",
"Theme/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { string.Format("{0}.Controllers", this.GetType().Namespace) }
);
How can I use this function to load a controller that is in another assembly?
Html.Action("Index","Home", new { area = "area" });
In addition I have a duplicate controller names in each assembly,
e.g. Namespace1.FooController and Namespace2.FooController
I don't have a problem with my routes. Also, I can call any controller in different assemblies via URL/routes.
But I cann't use these urls in my HtmlHelper.Action().
Actually I want to call an action in controller that is in another assembly and get the view of that action as HtmlString but processed.
If you are sure you don't have duplicate controller names in each assembly, e.g. Namespace1.FooController and Namespace2.FooController, then you can simply just add all the namespaces that should be searched in the array for the namespaces param.
However, if you do have duplicate names, then the route will end up matching multiple controllers, which is a problem. There's no order of ops to the namespaces parameter - all are searched and all are treated equally, despite the order of the namespaces. If that's the case, then you'll have to define multiple routes, each tied to a specific namespace.
UPDATE
actually i want call a action in controller in another assembly and get the view of that action as HtmlString but processed.
Oh. You mean you literally want to call the action like a method in another piece of code, rather than get to it via a URL? You could use something like WebClient to actually make a request and get the response as a string. That's admittedly probably not the most efficient way, but it likely would be the easiest.
The alternative is much more complicated. If it's just a regular action that returns a ViewResult, then that's what you'll actually get back from calling it like a method. However, from a quick debugging session, it appears that the ViewResult is not processed until after it's returned from the action and goes back into pipeline (not just another action). As a result, all you get is an object with the name of the view to be used and all the view data that should be used to render it with, i.e. not an actual rendered string that you could do anything with. However, using something like RazorEngine, you might be able to manually take the data from the ViewResult and do something with it.

WebAPI controller same action different parameters

I have a base controller as with 2 actions:
[ActionName("Find")]
[HttpGet]
public virtual IHttpActionResult Find(string name)
{
return null;
}
[ActionName("Find")]
[HttpGet]
public virtual IHttpActionResult Find(int number)
{
return null;
}
Some of my controllers use the different Find method, for example:
public override IHttpActionResult Find(string number)
{
return OK;
}
However, I get an error when calling this action from the client:
Multiple actions were found that match the request: \r\nFind on type API.Controllers.CustomerController
How can I solve this problem?
The only way to solve this is by changing the ActionName attribute for one of the actions.
ASP.NET MVC /Web API doesn't support two actions with the same name and same HTTP verb in the same controller.
Also take a look at this question (ASP.NET MVC ambiguous action methods) if you want to go for the 'hack solution' (my opinion).
Why don't you just pass both parameters to the same action method? And inside your method simply check if they are null and do something about it.
Use string and int? (nullable int) to allow both parameters to contain nulls.
This way you get to use one view without any attribute jiggery pockery.
I think, you should reconsider your endpoint structure:
An action that selects one element from a resource collection should do that along the key of the resource (i.e. the unique database key). This key can be either of type int or alphanum, but not both.
What you probably want to realize with one or both of your finds, is to establish a filter function. Filter parameters should be passed to REST endpoints as query string parameters.
Examples:
/api/employees → returns resource set with all employees
/api/employees/5 → returns single resource (one employee)
/api/employees?name=john → returns resource set with all employees named "john"
Example 3 is a filter, and I guess at least one of your finds is just that.

Is the Web API routing engine smart enough so that identical routing attributes can be applied to different methods?

Consider a method with this routing attribute:
[Route("api/Deliveries/{ID:int}/{CountToFetch:int}")]
The method with these routing attributes is called by the client passing a URI like this:
http://localhost:28642/api/deliveries/42/100
So this way, the path info ("api/Deliveries") and the args ({ID:int}/{CountToFetch:int}) appear to be homogeneous and/or homologous.
The method on the Controller that is called gets these args assigned to it, as this makes clear:
public IEnumerable<Delivery> GetBatchOfDeliveriesByStartingID(int ID, int CountToFetch)
{
return _deliveryRepository.GetRange(ID, CountToFetch);
}
This works fine.
BUT there is another, perhaps more "accepted" way of forming routing attributes, where args are excluded:
[Route("api/Deliveries")]
...wherewith GetBatchOfDeliveriesByStartingID() is now called this way:
http://localhost:28642/api/deliveries?ID=42&CountToFetch=100
This is fine (maybe); what gives me pause is, now that the routing attribute has been simplified so, how does the routing engine know the difference between GetBatchOfDeliveriesByStartingID(), and GetDeliveryById(), for instance, which currently has this routing attribute:
[Route("api/Deliveries/{ID:int}")]
...but under the new regime would have:
[Route("api/Deliveries")]
...and be called like so:
http://localhost:28642/api/deliveries?ID=42
IOW, the two methods have the exact same routing attribute applied to them.
Is the Web API routing mechanism smart enough to still invoke the appropriate method based on the args passed in the URI and/or based on the return type of the two methods being different (e.g., the former returns an IEnumerable of Delivery, whereas the latter returns a single Delivery)?
Yes, the Web API routing engine is, if not as smart as Einstein, at least as smart as your average Congressman
This code:
[Route("api/Deliveries")]
public Delivery GetDeliveryById(int ID)
{
return _deliveryRepository.GetById(ID);
}
[Route("api/Deliveries")]
public IEnumerable<Delivery> GetBatchOfDeliveriesByStartingID(int ID, int CountToFetch)
{
return _deliveryRepository.GetRange(ID, CountToFetch);
}
...works fine.
If I pass this:
http://localhost:28642/api/deliveries?ID=7
...GetDeliveryById() is called.
...and if I pass this:
http://localhost:28642/api/deliveries?ID=7&CountToFetch=42
...GetBatchOfDeliveriesByStartingID() is called.

What's the best way to generically map routes with required parameters?

I have several ASP.NET MVC controllers. Many of these take one or more required values (e. g. ids). Because these values are required, I'd like to make them part of the url path rather than query string arguments. For example:
// route should be MyController/Action1/[someKindOfId1]
public ActionResult Action1(int someKindOfId1) { ... }
// less commonly:
// route should be MyController/Action1/[someKindOfId2]/[someKindOfId3]
public ActionResult Action2(int someKindOfId2, int someOtherKindOfId3) { ... }
I'm looking for a way to Map these routes without manually listing out each one. For example, I currently do:
routes.MapRoute(
"Action1Route",
"MyController/Action1/{someKindOfId1}",
new { controller = "MyController", action = "Action1" }
);
Some ways I've considered:
* Use the default {controller}/{action}/{id} route, and just either rename my parameters to id or (not sure if this works) use the [Bind] attribute to allow bind them to the id route value while still having descriptive names. This still restricts me to a common controller/action base URL (not bad, but not the most flexible either as it ties URLs to the current code organization).
* Create an attribute which I could put on action methods to configure their routes. I could then reflect over all controllers and configure routes on application start.
Is there a best-practice/built-in approach for doing this?
Sadly, no. The method you describe is the only way with MVC Routing. If you're not going to use the default (or at least your own version of the default), you must add a separate route for each unique scheme.
However, I would encourage you to check out AttributeRouting, which for me at least, is far superior to managing routes in the traditional way. With AttributeRouting, you specify the URL for each controller action using, appropriately enough, an attribute. For example:
[GET("MyController/Action1/{someKindOfId1}")]
public ActionResult Action1(int someKindOfId1) { ... }
[GET("MyController/Action1/{someKindOfId2}/{someKindOfId3}")]
public ActionResult Action2(int someKindOfId2, int someOtherKindOfId3) { ... }
Only, you're not bound to using the controller/action route scheme either, so you can do something like:
[GET("foo/{someKindOfId1}")]
public ActionResult Action1(int someKindOfId1) { ... }
[GET("foo/{someKindOfId2}/{someKindOfId3}")]
public ActionResult Action2(int someKindOfId2, int someOtherKindOfId3) { ... }
And to even better, you can add a RoutePrefix attribute to your controller itself to specify a path partial that should apply to all actions in that controller:
[RoutePrefix("foo")]
public class MyController : Controller
{
[GET("{someKindOfId1}")]
public ActionResult Action1(int someKindOfId1) { ... }
[GET("{someKindOfId2}/{someKindOfId3}")]
public ActionResult Action2(int someKindOfId2, int someOtherKindOfId3) { ... }
}
There's support for handling areas, subdomains, etc. as well and you can even type-qualify parameters (e.g. {someKindOfId1:int} to make it only match if the URL part is an integer type). Give the documentation a read.
UPDATE
It's worth mentioning that ASP.NET 5 now has attribute routing built in. (It's actually using very similar code to AttributeRouting, submitted by the author of that package.) It's not really a good enough reason on its own to upgrade all your projects (since you can just add in the AttributeRouting package to get basically the same functionality), but if you're starting off with a new project, it's definitely nice to have.

ASP.NET MVC - POST Action Method with Additional Parameters from URL

With ASP.net MVC is it possible to POST a form to a controller action which includes parameters not in the form, but from the URL?
For example
The Action method in GroupController:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(int idOne, int idTwo, Model model)
{ ... }
The route:
"{controller}/{action}/{idOne}/{idTwo}"
Posted URL:
/Employee/Show/1/42
In this example, the form is being posted to a different controller, the model has the correct value, however the other parameters have default values 0.
The behavior I was expecting is that the ModelBinder would see that I have two parameters that match the given route, and assign the current values of 1 and 42 to the parameters in the same same way a GET operation works.
Is this behavior not supported, or am I missing something?
EDIT:
To be clear, the form on the Show view for the controller Employee contains a form which is posting to a different controller. We can call it Group.
The form action URL looks like this
/Groups/Create/0/0
The form is declared as follows
Html.BeginForm("Create", "Groups")
After trying many different overloads for Html.BeginForm I have found that the parameters are only mapped when the form action URL matches the current URL in the browser address bar.
So if i navigate to the URL /Groups/Create/1/42 I will have a new form. If I then submit the form, the URL route values are passed to the POST action.
If I understand your question correctly, you want the action of the rendered <form> element pointing to URL containing route values. This should be possible with one of the overloads of the HtmlHelper.BeginForm() extension method:
Html.BeginForm("action","controller", new { idOne=1, idTwo=2 }, FormMethod.Post);
Let me know if I got your question all wrong :)
I'm pretty sure that you can only post form data from inputs inside the form. Have you considered rendering the view in such a way to create form input values off of the URL (perhaps with an HTML helper?).
UPDATE: If you don't want to use the form at all, use ControllerContext.RouteData.Values["idOne"] as opposed to passing it in through the method signature.
I had a similar problem, the configuration RouteConfig has solved this problem.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
...
routes.MapRoute(
"MyRoute",
"{controller}/{action}/{idOne}/{idTwo}",
new
{
controller = "Employee", // as an example
action = "Show",
idOne = UrlParameter.Optional,
idTwo= UrlParameter.Optional
}, new { idOne = #"\d{1,5}" });
}
}
...And...
Html.BeginRouteForm()
#using (Html.BeginRouteForm("MyRoute", new { idOne = 1, idTwo= 2 }, FormMethod.Post))
Writes an opening tag to the response. When the user submits
the form, the request will be processed by the route target.
This member is overloaded. For complete information about this member,
including syntax, usage, and examples, click a name in the overload
list.
And all works
I recently had this issue too, and because I had a different route, it was mapping to the default route, without taking into account the extra route params that I was passing in.
So to get it working quickly, I wrote the form using form tags instead, and used #Url.Action to create the required action.
Once you get to your Create view, your route values used to get there need to be re-posted to the post action.
So, one option is having a pair of hiddens, to hold the ids that came from the route. This way, once you post the formas, its values are posted along with the other inputs.

Categories

Resources