Reputation: 5750
I have a pretty standard WebApi that does some basic CRUD operations.
I'm trying to add some different kind of lookups but am not quite sure how it's suppose to be done.
Here's my FoldersController currently
public class FoldersController : ApiBaseController
{
//using ninject to pass the unit of work in
public FoldersController(IApiUnitOfWork uow)
{
Uow = uow;
}
// GET api/folders
[HttpGet]
public IEnumerable<Folder> Get()
{
return Uow.Folders.GetAll();
}
// GET api/folders/5
public Folder Get(int id)
{
return Uow.Folders.GetById(id);
}
// POST api/folders
public HttpResponseMessage Post(Folder folder)
{
Uow.Folders.Add(folder);
Uow.Commit();
var response = Request.CreateResponse(HttpStatusCode.Created, folder);
// Compose location header that tells how to get this Folder
response.Headers.Location = new Uri(Url.Link(WebApiConfig.DefaultRoute, new { id = folder.Id }));
return response;
}
// PUT api/folders
public HttpResponseMessage Put(Folder folder)
{
Uow.Folders.Update(folder);
Uow.Commit();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
// DELETE api/folders/5
public HttpResponseMessage Delete(int id)
{
Uow.Folders.Delete(id);
Uow.Commit();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
}
what I would like to do is add a method that looks something like this
public IEnumerable<Folder> GetChildFolders(int folderID)
{
return Uow.Folders.GetChildren(folderID);
}
Since I already have the standard Get method in there, i'm not quite sure how to do so.
I initially thought I could just add a new route..something like
routes.MapHttpRoute(
name: "ActionAndIdRoute",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: null,
constraints: new { id = @"^/d+$" } //only numbers for id
);
And the just add something like an ActionName annotation to my method [ActionName("GetChildren")]
but that didn't fly.
Am I on the right track? How do I do something like this without adding another controller?
Upvotes: 3
Views: 16565
Reputation: 380
The idea behind WebAPI is to follow REST patterns, as Chris said. With that in mind, it's up to you to decide how your domain maps to that pattern. If child-folders are folders and use the same internal logic then maybe it makes perfect sense to put that Get in your FoldersController. If not, or if you want to perform all the REST methods on child folders, it may make more sense to create a ChildFoldersController.
Now that your app is organized sensibly, you can think about routing. WebAPI now supports attribute routing. If you add this line to your WebApiConfig.Register -- config.MapHttpAttributeRoutes();
-- like so:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
);
You can then put your route on the action itself:
[HttpGet]
[Route("folders/{folderID}/children")] // <-- notice the route here
public IEnumerable<Folder> GetChildFolders(int folderID)
{
return Uow.Folders.GetChildren(folderID);
}
Now all your REST calls to the path "folders/{folderID}" will work using the default route, while any gets on that route that include "/children" will hit that action. It also makes the call's behavior very clear to the caller of your API.
You can also still do this with the normal routing pattern:
// default route
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// folder children route
config.Routes.MapHttpRoute(
name: "FolderChildrenApi",
routeTemplate: "api/folders/{folderID}/children",
defaults: new { controller = "Folders", action = "GetChildFolders" }
);
You could also leave controllers as a variable in that custom route, if you have other resources that have children, and you can still put that action in a separate controller that also handles calls to the route "childFolders/{folderID}".
Routing is very flexible. Just make sure to design it so that it makes sense at a glance for both the api callers and the people maintaining your software (including you).
Here's some attribute routing info: Attribute Routing in WebAPI 2
Upvotes: 3
Reputation: 9167
You may not like this answer, but I feel it's the right one. WebAPI was designed to only have 5 calls, GET (one item / list items), POST, PUT and DELETE per entity type. This allows for REST URLs, such as Folders/Get/5, Folders/Get etc.
Now, in your scenario, you're wanting ChildFolders, which I can understand aren't different objects, but they are different entities in terms of REST (ChildFolders/Get) etc. I feel this should be another WebAPI controller.
There's ways of patching up the Http Routes to manage for this, but I don't feel it's how Web API was designed to work and it enforces you to follow REST data-by-entity-type protocols... otherwise why not just use .NET MVC Controllers for your AJAX calls?
Upvotes: 11
Reputation: 4978
Make your routes as follows:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = @"\d*" }
);
config.Routes.MapHttpRoute(
name: "DefaultApiPlusActionAndFolderid",
routeTemplate: "api/{controller}/{action}/{folderID}",
defaults: null,
constraints: new { action = @"[a-zA-Z]+", folderID = @"\d+" }
);
You can then have methods such as
[ActionName("GetChildren")]
public IEnumerable<Folder> GetChildFolders(int folderID)
{
return Uow.Folders.GetChildren(folderID);
}
Which you can call with /api/folders/getchildren/123
.
Just make sure that the parameter value in the method is folderID rather than id.
Upvotes: 0
Reputation: 2437
Actions are a great way to have two Get methods inside a WebApi controller.
Here is what I do in order to have different actions, and also an optional extra ID parameter for some of the actions:
Inside WebApiConfig.cs
I have the following:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}/{actionId}",
defaults: new
{
id = RouteParameter.Optional,
actionId = RouteParameter.Optional,
action = "DefaultAction"
}
);
and in the controller:
[ActionName("DefaultAction")]
public AdGetResponse Get(int id)
{
...
}
[ActionName("AnotherAction")]
public HttpResponseMessage GetAnotherAction(int id, int actionId)
{
}
Upvotes: 0
Reputation: 10175
One way is to write a new route specific to the GetChildFolders
action.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi-GetChildFolders",
routeTemplate: "api/{controller}/GetChildFolders/{id}",
defaults: new { action = "GetChildFolders" }
);
Upvotes: 0