MVC Routing ambiguous actions - asp.net-mvc

Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
public class my1Controller: Controller
[Route("path/{param1}", Name = "test1")]
public ActionResult myaction1(string param1)
public class my2Controller: Controller
[Route("path/{param2}", Name = "test2")]
public ActionResult myaction2(string param2)
Is there anyway of getting around this? For historical SEO I need to have two similar urls that have a different single string param.

Your URLS are identical, there is no way to distinguish between them, and both controllers/actions match. That is because the parameter name has no value in picking between the two routes
You could use inline constraints applied to the param1 and param2 to help routing pick one of them. Or make sure "path" is different

Related

MVC Route conflict seems strange

I am using MVC 5.1 with AutoFac.
I dont understand why the below route from each controller conflict with this URL: https://localhost:44300/Home/login
I thought it would map to the first method. I get this error though:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
AllThings.WebUI.Controllers.AccountController
AllThings.WebUI.Controllers.PostController
public class AccountController : Controller
{
//
// GET: /Account/Login
[Route("~/{site}/Login")]
[Route("~/Account/Login")]
[Route("~/{country:maxlength(2)}/{site}/Login")]
[Route("~/{country:maxlength(2)}/Account/Login")]
[AllowAnonymous]
public ActionResult Login(string returnUrl, string country, string site)
{
return View();
}
}
public class PostController : Controller
{
[Route("~/{site}/{CategoryUrl?}")]
[Route("~/{country:maxlength(2)}/{site}/{CategoryUrl?}", Name = "ResultList")]
[AllowAnonymous]
public ActionResult List(string country, string site, SearchCriteriaViewModel searchCriteriaViewModel)
{
return View("List", searchCriteriaViewModel);
}
}
The main problem is that you have 3 possible routes that can match /Home/Login.
[Route("~/{site}/Login")]
[Route("~/Account/Login")]
[Route("~/{site}/{CategoryUrl?}")]
Liberal use of placeholders, especially that is all you have in a URL template definition is not a good thing. You should use literals in the URL or if you use placeholders, there should be constraints on them so they don't conflict.
Note that the following routs conflict as well:
[Route("~/{country:maxlength(2)}/{site}/Login")]
[Route("~/{country:maxlength(2)}/Account/Login")]
[Route("~/{country:maxlength(2)}/{site}/{CategoryUrl?}", Name = "ResultList")]
Any one of them could match UK/Account/Login.
Additionally, using a tilde (~) is to override a route prefix (see the MSDN documentation). If your controller doesn't define one, you should just start with the first segment or placeholder.

ASP.NET MVC Routing: Clean Urls - from camel case to hyphenated words

I currently have an action defined in my controller:
// GET: /schools/:cleanUrlName/data-loggers
public ActionResult DataLoggers(string cleanUrlName)
{
return View();
}
This works when I hit "/schools/brisbane-state-high-school/dataloggers", however - as per the comment - I want to access it via a slightly cleaner url (using hyphens): "/schools/brisbane-state-high-school/data-loggers". I know I could write a route to accomplish this, but I was hoping I wouldn't have to write a new route for every multi-worded action/controller. Is there a better way to address this?
You can use the ActionNameAttribute to create an alias for your action name.
So you just need to annotate your multi worded actions:
[ActionName("data-loggers")]
public ActionResult DataLoggers(string cleanUrlName)
{
return View("DataLoggers");
}
But because this affects also the view discovery therefore you need to return View("DataLoggers") so you are probably better with creating custom routes for your multi worded actions.

Is it possible to have duplicate action names and parameter list for post and get?

is it possible to have 2 actions with the same name and parameters but one's a post, the other a get? e.g Delete(id) and [HttpPost]Delete(id)...i get an error saying that this is not allowed...
Yes, it's possible. Just use ActionName attribute on one action:
public ActionResult Delete(int id)
{
//...
return View();
}
[HttpPost]
[ActionName("Delete")]
public ActionResult Delete_Post(int id)
{
//...
return View();
}
The reason you get the error that it is not allowed is because C# itself gets confused. While in MVC you can add attributes to specify whether a function is HttpGet or HttpPost, that doesn't help C# determine the difference between one or the other. In order to have 2 functions with exactly the same name, the parameter list needs to be different.
As frennky pointed out, the ActionName attribute works in MVC because MVC uses aliases as part of the process for determining which action to call (along with attributes, but not parameters).
As a side note, it's probably best not to have a Delete action on a GET request. You don't want a crawler or some other bot accidently hitting the wrong link :P

How to get currently executing area?

I have a class used by controllers at [Project].Controllers and by controllers at different areas. How could I determine where the controller is at? (I guess I could look at the HttpContext.Current.Request's properties -but I am looking for a "proper" MVC way). Thank you.
That is:
[Project].Helpers // called by:
[Project].Controllers
[Project].Areas.[Area].Controllers
// how could I determine the caller from [Project].Helpers?
We purposefully did not expose a way to get the current area name from an MVC request since "area" is simply an attribute of a route. It's unreliable for other uses. In particular, if you want your controllers to have some attribute (think of the abstract term, not the System.Attribute class) which can be used by the helper, then those attributes must be found on the controllers themselves, not on the area.
As a practical example, if you want some logic (like an action filter) to run before any controllers in a particular area, you must associate the action filter with those controllers directly. The easiest way to do this is to attribute some MyAreaBaseController with that filter, then to have each controller that you logically want to associate with that area to subclass that type. Any other usage, such as a global filter which looks at RouteData.DataTokens["area"] to make a decision, is unsupported and potentially dangerous.
If you really, really need to get the current area name, you can use RouteData.DataTokens["area"] to find it.
You should be able to get the area string from RouteData:
// action inside a controller in an area
public ActionResult Index()
{
var area = RouteData.DataTokens["area"];
....
return View();
}
.. so you can make an extension method for helpers like this:
public static class SomeHelper // in [Project].Helpers
{
public static string Area(this HtmlHelper helper)
{
return (string)helper.ViewContext.RouteData.DataTokens["area"];
}
}

Overloading asp.net MVC controller methods with same verb?

All the examples I've seen for overloading usually have only two methods of the same name with different parameters and one using the GET verb while the other uses POST. Is it possible to do two or more overloads on the same method, all with the same verb?
Here's an example of what I'm referring to: Can you overload controller methods in ASP.NET MVC?
I don't think you can overload the same action name with the one verb by default. As that other thread you point to says, you can overload the methods & then use an attribute to change the action that maps to the method, but I'm guessing that's not what you're looking for.
Another option that I've used before (depends on how complex/different your overloads are) is to simply use nullable values for the parameters & effectively merge your different signatures together. So instead of:
public ActionResult DoSomething(int id)...
public ActionResult DoSomething(string name)...
just have:
public ActionResult DoSomething(int? id, string? name)
Not the nicest solution, but if one overload just builds on another then its not too bad a compromise.
One final option that may be worth giving a go (I haven't tried it & don't even know if it'll work, but logically it should), is to write an implementation of the ActionMethodSelectorAttribute that compares the parameters passed in the ControllerContext to the method signature & tries to make a best match (i.e. try to resolve the ambiguity a bit more strictly than the default implementation).
I guess it is not. Since I found that the MVC framework didn't really care what you put in the parameter list, for example, my action is like:
public ActionResult Index(int id) {...}
It is ok to request like this: Domain.com/Index.aspx
or Domain.com/Index.aspx?id=012901
or even Domain.com/Index.aspx?login=938293
Since overloading in programming language means that you select different functions (with same name) using the input parameters, but MVC in this case didn't care about it! So other than ActionVerb overloading, I think it is not ok.

Resources