转自:http://blog.csdn.net/fangxinggood/article/details/6235662

WCF 很好的支持了 REST 的开发, 而 RESTful 的服务通常是架构层面上的考虑。 因为它天生就具有很好的跨平台跨语言的集成能力,几乎所有的语言和网络平台都支持 HTTP 请求,无需去实现复杂的客户端代理,无需使用复杂的数据通讯方式既可以将我们的服务暴露给任何需要的人,无论他使用 VB、Ruby、JavaScript,甚至是 HTML FORM,或者直接在浏览器地址栏输入。 
WCF 中通过 WebGetAttribute、WebInvokeAttribute (GET/PUT/POST/DELETE)、UriTemplate 定义 REST 的服务的调用方式, 通过WebMessageFormat (Xml/Json) 定义消息传递的格式。

1. 契约

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.Serialization;
  4. using System.ServiceModel;
  5. using System.ServiceModel.Web;
  6. namespace WcfRESTfulSvc1
  7. {
  8. [ServiceContract]
  9. public interface ITaskService
  10. {
  11. [OperationContract]
  12. [WebGet(UriTemplate="Tasks/Xml", ResponseFormat=WebMessageFormat.Xml)]
  13. List<Task> GetTasksXml();
  14. [OperationContract]
  15. [WebGet(UriTemplate = "Tasks/Json", ResponseFormat = WebMessageFormat.Json)]
  16. List<Task> GetTasksJson();
  17. [OperationContract]
  18. [WebInvoke(UriTemplate="Task/{title}", Method="GET", ResponseFormat=WebMessageFormat.Json)]
  19. Task GetTasksByTitle(string title);
  20. }
  21. [DataContract]
  22. public class Task
  23. {
  24. [DataMember]
  25. public string Title { get; set; }
  26. [DataMember]
  27. public string Detail { get; set; }
  28. [DataMember]
  29. public DateTime CreatedDate { get; set; }
  30. }
  31. }

2. 实现

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. namespace WcfRESTfulSvc1
  5. {
  6. public class TaskService : ITaskService
  7. {
  8. public List<Task> GetTasksXml()
  9. {
  10. return GetData();
  11. }
  12. public List<Task> GetTasksJson()
  13. {
  14. return GetData();
  15. }
  16. public Task GetTasksByTitle(string title)
  17. {
  18. return GetData().Where(t => t.Title == title).FirstOrDefault();
  19. }
  20. private static List<Task> GetData()
  21. {
  22. return new List<Task>
  23. {
  24. new Task { Title="Task1", Detail="Do Something 1", CreatedDate=DateTime.Now },
  25. new Task { Title="Task2", Detail="Do Something 2", CreatedDate=DateTime.Now },
  26. new Task { Title="Task3", Detail="Do Something 3", CreatedDate=DateTime.Now },
  27. new Task { Title="Task4", Detail="Do Something 4", CreatedDate=DateTime.Now },
  28. new Task { Title="Task5", Detail="Do Something 5", CreatedDate=DateTime.Now },
  29. };
  30. }
  31. }
  32. }

通过 WCF 4.0 里创建的 WCF Service Application 发布REST服务很简单,只需要在 svc 的 Markup 里加上 Factory:
<%@ ServiceHost Language="C#" Debug="true" Service="WcfRESTfulSvc1.TaskService" CodeBehind="TaskService.svc.cs"Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>
BTW: 不过这样,WCF的Metadata就不能访问到了,也就说不能访问到svc的wsdl了。

