听棠.NET

用积极乐观的心态,面对压力
posts - 307, comments - 10812, trackbacks - 112, articles - 5
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Rest in WCF - WCF的Json跨域

Posted on 2008-12-30 09:18 听棠.NET 阅读(...) 评论(...) 编辑 收藏

REST 最近很热门……
WCF 3.5 增加了对 REST 的支持 —— System.ServiceModel.Web。

对我而言 REST 并不是用来取代 WebService/WCF 的,它更多的是一种架构层面而非技术层面的概念和标准。使用唯一资源定位地址 URI,加上 HTTP 请求方法从而达到对一个发布于互联网资源的唯一描述和操作。这会带来很多好处:

1. 资源的唯一性

对下面这两个 URI 而言,搜索引擎会把它当作两个不相同的资源抓取,然后有可能被去重…… (这着实是件苦恼的事情)

http://www.xxx.com/product/123
http://www.xxx.com/product/123?op=delete

而使用 REST 进行表达,那么只有 "http://www.xxx.com/product/123" 才是资源的唯一地址,我们可以使用 HTTP 方法 GET 请求信息,使用 DELETE 来删除。

GET /product/123 HTTP/1.1
DELETE /product/123 HTTP/1.1

2. 更好的交互集成能力

相较 WCF、WebService 使用 SOAP、WSDL、WS-* 而言,几乎所有的语言和网络平台都支持 HTTP 请求。我们无需去实现复杂的客户端代理,无需使用复杂的数据通讯方式既可以将我们的服务暴露给任何需要的人,无论他使用 VB、Ruby、JavaScript,甚至是 HTML FORM,或者直接在浏览器地址栏输入。

这种方式是对外的,在内部具体的实现上,我们还是需要依赖某种特定的技术方案,包括我们今天要提到的 WCF 3.5 web。

REST 看似简单,但却不是个浅白的话题。每个人都有自己的理解和使用方式,它的出现带来了一种新的希望。

WCF 3.5 引入了 WebGetAttribute、WebInvokeAttribute、UriTemplate 来增加对 REST 的支持,这使得我们用很简单的方式就可以实现 RESTful WCF Service。

[DataContract]
public class User
{
  [DataMember]
  public string Name { get; set; }

  [DataMember]
  public int Age { get; set; }
}

[ServiceContract]
public interface IMyService
{
  [OperationContract]
  [WebInvoke(UriTemplate = "User/{name}/{age}", Method = "POST",
    ResponseFormat = WebMessageFormat.Json)]
  User CreateUser(string name, string age);

  [OperationContract]
  [WebGet(UriTemplate = "User/{name}", ResponseFormat = WebMessageFormat.Json)]
  //[WebInvoke(UriTemplate = "User/{name}", Method = "GET",
  //  ResponseFormat = WebMessageFormat.Json)]
  User GetUser(string name);

  [OperationContract]
  [WebInvoke(UriTemplate = "User/{name}", Method = "DELETE",
    ResponseFormat = WebMessageFormat.Json)]
  string DeleteUser(string name);

  [OperationContract]
  [WebInvoke(UriTemplate = "User/{name}", Method = "PUT",
    ResponseFormat = WebMessageFormat.Json)]
  string UpdateUser(string name);
}

public class MyService : IMyService
{
  public User GetUser(string name)
  {
    return new User { Name = name, Age = 12 };
  }

  public string DeleteUser(string name)
  {
    return "DELETE...";
  }

  public User CreateUser(string name, string age)
  {
    return new User { Name = name, Age = int.Parse(age) };
  }

  public string UpdateUser(string name)
  {
    return "UPDATE...";
  }
}

