WCF4.0 –- RESTful WCF Services (2) (实现增,删,改,查) 【转】

http://blog.csdn.net/fangxing80/article/details/6247297

RESTful服务就是为了实现一个易于整合的系统,可以跨平台跨语言的调用(如下图),【上篇】介绍了如何用WCF构建一个RESTful的服务。 本篇进一步通过一个实例记录如何实施一个具体的RESTful WCF服务以及客户端调用服务进行增,删,改,查。

WCF 4.0 其新功能之一就是 WCF 更容易以 REST API 来呈现,在 WCF 3.5 中的 WebGetAttribute 与 WebInvokeAttribute 中的 UriTemplate 参数原本不支持 REST URL 格式,为了 REST 功能,微软还特意发布了 WCF REST Starter Kit 组件,让开发人员可以利用 WCF 3.5 开发真正 REST-based 的应用程序,由 URL 对应到指定的 Service Contract 中的 Operation Contract,在 WCF 4.0 中,WCF 的核心已经融入了 REST Starter Kit 中的 URL 引擎,在 WebGetAttribute 与 WebInvokeAttribute 已经可以支持 REST 的功能,Windows Azure 许多服务的 REST API 就是利用 WCF 来开发的。

 

主要涉及以下内容: 1. 如何通过 JSON 数据进行交互; 2. 如何进行服务端的错误处理,并在客户端区分不同的异常; 3. 如何利用 Microsoft.HttpClient (微软提供的第3方组件);
本次示例的工程: PS: 上面虽然在一个solution里有两个工程,但是工程之前没有任何引用。完全靠Http消息传递。 1. 创建服务端工程: 通过VS2010的Extension Manager,可以下载一个“WCF REST Service Template”。通过这个我们可以快速创建一个WCF REST服务。它是一个创建在Web Application工程里的服务。和前一篇介绍的WCF服务不同的是在Globel.asax中的Application_Start事件中注册服务。并且注册的"TaskService"自动成为服务的基地址,即 http://<machine_name>:<port>/TaskService/

  1. public class Global : HttpApplication  
  2. {  
  3.     void Application_Start(object sender, EventArgs e)  
  4.     {  
  5.         RegisterRoutes();  
  6.     }  
  7.   
  8.     private void RegisterRoutes()  
  9.     {  
  10.         RouteTable.Routes.Add(new ServiceRoute("TaskService",   
  11.             new WebServiceHostFactory(), typeof(TaskService)));  
  12.     }  
  13. }  

2. 服务的实现: 服务端直接通过Linq2Entities操作DB,为了返回JSON的数据,参数和返回值都设计为POCO类。在Linq2Entities生成代码后,又拷贝了一份进行删减修改为POCO类。(现在有一个ADO.NET POCO Generator 的t4模板,不知道能否简化我现在的做法...)

  1. namespace WcfRestService2.Model  
  2. {  
  3.     [DataContract]  
  4.     public class PocoTask  
  5.     {  
  6.         [DataMember]  
  7.         public virtual int ID { get; set; }  
  8.         [DataMember]  
  9.         public virtual string Title { get; set; }  
  10.         [DataMember]  
  11.         public virtual string Detail { get; set; }  
  12.         [DataMember]  
  13.         public virtual int State { get; set; }  
  14.         [DataMember]  
  15.         public virtual DateTime UpdatedDate { get; set; }  
  16.     }  
  17. }  

REST中很好的利用了HTTP的GET/POST/PUT/DELETE方式,绑定到服务的不同方法上。比如GET方法不用客户端提供太多数据,正适合查询只提供主键或者查询字段的场景。POST则适合数据的插入,PUT则应用在数据更新,DELETE则直接用在数据删除上。当然通过URI的区别,也可以全部用POST或者PUT,只是语义符合调用场景的话,是的服务使用更易于理解和习惯。 服务端实现片段: (1) 查询(Http/GET),这里定义了访问的UriTemplate,完整访问地址就是"基地址+UriTemplate",比如: http://localhost:port/TaskService/Tasks/State/{state} 另外ResponseFormat设定为Json格式。 也可以在配置文件中,修改 <standardEndpoint> 节点的 defaultOutgoingResponseFormat 属性控制Response的格式。

  1. [WebGet(UriTemplate = "Tasks/State/{state}",   
  2.     ResponseFormat = WebMessageFormat.Json)]  
  3. public List<PocoTask> GetTasksByState(string state)  
  4. {  
  5.     using (var db = new TasksEntities())  
  6.     {  
  7.         int s = Int32.Parse(state);  
  8.         var query = db.Task.Where(t => t.State == s || -1 == s);  
  9.         return GetPocoData(query);  
  10.     }  
  11. }  