OK,在浏览器中键入 http://localhost:2571/TaskService.svc/Tasks/Xml  就能得到结果:

  1. <ArrayOfTask xmlns="http://schemas.datacontract.org/2004/07/WcfRESTfulSvc1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  2. <Task>
  3. <CreatedDate>2011-03-09T21:51:13.3376004+08:00</CreatedDate>
  4. <Detail>Do Something 1</Detail>
  5. <Title>Task1</Title>
  6. </Task>
  7. <Task>
  8. <CreatedDate>2011-03-09T21:51:13.3376004+08:00</CreatedDate>
  9. <Detail>Do Something 2</Detail>
  10. <Title>Task2</Title>
  11. </Task>
  12. <Task>
  13. <CreatedDate>2011-03-09T21:51:13.3376004+08:00</CreatedDate>
  14. <Detail>Do Something 3</Detail>
  15. <Title>Task3</Title>
  16. </Task>
  17. <Task>
  18. <CreatedDate>2011-03-09T21:51:13.3376004+08:00</CreatedDate>
  19. <Detail>Do Something 4</Detail>
  20. <Title>Task4</Title>
  21. </Task>
  22. <Task>
  23. <CreatedDate>2011-03-09T21:51:13.3376004+08:00</CreatedDate>
  24. <Detail>Do Something 5</Detail>
  25. <Title>Task5</Title>
  26. </Task>
  27. </ArrayOfTask>

客户端的调用利用System.Net.WebClient也很容易:

  1. var client = new WebClient();
  2. this.txtResponse.Text = client.DownloadString(url);

Json的返回结果:
[{"CreatedDate":"//Date(1299687080328+0800)//","Detail":"Do Something 1","Title":"Task1"},{"CreatedDate":"//Date(1299687080328+0800)//","Detail":"Do Something 2","Title":"Task2"},{"CreatedDate":"//Date(1299687080328+0800)//","Detail":"Do Something 3","Title":"Task3"},{"CreatedDate":"//Date(1299687080328+0800)//","Detail":"Do Something 4","Title":"Task4"},{"CreatedDate":"//Date(1299687080328+0800)//","Detail":"Do Something 5","Title":"Task5"}]

再来看看利用jQuery如何调用这个服务:

  1. <mce:script type="text/javascript" language="JavaScript"><!--
  2. $(document).ready(function () {
  3. $("#btnGet").click(function () {
  4. var url = $("#txtUrl").val();
  5. $.get(url, function (data) {
  6. for (var i = 0; i < data.length; i++)
  7. $("#divResponse").append("<li>" +
  8. data[i].Title + "&nbsp;-&nbsp;" +
  9. data[i].Detail + "</li>");
  10. });
  11. });
  12. });
  13. // --></mce:script>

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. private void RegisterRoutes()
  8. {
  9. RouteTable.Routes.Add(new ServiceRoute("TaskService",
  10. new WebServiceHostFactory(), typeof(TaskService)));
  11. }
  12. }

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。

前面2篇blog,主要在介绍REST WCF如何通过json/xml 和客户端完成交互。 这篇文章将对REST WCF的原生(RAW)流传输进行一次实例分析。

OK,还是利用 WCF REST Service Application 的工程模板,创建我们的REST WCF工程。

因为WCF REST服务不需要公开metadata, 所以 OperationContract 的定义也省略了。直接定义[ServiceContract]
如果修改模板中Service名,Global.asax中的RegisterRoutes方法也必须相应的修改。
下面是服务的实现:

  1. [ServiceContract]
  2. [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
  3. [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, Namespace="")]
  4. public class ImageService
  5. {
  6. [WebGet(UriTemplate = "{image}")]
  7. public Stream GetImage(string image)
  8. {
  9. var imageType = Path.GetExtension(image).TrimStart('.');
  10. WebOperationContext.Current.OutgoingResponse.ContentType = "image/" + imageType;
  11. var dir = System.Web.HttpContext.Current.Server.MapPath("~/Images");
  12. var file = Path.Combine(dir, image);
  13. return File.OpenRead(file);
  14. }
  15. [WebInvoke(UriTemplate = "Add/{image}", Method="POST")]
  16. public void AddImage(Stream stream, string image)
  17. {
  18. var dir = System.Web.HttpContext.Current.Server.MapPath("~/Images");
  19. var file = Path.Combine(dir, image);
  20. var bitmap = Bitmap.FromStream(stream);
  21. bitmap.Save(file);
  22. }
  23. [WebGet(UriTemplate = "ListAll", ResponseFormat=WebMessageFormat.Xml)]
  24. public string[] GetAllImageNames()
  25. {
  26. var dir = System.Web.HttpContext.Current.Server.MapPath("~/Images");
  27. var files = Directory.GetFiles(dir);
  28. var images = new List<string>();
  29. foreach (var file in files)
  30. {
  31. var ext = Path.GetExtension(file);
  32. if (ext == ".jpg" || ext == ".gif")
  33. images.Add(file);
  34. }
  35. for (int i = 0; i < images.Count; i++)
  36. images[i] = Path.GetFileName(images[i]);
  37. return images.ToArray();
  38. }
  39. }

