AmbigousActionException With Two Completely Different Routes - c#

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.

Related

Attribute Routing Inheritance

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.

How to make request from asp.net core web app to self

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

NETCORE MVC - How to work with nested, multi-parameterized routes

Looking for best practices when working with nested routes in .NET Core MVC.
Let's say CampusController.cs works with a base model:
[Route("api/campus/")]
public class CampusController : Controller
{
...
[HttpGet]
[Route("{campusId}")]
public IActionResult GetCampusInfo ([FromQuery]int campusId) { ... }
}
And BuildingController.cs works with a child model:
[Route("api/campus/{campusId}/building")]
public class BuildingController : Controller
{
...
[HttpGet]
[Route("{buildingId}")]
public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
[Route("{buildingId}/")]
public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
....
(more Action Methods)
}
If buildingId maps directly to the database it could retrieved even if the provided campusId isn't the parent. To keep the URL clean when calling /api/campus/{campusId}/building/{buildingId} I'd like to validate {campusId} and return a 4xx coded IActionResult if it's invalid. There has to be a better way than including validation logic in every Action Method inside BuildingController.
Is there a way to cascade multiple Action methods on different controllers? So that a validation method on CampusController would be called first and in turn call a method onBuildingController?
Is there a way to have a controller-level verification of campusId that could short circuit and return a ActionResult if validation fails?
EDIT: When I refer to validation logic I mean API signals; not the business-logic that actually determines if campusId is/isn't valid.
Thanks in advance!
If using placeholder in the route prefix you would also need to include it in the action itself
[Route("api/campus/{campusId:int}/building")]
public class BuildingController : Controller {
//...
[HttpGet]
[Route("{buildingId:int}")] // Matches GET api/campus/123/building/456
public IActionResult GetBuilding ([FromRoute]int campusId, [FromRoute]int buildingId) {
//... validate campus id along with building id
}
}
If concerned about repeated code for validation then create a base controller for campus related request and have a shared validation method.
Another option is to have a service/repository that can be used to verify campus id and its relation to the provided building id if needed.
It sounds like you want your users to provide a campusId when talking to the BuildingController, and your BuildingController to validate campusId in a DRY kind of way.
If that's the case, you can create an input model for your BuildingController methods:
public class BuildingIdInput
{
[Required]
public int? CampusId { get; set; }
[Required]
public int? BuildingId { get; set; }
}
Then you can let MVC bind user input to this model.
[Route("api/campus")]
public class BuildingController : Controller
{
[HttpGet]
[Route("{campusId}/building/{buildingId}")]
public IActionResult GetBuilding (BuildingIdInput input)
{
if (ModelState.IsValid)
{...}
}
}

How to put multiple GET methods in Web API2 controller?

I am doing a Web API 2 application and I have controller named NCT_ProcessSettings and already I have two GET methods as below.
1. public IEnumerable<Process_Settings> Get()
2. public HttpResponseMessage Get(int id)
Now I want to have third one as below (Same as first one but inside I will write different logic).
3. public IEnumerable<Process_Settings> Get() //Compiler will confuse which to pick?
I tried as below.
[HttpGet]
[Route("GetGlobalSettings")]
public IEnumerable<NCT_Process_Settings> GetGlobalSettings()
{
return entityObject.NCT_Process_Settings.Where(c => c.project_id == 0).ToList();
}
Below is my angularcode to call api.
var url = '/api/NCT_ProcessSettings/GetGlobalSettings';
May I have some idea how to fix this? Any help would be appreciated?
Enable attribute routing in WebApiConfig.cs before convention-based routes.
config.MapHttpAttributeRoutes();
Next update controller to use routing attributes. (note the route prefix)
[RoutePrefix("api/NCT_ProcessSettings")]
public class NCT_ProcessSettingsController : ApiController {
//GET api/NCT_ProcessSettings
[HttpGet]
[Route("")]
public IEnumerable<Process_Settings> Get() { ... }
//GET api/NCT_ProcessSettings/5
[HttpGet]
[Route("{id:int}")]
public HttpResponseMessage Get(int id) { ... }
//GET api/NCT_ProcessSettings/GetGlobalSettings
[HttpGet]
[Route("GetGlobalSettings")]
public IEnumerable<NCT_Process_Settings> GetGlobalSettings() { ... }
}
Read up more documentation here Attribute Routing in ASP.NET Web API 2
Used Action Name attribute
[ActionName("Get")]
public IEnumerable<Process_Settings> Get1()//used any name here
{
}

WebApi route identifying several controller types that match the same URL. How to solve?

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.

Categories