I always used this approach in my MVC applications before
[Route("admin")]
public class AdminController : Controller
{
}
[Route("products")]
public class ProductsAdminController :AdminController
{
[Route("list")]
public IActionResult Index()
{
...
}
}
Url.RouteUrl() for Index action returned /admin/products/list/
Now in .NET Core it ignores base class route attribute and result is just /products/list/
Is this new to .NET Core? Is there any setup so system can combine action + controller + base-controller routes?
I can't find a way to combine action + controller + base-controller automatically, but it is possible to achieve what you're looking for like this:
[Route("admin")]
public class AdminController : Controller { }
public class ProductsAdminController : AdminController
{
[Route("products/list")]
public IActionResult Index()
{
...
}
}
This approach ends up generating a URL of /admin/products/list, with the obvious downside that products is something that needs to be repeated for each action. This might be an acceptable compromise; that's up to you to decide. You could make it a bit better with a constant, like this:
private const string RoutePrefix = "products";
[Route(RoutePrefix + "/list")]
public IActionResult Index()
{
...
}
It's not pretty as it's just a workaround, but worth considering if you don't want to go with Chris Pratt's Areas suggestion.
As far as I'm aware, that never would have worked. You could use areas, and by applying the [Area] attribute to the base controller, you'd get the result you describe:
[Area("admin")]
public class AdminController : Controller
[Route("products")]
public class ProductsAdminController : AdminController
The same would work in Core, as well.
Anyone still looking for a way to define "baseURL/ControllerName" in .Net core 3.1.
You would need to define RouteAttribute on your base controller like this:
[Route("admin/[controller]")]
Note [controller] would allow the routing to automatically take the controller name in child class.
Related
I'm brand new to C# and ASP.Net
I am trying to model some endpoints:
/api/sites/
/api/sites/{id}/
/api/sites/{id}/readers/
/api/sites/{id}/readers/{id}/
I already have a SitesController
[Route("api/[controller]")]
[ApiController]
public class SitesController : ControllerBase {
[HttpGet("{id:length(24)}")]
public ActionResult<Site> Get(string id) {}
}
I've tried adding a ReadersController inside the SitesController class
[Route("{id:length(24)}/[controller]}")]
[ApiController]
public class ReadersController {
[HttpGet]
public string Get() => "test";
}
I can't hit the endpoint /api/sites/{id}/readers/ though so I'm not sure I'm doing this right. Is there some way I can do this while still using the [Route] tags?
You shouldn't be nesting controller classes. Make a new class for ReadersController then setup the routes like this:
[Route("api/[controller]")]
[ApiController]
public class SitesController : ControllerBase {
[HttpGet("{siteId:length(24)}")]
public ActionResult<Site> Get(string siteId) {}
}
[Route("api/sites/{siteId:length(24)}/[controller]")]
[ApiController]
public class ReadersController {
[HttpGet]
public string Get() => "test";
[HttpGet("{readerId:length(24)}")]
public ActionResult<Site> Get(string siteId, string readerId) {}
}
You can use routes.MapRoute to create more complex rules however if you want to keep it easy and specify routes for each individual endpoint then you can carry on using HttpGet or Route attributes. Or if you want to make it explicit you can write out the whole route in each HttpGet and ignore the Route attribute on the controller.
If you are building apis with asp.net core, please check this.
Link
You can use routes.MapRoute functions as many as you can.
Like this.
routes.MapRoute(
name: "default_route",
template: "{controller}/{action}/{id?}");
routes.MapRoute(
name: "default_route",
template: "{controller}/{action}/{id}/readers/{id?}");
Lets suppose I have a bunch of controllers which renders text parts. They are accessible via urls from a browser. But some of them are just a composition of others. What I want is request several texts and concat it. Why?
They are required time to construct, but highly probably already cached
Do not invent my own cache and use ASP.NET one
Probably I could System.Net.WebClient.DownloadString("https://myserver/path") however it is inefficient.
#derloopkat below kindly asked me about code sample
[ApiController]
[Route("foo")]
public abstract class FooController: ControllerBase
{
[HttpGet]
public string Get()
{
return "foo";
}
}
[ApiController]
[Route("bar")]
public abstract class BarController: ControllerBase
{
[HttpGet]
public string Get()
{
return "bar";
}
}
[ApiController]
[Route("bar_plus_foo")]
public abstract class CompositeController: ControllerBase
{
[HttpGet]
public string Get()
{
// TODO: how to fetch result of FooController and BarController
// to return concatenation?
}
}
we usually do not write business logic in controllers like this.
you can create a service and hide your business logic in that service. the code would be like this:
public class BarService
{
public string GenerateBar(string param)
{
return "bar";
//Or any business logic that you have
}
}
for example you can search for onion architecture and see examples in github
by the way dont make controllers abstract.
hope this helps
I have:
[RoutePrefix("api/Order")]
public class OrderController : ApiController
{
[Route("~/api/Order/{id}")]
[Route("~/api/ManualOrder/{id}")]
[HttpGet]
public Task<HttpResponseMessage> Get(Guid id)
{
//Implementation
}
[Route("ExampleOtherNormalMethod")]
[HttpGet]
public Task<HttpResponseMessage> ExampleOtherNormalMethod()
{
//Implementation
}
}
And:
[RoutePrefix("api/ManualOrder")]
public class ManualOrderController : ApiController
{
//Other methods
}
The strategy used by route rewriting is to get the "Get" method called either "/api/Order/1" or "/api/ManualOrder/1" both pointing to "OrderController", this works.
The problem is when I request any other method in the "ManualOrder" I think it gets lost and can not resolve and returns the exception:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
Project.ProxyAPI.Controllers.ManualOrderController
Projects.ProxyAPI.Controllers.OrderController
Does anyone know how to solve this without duplicating the "Get" method on both controllers?
P.s: This is a hypothetical example.
I'm sure your example is much more complex than the one you present here. But based on the information in your example you could let ManualOrderController inherit from OrderController. I think it makes more sense when you assign the routes. The Route-attributes will not be inherited, so it shouldn't be any problem.
Does anyone know how to solve this without duplicating the "Get"
method on both controllers?
Yes, it will be a duplicate, but it won't contain any logic it will just be a fall-through...
[RoutePrefix("api/Order")]
public class OrderController : ApiController
{
[Route("~/api/Order/{id}")]
[HttpGet]
public virtual Task<HttpResponseMessage> Get(Guid id)
{
//Implementation
}
[Route("ExampleOtherNormalMethod")]
[HttpGet]
public Task<HttpResponseMessage> ExampleOtherNormalMethod()
{
//Implementation
}
}
[RoutePrefix("api/ManualOrder")]
public class ManualOrderController : OrderController
{
[Route("~/api/ManualOrder/{id}")]
[HttpGet]
public override Task<HttpResponseMessage> Get(Guid id)
{
return base.Get(id);
}
//Other methods
}
There is a downside to this approach - ManualOrderController will expose methods from OrderController via your default routing table. Depending on what your routing table looks like api/ManualOrder/ExampleOtherNormalMethod may call ExampleOtherNormalMethod on OrderController. This may, or may not, be a desired behavior.
I am receiving an AmbiguousActionException between two actions which are part of two separate controllers, and I'm curious as to why. (The solution is simple - change the route on MyAdminController, so I'm not so much looking for that.)
Here is the one action:
[Route("Admin/Company")]
public class MyAdminController : Controller
{
[HttpGet("{id}/Details")]
public async Task<IActionResult> Details(int id)
{
// Do stuff.
}
}
And the second:
[Route("[controller]")]
public class ConfigurationController : Controller
{
[HttpGet("Scripts/{id}")]
public async Task<IActionResult> Scripts(long id)
{
// Do stuff.
}
}
ASP.NET Core 1, MVC 6 is the technology stack. I'm sure I'm missing some basic, key thing with routing, but I'm not understanding what.
Many action filters can be specified for the whole Controller in ASP.NET MVC, resulting in it being applied to all of the actions in the controller. For example:
[Authorize]
public class MyController : Controller
{
// ....
}
means that the [Authorize] applies to all of the actions in the controller, which is very convenient.
But when I try to put [HttpGet], [HttpPost] or [AcceptVerbs(...)] on the controller, the compiler complains that the attribute is only usable for methods (which is right, since they are defined with [AttributeUsage] pointing only toward methods).
What if I want to limit all actions in a controller to POST verb only?
My question is:
Is there any way in the framework to achieve this, without writing my own attribute?
Why are these attributes implemented this way, not allowing them on controllers?
HttpGet and HttpPost inherits from ActionMethodSelectorAttribute Class, and are valids only for methods. I think that you need create your own attribute.
Custom attribute to solve this:
public sealed class AllowedMethodsAttribute : ActionFilterAttribute
{
private readonly string[] methods;
public AllowedMethodsAttribute(params string[] methods) => this.methods = methods.Select(r => r.Trim().ToUpperInvariant()).ToArray();
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
if (!methods.Contains(actionContext.HttpContext.Request.Method))
{
actionContext.Result = new StatusCodeResult(405);
}
}
}
Usage:
[AllowedMethods("GET", "PATCH")]