Inheriting Route attribute messes up already existing actions in Web API - c#

I am trying to inherit Route attributes from a base Controller exactly according to this. Though it seems to work correctly, but it messes up the previously working actions.
Below are a minimal example of my base and child controllers.
[RoutePrefix("api/{controller}")]
public class MyController<TEntity, TDto>: ApiController{
[HttpGet]
public IEnumerable<TDto> All(){
...
}
[HttpGet, Route("lookup")]
public virtual IEnumerable<TDto> LookupData(){
...
}
}
[RoutePrefix("api/entity")]
public class EntityController : MyController<Entity, DTO>
{
}
After implementing the route attribute inheritance, the api/entity/lookup action works but in case of api/entity (for All), ActionSelector returns 2 actions, both All, and LookupData, thus causing error.
I am not sure why it is selecting an action with Route attribute even in case of a regular route. What I should do differently? Or is there any robust way to write a ActionSelector for this problem?

Try adding empty [Route] to All method:
[HttpGet]
[Route]
public IEnumerable<TDto> All(){
...
}

Related

Inherited razor page OnPostAsync - Multiple handlers matched

I have encountered an issue with my new test project when I inherit a razor page from another.
So I had an editor page and I wanted to use the same flow just with gave an extra param (id), the code was:
public class EditModel : BaseModel
{
public EditModel()
{
}
public Task<IActionResult> OnPostAsync(int id)
{
...
}
}
public class CreateModel : EditModel
{
public CreateModel()
{
}
public Task<IActionResult> OnPostAsync()
{
...
}
}
Also I defined the editor cshtml as:
#page "{id:int}"
...
and the create cshtml as:
#page
...
I would expect that the routing was obvious cuz editor's parameter is NOT optional, but create does not need a parameter, but I got the error:
Multiple handlers matched
If I define parameter in create page model too it starts working with 0 value.
I have two questions about that:
Can I define a routing template where I can explicitly forbid the parameter (and how) to avoid ambiguity? (Now I'm using default routing template.)
Shoud I avoid this kind of inheritance of razor pages? I can except that if it is by design and use another way to do it.
I would like to mention that I know I can define another handler methods different from default onget/onpost/etc and use it this way, however I think the above problem should work properly if I can define the routing in a good way.
Hi and welcome to the board! Before digging more about your purposes for this design, let first make something clear about the scenario.
You are probably using ASP.NET Core Razor Page, but you ended up with this line EditModel : BaseModel so I assume that there must be something like BaseModel : PageModel
Next thing I know that you make another inheritance which is CreateModel : EditModel and this time it bugged me out because this obviously will make EditModel inherit a method so-called Task<IActionResult> OnPostAsync(int id) because that what inheritance do!
For example:
public class Test1
{
public void sayHello(){
Console.WriteLine("hehe");
}
}
public class Test2 : Test1
{
public void sayHello(string inputValue){
Console.WriteLine(inputValue);
}
}
and the result when you have something like
Test2 test = new Test2();
test.sayHello();
test.sayHello("something");
The result would be
hehe
something
Once again, I'm not sure about your purposes on this methodology, so it would be nice if you can share some though so we both can evolve in something new.
UPDATE:
At this point, I understand that you created a problem for a case study. So let see if I came up with something right.
.NET core allows you to define some specific route inside Startup.cs beside the default routing, which obviously won't fit in this case.
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AddPageRoute("/edit", "{handler?}/{id?}");
options.Conventions.AddPageRoute("/create", "{handler?}");
});
}
Now, I believe if you put #page "{id:int}" in your .cshtml page, it would run as expected.

Why does MVC ignore controller methods in inherited controllers?

