您的位置:首页 > 编程语言 > ASP

Creating a REST service using ASP.NET Web API

2012-12-15 13:03 681 查看

Creating a REST service using ASP.NET Web API

By After2050, 13 Sep 2012




4.33 (3 votes)

Introduction

A service that is created based upon the architecture of REST is called as REST service. Although REST looks more inclined to web and HTTP its principles can be applied to other distributed communication systems also. One of the real implementation of REST architecture is the World Wide Web (WWW). REST based services are easy to create and can be consumed from a wide variety of devices.

There are many APIs available in different languages to create and consume REST services. The ASP.NET MVC beta 4 comes with a new API called ASP.NET Web API to create and consume REST services. While creating REST services it is important to follow the rules and standards of the protocol (HTTP). Without knowing the principles of REST it is easy to create a service that looks RESTful but they are ultimately an RPC style service or a SOAP-REST hybrid service. In this article we are going to see how to create a simple REST service using the ASP.NET Web API.

REST

In REST architecture there is always a client and a server where the communication is always initiated by the client. The client and server are decoupled by a uniform interface there by making both the client and server to develop independently. Every resource in the server is accessed by a unique address (URI). When the client access a resource the server returns a representation of the resource based upon the request header. The representations are usually called as media-types or MIME types.



REST architecture

Uniform Interface

An important concept of REST is the uniform interface. The uniform interface contains a set of methods that can be understood by both the client and the server. In the HTTP uniform interface the important methods are GET, POST, PUT, DELETE, HEAD and OPTIONS. It is important to choose the right method for the right operation. For ex. if the client is going to get the resource from the server then they should use GET method. Likewise the DELETE method should be used to delete the resource and other methods has to be used appropriately based upon the action performed on the server. I wrote an article about using HTTP methods in REST applications and you can read it here.

Status Codes

Like the uniform interface the status codes returned from the server to the client also important in RESTful applications. We will see more about this while doing the sample.

ASP.NET Web API

ASP.NET Web API was previously called as WCF Web API and recently merged into ASP.NET MVC 4 beta. You can download ASP.NET MVC beta 4 from here. The ASP.NET Web API comes with its own controller calledApiController. So now we got two types of controllers while developing MVC applications: one is the default MVCController and the other one is the ApiController. Choosing the right controller for the right job is important. For creating REST services we have to use the ApiController, basically these controllers return data. For returning views (aspx, cshtml) we have to use the default controller. In our sample we are going to create a service that returns only data and hence we go with ApiController.

Sample

In this sample we are going to create a simple service that manages one s daily tasks. The service exposes operations to get all tasks, get a single task, create a new task, edit a task and delete a task as well.

Before creating any REST service first we have to identify the different resources in the application and map the actions performed over them to the HTTP methods and address. In our example there is only one resource Task and below is the mapping table.

ActionMethodURI
Get all the tasksGET/tasks
Get a single taskGET/tasks/id
Create a new taskPOST/tasks
Edit a taskPUT/tasks/id
Delete a taskDELETE/tasks/id
Let's create a new ASP.NET MVC 4 web application.



Create ASP.NET MVC 4 web application
There are different templates available for creating a MVC 4 application. I ve selected the Empty template.



Empty Template
The solution contains lot of things that we don t require for creating a service like Content, Scripts, Views etc. After removing them the solution looks clean and lean as shown below.



Solution Explorer
If we look into the Global.asax.cs, there will be already two routes defined: one is for the mvc controller and the other one if for the api controller.


Collapse | Copy Code
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =
UrlParameter.Optional }
);
}

Since we are going to create a service that will return only data remove the mvc controller s route and modify the other accordingly.


Collapse | Copy Code
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}

Next thing we have to do is create the resource
Task
.


Collapse | Copy Code
public class Task
{
public int Id { get; set; }

public string Description { get; set; }

public int Priority { get; set; }

public DateTime CreatedOn { get; set; }
}

We have to create a controller that exposes methods performed on our task resource over HTTP. The "Add Controller" window displays different useful scaffolding options but I m interested on the "API Controller with empty read/write actions".

Below is our newly added controller


Collapse | Copy Code
public class TasksController : ApiController
{
// GET /api/tasks
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}

// GET /api/tasks/5
public string Get(int id)
{
return "value";
}

// POST /api/tasks
public void Post(string value)
{
}

// PUT /api/tasks/5
public void Put(int id, string value)
{
}

// DELETE /api/tasks/5
public void Delete(int id)
{
}
}

If you notice our
TaskController
it is derived from the new
ApiController
. The controller contains methods to get all the tasks, get a single task, create a task, edit a task and delete a task. One interesting thing to note down is the ASP.NET Web API uses convention over configuration. Any method starts with Get is automatically mapped with the HTTP GET method, any method that starts with Post is automatically mapped with the HTTP POST method and so on.

After modifying the arguments and the return types of the methods the controller looks as below.


Collapse | Copy Code
public class TasksController : ApiController
{
public IEnumerable<Task> Get()
{
throw new NotImplementedException();
}

public Task Get(int id)
{
throw new NotImplementedException();
}

public HttpResponseMessage<Task> Post(Task task)
{
throw new NotImplementedException();
}

public Task Put(Task task)
{
throw new NotImplementedException();
}

public HttpResponseMessageDelete(int id)
{
throw new NotImplementedException();
}
}

Before implementing the methods we need a repository to store our tasks. For simplicity I ve used an in-memory cache repository. Here is our cache repository's interface and implementation.


Collapse | Copy Code
public interface ITaskRepository
{
IEnumerable<Task> Get();

Task Get(int id);

Task Post(Task Task);

Task Put(Task Task);

bool Delete(int id);
}