- GetImage 用于返回图片 Stream, 
- AddImage 用于接受图片 Stream 并保存, 
- GetAllImageNames 返回所有图片名。

在Http协议中,通过content-type控制客户端的解析行为。当content-type是"image/jpg"或者是"image/gif"的时候,
浏览器就能够直接显示图片了。因此,在代码里有这样的设定:
WebOperationContext.Current.OutgoingResponse.ContentType = "image/" + imageType;
还可以通过 OutgoingResponse.Header 控制缓存。另外,这个REST WCF是建立在一个Web Application中,
我们还可以利用  System.Web.HttpContext.Current.Server.MapPath 方法,获得物理路径。

我们知道一般的WCF服务中,可以实现Streamed传输。在那种场合下,服务契约必须的参数或者返回值必须是Stream,且不能有别的类型的参数(要传递别的参数可以通过MessageHeader)。但是在上面的示例代码中,我定义的 AddImage 就有两个参数: 
一个是Stream,还有一个是string。这个string参数其实是通过url传递的: UriTemplate = "Add/{image}",且这两个参数没有顺序要求。
如果没有写"{image}"会抛出下面的异常(因为Request的Body已经被定义为Stream,图片名只能通过Url传过来。)
Operation 'AddImage' of contract 'ImageService' specifies multiple request body parameters to be serialized without any wrapper elements. At most one body parameter can be serialized without wrapper elements. Either remove the extra body parameters or set the BodyStyle property on the WebGetAttribute/WebInvokeAttribute to Wrapped.

运行服务,直接在浏览器里输入服务URL:http://localhost:3577/ImageService/GIF002.gif 我们就可以看到结果了。
另外,利用REST WCF返回原生流的特性,我们完全可以取代以前在 ASP.NET 中专门生成验证码或者图片读取步骤的页面类,
是不是很方便?

为了体现 REST WCF 的便利性, 我设计了一个 WPF 的客户端:

客户端代码:

  1. public partial class MainWindow : Window
  2. {
  3. public MainWindow()
  4. {
  5. InitializeComponent();
  6. }
  7. private void Window_Loaded(object sender, RoutedEventArgs e)
  8. {
  9. op.RestoreDirectory = true;
  10. op.Filter = "Jpeg Files(*.jpg)|*.jpg|Gif Files(*.gif)|*.gif";
  11. BindData();
  12. }
  13. private void BindData()
  14. {
  15. var url = "http://localhost:3577/ImageService/ListAll";
  16. var client = new HttpClient();
  17. var stream = client.Get(url).Content.ReadAsStream();
  18. var dataSer = new DataContractSerializer(typeof(string[]));
  19. var obj = (string[])dataSer.ReadObject(stream);
  20. this.listBox1.DataContext = obj;
  21. }
  22. private Microsoft.Win32.OpenFileDialog op = new Microsoft.Win32.OpenFileDialog();
  23. private void button1_Click(object sender, RoutedEventArgs e)
  24. {
  25. var ret = op.ShowDialog();
  26. if (!ret.HasValue || !ret.Value || !op.CheckFileExists) return;
  27. var file = op.FileName;
  28. var name = System.IO.Path.GetFileName(file);
  29. var url = "http://localhost:3577/ImageService/Add/" + name;
  30. var client = new HttpClient();
  31. var content = HttpContent.Create(File.OpenRead(file));
  32. var resp = client.Post(url, content);
  33. resp.EnsureStatusIsSuccessful();
  34. BindData();
  35. }
  36. private void button2_Click(object sender, RoutedEventArgs e)
  37. {
  38. this.Close();
  39. }
  40. }
  41. [ValueConversion(typeof(string), typeof(string))]
  42. public class UriConverter : IValueConverter
  43. {
  44. public object Convert(object value, Type targetType,
  45. object parameter, System.Globalization.CultureInfo culture)
  46. {
  47. if (value == null) return null;
  48. string sourceValue = value.ToString();
  49. return "http://localhost:3577/ImageService/" + sourceValue;
  50. }
  51. public object ConvertBack(object value, Type targetType,
  52. object parameter, System.Globalization.CultureInfo culture)
  53. {
  54. return value;
  55. }
  56. }

