ASP Core: how to route to API Controller that is located in the area folder? - asp.net-core

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}");
});

Related

Adding Attribute Route on Controller adds Parameter to Url

I want to change the URL that will access my controller, but when I do, instead of getting controller/action/id, I get controller/action?id=(the id number) in the URL.
I am using default routing in my MVC .Net Core.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
On my controller I have [Route("DifferentName/{action=index}")] for attribute routing.
I have tried adding [Route("DifferentName/{action=index}/{id?}")]
but I get an exception "RoutePatternException: The route parameter name 'id' appears more than one time in the route template."
These attributes are incorrect according to the documentation:
[Route("DifferentName/{action=index}/{id?}")]
[Route("DifferentName/{action=index}")]
This is how it should look like for controller when we want to only change its part of URL:
[Route("DifferentName/[action]")]
[action]
[action] will be replaced by your action name when asp .net core will be resolving URLs.
More information about routing and can be found in the documentation
but I get an exception "RoutePatternException: The route parameter
name 'id' appears more than one time in the route template."
I have reproduced your error. I need to confirm with you whether the reason for your error is to add Route attribute to both controller and action.
If so, as the exception points out, you don't need to specify route repeatedly.
Modify the following settings to implement the url like 'Differentname/action/5':
[Route("DifferentName/{action=index}")]
public class ShowPdfController : Controller
{
public IActionResult Index()
{
return View();
}
[Route("{id?}")]
public IActionResult Action(int id)
{
return View();
}
}
Then, you can see the test result of this setting:

Does ASP.NET WebAPI conventional routing works without specifying action and HTTP verb constraints?

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?}"
);
});

Routing is not working for web api with realtions

I am creating web api using asp.net core. The api end point is logically mapped to resource's relations based on guidelines here
So my API looks like
http://tax.mydomain.com/api/v1/clients/1/batches/12/start
Where Client is parent of Batch, 1 is clientid and 12 is batchid, and Start is POST action method.
Here is the corresponding controller
public class TaxController : Controller
{
[HttpPost]
[Route("clients/{clientid}/batches/{batchid}/start")]
public void Start([FromRoute]string clientId, [FromRoute]string batchId,
[FromBody]IEnumerable<string> urls)
{
// do something
}
}
since api/v1 is common to all controllers i configured that in startup's Configure method. Also i want Home as default controller.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc(routes =>
{
routes.MapRoute("default","api/v1/{controller=Home}/{action=Index}/{id?}");
});
}
However client is getting not found error for api http://tax.mydomain.com/api/v1/clients/1/batches/12/start
Any controller methods that do not have a route attribute use convention-based routing.
When you use [Route] attribute, you define attribute routing and so conventional routing is not used for that action/controller. Therefore, your controller is accessible by
http://tax.mydomain.com/clients/1/batches/12/start
As an option, you can use the fact, that attribute routes can be combined with inheritance. Set a Route attribute on the entire controller and this will work as route prefix (the same behavior as [RoutePrefix] attribute in WebApi):
[Route("api/v1")]
public class TaxController : Controller
{
}
More general example from routing documentation:
[Route("api/[controller]")]
public abstract class MyBaseController : Controller { ... }
public class ProductsController : MyBaseController
{
[HttpGet] // Matches '/api/Products'
public IActionResult List() { ... }
[HttpPost("{id}")] // Matches '/api/Products/{id}'
public IActionResult Edit(int id) { ... }
}
There are two things wrong with your setup
You call http://tax.mydomain.com/clients/1/batches/12/start but you don't have specified the controller name within it. This route looks for a controller named ClientsController. So the correct url would have to be http://tax.mydomain.com/tax/clients/1/batches/12/start instead
You seem to be using default MVC/Viewbased route, but your url suggest you use WebAPI.
When you use WebAPI to create a Rest service, you don't have any actions. Instead, actions map to the Http Verbs (GET (Read), PUT (update/replace), POST (insert), DELETE).
So for REST Services your default route should look like this instead: api/v1/{controller=Home}/{id?}

how to write MapRoute mvc4

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

MVC2 Routing with WCF ServiceRoute: Html.ActionLink rendering incorrect links!

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