class Program
{
  static void Main(string[] args)
  {
    var host = new WebServiceHost(typeof(MyService), new Uri("http://localhost:81"));
    host.Open();

    var client = new WebClient();
    client.Headers.Add("content-type", "application/x-www-form-urlencoded");

    // POST /GetUser/tom/21 HTTP/1.1
    var s1 = client.UploadString("http://localhost:81/User/tom/21", "POST", String.Empty);
    Console.WriteLine(s1);

    // GET /GetUser/tom HTTP/1.1
    var s2 = client.DownloadString("http://localhost:81/User/tom");
    Console.WriteLine(s2);

    // DELETE /GetUser/tom HTTP/1.1
    var s3 = client.UploadString("http://localhost:81/User/tom", "DELETE", String.Empty);
    Console.WriteLine(s3);

    // PUT /All/tom HTTP/1.1
    var s4 = client.UploadString("http://localhost:81/User/tom", "PUT", String.Empty);
    Console.WriteLine(s4);

    Console.WriteLine("Press any key to exit...");
    Console.ReadKey(true);
  }
}

输出:
{"Age":21,"Name":"tom"}
{"Age":12,"Name":"tom"}
"DELETE..."
"UPDATE..."

WebServiceHost 继承自 ServiceHost,能自动创建所需服务端点(Endpoint、WebHttpBinding)等细节。当然,我们依旧可以使用原有的 WCF client proxy 来调用上面的服务。
var factory = new WebChannelFactory<IMyService>(new Uri("http://localhost:81"));
var proxy = factory.CreateChannel();
using (proxy as IDisposable)
{
  Console.WriteLine(proxy.CreateUser("zhangsan", "23").Name);
  Console.WriteLine(proxy.DeleteUser("lisi"));
}

或许我们会觉得为一个资源创建四个方法显得很繁琐,那么我们可以按下面的方式进行,只不过对 WCF client proxy 就有些麻烦了。
[ServiceContract]
public interface IMyService
{
  [OperationContract]
  [WebInvoke(UriTemplate = "/User/*", Method = "*", ResponseFormat = WebMessageFormat.Json)]
  string User();
}

public class MyService : IMyService
{
  public string User()
  {
    var request = WebOperationContext.Current.IncomingRequest;

    var method = request.Method;
    var args = request.UriTemplateMatch.WildcardPathSegments;

    switch (method)
    {
      case "POST":
        return "POST...";
      case "DELETE":
        return "DELETE...";
      case "PUT":
        return "UPDATE...";
      default:
        return "GET...";
    }
  }
}

WebOperationContext 提供了大量的方法,可以获取所有的请求细节,也可以对返回信息进行设置。

VS2008 提供了 AJAX-enabled WCF Service,我们只需在服务上面添加一个 [WebGet] 即可实现 IIS-Host RESTful WCF Service。
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1
{
  // Add [WebGet] attribute to use HTTP GET
  [OperationContract]
  [WebGet(ResponseFormat = WebMessageFormat.Xml)]
  public string Hello(string name)
  {
    return "hello " + name;
  }
}

在浏览器中输入 "http://localhost:1178/Service1.svc/Hello?name=abcxxxxx" 就可以看到调用结果。

有几点需要注意:

1. 缺省情况下,我们无法使用 UriTemplate,必须把配置文件中 "enableWebScript" 注释掉。
<behavior name="Test.WCF.Service1AspNetAjaxBehavior">
  <!--<enableWebScript />-->
</behavior>

2. 必须在 .svc 中添加 "WebServiceHostFactory"。

Hello.svc
<%@ ServiceHost Language="C#" Debug="true" Service="Test.WCF.Service1" CodeBehind="Service1.svc.cs" Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

我们改造一下,使其能支持 GET/POST 和 UriTemplate。
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1
{
  [OperationContract]
  [WebInvoke(Method = "*", UriTemplate = "Hello/{name}", ResponseFormat = WebMessageFormat.Xml)]
  public string Hello(string name)
  {
    return "hello " + name;
  }
}

OK,除了可以在浏览器输入 "http://localhost:1178/Service1.svc/Hello/abcooooox" 调用外,我们还可以用下面的代码执行 POST 动作。
client.UploadString("http://localhost:1178/Service1.svc/Hello/xxyyzz", "POST", String.Empty);