(2) 新建(Http/POST),POST里的数据格式通过RequestFormat定义为Json,WCF框架接受到Json数据的请求,会自动反序列化成PocoTask实例。然后我又创建真正EF里的Entity实例,将PocoTask数据复制到Entity里,插入DB。

  1. [WebInvoke(UriTemplate = "Tasks/Add", Method = "POST",   
  2.     RequestFormat=WebMessageFormat.Json)]  
  3. public void Create(PocoTask pocoTask)  
  4. {  
  5.     var ctx = WebOperationContext.Current;  
  6.     ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;  
  7.     try  
  8.     {  
  9.         using (var db = new TasksEntities())  
  10.         {  
  11.             var task = new Task();  
  12.             CopyValue(pocoTask, task);  
  13.             task.UpdatedDate = DateTime.Now;  
  14.             db.AddToTask(task);  
  15.             db.SaveChanges();  
  16.         }  
  17.         ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Created;  
  18.     }  
  19.     catch (Exception ex)  
  20.     {  
  21.         ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.ExpectationFailed;  
  22.         ctx.OutgoingResponse.StatusDescription = ex.Message;  
  23.     }  
  24. }  

(3) 更新(Http/PUT),先通过id查出Entity,再将客户端的数据更新到Entity上。

  1. [WebInvoke(UriTemplate = "Tasks/{id}", Method = "PUT",   
  2.     RequestFormat=WebMessageFormat.Json)]  
  3. public void Update(string id, PocoTask pocoTask)  
  4. {  
  5.     var ctx = WebOperationContext.Current;  
  6.     ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;  
  7.     try  
  8.     {  
  9.         using (var db = new TasksEntities())  
  10.         {  
  11.             var nId = Convert.ToInt32(id);  
  12.             var target = db.Task.SingleOrDefault(t => t.ID == nId);  
  13.             target.Title = pocoTask.Title;  
  14.             target.Detail = pocoTask.Title;  
  15.             target.State = pocoTask.State;  
  16.             target.UpdatedDate = DateTime.Now;  
  17.             db.SaveChanges();  
  18.         }  
  19.         ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Accepted;  
  20.     }  
  21.     catch (Exception ex)  
  22.     {  
  23.         ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.ExpectationFailed;  
  24.         ctx.OutgoingResponse.StatusDescription = ex.Message;  
  25.     }  
  26. }  

(4) 删除(Http/DELETE)

  1. [WebInvoke(UriTemplate = "Tasks/{id}", Method = "DELETE")]  
  2. public void Delete(string id)  
  3. {  
  4.     var ctx = WebOperationContext.Current;  
  5.     ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;  
  6.     try  
  7.     {  
  8.         using (var db = new TasksEntities())  
  9.         {  
  10.             var nId = Convert.ToInt32(id);  
  11.             var task = db.Task.SingleOrDefault(t => t.ID == nId);  
  12.             db.Task.DeleteObject(task);  
  13.             db.SaveChanges();  
  14.         }  
  15.         ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Accepted;  
  16.     }  
  17.     catch (Exception ex)  
  18.     {  
  19.         ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.ExpectationFailed;  
  20.         ctx.OutgoingResponse.StatusDescription = ex.Message;  
  21.     }  
  22. }  

服务端的异常处理中通过 OutgoingResponse.StatusCode 返回不同的Code,这样客户端通过这些Code就知道服务端出现了什么错误。但是目前我还不知道OutgoingResponse.StatusDescription如何在客户端获得。如果可以的话,我们就可以知道错误的详细内容。 本示例中: a) 查询成功 —— System.Net.HttpStatusCode.OK (默认)

 

