
Greetings.
In the summer, the release of a new version of the framework was released, but it turned out to work with it only recently. In the new version many useful pieces were added, about one of them, namely ApiController, I would like to tell today.
Thanks to them, it became possible to do RESTFull Api without any extra effort. On a small example at the same time we will sort work with OData.
Create a new ASP MVC 4
Empty Project . For example, let's create a controller that will implement the functionality for working with topics. First, let's add a simple model:
public class Topic { public int Id { get; set; } public string Title { get; set; } }
Add a new controller, inherit it from
ApiController , so far without any actions:
public class TopicController : ApiController { }
Now our controller is available at:
localhost / api / topic . If we go through it, we will receive a message that no action was found in our controller that implements the response to the GET request. So add it to our controller:
public class TopicController : ApiController { public ICollection<Topic> Get() { return new Collection<Topic> { new Topic { Id = 1, Title = " 1"}, new Topic { Id = 2, Title = " 2"} }; } }
If we make a request to
localhost / api / topic , we get the following answer:
<ArrayOfTopicModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ApiControllerTutorial.Models"> <TopicModel> <Id>1</Id> <Title> 1</Title> </TopicModel> <TopicModel> <Id>2</Id> <Title> 2</Title> </TopicModel> </ArrayOfTopicModel>
Why the answer is in XML format? Because if we did not specify a
Content-Type in the request, the serializer will return XML to us by default. Let's get in JSON format. To do this, you can use the convenient application for Chromium -
REST Console (for the help of similar plugins / extensions for other browsers, I will be grateful). We specify in the
Content-Type "json" and get:
[{"Id":1,"Title":" 1"},{"Id":2,"Title":" 2"}]
We received a collection of topics. Add a new action to the controller to get one topic by its ID:
public Topic Get(int id) { return new Topic { Id = id, Title = String.Format(" {0}", id) }; }
A request to
localhost / api / topic / 5 will return the following response to us:
{"Id":5,"Title":" 5"}
Add an action to add our topic:
public string Put(Topic model) { return String.Format(" '{0}' !", model.Title); }
And send the following request to
localhost / api / topic :
{'Title':' '}
We will also indicate the necessary parameters in the request:
Request Method -
PUT and
Content type -
application / json (Do not confuse this Content type with the one I mentioned above. This one is indicated in the
Content Headers so that the binder knows what format the data came to him , and for querying topics, we specified the Content type in
Accept for the serializer). And get in response:
" ' ' !"
By the way about the return value. In our case, I returned the string with the message. You can also return
HttpResponseMessage and manipulate response codes, depending on the success / failure of the operation:
public HttpResponseMessage Put(Topic model) { if(String.IsNullOrEmpty(model.Title)) return new HttpResponseMessage(HttpStatusCode.BadRequest); return new HttpResponseMessage(HttpStatusCode.Created); }
I will not describe the action for the
POST method, since differences from
PUT - no. Add the last
DELETE action:
public string Delete(int id) { return String.Format(" {0} !", id); }
Routing
And what if we want to use our controller to provide simple api, without the support of methods?
Add a new controller with two actions:
public class TestRouteController : ApiController { public string GetTopic(int id) { return String.Format("Topic {0}", id); } public string GetComment(int id) { return String.Format("Comment {0}", id); } }
If we send a request to
localhost / api / testroute / 5 , we get an error:
Multiple actions were found that match the request . This is due to the fact that the Selector does not know what action to choose. Let's open WebApiConfig.cs and look at the route specified there:
public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }
As you can see, we have not defined an action definition for the controller in the template. The required controller action is selected based on the Method of the request. Add one more route below:
config.Routes.MapHttpRoute( name: "DefaultApiWithAction", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } );
After that, if you make a request to our controller with a direct indication of the necessary action (
localhost / api / testroute / gettopic / 5 ), we will receive a response. Or you can set the desired route for the action itself using the
ActionName attribute:
[ActionName("topic")] public string GetTopic(int id) { return String.Format("Topic {0}", id); }
Now you can query
localhost / api / testroute / topic / 5Odata
As you have already noticed, actions by the controller can return arbitrary objects or collections of objects and they will be successfully serialized. Previously, we needed to return an
ActionResult , and before that we manually serialize our data. In this regard, another interesting opportunity. First, install OData (
Nuget ) using the package manager:
PM> Install-Package Microsoft.AspNet.WebApi.OData -Pre
Add a new controller:
public class OdataController : ApiController { [Queryable] public IQueryable<Topic> Get() { return new EnumerableQuery<Topic>( new Collection<Topic> { new Topic{ Id = 1, Title = "1"}, new Topic{ Id = 2, Title = "2"}, new Topic{ Id = 3, Title = "3"}, new Topic{ Id = 4, Title = "4"}, new Topic{ Id = 5, Title = "5"} }); } }
Make a request to
localhost / api / odata :
[{"Id":1,"Title":"1"},{"Id":2,"Title":"2"},{"Id":3,"Title":"3"},{"Id":4,"Title":"4"},{"Id":5,"Title":"5"}]
Nothing surprising happened. But let's take a closer look at the Get method of our controller. It returns
IQueryable and is marked with the
[Queryable] attribute, which means that you can apply additional queries to our collection using OData directly in the query. Let's make several queries with different parameters and look at the answer:
Request | Answer |
---|
localhost / api / odata | [{"Id": 1, "Title": "1"}, {"Id": 2, "Title": "2"}, {"Id": 3, "Title": "3"}, {"Id": 4, "Title": "4"}, {"Id": 5, "Title": "5"}] |
localhost / api / odata ? $ skip = 2 | [{"Id": 3, "Title": "3"}, {"Id": 4, "Title": "4"}, {"Id": 5, "Title": "5"}] |
localhost / api / odata ? $ skip = 1 & $ top = 2 | [{"Id": 2, "Title": "2"}, {"Id": 3, "Title": "3"}] |
localhost / api / odata ? $ filter = (Id gt 1) and (Id lt 5) | [{"Id": 2, "Title": "2"}, {"Id": 3, "Title": "3"}, {"Id": 4, "Title": "4"}] |
localhost / api / odata ? $ filter = (Id gt 1) and (Id lt 5) & $ orderby = Id desc | [{"Id": 4, "Title": "4"}, {"Id": 3, "Title": "3"}, {"Id": 2, "Title": "2"}] |
Magic, is not it?
')
Submitting forms to controllers:
http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-1#sending_complex_typesEverything about OData:
http://msdn.microsoft.com/en-us/library/ff478141.aspxArchive with the project:
yadi.sk/d/k2KaG0cL1fXhA