WebApi2 attribute routing inherited controllers - c#

I'm trying to create basic REST api with a base controller like so:
Base class:
public abstract class WebApiEntityController<TEntity> : ApiController
where TEntity : EntityBase<TEntity, int>
{
private readonly IRepository<TEntity> _repository;
protected WebApiEntityController(IRepository<TEntity> repository)
{
_repository = repository;
}
[Route("")]
[WebApiUnitOfWork]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.ToList());
}
[..........]
Derived class:
[RoutePrefix("api/TimesheetTask")]
public class TimesheetTaskController : WebApiEntityController<TimesheetTask>
{
private readonly IRepository<TimesheetTask> _timeSheetTaskRepository;
public TimesheetTaskController(IRepository<TimesheetTask> timeSheetTaskRepository) : base(timeSheetTaskRepository)
{
_timeSheetTaskRepository = timeSheetTaskRepository;
}
}
but calling GET on the route ~/api/TimesheetTask/ results in a 404 not found.
According to this answer, attribute routes cannot be inherited. So my question is, how can I write a consistent API for all my domain models without having to copy and paste code?
I know I can do convention routing with this configuration:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
but then I'll have to specify the action, and my endpoints will be
/api/{controller]/Get
/api/{controller]/Post
and I don't want that. I can also remove the {action} part of the routeTemplate, but then how will I route to custom actions?
If anyone can help, that would be appreciated. Also, the next step for my domain model API would involve supporting querying, and that can easily get complicated. Is there a library that generates these routes for you? If anyone can help me find such a library it would be much appreciated.

The answer you quoted has since been updated. As of WebApi 2.2 they created an extensibility point to allow the feature you wanted.
Attribute rout can be inherited, but you need to configure it. I had the same requirement for a base API controller and after searching came across the same answer you quoted.
.NET WebAPI Attribute Routing and inheritance
You need to overwrite the DefaultDirectRoutePrivider:
public class WebApiCustomDirectRouteProvider : DefaultDirectRouteProvider {
protected override System.Collections.Generic.IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(System.Web.Http.Controllers.HttpActionDescriptor actionDescriptor) {
// inherit route attributes decorated on base class controller's actions
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>(inherit: true);
}
}
With that done you then need to configure it in your web api configuration
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
.....
// Attribute routing. (with inheritance)
config.MapHttpAttributeRoutes(new WebApiCustomDirectRouteProvider());
....
}
}

Related

How to disable REST convention from interfering with my ApiController

This is my api configuration class:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
}
}
This is my api controller class:
public class RoleController : ApiController
{
// Some action that works fine...
// Another action that works fine...
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
I am calling my actions using POST and they are working fine.
But, when I try to call the Delete action using POST I get the following error:
405 Method Not Allowed
The requested resource does not support http method 'POST'.
Clearly, this is because ApiController enforces REST convention
which expects DELETE verb for Delete action.
Now, how do I disable this REST convention constraints
and write my actions in a classic manner?
You can use the HttpPostAttribute to enforce the Action to accept only POST:
public class RoleController : ApiController
{
[HttpPost]
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
You may want to keep the REST conventions while allowing certain clients (like HTML forms) to properly use you actions.
So, you can use a combination of HttpPostAttribute and HttpDeleteAttribute or AcceptVerbsAttribute (which allows multiple verbs) to allow multiple verbs:
public class RoleController : ApiController
{
[HttpPost, HttpDelete]
// OR
[AcceptVerbs("DELETE", "POST")
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
If you don't want magic verbs and magic action names you can use route attributes.
Delete config.Routes.MapHttpRoute and set:
config.MapHttpAttributeRoutes();
Now you have to set the routes manually:
[RoutePrefix("~/Role")]
public class RoleController : ApiController
{
[HttpPost]
[Route("~/Delete")]
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
In your case I'd stop using any kind of REST conventions.
Instead of having a Delete method on the Role controller you can have a DeleteRole method and allow POST on it. This way nothing will interfere with what you want to do. Nothing forces you to build a REST api if that's not what you need.
There are several things you could do to still build a nice api.
For example, you could return an IHttpActionResult
your method could look like this:
public class RoleController : ApiController
{
[HttpPost]
public IHttpActionResult DeleteRole([FromBody]int RoleID)
{
if ( RoleID <= 0 )
{
return BadRequest();
}
var deleteResult = RoleBL.Delete(RoleID);
return Ok(deleteResult);
}
}
You still return the same object but it's wrapped inside an object with a proper http code so your code which deals with the result, won't change much.

Custom route in web api just redirects to current page

Being a noob in MVC web api there is probably something very obvious I'm missing..
However, In my ProjectController I have the following method with attributes (I know this kind of method should be POST but just testing easily...):
[Route("api/projects/archive/{id:int}")]
[HttpGet]
public void Archive(int id)
{
_projectService.Archive(id);
}
However, when I open my URL such as:
http://localhost:49923/api/projects/archive/1
The page simply redirects to the current URL, and the archive method is not called. I also have a breakpoint at the method to verify it's not hit.
My best guess is I also have to update my web api route config which is the default, but I just assumed the route attribute was enough?
Here is my web api route config:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional});
config.Formatters.JsonFormatter.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("text/html"));
}
What am I doing wrong here? :-)
EDITS:
Clearification 1 - my ProjectController:
public class ProjectsController : ApiController
{
private ProjectService _projectService;
public ProjectsController()
{
_projectService = new ProjectService();
}
[Route("api/projects/archive/{id:int}")]
[HttpGet]
public void Archive(int id)
{
_projectService.Archive(id);
}
}
Clearification 2 - redirect:
So lets say I stand on the homepage (/). I then go to the URL "http://localhost:49923/api/projects/archive/1", it will just reload page and leave my back at the home page.
The Web API config is configured correctly.
Ensure that the controller and the action are constructed properly
public class ProjectController : ApiController {
//...other code removed for brevity
[HttpGet]
[Route("api/projects/archive/{id:int}")]//Matches GET api/projects/archive/1
public IHttpActionResult Archive(int id) {
_projectService.Archive(id);
return Ok();
}
}
Its bit late to answer but hope you find it useful,
Many times the way how we write the code help us find the solution to the problem,
As Nkosi already answered, that the constructing controller and the action method properly will resolve the issue.
Its always helpful to check the method first rather then looking at the route.config because by default it will be the same unless you provide your custom attribute.

web api in MVC no longer working

I am using VS 2017 community. I have been building web api s for years. But something must have changed as I cannot get the simplest example to work.
I have a simple controller in the controller folder
public class TestApi : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
I have the necessary code in application start:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
But when I try and test the web api with a get like:
http://localhost:54014/api/testapi
I always get an xml message
Error
Message
No HTTP resource was found that matches the request URI
'http://localhost:54014/api/testapi'.
/Message
MessageDetail
No type was found that matches the controller named 'testapi'.
/MessageDetail
/Error
Here is the WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I am a couple of hours into head scratching on this. As I say I have built many MS web api implementations and this one has me baffled.
You should add Controller suffix to your class name.
public class TestApiController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
When the app starts, the asp.net mvc frameworks looks for classes (which are inherited from ApiController or Controller) with this suffix and register them when building the route table. When a request comes to your app, the framework again look into the route table and direct the request to the corresponding controller and action method.
Make this change, rebuild your project and try again.
In addition to the already provided answer (which is correct by the way) you can also consider using attribute routing over convention-based routing, where in this case it would not matter what the name of the controller is.
You already have it enabled Based on the WebApiConfig.cs and
config.MapHttpAttributeRoutes();
So now it is just a matter of putting the attributes where needed
[RoutePrefix("api/testapi")]
public class TestApi : ApiController {
[HttpGet]
[Route("")] //Matches GET api/testapi
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
}
Reference: Attribute Routing in ASP.NET Web API 2

ASP.NET Web Api 2 routing convention strange implementation

We all know Web API 2 routing conventions and how we can use the following to automatically map onto ApiController types and actions:
config.Routes.MapHttpRoute(
"Default",
"api/{controller}/{action}"
);
public class AbcdefghijklmnoController : ApiController
{
public int Test()
{
return 5;
}
}
This works as expected, I get 5 when calling api/Abcdefghijklmno/test. But now I want to create Controller types that don't explicitely end with Controller in the end, combined with a custom Controller discovery mechanism as outlined in http://www.strathweb.com/2013/08/customizing-controller-discovery-in-asp-net-web-api/.
public class Abcdefghijklmno : ApiController
{
public int Test()
{
return 5;
}
}
The crazy thing is now that I can get the value 5 from route api/abcde/test, so what the convention based routing is really doing is simply chopping off the length of the string "Controller" from the type's name. Apart from this extremely naive implementation from Web API 2, is there any way to circumvent this?

Routing Post to a non standard uri web api

I´m writing a REST web api and I need to have an endpoint like /api/users/{id}/modify or http://localhost:8080/api/users/6/modify using a POST method.
I have a UsersController class with al read/write actions but I´m only able to access the post method by accessing /api/users, not /api/users/6/modify. I need to expand the hierarchy(if that is well said).
How can I do to achieve this?
You can use the Attribute Routing of asp.net web api.
The first thing is to enable it over the HttpConfiguration, in asp.net web api template, you can see it on the WebApiConfig.cs file.
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown...
}
}
}
After that you can define a controller which should inherits from ApiController and you can use the Route attribute to define a custom route, for sample:
[RoutePrefix("api/users")]
public class UsersController : ApiController
{
[HttpPost]
[Route("{id}/modify")]
public HttpResponseMessage PostModify(int id)
{
// code ...
}
}
The RoutePrefix will define a prefix for all actions on the controller. So, to access the PostModify you should use a route like /api/users/6/modify in a post action. If you do not want it, just remove the RoutePrefix and define the complete url on the route attribute, like this: /api/users/{id}/modify.
You also can guarantee the type of the id argument defining a route like this:
[RoutePrefix("api/users")]
public class UsersController : ApiController
{
[HttpPost]
[Route("{id:int}/modify")]
public HttpResponseMessage PostModify(int id)
{
// code ...
}
}

Categories