b) 创建成功 —— System.Net.HttpStatusCode.Created c) 更新成功 —— System.Net.HttpStatusCode.Accepted d) 删除成功 —— System.Net.HttpStatusCode.Accepted

System.Net.HttpStatusCode的枚举可以参看 MSDN: http://msdn.microsoft.com/en-us/library/system.net.httpstatuscode.aspx
3. 客户端实现:
因为REST 是基于HTTP的, 所以对于 REST 的客户端的开发者,无法像传统的 WebService或者其他的WCF服务通过引用wsdl,享受“奢侈”的代码生成,而使用强类型的本地代理调用服务。 开发者只能通过 Http Request 的组装, 但正因为这种直接的HttpRequest组装,而使得客户端真正是语言无关的。这里不得不提一下 Microsoft.Http.dll 和 Microsoft.Http.Extensions.dll,它们是微软提供的REST客户端包。可以更加方便地操作 HttpRequest/Response,你可以在这里下到: http://aspnet.codeplex.com/releases/view/24644 
客户端片段: (1) 查询(HTTP/GET), 使用 HttpClient.Get 方法,返回的是HttpResponseMessage,HttpResponseMessage.Content 返回的是Json数据。再通过 Json.NET 第3方组件进行反序列化。另外,为了是在客户端里能通过实例化的类来操作数据,所以在客户端单独再定义了 Task 类。(因为客户端无法通过wsdl生成代理)

  1. // Get Data by state   
  2. var client = new HttpClient();  
  3. var strUrl = "http://localhost:1180/TaskService/Tasks/State/{0}";  
  4. strUrl = string.Format(strUrl, comboBox1.SelectedValue);  
  5. var response = client.Get(strUrl);  
  6. response.EnsureStatusIsSuccessful();  
  7. var json = response.Content.ReadAsString();  
  8. var data = JsonConvert.DeserializeObject<List<Task>>(json);  

(2) 新建(HTTP/POST),将数据序列化成Json格式放进 HttpContent 再使用 HttpClient.Post 提交 HttpContent 数据。 HttpContent 需要指定 ContentType 是Json格式的。

  1. // Add Task  
  2. var task = GetTask();  
  3. var client = new HttpClient();  
  4. var strUrl = "http://localhost:1180/TaskService/Tasks/Add";  
  5. var response = client.Post(strUrl, GetContent(task));  
  6. response.EnsureStatusIsSuccessful();  

(3) 更新(HTTP/PUT)

  1. // Update Task  
  2. var task = GetTask();  
  3. var client = new HttpClient();  
  4. var strUrl = "http://localhost:1180/TaskService/Tasks/{0}";  
  5. strUrl = string.Format(strUrl, task.ID);  
  6. var response = client.Put(strUrl, GetContent(task));  
  7. response.EnsureStatusIsSuccessful();  

(4) 删除(HTTP/DELETE)

  1. // Delete Task  
  2. var task = GetTask();  
  3. var client = new HttpClient();  
  4. var strUrl = "http://localhost:1180/TaskService/Tasks/{0}";  
  5. strUrl = string.Format(strUrl, task.ID);  
  6. var response = client.Delete(strUrl);  
  7. response.EnsureStatusIsSuccessful();  

哦,还漏了个 GetContent(Task task) 方法:

  1. private HttpContent GetContent(Task task)  
  2. {  
  3.     var strContent = JsonConvert.SerializeObject(task);  
  4.     var data = System.Text.Encoding.UTF8.GetBytes(strContent);  
  5.     return HttpContent.Create(data, "application/json");  
  6. }  

response.EnsureStatusIsSuccessful 用来检查 Response.StatusCode。
最后,留下些关于REST WCF设计的思考: (1) 貌似不支持事务? (2) 类型不能过于复杂? (3) 元数据重复定义?

posted @ 2016-02-02 16:27  J.Y  阅读(154)  评论(0编辑  收藏  举报