Url routing getting confused between 2 routing rules - c#

I have two similar routing rules for my website. One for categories and another for brands
app.MapControllerRoute(
name: "CategoryPage",
pattern: "shop/{controller}/{id}/{pageNumber}/{pageSize}/{*categoryName}",
defaults: new { area = "shop", controller = "category", action = "index" });
app.MapControllerRoute(
name: "BrandPage",
pattern: "shop/{controller}/{id}/{pageNumber}/{pageSize}/{*brandName}",
defaults: new { area = "shop", controller = "brand", action = "index" });
All that is different is the controller and the brand / category name.
my urls should look like this.
shop/Category/79/1/80/Clothing-Accessories
shop/Brand/79/1/80/my-brand
But the second routing rule in my list always shows up as
shop/Brand/159/1/80?brandName=Anchor-Crew
I thought with a different controller name it could tell which one to use but that does not seem to be the case. One possible solution is I give them both similar names such as 'slug'.
Update to include controllers
public async Task<IActionResult> Index([FromRoute] long id, int pageNumber, int pageSize, string brandName)
{
PaginatedList<Product> products = GetProducts(id, pageNumber, pageSize);
Brand brand = await _brandService.GetAsync(id);
public async Task<IActionResult> Index([FromRoute]long id, int pageNumber, int pageSize, string categoryName)
{
Category? category = await _categoryService.Get(id, true);

I think the following quotes from the docs are applicable here:
Conventional routing is used with controllers and views. The default route:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
This mapping:
Is based on the controller and action names only.
Isn't based on namespaces, source file locations, or method parameters.
Routing does not take in account action parameter names, so from the routing point of view you have defined two routes which are the same when only segments are used.
Conventional routes are applied in order of definition (see the corresponding warning in the docs):
MapControllerRoute and MapAreaRoute automatically assign an order value to their endpoints based on the order they are invoked. This simulates long-time behavior of controllers without the routing system providing the same guarantees as older routing implementations.
so changing the route definition order has the observed effect of switching the selected controller.
It seems that providing last parameter as query string parameter lets the routing to select the correct controller (can't find in the docs why).
You can try using "more specific" route for the earlier declared route to match needed controller explicitly:
// remove controller template param and hardcode controller
app.MapControllerRoute(
name: "CategoryPage",
pattern: "shop/category/{id}/{pageNumber}/{pageSize}/{*categoryName}",
defaults: new { area = "shop", controller = "category", action = "index" });
app.MapControllerRoute(
name: "BrandPage",
pattern: "shop/{controller}/{id}/{pageNumber}/{pageSize}/{*brandName}",
defaults: new { area = "shop", controller = "brand", action = "index" });

Related

Why does an ASP.NET MVC route have a name?

In an ASP.NET MVC 5 web application, there is a RouteConfig class that registers many routes. In all of the examples I have seen so far, only the "Default" route has a non-empty name. The URL pattern and default route values seem to be sufficient to correctly associate any URL to the controller and action to execute. So, is there any benefit to naming a route? For debugging or logging? Just for self-documenting the code?
For example:
public class RouteConfig
{
public static void RegisterRoutes( RouteCollection routes )
{
routes.IgnoreRoute( "{resource}.axd/{*pathInfo}" );
// Most pages.
routes.MapRoute( name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "home", action = "index", id = UrlParameter.Optional }
// A special page.
// Should the route have a name?
routes.MapRoute( name: "",
url: "special",
defaults: new { controller = "special", action = "index", HttpVerbs = HttpVerbs.Get } );
}
}
The main reason to name a route is so that if you need a link to a route from somewhere in the app, you can reference the name. That way if the actual URL/path changes you don't break everywhere in the code that references that route.

How to bypass all other MVC routes when a URL contains a specific query parameter?

I need to capture any request that contains a query parameter of URLToken, such as in this URL:
http://test.server.com/product?URLToken=4abc4567ed...
and redirect it to a specific controller and action.
I have tried setting up various routes with constraints including the one shown below.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "ssocapture",
template: "{*stuff}",
defaults: new { controller = "Account", action = "SingleSignOn" },
constraints: new { stuff= #"URLToken=" } );
routes.MapRoute(
name: "default",
template: "{controller=home}/{action=index}/{id?}");
});
Break points at the beginning of SingleSignOn are never hit via this rule (the following direct link to the action does hit the break point, so I know the controller and action are working).
http://test.server.com/account/singlesignon?URLToken=4abc4567ed...
What I am I missing / doing wrong ?
Routes are not designed to do that. To achieve your goals, simply add a middleware before UseMVC()
app.Use((ctx , next)=>{
var token = ctx.Request.Query["URLToken"].FirstOrDefault();
if(token!=null){
ctx.Response.Redirect($"somecontroller/specificaction/{token}"); // redirect as you like
// might be :
// ctx.Response.Redirect($"Account/SingleSignOn/{token}");
}
return next();
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
I think it's quite easy to do within your controller if you don't want to use the middleware. Another benefit you will get is that you can set the RouteName for all other routes and simply redirect to the route using RedirectToRoute method. So within your UrlToken action:
[Route("[action]"]
public IActionResult SingleSignOn(string urlToken)
{
If (!string.IsNullOrWhitespace(urlToken))
{
return RedirectToRoute("RouteName"):
}
}
For the above to work you have to specify the RouteName for other actions either by using AttributeRouting or define globally in the startup:
[Route("[action]", Name = "otherroute")]
public IActionResult OtherAction(string param1, string param 2)
{
//...
}
So simply replace the "RouteName" in your SingleSignOn action to "otherroute" and it will work. If you need to pass the route parameters to the "otherroute" you can use one of the overloads of RedirectToRoute method. I hope this helps.

ASP.NET MVC new Routes not working

I am using ASP.NET MVC and I am trying to create a new route for a parameter like so:
config.Routes.MapHttpRoute(
name: "MarkOnline",
routeTemplate: "api/{controller}/{id}",
defaults: new { offline = RouteParameter.Optional }
);
and here is my method call I am trying to use inside my API Controller
public void MarkOnline(string offline)
{
}
however what gets returned is my Entity Framework GetData method in the API Controller, which is this:
public IQueryable<VistaLCPreview> GetData()
{
return db.Data;
}
What am I doing wrong?
In this context, GetData is being called due to the fact that it has a prefix of Get. There's a convention that maps HTTP GET to functions prefixed with Get, HTTP POST to PostXXX, etc. GetData is being resolved by the default HTTP route, which specifies an optional id parameter and is not present in your expected GetData URL example (which is what you want there).
The MapHttpRoute from your example is not going to match, due to the id parameter in the routeTemplate, which has not been defaulted to RouteParameter.Optional. This route is actually unnecessary - You do not need to include query-string parameters in this route definition. Query-string parameters are simply mapped into the arguments passed into the actions (offline in your case).
Because MarkOnline is not prefixed with one of the HTTP Verbs as I mentioned above, it is not being matched by the default HTTP route. To fix your problem you simply need to do two things:
Remove the MapHttpRoute that you added. This is not needed as the default HTTP route I've already mentioned will cover your use-case.
Add the HttpGet attribute to your MarkOnline method. This will cause the routing to pick up MarkOnline when an offline query-string parameter is found, but call GetData when it is not.
Your route is not configured correctly, you are not specifying the default action on your controller.
It should be something like this:
routes.MapRoute(
name: "MarkOnlineRoute",
url: "api/{controller}",
defaults: new { action = "MarkOnline" }
);
But also notice that the order which you configure your routes is important, it should be located before the default route configuration:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And as a reference, this is my test controller:
public class AvailabilityController : Controller
{
// GET: MarkOnline
public void MarkOnline(string offline)
{
//return Json(new { isOnline = true, name=offline }, JsonRequestBehavior.AllowGet);
}
}
And it is called with: http://localhost/api/availability?offline=xxx#xxx.com

How to distinguish between two routes: one for parameterless action and one for the index action with one parameter?

I have a controller with multiple actions and I set up the following routes:
routes.MapRoute(
name: "MyCustomRoute",
url: "MyTarget/{option}",
defaults: new { controller = "MyTarget", action = "Index", option = "" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The main idea here is to call the Index action of the MyTarget controller as default, passing only the argument in the URL.
The lightweight controller looks like this:
public class MyTargetController : Controller
{
public ActionResult Index(string option)
{ ... }
public ActionResult FirstAction()
{ ... }
public ActionResult SecondAction(param list)
{ ... }
}
The MyCustomRoute is set to map MyWebsite/MyTarget/randomOption to the Index action, passing randomOption as the option parameter. The problem is that this route catches all the other actions too: MyWebsite/MyTarget/FirstAction and MyWebsite/MyTarget/SecondAction (ignore the lack of parameters) are mapped to the Index action and their names are routed as the option parameter.
I don't want to change the URL into something like MyWebsite/MyTarget/Index/randomOption. Is there a clear way for distinguishing between a default action with one parameter and other actions, which may or may not have parameter?
EDIT: the following workarounds can be implemented, balancing the advantages and disadvantages:
all actions except Index can be moved to a helper controller: creates two separate controllers processing the same logic;
a custom route constraint can be created, that checks if the parameter value corresponds to the name of an existing action (except Index): needs a way of storing the names of the actions and needs the list to be updated every time a new action is added to the controller (Reflection might be a better approach).
None of the above workarounds seam to be elegant and without any "special" caring.

asp.net-mvc routing issue : parameters dictionary contains a null entry for parameter

I am trying to set up custom routing with the following mapped route
edit: my full route config
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
#region FixtureAdmin
routes.MapRoute(
name: "FixtureEdit",
url: "{controller}/{action}/{id}",
defaults: new { controller = "FixtureAdmin", action = "Edit", id = UrlParameter.Optional }
);
#endregion
#region Results
routes.MapRoute(
name: "ResultAdd",
url: "{controller}/{action}/{fixtureId}",
defaults: new { controller = "Result", action = "Add", fixtureId = UrlParameter.Optional }
);
#endregion
And my controller code
public ActionResult Add(int fixtureId)
{
// return model to view etc..
}
This is coming up with the exception, even though I have specified the parameter as optional.
The parameters dictionary contains a null entry for parameter 'fixtureId'
The strange thing is, if I change the parameter of the Add action to just 'Id' then the following URL will work Result/Add/1. I'm confused, is there some default routing that is overriding my custom one? Why would changing the parameter to just 'Id' work?
Edit
Just to test, I added another parameter to the action
public ActionResult Add(int? fixtureId, int? testId)
I then edited the route accordingly and now it works, so I reckon it is an issue with default routing.
Use a nullable int in your Controller Action.
public ActionResult Add(int? fixtureId)
{
// return model to view etc..
}
But the question is, if that is indeed an ID, how would it react if a null/blank ID is requested by the user? Is that ID a key in your DB? You can make it nullable if you are able to handle or provide a default page if the ID is blank/null.
EDIT:
This is because ASP.NET will assume that an unidentified parameter in your request is the id, in your case, Results/Add/1, 1 is unidentified. If you want to make that code work with using fixtureId, you should use Results/Add?fixureId=1. So ultimately, it's not because of the routing, but instead, it's because of the parameter in the Action that you have.
EDIT2:
Also, what you are experiencing there is called a routing conflict. Your routes are conflicting with the Default. You can try to apply constraints.
2,
from your post i think your problem is putting your custom route after default, like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
name: "ResultAdd",
url: "{controller}/{action}/{fixtureId}",
defaults: new { controller = "Home", action = "Add", fixtureId = UrlParameter.Optional }
so:
1/ exception "The parameters dictionary contains a null entry for parameter 'fixtureId'" will come if you dont give the specific route name for any action link or route form because MVC will use default route to routing. So you need to give specific route name to your custom route can be worked, like this:
#Html.ActionLink("Test custom Route", "Add", "Home", new { fixtureId = 1 }, "ResultAdd")
Cheer
Look at this method of adding what the developer calls a 'NullableConstraint' clicky link So if the optional parameter is supplied you can do some checking on it's value.
And also look at the answer following the accepted answer for what seems a simpler, cleaner solution.

Categories

Resources