I have a api controller :
public class ExchangesController : ApiController
{
[HttpGet]
public List<ExchangesTrade> GetTrades(long tid)
{
I want to be able to call it from browser like /api/USD/trades.json?tid=5
How should I write "routes.MapRoute" in my RouteConfig ?
Firstly, routes.MapRoute will add routes for a traditional MVC app, if you want to add routes for your web api you need to add Http routes using MapHttpRoute on your web api HttpConfiguration routes.
In your web api config, you can add a URI path mapping extension like this:
config.Formatters
.JsonFormatter
.MediaTypeMappings
.Add(new UriPathExtensionMapping("json", "application/json"));
Add a route like this:
config.Routes.MapHttpRoute(
name: "ExchangesRouteWithExtensions",
routeTemplate: "api/USD/{action}.{ext}/{tid}",
defaults: new { controller = Exchanges, tid = RouteParameter.Optional }
);
Then access you endpoint like this:
api/USD/trades.json?tid=5
Related
In our classic ASP.NET WebAPI project, we could declare a route and the framework would select the correct action based on the HTTP verb in the request.
However in .NET Core WebAPI, I tried the following route configuration
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "DefaultRoute",
pattern: "{controller}/{id?}"
);
});
My controller has one method
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public WeatherForecast Get()
{
//return weather forecast
}
}
When trying the following URL, I get 404 whereas in a similar classic ASP.NET WebAPI project it would automatically execute the Get method.
https://localhost/weatherforecast
Does that mean for conventional routing we need to add multiple routes with same pattern, with default action and HTTP method constraints for it to work properly?
This question is only about conventional routing, suggesting to switch to attribute routing is not an answer.
I found a question that tries to simulate this behavior in classic ASP.NET WebAPI in ASP.NET Core: Route action based on HTTP verb?
The example is in .NET Core 2 and MVC, but trying it in .NET Core 3 WebAPI works the same.
Seems the answer is No, in ASP.NET Core WebAPI, if the route doesn't have action in the route pattern and no HTTP method constraints, the framework won't automatically try to match with actions based on HTTP verb in the requests.
In order to achieve this, multiple routes with default actions and Verb constraints need to be added.
Routing is responsible for mapping request URL to an endpoint and it comes with two types Conventional and Attributes routing.
And from your question, you are expecting conventional routing with default rout which you can achieve it .NET CORE using below line of code.
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Search}/{action}/{id?}");
});
Note: But keep in mind that convetional routing will not work if you decorate your controller with [ApiController] attribute.
By default .NET CORE supports attribute routing so you have to prefix the route by placing [Route] attribute on a controller level. Please see below example
[Route("api/[controller]")]
[ApiController]
public class SearchController : ControllerBase
{
[HttpGet("{company}")]
public IActionResult Get(string company)
{
return Ok($"company: {company}");
}
[HttpGet("{country}/{program}")]
public IActionResult Get(string country, string program)
{
return Ok($"country: {country} program: {program}");
}
}
The above code will work as you expected (Attribute routing).
If you are decorating your controller by [ApiController] attribute then you have to use Attribute routing and any conventional routing defined in startup class will be overridden. Please see more details here.
Does that mean for conventional routing we need to add multiple routes with same pattern, with default action and HTTP method constraints for it to work properly?
Yes, in asp.net core web api, if you want to use conventional routing, you need to remove [ApiController] attribute and [Route] attribute firstly and use the following route with default action
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=WeatherForecast}/{action=Get}/{id?}");
});
Refer to Conventional Routing in ASP.NET Core API
Update:Using Url Rewriting
You could always write your own url rewrite rules to meet your requirements.Refer to below demo which deal with url like /weatherforecast:
Create Rewrite Rules:
public class RewriteRuleTest : IRule
{
public void ApplyRule(RewriteContext context)
{
var request = context.HttpContext.Request;
var path = request.Path.Value;// path= "/weatherforecast" for example
if(path !=null)
{
context.HttpContext.Request.Path = path + "/" + request.Method;
// "/weatherforecast/post"
}
}
}
Startup.cs
app.UseRewriter(new RewriteOptions().Add(new RewriteRuleTest()));
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "GetRoute",
pattern: "{controller=WeatherForecast}/{action=Get}/{id?}"
);
});
The structure:
+ MyProj
+ Areas
+ Configuration
- Pages
- ConfigurationApiController.cs
To create controller without Controllers folder was proposed by VS2017 and it is ok for me since I use Razor Pages and do not need Controllers folder:
Those doesn't work:
http://localhost:8080/api/Users
http://localhost:8080/api/GetUsers
http://localhost:8080/Configuration/api/Users
http://localhost:8080/Configuration/api/GetUsers
Controller defined:
[Route("api")]
[Produces("application/json")]
[ApiController]
public class ConfigurationApiController : ControllerBase
{
private readonly ApplicationSettings applicationSettings;
[HttpGet]
public ActionResult GetUsers()
{
Mvc routing configured standard way:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
How to route to GetUsers action of ConfigurationApiController ?
Modify the api route and add the Area Attribute to provide the area name for [area] route.
[Area("Configuration")]
[Route("[area]/api/[controller]")]
[ApiController]
public class ConfigurationApiController : ControllerBase
{
}
That's all, and it can be accessed at http://localhost:8080/Configuration/api/ConfigurationApi
Some other routing options:
Using AspNetCore.RouteAnalyzer working option found: http://localhost:8080/api (yes, without action)
After removing web APIs attributes
// [Route("api")]
// [Produces("application/json")]
// [ApiController]
then http://localhost:8080/ConfigurationApi/GetUsers
it could be ok but there is no area in the routing and it seems "routing to the area by conventions" doesn't work is asp core:
ASP Core: how to configure area for api controller without AreaAttribute (or how to enable convention area routing for Api controller)?
and https://github.com/aspnet/AspNetCore/issues/7042
Also in this case ContentResult { Content = json, ContentType = "application/json" } should be return but this is ok for me since I prefer to use in place serialization instead of stream serializers.
This routes to http://localhost:8080/Configuration/api
[Area("Configuration")]
[Route("[area]/api")]
[Produces("application/json")]
[ApiController]
other option: [Route("[area]/api/[action]")] routes to http://localhost:8080/Configuration/api/GetUsers
when removing area attribute throws the run-time error Error: While processing template '[area]/api', a replacement value for the token 'area' could not be found. Available tokens: 'action, controller'. To use a '[' or ']' as a literal string in a route or within a constraint, use '[[' or ']]' instead.
//[Area("Configuration")]
[Route("[area]/api")]
[Produces("application/json")]
[ApiController]
To support #Url.Action(action: "myArea", controller: "myControllerApi") routing should be configured manually.
Asp Core routes:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "defaultArea",
template: "{area:exists}/{controller}/{action}"); // matches only those where area route value is defined
});
Asp Core 3 routes (startup Configure):
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "defaultArea",
pattern: "{area:exists}/{controller}/{action}");
});
I have a contoller named customer and an action method for GetAllCustomers which recieves single string input parameter as FirstName. The issue is that I want the url to be like
MyAPI/Customer/MyFirstName
but the above stated URL is not working for me instead it working as below
MyAPI/Customer?firstname=MyFirstName
How can I make it work like the first url using ApplicationRouting (nuget package)? I have configured the attrbute route for the action method as below.
[GET("Customer/{firstname}")]
public List<Customer> GetAllCustomersFirstname(String firstname)
{
//code goes here
}
Edit
Here is the reference whatI am trying to achieve but it is not happening for me in ApiController
Attribute Routing Github
Open up ApiConfig in the AppStart folder. You need to add the route for that particular action. I think you'll need more than just what you showed there for route since you have to tell it which action to call and what methods to allow. E.g.
config.Routes.MapHttpRoute(
name: "yourRoute",
routeTemplate: "api/{controller}/{firstname}",
defaults: new { controller = "Customer", action = "GetAllCustomersFirstname" });
My WebApiConfig.cs Register method looks like this:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ClassroomContentApi",
routeTemplate: "classroomContent/{controller}/{id}",
defaults: new {id = RouteParameter.Optional}
);
// Default
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional});
MediaTypeHeaderValue appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
My Controllers folder in my MVC project is structured like this (these are not the real names of the controllers but that's not important):
Controllers
ClassroomContent
ClassroomController.cs
ClassroomController2.cs
ClassroomController3.cs
SchoolInfo
CampusController.cs
CampusController2.cs
CampusController3.cs
StudentInfo
StudentController.cs
StudentController2.cs
StudentController3.cs
etc...
I want all the controllers in the ClassroomContent folder to use the first route above, and all the other controllers (in any other folders) to use the second route above.
The issue I am having is that the first route above picks up web api actions in controllers outside the ClassroomContent controllers folder, and vice versa.
For example, I can access http://MYHOST/classroomContent/Campus or http://MYHOST/api/Classroom. I want to be able to access the Classroom controller only from http://MYHOST/classroomContent/Classroom and the Campus controller only from http://MYHOST/api/Campus.
I understand that this is "expected behavior" in that nothing is preventing this from the perspective of how my routes are configured. But how do I prevent it?
What I have considered:
Using areas. However, from what I read, they are not supported for Web API (at least not out of the box). I could try to explore the option of trying to implement areas functionality, but it seems like a lot when I am not even sure that they are the right solution.
Defining a separate route for each controller. However, I have many controllers, so this seems very cumbersome.
What would be the best way to approach this problem? Is there any way to do it without trying to implement areas for Web API myself? Ideally I'd be able to somehow "filter" which controllers are valid values for the {controller} parameter of each route template.
RouteConstraints should also work if you prefer not to use attribute routing.
Assuming your controllers are named: ClassRoom1Controller, ClassRoom2Controller etc the following should work (note that you can use regular expressions to configure the constraints so there should be many possibilities to configure this as you like):
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ClassroomContentApi",
routeTemplate: "classroomContent/{controller}/{id}",
defaults: new {id = RouteParameter.Optional},
constraints: new { controller = "classRoom1|classRoom2|classRoom3" }
);
// Default
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional},
constraints: new { controller = #"^((?!(classRoom1|classRoom2|classRoom3)).)*$" });
MediaTypeHeaderValue appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
This link should help you : http://aspnetwebstack.codeplex.com/wikipage?title=Attribute%20routing%20in%20Web%20API
You can define custom routes at controller level with Web Api 2.
I have a WCF service that lives side-by-side with an MVC2 web site. I'd like for my URL for the service to look like this:
http://localhost/projdir/Service
The MVC site is in its infancy so it still has all its boilerplate controllers etc.
The following code works at first glance in global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new ServiceRoute("Service", new ServiceHostFactory(),
typeof(MyService)));
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index",
id = UrlParameter.Optional } // Parameter defaults
);
}
The service appears just where I described and works as advertised. Great.
However, I just noticed that ordering my code this way changes all of my ActionLink's. For example, the "About" tab on the MVC site now appears like this:
http://localhost/projdir/Service?action=About&controller=Home
This is obviously incorrect (it should be http://localhost/projdir/Home/About/ ).
If I move the ServiceRoute addition below the default MapRoute() call, then I get a missing controller error. (Actually I get a "StructureMapControllerFactory did not return an instance of a controller" error, because I'm wired up with StructureMap, duh, it's not a controller to begin with.)
Interestingly, it only seems to be affecting the output of Html.ActionLink(). I can manually type in http://localhost/projdir/Home/About/ and get to the correct page.
What horribly obvious newbie mistake am I making?
Try moving the Service route after the MVC route. But to avoid the "missing controller" error that you got before, add the MVC route with a Route Constraint. These route constraints can be Regex - basically you'd want your route constraint to be any controller that is not "Service". When a request for "Service" is requested, it will make it fall through and his the WCF Service Route.
I resolved with that:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = "^(?!api).*" }
);
routes.Add(new ServiceRoute("api", new DataServiceHostFactory(), typeof(dwService)));
I hope this good for you
Another solution is to inherit the ServiceRoute and override the GetVirtualPath method to return null as described here
public class AppServiceRoute : ServiceRoute
{
public AppServiceRoute(string routePrefix, ServiceHostFactoryBase serviceHostFactory, Type serviceType)
: base(routePrefix, serviceHostFactory, serviceType)
{
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
This way, reverse route mapping never select this route for any Action. Works like a charm