实现的注意点:
1) 获得所有图片名,服务端返回的是string[]的序列化的xml,用DataContractSerializer反序列化。
    var dataSer = new DataContractSerializer(typeof(string[]));
2) WPF中的OpenFileDialog在Microsoft.Win32命名空间下。(必须引用WindowsBase.dll)
3) 利用 Microsoft.Http.HttpClient 上传图片, 实例化 HttpContent, 通过client.Post上传
    var url = "http://localhost:3577/ImageService/Add/" + name;
    var client = new HttpClient();
    var content = HttpContent.Create(File.OpenRead(file));
    var resp = client.Post(url, content);
    resp.EnsureStatusIsSuccessful();
4) 定义了一个Converter, 将 ListBox 绑定到 Image 控件的 Source
    Source="{Binding ElementName=listBox1, Path=SelectedItem, Converter={StaticResource uriConv}}"

源代码下载:REST WCF Raw Stream 示例代码


在REST架构的WCF服务中,它不像一般的WCF服务绑定,有配套的安全模式,实现起来那么简单。REST WCF服务只能在传输层加密,而一般的WCF 服务可以在消息层加密。因此 REST WCF服务启用ASP.NET兼容模式后,它的安全是由ASP.NET来保证的。本篇文章主要介绍在 REST WCF 中如何实现最简单的 Username 验证。

在SOAP协议的WCF中,可以通过SOAPHeader(MessageHeader)来实现用户名密码的传输,早在WebService时代我们就这么用过了。在REST WCF中,我们可以利用 HttpHeader 来完成这一目标。 (你可不会想在每个服务契约里加上用户和密码的参数吧...)

首先在服务中加入如下方法用于校验,Header的信息:如果 Header 中 Authorization 的字符串不是"fangxing/123" 那么就将返回 405 MethodNotAllowed 的错误。这个字符串的内容可以自定义,反正服务端根据某种规则检查这个字符串。

  1. private bool CheckAuthorization()
  2. {
  3. var ctx = WebOperationContext.Current;
  4. var auth = ctx.IncomingRequest.Headers[HttpRequestHeader.Authorization];
  5. if (string.IsNullOrEmpty(auth) || auth != "fangxing/123")
  6. {
  7. ctx.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed;
  8. return false;
  9. }
  10. return true;
  11. }

然后在每一个服务契约的实现中,都去调用它。
[WebGet(UriTemplate = "All")]
public List<Task> GetTask()
{
    if (!CheckAuthorization())
        return null;
    return GetData();
}

[WebGet(UriTemplate = "{taskId}")]
public Task GetTaskById(string taskId)
{
    if (!CheckAuthorization())
        return null;
    return GetData().FirstOrDefault(t => t.Id==taskId);
}

现在的服务,如果直接通过浏览器访问,将得到 405 MethodNotAllowed 的错误:

