MVC Alternative routing is failing with optional parameter - c#

I am trying to implement alternative routing in my MVC 5 web app.
I have the controller code:
namespace MyMvcProject.Web.Controllers
{
[RoutePrefix("account")]
public class AccountController : Controller {
[Route("{param}")]
[Authorize]
public ActionResult Index(string param = null) {
...
which works great when hitting the url: http://example.com/account/testparam.
However, I would like to be able to have the value param as an optional parameter.
I have tried changing [Route("{param}")] to [Route("{param}*")] but then the Index() method is never entered. I have also tried changing it to [Route("{param:string=Test}")] but I get a routing runtime error with the term string.
My RoutConfig.cs contains:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{param}",
defaults: new { controller = "Home", action = "Index", param = UrlParameter.Optional }
);
}
Does anyone have any idea how I could force Index() to have an optional parameter using this alternative routing syntax? It's quite useful in other parts of my app, so I'd like to keep the RoutePrefix and Route decorators.
UPDATE
I'm still trying to figure this out, so I changed my route decorator to [Route("{param:int=0}")] and my constructor to public ActionResult Index(int id) and it works as expected (i.e., http://example.com/account behaves as if http://example.com/account/0 was entered. This is exactly what I want, only using string datatypes.
When I change the decorator to: [Route("{id:string=\"\"}")] and the constructor to public ActionResult Index(string id) and I see the runtime error:
The inline constraint resolver of type 'DefaultInlineConstraintResolver' was unable to resolve the following inline constraint: 'string'.

Found the answer here. I need to make param nullable using ?.
[Route("{param?}")]
[Authorize]
public ActionResult Index(string param = null) {
...
}
Hopefully this will help someone in the future.
Not many references that I could find on the topic.

#Brett's answer is great: adding ? to the parameter in the Route attribute and then providing a default value in the Action signature allows that parameter to be optional.
And here's an excellent article from Mike Wasson on Attribute Routing in Web API 2, which includes a piece on optional parameters.
Mike also talks about applying multiple constraints, and says:
You can apply multiple constraints to a parameter, separated by a colon.
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { ... }
which also works great...until you want multiple constraints and an optional parameter.
As we know, applying ? allows the parameter to be optional. So taking the above example, one might assume that
[Route("users/{id:int?:min(1)}")]
public User GetUserById(int id = 1) { ... }
constrains the id parameter to be integers greater than 0, or empty (in which case the default of 1 is used). In reality, we get the error message:
The inline constraint resolver of type 'DefaultInlineConstraintResolver' was unable to resolve the following inline constraint: 'int?'.
It turns out that the order of the constraints matters! Simply putting the optional constraint ? after all other constraints gives the intended behavior
[Route("users/{id:min(1):int?}")]
public User GetUserById(int id = 1) { ... }
This works for more than 2 constraints as well, e.g.
[Route("users/{id:min(1):max(10):int?}")]
public User GetUserById(int id = 1) { ... }

Related

default route with id does not working in asp.net core

This is my controller:
public class HomeController : Controller
{
public IActionResult Index()
{
var route = Request.Path.Value;
return View("index" as object);
}
[HttpGet("{id}")]
public IActionResult Index(int id)
{
return View("index id" as object);
}
}
This is my route:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
url : /1 -> return index with id
url : /Home/Index/1 -> return index without id
I don't understand why?
You're using mixed routing - you've got conventional routing for the first action and attribute routing for the second.
When you navigate to /1, you hit the second action with the id parameter, because it's been set to use attribute routing for a path of /{id} (by using [HttpGet("{id}")]): Attribute routing overrides conventional routing.
When you navigate to /Home/Index/1, you hit your first action without the id parameter, simply because the other action you have is no longer a match given that it's been set to use attribute routing (/{id}), so it no longer matches /Home/Index/1 at all. With your conventional routing template from UseMvc, you've said that id is optional and so the match is still valid.
In order to achieve what you're looking for, you can use attribute routing exclusively for this controller. Here's what that would look like:
[Route("/")]
[Route("[controller]/[action]")]
public class HomeController : Controller
{
public IActionResult Index()
{
...
}
[HttpGet("{id}")]
public IActionResult Index(int id)
{
...
}
}
The addition of the two [Route(...)] attributes here adds support for the following two routes:
/ and /{id}.
/Home/Index and /Home/Index/{id}.
[controller] and [action] are placeholders that represent the name of the controller and action respectively - You could also just use the literal values Home and Index if you'd prefer something more fixed.
You don't necessarily need both [Route(...)] attributes, but the / version ensures the root of the site also matches the same controller/action pair.

MVC Conventional and Attribute routing not working together

I am using conventional routing on an ASP.Net MVC project and would like to enable Attribute routing in parallel. I have created the following but I am getting a 404 on the conventional route when enabling attribute routing
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Controller
[RoutePrefix("Registration")]
public class RegistrationController : Controller
{
[HttpGet]
[Route("Add/{eventId}")]
public ActionResult Add(int eventId)
{
}
}
Calling
http://localhost/Registration/Add/1
Works, while calling
http://localhost/Registration/Add?eventId=1
No longer works and responds with 404 NotFound
Should work if you make the {eventId} template parameter optional in the route template
[RoutePrefix("Registration")]
public class RegistrationController : Controller {
//GET Registration/Add/1
//GET Registration/Add?eventId=1
[HttpGet]
[Route("Add/{eventId:int?}")]
public ActionResult Add(int eventId) {
//...
}
}
The reason the two were not working is that the route template Add/{eventId} means that the route will only match if the {eventId} is present, which is why
http://localhost/Registration/Add/1
works.
By making it (eventId) optional eventid? it will allow
http://localhost/Registration/Add
to work as the template parameter is not required. This will now allow query string ?eventId=1 to be used, which the routing table will use to match the int eventId parameter argument on the action.
http://localhost/Registration/Add?eventId=1
I also got this issue. Which MVC version are you using?
I faced this issue with MVC in asp.net core.
I think this is a flaw as if you provide Routing attribute on any action method, its conventional route is over ridden and is not longer available so you get 404 error.
For this to work, you can provide another Route attribute to your action method like this. This will work
[Route("Add/{eventId}")]
[Route("Add")]

ASP.NET MVC Attribute Routing - parameter is always null

Recently I have faced the following issue. Let's suppose that we have following controller with GET method inside:
[RoutePrefix("admin-panel")]
public class AdminPanelController : Controller
{
[Route("places/edit/{placeId}")]
public ActionResult EditPlace(int? placeId)
{
return View("EditPlace", new EditPlaceViewModel(...));
}
}
Now we can access this method by url:
(...)/admin-panel/places/edit/123
The problem is that the placeId parameter is always null.
If I change the EditPlace method routing rule to following:
[RoutePrefix("admin-panel")]
public class AdminPanelController : Controller
{
[Route("places/{placeId}/edit")]
public ActionResult EditPlace(int? placeId)
{
return View("EditPlace", new EditPlaceViewModel(...));
}
}
Everything starts working properly - placeId parameter is being passed successfuly.
What am I missing here? Why can't I use first solution?
Thanks in advance!
#update
OK, I've missed that I have the POST methods with the same routing rules which look like:
[HttpPost]
[Route("places/edit/{placeId}")]
[MultipleSubmitButton(Name = "action", Argument = "NextEditStep")]
public ActionResult NextEditStep(int? placeId, EditPlaceViewModel model)
{
// do some operations with posted model
return View("EditPlace", new EditPlaceViewModel(...));
}
[HttpPost]
[Route("places/edit/{placeId}")]
[MultipleSubmitButton(Name = "action", Argument = "PreviousEditStep")]
public ActionResult PreviousEditStep(int? placeId, EditPlaceViewModel model)
{
// do some operations with posted model
return View("EditPlace", new EditPlaceViewModel(...));
}
If I comment them out, the problem walk away, but to be honest - I need it due to form generating. Is there any chance to have those 3 methods with the same routing rules?
I have similar controller with similar 3 methods (1 GET & 2 POSTS) but they do not have any route parameters. Anyway this routing works great and behaves as expected. The only difference is that the first one have route parameters and the second does not.
[Route("places/edit/{placeId: int}")]
Try this
It's possible to use optional parameters in route, like this:
[Route("places/edit/{placeId?}")]
(edit)
https://blogs.msdn.microsoft.com/webdev/2013/10/17/attribute-routing-in-asp-net-mvc-5/#optionals-and-defaults

ASP.NET MVC enum argument in controller mapping

ASP.NET MVC provides simple templates for controller methods such as Details, and can have something like:
public ActionResult Details(int id)
{
// do something
}
This can be accessed by: http://localhost:port/Controller/Details/id
What I'm trying to do is instead provide a different type like:
public enum MyEnum
{
All,
Pending,
Complete
}
And then I setup my controller method like:
public ActionResult MyMethod(MyEnum myEnum = MyEnum.Pending)
{
// do something
}
This works fine for: http://localhost:port/Controller/MyMethod/ because it uses the default argument.
To specify a different argument I have to do http://localhost:port/Controller/MyMethod?myEnum=All and that works.
I'm wondering, is it possible for me to be able to do http://localhost:port/Controller/MyMethod/All instead of using ?myEnum=All?
Upon trying to do it that way I get a 404 exception which is understandable, but why doesn't this happen for id in Details?
Can I change the MapRoute which is currently: url: "{controller}/{action}/{id}" to allow me to achieve it with my own type?
What I've tried so far:
I only want this route enforcement for one of my schemes such as http://localhost:port/Controller/MyMethod/{ViewType}, I tried this but it doesn't seem to do anything:
routes.MapRoute(
"MyRoute",
"MyController/Index/{MyEnum}",
new { controller = "MyController", action = "Pending" }
);
/Controller/MyMethod/All will actually work. The problem is with the default route, which will consider All to be the id route parameter, which doesn't line up with what your action is using as a parameter. It would actually work fine if your action signature was:
public ActionResult MyMethod(MyEnum id = MyEnum.Pending)
Since it will then bind All to the right thing.
You could add another route for this use-case, but you'll need to be careful that you don't just create another "default" route, which will take over. In other words, you'll have to fix part of the URL:
routes.MapRoute(
"MyCustomRoute",
"Controller/{action}/{myEnum}",
new { controller = "Controller", action = "MyMethod", myEnum = MyEnum.Pending }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Then, by the mere presence of the /Controller/ prefix to the route, it will use your custom route instead, and fill in All for the myEnum param, rather than hitting the default route and try to fill in id.
However, be advised that when using enums as route params, they must be exact matches. So, while /Controller/MyMethod/All will work, /Controller/MyMethod/all will not. To get around this, you'll have to create a custom model binder. I did a quick search and found the following article which may help you in that regard.
You can indeed. Do not change the default route "{controller}/{action}/{id}", but rather add one before the default. This new one needs to be fairly specific:
routes.MapRoute(
"EnumRoute",
"Controller/MyMethod/{myEnum}",
new { controller = "Controller", action = "MyMethod", myEnum = UrlParameter.Optional }
);
What that basically says is "when you see request to literally Controller/MyMethod/whatever, use this controller and that method and pass whatever as parameter of the request". Note that actual controller does not necessary have to be what route says in the url, although it is a good idea to stick to that.

Can you overload controller methods in ASP.NET MVC?

I'm curious to see if you can overload controller methods in ASP.NET MVC. Whenever I try, I get the error below. The two methods accept different arguments. Is this something that cannot be done?
The current request for action 'MyMethod' on controller type 'MyController' is ambiguous between the following action methods:
You can use the attribute if you want your code to do overloading.
[ActionName("MyOverloadedName")]
But, you'll have to use a different action name for the same http method (as others have said). So it's just semantics at that point. Would you rather have the name in your code or your attribute?
Phil has an article related to this: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx
Yes. I've been able to do this by setting the HttpGet/HttpPost (or equivalent AcceptVerbs attribute) for each controller method to something distinct, i.e., HttpGet or HttpPost, but not both. That way it can tell based on the type of request which method to use.
[HttpGet]
public ActionResult Show()
{
...
}
[HttpPost]
public ActionResult Show( string userName )
{
...
}
One suggestion I have is that, for a case like this, would be to have a private implementation that both of your public Action methods rely on to avoid duplicating code.
Here's something else you could do... you want a method that is able to have a parameter and not.
Why not try this...
public ActionResult Show( string username = null )
{
...
}
This has worked for me... and in this one method, you can actually test to see if you have the incoming parameter.
Updated to remove the invalid nullable syntax on string and use a default parameter value.
No,No and No. Go and try the controller code below where we have the "LoadCustomer" overloaded.
public class CustomerController : Controller
{
//
// GET: /Customer/
public ActionResult LoadCustomer()
{
return Content("LoadCustomer");
}
public ActionResult LoadCustomer(string str)
{
return Content("LoadCustomer with a string");
}
}
If you try to invoke the "LoadCustomer" action you will get error as shown in the below figure.
Polymorphism is a part of C# programming while HTTP is a protocol. HTTP does not understand polymorphism. HTTP works on the concept's or URL and URL can only have unique name's. So HTTP does not implement polymorphism.
In order to fix the same we need to use "ActionName" attribute.
public class CustomerController : Controller
{
//
// GET: /Customer/
public ActionResult LoadCustomer()
{
return Content("LoadCustomer");
}
[ActionName("LoadCustomerbyName")]
public ActionResult LoadCustomer(string str)
{
return Content("LoadCustomer with a string");
}
}
So now if you make a call to URL "Customer/LoadCustomer" the "LoadCustomer" action will be invoked and with URL structure "Customer/LoadCustomerByName" the "LoadCustomer(string str)" will be invoked.
The above answer i have taken from this codeproject article --> MVC Action overloading
To overcome this problem you can write an ActionMethodSelectorAttribute that examines the MethodInfo for each action and compares it to the posted Form values and then rejects any method for which the form values don't match (excluding the button name, of course).
Here's an example:- http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/
BUT, this isn't a good idea.
As far as I know you can only have the same method when using different http methods.
i.e.
[AcceptVerbs("GET")]
public ActionResult MyAction()
{
}
[AcceptVerbs("POST")]
public ActionResult MyAction(FormResult fm)
{
}
I have achieved this with the help of Attribute Routing in MVC5. Admittedly I am new to MVC coming from a decade of web development using WebForms, but the following has worked for me. Unlike the accepted answer this allows all the overloaded actions to be rendered by the same view file.
First enable Attribute Routing in App_Start/RouteConfig.cs.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Optionally decorate your controller class with a default route prefix.
[RoutePrefix("Returns")]
public class ReturnsController : BaseController
{
//.......
Then decorate your controller actions that overload each other with a common route and parameters to suit. Using type constrained parameters you can use the same URI format with IDs of different types.
[HttpGet]
// Returns
public ActionResult Index()
{
//.....
}
[HttpGet]
[Route("View")]
// Returns/View
public ActionResult View()
{
// I wouldn't really do this but it proves the concept.
int id = 7026;
return View(id);
}
[HttpGet]
[Route("View/{id:int}")]
// Returns/View/7003
public ActionResult View(int id)
{
//.....
}
[HttpGet]
[Route("View/{id:Guid}")]
// Returns/View/99300046-0ba4-47db-81bf-ba6e3ac3cf01
public ActionResult View(Guid id)
{
//.....
}
Hope this helps and is not leading somebody down the wrong path. :-)
You could use a single ActionResult to deal with both Post and Get:
public ActionResult Example() {
if (Request.HttpMethod.ToUpperInvariant() == "GET") {
// GET
}
else if (Request.HttpMethod.ToUpperInvariant() == "POST") {
// Post
}
}
Useful if your Get and Post methods have matching signatures.
I've just come across this question and, even though it's quite old now, it's still very relevant. Ironically, the one correct comment in this thread was posted by a self-confessed beginner in MVC when he wrote the post. Even the ASP.NET docs are not entirely correct. I have a large project and I successfully overload action methods.
If one understands routing, beyond the simple {controller}/{action}/{id} default route pattern, it might be obvious that controller actions can be mapped using any unique pattern. Someone here talked about polymorphism and said: "HTTP does not understand polymorphism", but routing has nothing to do with HTTP. It is, simply put, a mechanism for string pattern matching.
The best way to make this work is to use the routing attributes, for example:
[RoutePrefix("cars/{country:length(3)}")]
public class CarHireController
{
[Route("{location}/{page:int=1}", Name = "CarHireLocation")]
public ActionResult Index(string country, string location, int page)
{
return Index(country, location, null, page);
}
[Route("{location}/{subLocation}/{page:int=1}", Name = "CarHireSubLocation")]
public ActionResult Index(string country, string location, string subLocation, int page)
{
//The main work goes here
}
}
These actions will take care of urls like /cars/usa/new-york and /cars/usa/texas/dallas, which will map to the first and second Index actions respectively.
Examining this example controller it's evident that it goes beyond the default route pattern mentioned above. The default works well if your url structure exactly matches your code naming conventions, but this is not always the case. Code should be descriptive of the domain, but urls often need to go further because their content should be based on other criteria, such as SEO requirements.
The benefit of the default routing pattern is that it automatically creates unique routes. This is enforced by the compiler since urls will match unique controller types and members. Rolling your own route patterns will require careful thought to ensure uniqueness and that they work.
Important note The one drawback is that using routing to generate urls for overloaded actions does not work when based on an action name, e.g., when using UrlHelper.Action. But it does work if one uses named routes, e.g., UrlHelper.RouteUrl. And using named routes is, according to well respected sources, the way to go anyhow (http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/).
Good luck!
You can use [ActionName("NewActionName")] to use the same method with a different name:
public class HomeController : Controller
{
public ActionResult GetEmpName()
{
return Content("This is the test Message");
}
[ActionName("GetEmpWithCode")]
public ActionResult GetEmpName(string EmpCode)
{
return Content("This is the test Messagewith Overloaded");
}
}
I needed an overload for:
public ActionResult Index(string i);
public ActionResult Index(int groupId, int itemId);
There were few enough arguments where I ended up doing this:
public ActionResult Index(string i, int? groupId, int? itemId)
{
if (!string.IsNullOrWhitespace(i))
{
// parse i for the id
}
else if (groupId.HasValue && itemId.HasValue)
{
// use groupId and itemId for the id
}
}
It's not a perfect solution, especially if you have a lot of arguments, but it works well for me.
I have faced same issue in my application too. Without Modifiyig any Method information, I have provided [ActionName("SomeMeaningfulName")] on Action head. issue resolved
[ActionName("_EmployeeDetailsByModel")]
public PartialViewResult _EmployeeDetails(Employee model)
{
// Some Operation
return PartialView(model);
}
}
[ActionName("_EmployeeDetailsByModelWithPagination")]
public PartialViewResult _EmployeeDetails(Employee model,int Page,int PageSize)
{
// Some Operation
return PartialView(model);
}
Create the base method as virtual
public virtual ActionResult Index()
Create the overridden method as override
public override ActionResult Index()
Edit: This obviously applies only if the override method is in a derived class which appears not to have been the OP's intention.
I like this answer posted in another thread
This is mainly used if you inherit from another controller and want to override an acction from the base controller
ASP.NET MVC - Overriding an action with differing parameters
There is only one public signature allowed for each controller method. If you try to overload it, it will compile, but you're getting the run-time error you've experienced.
If you're not willing to use different verbs (like the [HttpGet] and [HttpPost] attributes) to differentiate overloaded methods (which will work), or change the routing, then what remains is that you can either provide another method with a different name, or you can dispatch inside of the existing method. Here's how I did it:
I once came into a situation where I had to maintain backwards compatibility. The original method expected two parameters, but the new one had only one. Overloading the way I expected did not work because MVC didn't find the entry point any more.
To solve that, I did the following:
Changed the 2 overloaded action methods from public to private
Created one new public method which contained "just" 2 string parameters. That one acted as a dispatcher, i.e.:
public ActionResult DoSomething(string param1, string param2)
{
if (string.IsNullOrEmpty(param2))
{
return DoSomething(ProductName: param1);
}
else
{
int oldId = int.Parse(param1);
return DoSomething(OldParam: param1, OldId: oldId);
}
}
private ActionResult DoSomething(string OldParam, int OldId)
{
// some code here
return Json(result);
}
private ActionResult DoSomething(string ProductName)
{
// some code here
return Json(result);
}
Of course, this is a hack and should be refactored later. But for the time being, it worked for me.
You can also create a dispatcher like:
public ActionResult DoSomething(string action, string param1, string param2)
{
switch (action)
{
case "update":
return UpdateAction(param1, param2);
case "remove":
return DeleteAction(param1);
}
}
You can see, that UpdateAction needs 2 parameters, while DeleteAction just needs one.
Sorry for the delay. I was with the same problem and I found a link with good answers, could that will help new guys
All credits for BinaryIntellect web site and the authors
Basically, there are four situations: using differents verbs, using routing, overload marking with [NoAction] attribute and change the action attribute name with [ActionName]
So, depends that's your requiriments and your situation.
Howsoever, follow the link:
Link:
http://www.binaryintellect.net/articles/8f9d9a8f-7abf-4df6-be8a-9895882ab562.aspx
This answer for those who struggling with the same issue. You can
implement your own custom filter based on
ActionMethodSelectorAttribute. Here I found the best solution
for solving your question. Works fine on .net 5 project.
If you try to implement the same logic as was in web api controllers then use Microsoft.AspNetCore.Mvc.WebApiCompatShim. This nuget package provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations. Please check this answer but consider that
starting with ASP.NET Core 3.0, the Microsoft.AspNetCore.Mvc.WebApiCompatShim package is no longer available.
If this is an attempt to use one GET action for several views that POST to several actions with different models, then try add a GET action for each POST action that redirects to the first GET to prevent 404 on refresh.
Long shot but common scenario.

Categories

Resources