Collapse | Copy Code
public class TaskRepository: ITaskRepository
{
private List<Task> Tasks
{
get
{
if (HttpContext.Current.Cache["Tasks"] == null)
HttpContext.Current.Cache["Tasks"] = new List<Task>();

return HttpContext.Current.Cache["Tasks"] as List<Task>;
}
set
{
HttpContext.Current.Cache["Tasks"] = value;
}
}

public IEnumerable<Task> Get()
{
return Tasks;
}

public Task Get(int id)
{
return Tasks.Find(t => t.Id == id);
}

public Task Post(Task task)
{
task.Id = Tasks.Max(t => t.Id) + 1;
Tasks.Add(task);

return task;
}

public Task Put(Task task)
{
var t = Get(task.Id);

if (t == null)
throw new Exception(string.Format("Task with id {0} not exists.", task.Id));

t.Description = task.Description;
t.Priority = task.Priority;

return t;
}

public bool Delete(int id)
{
var t = Get(id);

if (t == null)
return false;

Tasks.Remove(t);

return true;
}
}

The
TaskRepository
class is simple and straight forward, it uses
HttpContext.Current.Cache
to store the tasks. The
TaskController
needs an
ITaskRepository
implementation and we will see at the end how we can inject it to the constructor using the extension points of Web API.


Collapse | Copy Code
private readonly ITaskRepository_taskRepository;

public TasksController(ITaskRepositorytaskRepository)
{
_taskRepository = taskRepository;
}

Now we have a repository for controller to store and maintain tasks. Let s go ahead and implement the controller methods.

Get()

This method returns all the tasks from the repository. The implementation of the method is straight and simple.


Collapse | Copy Code
public IEnumerable<Task> Get()
{
return _taskRepository.Get();
}

Get(id)

This method returns a task based upon the id. As per the HTTP standards when the resource requested by the client not exists in the server it should return the status 404.


Collapse | Copy Code
public Task Get(int id)
{
var task = _taskRepository.Get(id);

if (task == null)
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.NotFound,
Content = new StringContent("Task not found")
});
}

return task;
}

In the above method if the task not exists we are throwing a
HttpResponseException
passing the
HttpResponseMessage
to the constructor. In the
HttpResponseMessage
we have set the status as
HttpStatusCode.NotFound
(404) and some optional content that will be written to the response body.

Post(Task task)

The HTTP POST method should be used when you are trying to add a new resource and the address in which the newly added resource can be accessed will be defined by the server. If the resource is created successfully we have to return the status code 201 (means created) along with the URI by which the resource can be accessed.


Collapse | Copy Code
public HttpResponseMessage<Task> Post(Task task)
{
task = _taskRepository.Post(task);

var response = new HttpResponseMessage<Task>(task, HttpStatusCode.Created);

string uri = Url.Route(null, new { id = task.Id });
response.Headers.Location = new Uri(Request.RequestUri, uri);

return response;
}

Put(Task task) and Delete(Task task)

Here are the implementations for the Put and Delete methods.


Collapse | Copy Code
public Task Put(Task task)
{
try
{
task = _taskRepository.Put(task);
}
catch (Exception)
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.NotFound,
Content = new StringContent("Task not found")
});
}

return task;
}



Collapse | Copy Code
public HttpResponseMessageDelete(int id)
{
_taskRepository.Delete(id);

return new HttpResponseMessage
{
StatusCode = HttpStatusCode.NoContent
};
}

Normaly we think of returning a 404 when the resource client tries to delete not exists but as per the REST theory the DELETE method should be idempotent, that is how many times the client calls the Delete method the server should behave same as in the initial request.

Here is the complete controller code,


Collapse | Copy Code
public class TasksController : ApiController
{
private readonly ITaskRepository_taskRepository;

public TasksController(ITaskRepositorytaskRepository)
{
_taskRepository = taskRepository;
}

public IEnumerable<Task> Get() { return _taskRepository.Get(); }

public Task Get(int id) { var task = _taskRepository.Get(id); if (task == null) { throw new HttpResponseException(new HttpResponseMessage { StatusCode = HttpStatusCode.NotFound, Content = new StringContent("Task not found") }); } return task; }

public HttpResponseMessage<Task> Post(Task task) { task = _taskRepository.Post(task); var response = new HttpResponseMessage<Task>(task, HttpStatusCode.Created); string uri = Url.Route(null, new { id = task.Id }); response.Headers.Location = new Uri(Request.RequestUri, uri); return response; }

public Task Put(Task task) { try { task = _taskRepository.Put(task); } catch (Exception) { throw new HttpResponseException(new HttpResponseMessage { StatusCode = HttpStatusCode.NotFound, Content = new StringContent("Task not found") }); } return task; }

public HttpResponseMessageDelete(int id)
{
_taskRepository.Delete(id);

return new HttpResponseMessage
{
StatusCode = HttpStatusCode.NoContent
};
}
}

Our service is pretty much ready and one final thing we have to do is inject the
ITaskRepository
implementation to the
TaskController'
s constructor. Since the constructor is called by the framework we have to find the extension point to do that. Luckily Web API provides a very easy way to do that through the
ServiceResolver
class.

The
ServiceResolver
class has a method
SetResolver
that comes for the rescue. This method takes two lambda expressions as parameters, the first expression is used to return a single instance of the service and the second one is used to return multiple instances. I ll write in future a separate post about resolving dependencies in Web API. So here is the code that we have to insert into the
Application_Start
event of Global.asax.cs.


Collapse | Copy Code
GlobalConfiguration.Configuration.ServiceResolver.SetResolver
(

t =>
{
if (t == typeof(TasksController))
{
return new TasksController(new TaskRepository());
}

return null;
},

t => new List<object>()
);

So our service is finally ready. We can host it either in IIS or through self-hosting.

Download Sample
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: