Attribute routing and inheritance - asp.net-web-api

I am playing around with the idea of having a base controller that uses a generic repository to provide the basic CRUD methods for my API controllers so that I don't have to duplicate the same basic code in each new controller. But am running into problems with the routing attribute being recognized when it's in the base controller. To show exactly what the problem I'm having I've created a really simple WebAPI controller.
When I have a Get method in the main Controller and it inherits from the ApiController directly I don't have any problems and this works as expected.
[RoutePrefix("admin/test")]
public class TestController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
When I move the Get method into a base controller it is returning the contents of the 404 page.
[RoutePrefix("admin/test")]
public class TestController : TestBaseController
{
}
public class TestBaseController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
Some more interesting notes:
I can access the action at GET /Test/1. So it is finding it based on the default route still.
When I try to access POST /admin/test, it returns the following JSON
{
"Message":"No HTTP resource was found that matches the request URI 'http://test.com/admin/test'.",
"MessageDetail":"No type was found that matches the controller named 'admin'."
}
Does anyone know of a way to get the routing to work with attributes from a base controller?

Attribute routes cannot be inherited. This was a deliberate design decision. We didn't feel right and didn't see valid scenarios where it would make sense to inherit them.
Could you give a more realistic scenario as to where you would want to use this?
[Update(3/24/2014)]
In the upcoming 5.2 release of MVC Web API, there is going to be an extensibility point called System.Web.Http.Routing.IDirectRouteProvider through which you can enable the inheritance scenario that you are looking for here. You could try this yourself using the latest night builds(documentation on how to use night builds is here)
[Update(7/31/2014)]
Example of how this can be done in Web API 2.2 release:
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
//---------
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
// inherit route attributes decorated on base class controller's actions
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}

Using Web API 2.2, you can:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
as outlined here: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22

Got it.
[Route("api/baseuploader/{action}")]
public abstract class BaseUploaderController : ApiController
{
[HttpGet]
public string UploadFile()
{
return "UploadFile";
}
}
[Route("api/values/{action}")]
public class ValuesController : BaseUploaderController
{
[HttpGet]
public string Get(int id)
{
return "value";
}
}
One caveat here is that the route action paramter must be the same as the action name. I could not find a way to get around that. (You cannot rename the route with a RouteAttribute)

Related

Multiple versions of one controller in Swashbuckle

I am using WebApi and Swagger/Swashbuckle.
I have one version of all my controllers but for one of the controllers, I want to introduce versioning but keep the old version as well.
So for ControllerA where there is just one version I want to carry on doing this.
http://mysite/api/ControllerA
For ControllerB where there are just two versions I want
http://mysite/api/v1/ControllerB
http://mysite/api/v2/ControllerB
I can always redirect the unversioned url to v1 if that is possible.
http://mysite/api/ControllerB
I think I fundamentally need to use a SingleApiVersion because most of my controllers only have one version. Inside SwaggerConfig I have the line:
c.SingleApiVersion("v1", "MySite.Api");
However, when I try this I cannot see MyController in the Swagger UI or call it in Postman
[Route("api/v{version:apiVersion}/[controller]")]
public class MyController : ApiController
{
[ApiVersion("1.0")]
[HttpPost]
public HttpResponseMessage MyMethod([FromBody] MyRequest myRequest)
{
return MyCode(myRequest);
}
[ApiVersion("2.0")]
[HttpPost]
public HttpResponseMessage MyMethod([FromBody] MyRequest myRequest)
{
return MyNewCode(myRequest);
}
}
I think I am probably mixing up the Single and Multiple attributes but I don't want to use multiple versions of the whole API but I only have one with just new versions of one controller. Can this be done?
Have you tried this?
[ApiVersion( "2.0" )]
[ApiVersion( "3.0" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class HelloWorldController : ApiController
{
public string Get() => "Hello world v2!";
[MapToApiVersion( "3.0" )]
public string GetV3() => "Hello world v3!";
}
https://github.com/microsoft/aspnet-api-versioning/wiki/Versioning-via-the-URL-Path

Web Api Controller not resolving action

I am using .NET Core 2.2 and I have the controller below
[Route("api/[controller]")]
[ApiController]
public class CarsController : ControllerBase
{
[HttpPost]
[Route("api/cars/search")]
[ActionName("search")]
public ActionResult<IEnumerable<string>> SearchForCar([FromBody] SearchCriteria searchCriteria)
{
return new string[] { "value1", "value2" };
}
}
I am new to pure web api controllers.
I am confused about why when I post json to
http://localhost:51285/api/cars/search
I get 405 method not allowed?
I would normally have a route of
[Route("api/[controller]/action")]
That does work (once I remove route from the method attributes), but this wasn't the default provided in the template
Could someone let me know what I am missing?
Am I breaking convention by changing to
[Route("api/[controller]/[action]")]
Cheers
Paul
Since you not using the root slash in your action "/", the MVC middleware will search concatenating the route for controller and the action, should work like this
[Route("/api/[controller]/[action]")] // Check the root slash as first character
public ActionResult<IEnumerable<string>> SearchForCar([FromBody]
SearchCriteria searchCriteria)
Or like this
[Route("api/[controller]/[action]")] [ApiController] public class
CarsController : ControllerBase
Or
In controller:
[Route("api/[controller]")] [ApiController] public class
CarsController : ControllerBase
In action:
[Route("search")] // [action] Takes the method name
public <ActionResult<IEnumerable<string>>
SearchForCar([FromBody] SearchCriteria searchCriteria)

Web Api Core 2 distinguishing GETs

Why can't Web API Core 2 tell these apart?
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values?name=dave
[HttpGet]
public string Get(string name)
{
return $"name is {name}";
}
Here's what happens -
Both http://localhost:65528/api/values and http://localhost:65528/api/values?name=dave cause the first Get() method to execute.
This exact code works fine in Web Api 2.
I know multiple ways of getting around this, but I don't know why it happens.
Can someone explain why this has changed?
I don't think you can even compile your code in ASP.NET Core Mvc 2.0 since you have 2 actions mapped to same route [HttGet] api/values:
AmbiguousActionException: Multiple actions matched.
Remember, ASP.NET Web API uses the HTTP verb as part of the request to figure which action to call. Although it uses conventional routing (you name your actions Get, Post, Put and Delete, etc) if you don't have route attribute specify, I would highly recommend to always use routing attribute to annotate your controllers and actions.
Api Design time
Now it's up to you to design the route, as a developer. Remember the route is supposed to be a Uri that can identify a resource / resources.
Use the name as identifier along with the route
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")] // constraint as a string
public IActionResult GetByName(string name)
{
...
}
}
Use the name as filter, against the resource collection
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
// api/customers?name=dave
[HttpGet]
public IActionResult Get(string name)
{
...
}
}
To confuse you more
api/customers/dave will still execute GetById first!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
[HttpGet("{name}")]
public IActionResult GetByName(string name)
{
...
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
...
}
}
Both methods GetByName and GetById are potential candidates but MVC picks GetById method first because MVC compares the method/template name {name} and {id} through case-insensitive string comparison, and i comes before n.
That's when you want to put constraints.
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
You can also specify the Ordering too!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/portland
[HttpGet("{city:alpha}", Order = 2)]
public IActionResult GetByCity(string city)
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}", Order = 1)]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
Without the Order, the method GetByCity will be in favor than GetByName because character c of {city} comes before the character n of {name}. But if you specify the order, MVC will pick the action based on the Order.
Sigh the post is too long....
Because in your case the best match in the route pipeline is the default httpget attribute (the one that get all). The query is a regular string so if you don't ask what you want from the query the best match is still the one that get all.
[HttpGet]
public string Get([FromQuery]string name)
{
return $"name is {name}";
}
The [FromQuery] is pointing to the key "name" in the query string to get the value.
Your should read Routing in asp.net core

Passing ViewModel to Web-Api action

Is it possible to pass an ViewModel object to WebApi controller action instead of separate params?
Instead of using:
public class ContactsController : ApiController
{
public IEnumerable<Contact> GetContacts(string p1, string p2)
{
// some logic
}
}
I would like to use:
public class ContactsController : ApiController
{
public IEnumerable<Contact> GetContacts(TestVM testVM)
{
// some logic
}
}
public class TestVM
{
public string P1 { get; set; }
public string P2 { get; set; }
}
This doesn't seem to work for me. When I call /api/contacts/?P1=aaa&P2=bbb the testVM object doesn't get populated (null).
Also, I would like the TestVM to have valdiation attribtues defined and use ModelState.IsValid in my API controller.
Unless told otherwise WebApi will deserialise complex models using the content/body of the request. To tell WebApi to use the Url to construct the model you need to specify the [FromUri] attribute:
public IEnumerable<Contact> GetContacts([FromUri]TestVM testVM)
{
// some logic
}
I know it's kind of late to post another answer but I thought it could be useful for anyone who uses .net core as a web API service
public IEnumerable<Contact> GetContacts([FromQuery]TestVM testVM)

Mvc3 - Best practice to deal with data which are required for (almost) all requests?

I am creating an application in mvc3 and wondering how to deal with database data which is required for all application requests, some of them depends on a session, some of them depends on url pattern basically all data is in database.
Like to know best practice
What I do in my applications and consider to be the best practice is to load your common data to the ViewBag on the Controller constructor.
For every project, I have a DefaultController abstract class that extends Controller. So, every controller in the project must inherit from DefaultController, instead of Controller. In that class' constructor, I load all data common to the whole project, like so:
// DefaultController.cs
public abstract class DefaultController : Controller
{
protected IRepository Repo { get; private set; }
protected DefaultController(IRepository repo)
{
Repo = repo;
ViewBag.CurrentUser = GetLoggedInUser();
}
protected User GetLoggedInUser()
{
// your logic for retrieving the data here
}
}
// HomeController.cs
public class HomeController : DefaultController
{
public HomeController(IRepository repo) : base(repo)
{
}
// ... your action methods
}
That way you will always have the logged in user available in your views.
I do the same as #rdumont but with one exception: I create a CommonViewModel which I use to define all common properties that I use.
public class CommonViewModel
{
public string UserName {get;set;}
public string Extension {get;set; }
}
Declare a property in the base controller:
public abstract class BaseController : Controller
{
protected CommonViewModel Commons { get; private set; }
protected virtual void OnResultExecuting(ResultExecutingContext filterContext)
{
ViewBag.Commons = Commons;
}
}
By doing so I get everything almost typed. The only cast that I need to do is to cast ViewBag.Commons to the CommonViewModel.
Best is to avoid ViewBag at all.
See this answer, which details how to use Html.RenderAction() for that purpose:
Best way to show account information in layout file in MVC3
I'd suggest using a base ViewModel class.
So a base class with properties/functions which should be available at any point.

Resources