So I have defined a default route in my Startup.cs that looks something like:
app.UseMvc(routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
"api",
"Api/{controller=Home}/{action=Index}/{id?}");
});
I'm attempting to extend my default routing to map into my actions that don't use id as the parameter name.
For example, I have a method like the following:
public async Task<IActionResult) Edit(UserRequest request)
{
// do stuff
}
Previously, we were simply using id for this call, but we've since extended security and built some custom ActionFilters that do some behind the scenes validation. I've already built a custom model binder that works perfectly fine if we keep the UserRequest parameter name as id, but as soon as we change it to anything else, the routing fails to map it properly, and instead we just get a null value.
How would I go about modifying my default route to allow multiple different parameter names instead of just id?
Related
I have the following case: in .NET framework there was the possibility to define a global route for API controllers and their methods. By getting the defaults and add a controller route you could leave out the [Route] attribute for the controllers. In addition to that, the route for the methods was adapted from the name of the method, so you could exclude the [Route] attribute there too.
So when you mapped for example "{controller}" and have a controller called "MyController" + have a method called "Post" and a method called "Delete" you just have to use the route "MyController" and depending on the action that is chosen either the "Post" method or the "Delete" method was called.
I want to reach the same in .NET 6 but I'm not sure how to achieve that here.
I've tried the following:
app.UseEndpoints(endpoints =>
{
IDictionary<string, object> defaults = new Dictionary<string, object>();
defaults.Add("controller", "{controller}");
endpoints.MapControllerRoute(name: "default", pattern: "{controller}", defaults: defaults);
endpoints.MapControllers();
});
Didn't work so far. As soon as I start my rest service I get the error that the [Route] attribute is missing.
Any ideas how to achieve that?
Seems like you are looking for a definition of the default conventional routing.
You can define it like in example below:
app.MapControllers();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=your_default_controller}/{action=defaul_action}/{id?}");
But you should take to account, that using the conventional routing for the REST API isn't the best solution. The Microsoft documentation recommend to use attribute routing.
NOTE: If you are going to use the conventional routing like defined above you should remove the [ApiController] and the [Route("[controller]")] attributes, that usually is added by default .NET REST API template.
See the additional article:
Attribute routing for REST APIs
Set up conventional route
Edit: To be clear, I dont actually care if its the csproj. I just want to be able to globally define a root name for all my routes.
I am new to dotnet core 2 and c# in general. I am building a webapi template for the rest of my team to use, but I want to be able to put the csproj name as part of the route so people dont have to change it in every controller.
So for example:
WebStoreAbc.csproj
--------
[Route("api/v1/#CSPROJNAME#/products")]
public class ProductsController : Controller
{
... controller logic ...
}
Registers at runtime as:
/api/v1/WebStoreAbc/products
This way, I dont have to configure routes for a bunch of controllers everytime I make a new project from the template.
Edit 2: I used Mahmoud Heretani's answer below and tweaked it.
string projectName = "template";
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/v1/" + projectName.ToLower() + "/{controller=Home}/{action=Index}/{id?}");
});
}
It think that you cannot insert a variable inside [Route] attribute, because it required a constant value, however a simple trick inside your Configure method should do the trick:
var projectName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/v1/" + projectName + "/{controller=Home}/{action=Index}/{id?}");
});
But in your case, I would recommend that you create an assembly (class library) contains a method to configure the route using the assembly name (just like the previous example), and then use this method across your projects.
I have a typical ASP.NET MVC controller, but I just want to change its route. The default route now is:
Blog/{controller}/{action}/{id}
I want to change the route of a specific controller to
Blog/Admin/{controller}/{action}/{id}"
I tried to achieve this by adding the Route, RouteArea and RoutePrefix attributes to the controller but without any success.
How can I achieve this?
Add this route prior to the default
routes.MapRoute(
name: "BlogAdmin",
url: "Blog/Admin/{action}/{id}",
defaults: new { controller = "YourSpecificControllerName", action = "Index or other default action name", id= UrlParameter.Optional });
Since this is for a specific you don't need {controller} part in your url. If you still want to specify it change the url argument to "Blog/Admin/YourSpecificControllerName/{action}/{id}" where YourSpecificControllerName is the name of your controller.
Also since the order of rote registration matters make sure that this route registered prior to the the default one
I have an Asp.net MVC web application that uses convention based routing. I recently added some Web Api 2 controllers, for which I used attribute routing. Despite the documentation claiming that you can use both, I can either get the (attribute routed) API methods to work, or the (convention routed) web application methods.
This is RouteConfig.RegisterRoutes():
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Tables", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "Foo.Cms.Controllers" }
);
}
This is WebApiConfig.Register():
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// The models currently only serialize succesfully to xml, so we'll remove the json formatter.
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
}
And this is Application_Start():
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
}
This way, only the routing to the web api controllers works. If I switch GlobalConfiguration.Register() and RouteConfig.RegisterRoutes(), like so:
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(WebApiConfig.Register);
...only the convention-based routing works.
I'm at a loss. What's going on here?
Edit:
What I'm trying to achieve:
The application currently uses the basic {controller}/{action}/parameters convention. So I have a controller called ElementsController that has, for instance, an Index() method that is routed to /Elements or a ListPublic() method that is routed to /Elements/ListPublic. I achieved this with the convention based routing mentioned above.
I also have a bunch of Web Api controllers (for instance, TablesController) that I want to route to using a /api/v0/tables route. I tried to achieve this like so:
[RoutePrefix("api/v0/tables")]
public class TablesController : ApiController
{
[Route()]
public string Get()
{
// ...
}
}
As you can see, it's not the same route pattern: api calls are all prefixed with api/v0/. For some reason though, it still appears to treat them as the default {controller}/{action} routes.
What's occurring is that the "first registered" route is taking effect. If I have a MVC route defined as
{controller}/{action}/{id}
and a Web API route defined as
{controller}/{action}/{id}
The first registered route will take effect.
Why is this the case? Imagine you send a request to the server
foo/bar/1
Which route does this match?
Both!
It will choose the first result that matches the route regardless of the type of routing used. If you want some examples as to how to make these routings work, check out this link
If an URL matches two different route templates, it doesn't matter if they refer to MVC or Web API, the only rule is that the first registered route will be used to select the action to execute.
In your particular case, if you make a GET request to this URL: /api/v0/tables, the action selector starts checking the registered routes.
The first route that you're registering is an MVC route like this: /{controller}/{action}/{id}. So, the template matches with this values:
controller = "api"
action = "v0"
id = "tables"
If you register the attribute routes before the MVC route, the first matching route template will be your route attribute template, so the Web API controller action will be correctly selected.
In other words, if you need to route Web API and MVC in the same application you have to use routes which match different URLs for each action, and be careful with the order in which they are registered: register first the more specific templates, and then the more generic ones, so that the generic templates doesn't swallow the URIs which should be matched with the specific templates.
This is not the only option. You can also use route constraints, for example, if all your API has a particular naming convention which can be checked with a constraint (for example a Regex constraint), you can still use convention routing for Web API.
NOTE: apart from the route matching, it's also necessary that the HTTP method (like POST, GET, etc.) is supported by the action. So you can have two similar actions in the same route that accept different methods, and the Action selector would know which one to choose, but this doesn't solve your problem
Can someone please explain what the following function does. I am learning Asp.net MVC and unable to understand which controller is called when and renders which view.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//register custom routes (plugins, etc)
var routePublisher = EngineContext.Current.Resolve<IRoutePublisher>();
routePublisher.RegisterRoutes(routes);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "Nop.Web.Controllers" }
);
}
This code is from nopCommerce source-code. I can't understand the URL routing for this project
The logic for this is in the System.Web.Mvc.MvcHandler class, the System.Web.Mvc.DefaultControllerFactory class, and the System.Web.Mvc.ControllerActionInvoker class. .NET Reflector is your friend.
Basically, the MVC framework:
Uses reflection to get all the controllers in the application project.
Then it does something like IEnumerable<string> controllerNames = controllerTypes.Select(controllerType => controllerType.Name.Replace("Controller",string.Empty));. It then tries to match the first path segment, {controller}, to one of these sanitized controller type names (case-insensitive).
Then, it looks at this controller's public methods that have a return type that is of type ActionResult or some derivative. It matches the method name to the second path segment, {action}, as the action method to be called.
If the selected method has a parameter that is named id, then it matches the third path segment {id} to that value, and passes it to the method. Otherwise, the optional id parameter is ignored.
If the ActionResult type that is returned is a derivative of ViewResultBase then the IViewEngine tries to locate a corresponding view in the project using whatever conventions have been specified for that view engine. The WebFormViewEngine, for example, looks in the project for ~/Views/{controller}/{action}.ascx, ~/Views/{controller}/{action}.aspx, ~/Views/Shared/{action}.ascx, ~/Views/Shared/{action}.aspx by default.
If you want to further understand how routing works in MVC, I would highly suggest Scott Gu's article on MVC Routing.
As far as the IRoutePublisher method, that looks like a nopCommerce specific method that automatically registers additional routes specific to nopCommerce's configuration. If you are interested in how nopCommerce's specific routing conventions work, you can download the source code from the nopCommerce codeplex page and do a search for its default IRoutePublisher implementation.
Update The default IRoutePublisher is here: http://nopcommerce.codeplex.com/SourceControl/changeset/view/7e34dd9d98f3#src%2fPresentation%2fNop.Web.Framework%2fMvc%2fRoutes%2fRoutePublisher.cs . Basically, it gets all implementations of IRouteProvider and registers their route definitions in order according to their priority.
The default route providers are: Nop.Web.Infrastructure.RouteProvider and Nop.Web.Infrastructure.UpgradeRouteProvider
nopCommerce employs a loosely coupled infrastructure that registers routes for each plugin separately.
So If you need to understand what's going on, check the nopCommerce source code and look for RouteProvider classes, that each plugin has. They are dynamically loaded on application start.
If you need to create your own routes, you can still do that the traditional way -- but be aware, that there might be some clashes.
(Disclaimer: I just looked at the source code, don't know anything else about it).