客户端只要相应的验证信加到 RequestHeader 中去,就可以访问了。客户端可以使用单例模式设计 Client 对象。
这样就不用每次调用都去加验证信息了。

  1. var url = "http://localhost:3433/TaskService/All";
  2. var client = new HttpClient();
  3. client.DefaultHeaders.Add("Authorization", "fangxing/123");
  4. var resp = client.Get(url);

* 这里使用的是 Microsoft.Http.HttpClient (WCF REST Starter Kit) 而非 System.Net.WebClient

回头看服务端代码,每个服务实现中都需要加上 CheckAuthorization() 是不是很烦?
OK,我们知道这个 REST WCF服务是承载在一个Web Application上的, 通过往 RouteTable 中注册 WebServiceHostFactory 来激活服务对象的。 那么只要对这个 WebServiceHostFactory 做些“手脚”,就可以实现服务端验证的统一拦截,代码如下。(一般的 WCF 也可以利用此方法对 MessageHeader 进行拦截校验)

  1. public class SecureWebServiceHostFactory : WebServiceHostFactory
  2. {
  3. protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
  4. {
  5. var host = base.CreateServiceHost(serviceType, baseAddresses);
  6. host.Authorization.ServiceAuthorizationManager = new MyServiceAuthorizationManager();
  7. return host;
  8. }
  9. public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
  10. {
  11. var host = base.CreateServiceHost(constructorString, baseAddresses);
  12. host.Authorization.ServiceAuthorizationManager = new MyServiceAuthorizationManager();
  13. return host;
  14. }
  15. }
  16. public class MyServiceAuthorizationManager : ServiceAuthorizationManager
  17. {
  18. protected override bool CheckAccessCore(OperationContext operationContext)
  19. {
  20. var ctx = WebOperationContext.Current;
  21. var auth = ctx.IncomingRequest.Headers[HttpRequestHeader.Authorization];
  22. if (string.IsNullOrEmpty(auth) || auth != "fangxing/123")
  23. {
  24. ctx.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed;
  25. return false;
  26. }
  27. return true;
  28. }
  29. }

RegisterRoutes 里的工厂类也需要相应的修改下:

  1. var securewebServiceHostFactory = new SecureWebServiceHostFactory();
  2. RouteTable.Routes.Add(new ServiceRoute("TaskService",
  3. securewebServiceHostFactory, typeof(TaskService)));

这样服务端代码就可以去掉 CheckAuthorization() 而把验证工作都交给 SecureWebServiceHostFactory 了。

这种验证方式,其实也是现在 Windows Auzer Access Control 的原型。 只不过这个 Authoriztion 的服务是专门的Services罢了。
1. 客户端先从发布令牌的服务获取令牌; 2. 客户端拿着令牌提交到现在的服务; 3.服务端将客户端令牌拿到发布令牌的服务上校验。

源码下载:http://download.csdn.net/download/fangxinggood/3686322

程序员的基础教程:菜鸟程序员

