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
Related
I have a .NET 6 ASP.net Core web application where I want to configure all calls with a particular path prefix to map to a specific controller.
What I want is for all calls that are of the form http://myhost/ipc/some_action to invoke the action some_action of the controller LocalIpcController. Here's how I setup my route in Startup class:
//Configure routing
app.UseEndpoints(endpoints =>
{
//Local IPC endpoint
endpoints.MapControllerRoute(
name: "Ipc",
pattern: "ipc/{action}",
defaults: new { controller = "LocalIpc" }
);
}
However, it is not working. Specifically:
If I make a call to http://myhost/ipc/some_action I get a 404 error
If I make a call to http://myhost/some_action it works correctly
So it looks like the /ipc prefix in the path is completely ignored. Why is this happening?
PS: I know I can also use the [Route] attribute on the controller to do this, but I want to why it isn't working via MapControllerRoute() and what I am doing wrong.
I tested it out and it is working for me. Here is the pipeline configuration and the test controller:
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// Don't forget to use the UseRouting middleware.
app.UseRouting();
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseEndpoints( endpoints => {
//Local IPC endpoint
endpoints.MapControllerRoute(
name: "Ipc",
pattern: "ipc/{action}",
defaults: new { controller = "LocalIpc" }
);
});
Note: do not forget to include the app.UseRouting();
public class LocalIpcController : ControllerBase
{
public LocalIpcController()
{
}
[HttpGet]
public IActionResult Get()
{
return Content("I'm here.");
}
}
Also I would like to add, that defining the URL routes in endpoints middleware options is not in my opinion the best approach from readability perspective and I would use it only in some edge cases. It is much more clear to specify it in Route attribute (or you can even put it into HTTP method attribute constructor e.g. [HttpGet("ipc/some_action")]).
It turns out, I didn't understand properly how the routing attributes work in ASP.net.
In my controller, the action methods were marked like this:
[HttpGet("some_action")]
public IActionResult MyMethod()
{
}
I was under the impression that "some_action" would be appended to the path indicated in the configuration phase, to obtain "ipc/some_action", but apparently that is not the case, the route specified in the attribute seems to override the one specified in the MapControllerRoute() method.
How can I use attribute routing in the Home Controller, like
[Route("")]
[Route("Home")]
[Route("Home/Index")]
public IActionResult Index()
{
return View();
}
to get to the main page and not have
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
in Startup.cs?
If you use net core your startup could be like this:
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Whether you do not use attribute routing, you have to use endpoint. The endpoint is a mapping to the routing template.
When the first HTTP request comes in, the Endpoint Routing middleware will map the request to an Endpoint. It will use the EndpointDataSource created when the App starts, use it to traverse to find all available Endpoints, and check the routing and metadata associated with it in order to find the most matching Endpoint.
Once an Endpoint instance is selected, it will be attached to the requested object so that it can be used by subsequent middleware.
Finally, at the end of the pipeline, when the Endpoint middleware is running, it will execute the request delegation associated with the Endpoint. This request delegate will trigger and instantiate the selected Controller and Action methods, and generate a response. Finally, the response is returned from the middleware pipeline.
Take a look at the following flow chart.
About attribute routing, it improves the freedom of routing and also provides good support for restful api.
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?
I'm trying to solve an issue where my controllers are decorated with a RoutePrefix like this:
[RoutePrefix("api/v{version:apiVersion}/users")]
But Swagger displays the urls in the TOC like this:
/api/v{version}/users/search
I was experimenting with the EnableSwagger routeTemplate parameter but I ended up getting the errors displayed in the inline comments below:
GlobalConfiguration.Configuration
.EnableSwagger(
//"v{version:apiVersion}", //A potentially dangerous Request.Path value was detected from the client (:).
//"v{version}", //Can't read swagger JSON from http://localhost:52056/v{version}
//"{version}", //Can't read swagger JSON from http://localhost:52056/{version}
//"v2", //Can't read swagger JSON from http://localhost:52056/v2
c =>
{
//...
}
}
What is the proper usage of the EnableSwagger routeTemplate parameter? And what type of use case scenarios is this parameter designed to address? Is this parameter designed to assist with the particular problem I described? Or am I attempting to use this parameter incorrectly or improperly?
From my understanding, it's to allow customization of the routes to not be defined by the traditional ASP.NET MVC/Web API route configs, like so :
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
While you might find something like the above in a WebApiConfig.cs file, you can change the path for Swagger JSON endpoints using the RouteTemplate.
From the Readme :
By default, Swagger JSON will be exposed at the following route -
"/swagger/{documentName}/swagger.json". If necessary, you can change
this when enabling the Swagger middleware. Custom routes MUST include
the {documentName} parameter.
app.UseSwagger(c =>
{
c.RouteTemplate = "api-docs/{documentName}/swagger.json";
});
NOTE: If you're using the SwaggerUI middleware, you'll also need to update its configuration to reflect the new endpoints:
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/api-docs/v1/swagger.json", "My API V1");
})
For versioning APIs, you can write a custom resolver most likely. And to that point, I think you've made it a little harder on yourself by versioning your APIs this way. If you simply created a v1/{resource} and v2/{resource} you could certainly write a custom resolver to look at the version in the route and use the c.MultipleApiVersions() API to wire everything up according to the route.
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