... which may not really be the question at all, but that's what it looks like to me right now.
I have a controller structure that has several layers of inheritance. The base controller is the one that implements Controller and has a method called Create(Guid var1, DateTime? var2) and is called like /MyDomain/Create/00000-0000-0000-000000.
I'm currently trying to implement a method in a controller lower down in the inheritance tree with the signature Create() which takes a QueryString parameter. (/MyDomain/Create?otherVar=somevalue) However, ASP.NET decides this is not valid as an "endpoint" and throws an error message saying The parameters dictionary contains a null entry for parameter 'var1' of non-nullable type 'System.Guid' for method 'System.Web.Mvc.ActionResult Create(System.Guid, System.Nullable`1[System.DateTime])'
I don't really know what is going on here. If I try to call another method on the same controller (with a name that is unique and not used higher up in the inheritance stack e.g. /MyDomain/DifferentMethod) it works without a problem.
My google-fu is coming up short on this problem.
So you have something like:
public abstract class BaseController{
[HttpGet]
public IActionResult Create(Guid var1, DateTime var2){..
}
and
public class SomeClassController : BaseController{
[HttpGet]
public IActionResult Create(){..
}
The problem is that you cannot have 2 routes with the same name and different signature.
This is because the routing don't know exactly where you want to go: with the url '/blalbalba/Create/' witch one you want?
The base class or the inherited once?
It's not so obvious.
Ps take a look on this answer:
ASP.NET MVC ambiguous action methods

Optional parameters at the beginning of route

I'm trying to create a route that satisfies below two scenarios:
en-us/mycontroller/action/1
mycontroller/action/1
Basically what I need is the below rule. But it seems that the system is always expecting the locale parameter, even though it's optional.
[Route("{locale?}/[controller]/[action]")]
public class MyController : Controller
{
[Route("{id}")]
public IActionResult Action(int id) {
return View();
}
}
Any idea how I can create a rule that supports above scenarios?
Optional parameters in routes must be at the end. There's no way around that. However, there's an alternate way to handle it: just add multiple routes:
[Route("{locale}/[controller]/[action]")]
[Route("[controller]/[action]")]
Now it will respond to either way. FWIW, you might want to consider adding some constraints to the locale param, though, so it doesn't match everything.

MVC 3 Call Controller method directly but also invoke the ActionFilterAttribute?

How would you go about invoking the ActionFilterAttribute when you are calling a controllers method directly? Imagine the following:
public class ApiController : Controller {
public ActionResult CallSomething() {
return IsCalled();
}
[MyAction]
public ActionResult IsCalled() {
return View();
}
}
So the request comes into /Api/CallSomething, the CallSomething() method internally calls another method (in this case IsCalled()) but when that method is called the ActionFilterAttribute is invoked and runs.
At the moment the ActionFilterAttribute will only be invoked if it's part of the original request, which makes sense because then it's part of the whole MVC lifecycle and all I'm doing is just calling a method directly, I'd need to do a RedirectToAction if I wanted it to get fired by MVC. RedirectToAction alters the URL so this isn't suitable for my purposes.
I've poked about the System.Web.Mvc code but I can't see how or when its being invoked. I figure that if it can do it then so can I potentially using reflection. In this event the Views aren't relevant as I am override the ExecuteResult() to write my own content into the http response.
Anyway I've resigned myself to this maybe not being possible but I'd just thought it would be worth a try.
There is no practically correct way to achieve that. So don't attempt to do it. Controller actions shouldn't be calling themselves as in your example. Use a redirect in this case. Or have the client send the request directly to the second action if you don't want the redirect.
Oh and if you have some common functionality between those controller actions that you want to reuse, simply externalize this functionality into some other layer. There are many ways to provide common functionality in ASP.NET MVC 3 depending on the exact scenario.
The simplest way to do what you're talking about is just to factor out the functionality that you want to execute as a filter and ad-hoc. It doesn't make sense to force a filter to execute where it shouldn't be executing. But it might make sense to want to call some part of the filter ad-hoc. The simplest way to do this is with a plain old static method:
public class ApiController : Controller {
public ActionResult CallSomething() {
MyActionFilter.CommonStaticMethodThatIsAlsoUsedInTheNormalFilter();
return IsCalled();
}
[MyActionFilter]
public ActionResult IsCalled() {
return View();
}
}
You should be more specific about what you're trying to achieve, there might be other features of the framework that are a better fit.

Removing an inherited Attribute in .net

I got a ASP.NET MVC controller like this
[Authorize]
public class ObjectController : Controller
{
public ObjectController(IDataService dataService)
{
DataService = dataService;
}
public IDataService DataService { get;set;}
}
The Authorize attribute is defined as "Inherited=true" in the framework. So when i make the next controller:
public class DemoObjectController : ObjectController
{
public DemoObjectController(IDataService dataService)
: base (dataService)
{
DataService = new DemoDataService(DataService);
}
}
It gets the authorize attribute, but i don't want it here. I want the Demo Object controller to be available to everyone, cause it just uses fake data.
I guess I'll implement my own Authorize attribute that don't get inherited, for i can't find any way to remove the attribute from the inherited class.
Since it is marked as inherited, there isn't much you can do in this case (since you don't control the code that is checking for the attribute via reflection). Implementing your own attribute seems the most practical option.
With MVC you can also often achieve the same functionality with overrides (the On* methods), which might be worth looking into.
If a base type requires authorization then all child types ought to require authorization as well. I think that you ought to inherit from a different type but a workaround would be to declare your own AthorizeAttribute that works for this particular instance.

Categories