WCF4.0 –- RESTful WCF Services的更多相关文章

  1. WCF4.0 –- RESTful WCF Services (1) (入门)

    WCF 很好的支持了 REST 的开发, 而 RESTful 的服务通常是架构层面上的考虑. 因为它天生就具有很好的跨平台跨语言的集成能力,几乎所有的语言和网络平台都支持 HTTP 请求,无需去实现复 ...

  2. CRUD using Spring MVC 4.0 RESTful Web Services and AngularJS

    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...

  3. IIS8.0 部署WCF Services

    今天在Win 8的IIS上部署WCF Services,访问SVC文件时出现找不到处理程序的错误,以前遇到这个问题时都是尝试通过注册asp.net的方式处理一下,但是在Win8下这招不灵了,出现如下提 ...

  4. WCF学习之旅—WCF4.0中的简化配置功能(十五)

    六 WCF4.0中的简化配置功能 WCF4.0为了简化服务配置,提供了默认的终结点.绑定和服务行为.也就是说,在开发WCF服务程序的时候,即使我们不提供显示的 服务终结点,WCF框架也能为我们的服务提 ...

  5. 如何创建一个RESTful WCF Service

    原创地址:http://www.cnblogs.com/jfzhu/p/4044813.html 转载请注明出处 (一)web.config文件 要创建REST WCF Service,endpoin ...

  6. WCF4.0安装 NET.TCP启用及常见问题

    WCF4.0安装及NET.TCP启用 WCF 4.0 一般默认安装.net Framework 4.0的时候已经安装. 但如果先装.net framework 4.0,后装IIS,就会出现问题.需要重 ...

  7. Jersey the RESTful Web Services in Java

    Jersey 是一个JAX-RS的实现, JAX-RS即Java API for RESTful Web Services, 支持按照表述性状态转移(REST)架构风格创建Web服务. REST 中最 ...

  8. 使用 Spring 3 来创建 RESTful Web Services

    来源于:https://www.ibm.com/developerworks/cn/web/wa-spring3webserv/ 在 Java™ 中,您可以使用以下几种方法来创建 RESTful We ...

  9. cxf开发Restful Web Services

    一.restful web services rest全称是Representation State Transfer(表述性状态转移).它是一种软件架构风格,只是提供了一组设计原则和约束条件.在re ...

随机推荐

  1. bzoj 3124 直径

    Written with StackEdit. Description 小\(Q\)最近学习了一些图论知识.根据课本,有如下定义. 树:无回路且连通的无向图,每条边都有正整数的权值来表示其长度.如果一 ...

  2. vc++ windows获取计算机信息

    在软件开发中,我们经常要获当前系统的版本号,判断当前是什么系统,获取获取物理内存和可用内存大小,获取CPU名称.内核数目.主频,获取MAC地址,获取屏幕分辨率,下面的这个c++类将包含所有这些信息. ...

  3. block的基本使用

    block用来保存一段代码 block的标志:^ block跟函数很像: 1. 可以保存代码 2. 有返回值 3. 有形参 4. 调用方式一样 定义bolock变量 例1: void (^myBloc ...

  4. UVA11796 Dog Distance

    题意 PDF 分析 问题可以转化为小问题,即两条狗分别在线段上运动. 然后用相对运动知识可以认为甲不动,乙在线段上运动. 小问题就转化为点到线段的最小或最大距离. 时间复杂度\(O(I \times ...

  5. hdu 5730 Shell Necklace——多项式求逆+拆系数FFT

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=5730 可以用分治FFT.但自己只写了多项式求逆. 和COGS2259几乎很像.设A(x),指数是长度,系数 ...

  6. sql server中类似oracle中decode功能的函数

    sqlserver 2008 写法 select t.PROJECTNAME, t.BUILDCONTENTSCALE, CASE t.PROJECTLEVEL ' THEN '国家重点' ' THE ...

  7. shell变量(字符串)间的连接

    shell变量(字符串)间的连接 对于变量或者字符串的连接,shell提供了相当简单的做法,比string的连接还要直接. 直接放到一起或用双引号即可. 例如$a, $b,有 c=$a$b c=$a& ...

  8. <转>CentOS 7 安装配置 NFS

    CentOS 7  安装配置 NFS 环境 nps 192.168.1.97 client 192.168.1.98 一.yum 安装 yum -y install nfs-utils rpcbind ...

  9. 1100 Mars Numbers

    题意:进制转换. 思路:注意当数字是13的倍数时,只需高位叫法的单词.比如26,是“hel”,而不是“hel tret”.我被坑在这里了!对应语句1的处理.另外,在输入n和n个字符串之间需要一个吸收字 ...

  10. python开发进程:进程开启方式&多进程

    一,进程的开启方式 利用模块开启进程 from multiprocessing import Process import time,random import os def piao(name): ...