基於.Net FrameWork的 RestFul Service


關於本文

這篇文章的目的就是向大家闡述如何在.net framework 4.0中創建RestFul Service並且使用它。

什么是web Services,什么是WCF

首先講到的是web Service, 它是一種能夠讓客戶端程序在web頁面上通過HTTP協議請求需要數據的部件。我們可以用Asp.net創建普通的Web Services並且讓這些Services能夠被客戶端程序所調用。

其次說到的是Web Services,它是一個編程平台,它能夠通過遵循Simple Object Access Protocol (SOAP)方式來接收或者是發送數據。

然后就是WCF了,它是能夠編寫基於service-oriented architecture (SOA)服務的編程模型。通過它,開發人員可以編寫出跨平台的,安全的,可靠的解決方案出來。

WCF可以為各種各樣的客戶提供集中式的運算服務,客戶可以調用多個服務,同時,相同的服務也能夠被多個用戶所調用。

在創建我們的WCF項目之前,我們最好能夠看一下這篇文章:Introduction of Window Communication Foundation

下面是WebServices和WCF Services的數據比對:

 

Web Services WCF Services
Web Services能夠用來開發基於SOAP的消息發送和接收的應用,這些消息是XML格式的,這些xml格式的消息可以通過.net Framework 提供的工具類來進行序列化。這種可以能夠自動生成元數據(metadata)來描述Web Services的技術,我們稱為: The Web Services Description Language ( WSDL )(注意:WSDL是一種描述WebService服務以及說明如何與Web服務進行通信的XML語言。) WCF Services能夠用來開發基於多種協議格式的應用,SOAP只是其默認的格式。它的消息格式也是使用XML,但是序列化這些xml數據的方式有很多種。它可以通過WSDL自動的生成能夠描述其應用的元數據,同時也能夠利用工具來生成。

對於WCF來說,發送消息不局限於HTTP方式,TCP或者是其他的網絡協議也可以。並且在這些協議中進行切換,也是輕而易舉的事兒。它除了能夠放到服務器上運行之外,還能夠支持宿主寄存,它同時能夠非常容易的支持最新的Web Services標准(SOAP 1.2 and WS-*),
它除了能夠以SOAP的方式發送數據,也能夠用其他的方式來進行。
XmlSerializer DataContractSerializer
當Asp.net從服務器發送數據或者是接收客戶端傳來的數據的時候,它是利用XmlSerializer來進行數據的轉換的。 DataContractSerializer表明,有0個或者是多個屬性或者是字段需要被序列化;而DataMemberAttribute 則表明一個指定的屬性或者字段需要被序列化。DataContractAttribute可以被應用到具有Public 或者是Private的屬性或者字段中,所有被標記上DataContractAttribute的屬性或者字段,在WCF中被稱作是數據契約(DataContracts),他們將會被DataContractSerializer序列化成XML結構。
System.Xml.Serialization下的XmlSerializer類及其各種特性,可以讓各種.net framework 類型轉換成xml結構,它對XML提供了非常好的控制。 DataContractSerializer,DataContractAttribute,DataMemberAttribute對XML提供了很有限的控制,所以,你只能夠通過命名空間去甄別。你可以利用DataContractSerializer去操控除了xml之外的各種數據結構,這種沒有添加過多限制的操作,能夠讓DataContractSerializer更易於操控。
和DataContractSerializer相比,它的性能差一些 DataContractSerializer性能好一些,性能一般會提升10%左右
XmlSerializer不會預知應該或者是不應該包含什么字段或者是屬性到XML中。 利用DataMemberAttribute,在DataContractSerializer進行序列化的時候,可以非常明確地知道數據結構。
XmlSerializer只能夠將Public類型的成員進行序列化。 DataContractSerializer不會類型成員的屬性進行檢測。
只有繼承自IEnumerable或者是ICollection接口的集合類型才能夠被序列化成XML。 DataContractSerializer可以將任何.net類型轉換成xml結構.
繼承自IDictionary接口的類,比如說Hashtable,是不能夠被序列化成XML的。 繼承自IDictionary接口的類,比如說Hashtable,能夠被序列化成XML。
XmlSerializer不支持版本控制 DataContractSerializer支持版本控制。
XmlSerializer對序列化的xml語義結構基本相同。 DataContractSerializer序列化為xml的時候,需提供明確的命名空間。

 

什么是REST和 RESTFul?

Representational State Transfer(REST)在2000年被Roy Fielding提出。它是利用World Wide Web的相關技術以及協議來構建大規模的網絡軟件的構架方式。它意在說明,數據資源是可以被定義,被發布的,尤其是在消息交換的簡易性和可擴展性方面。

早在2000年的時候, 制定HTTP規范的首席作者之一,Roy Fielding,寫了一篇名為“Architectural Styles and the Design of Network-based Software Architectures.”的博士論文,在文中,作者如是說。

REST,一種能夠構建分布式超媒體驅動程序的構架方式,它包括通過利用標准的HTTP Verbs(GET, POST, PUT, and DELETE)來定義資源,從而構建Resource-Oriented Architecture (ROA)程序。這種構建可以通過Uniform Resource Identifier(URI)發布給使用者。

REST 是不綁定任何技術或者是平台的。它只是用來把工作設計成能像Web那樣。人們經常把這種方式定義為“RESTFul Services”,但是事實上,利用REST 構架方式開發的WCF Services,也能夠被稱作是RESTFul Services。

WCF Services RESTFul Services
各自的網絡協議之間,需要創建端點。 可以通過HTTP Web方式來進行數據的收發操作。
需要Remote Procedural Call(RPC)的支持 需要定義基於HTTP的統一接口
客戶端需要添加對服務端的引用 不需要客戶端添加對服務端的引用

關於案例代碼?

在WCF 中,REST,其實就是一系列的.net framework 類和visual studio的一些特性和模板,通過這些東西,用戶可以創建並且使用基於REST方式的WCF服務。這些服務是需要.net 3.5 SP1中的WCF Web編程模型支持的。在后面的附件中,已經包含了所有的源代碼,示例,以及單元測試。

創建基礎的RESTFul Service

步驟一:

利用Visual Studio 2010創建一個新的WCF 項目:

QQ截圖20131022141221

步驟二:

刪掉自帶的IService1.cs以及Service1.svc文件,同時創建以下兩個文件:IRESTService.cs &RESTService.cs.

QQ截圖20131022141505

步驟三:

在IService.cs文件中創建Person 實體類以及一些簡單的屬性,同時加上DataContract & DataMember特性以便於DataContractSerialization進行序列化:

[DataContract]
public class Person
{
    [DataMember(order=1)]
    public string ID;
    [DataMember(order=2)]
    public string Name;
    [DataMember(order=3)]
    public string Age;
    [DataMember(order=4)]
    public string Address;

} 注意,一定要加上Order=*,否則,客戶端向服務端添加數據的時候,會出現某些數據字段沒有值的情況。

步驟四:

在IRESTService接口文件中創建方法,利用ServiceContract和OperationContrat進行修飾:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace RESTFulDaemon
{
    [ServiceContract]
    public interface IRestSerivce
    {
        //POST operation
        [OperationContract]
        [WebInvoke(
             UriTemplate = ""
            ,ResponseFormat = WebMessageFormat.Xml
            ,RequestFormat = WebMessageFormat.Xml
            ,Method = "POST"
            )]
        Person CreatePerson(Person createPerson);

        //Get Operation
        [OperationContract]
        [WebGet(
            UriTemplate = ""
          , ResponseFormat = WebMessageFormat.Xml
          , RequestFormat = WebMessageFormat.Xml
            )]
        List<Person> GetAllPerson();

        [OperationContract]
        [WebGet(
            UriTemplate = "{id}"
          , ResponseFormat = WebMessageFormat.Json
          , RequestFormat = WebMessageFormat.Json
            )]
        Person GetAPerson(string id);

        //PUT Operation
        [OperationContract]
        [WebInvoke(
            UriTemplate = ""
          , ResponseFormat = WebMessageFormat.Json
          , RequestFormat = WebMessageFormat.Json
          , Method = "PUT"
          )]
        string UpdatePerson(Person updatePerson);

        //DELETE Operation
        [OperationContract]
        [WebInvoke(
              UriTemplate = ""
            , ResponseFormat = WebMessageFormat.Json
            , Method = "DELETE"
            )]
        string DeletePerson(string id);
    }
}

代碼中,可以利用JSON來組織數據,也可以利用XML來組織數據 

步驟五:
WebGet 操作只是從服務端接收數據的操作,它可以被REST編程模型所調用。它會和OperationContact一起被應用到方法上,並且會帶有UriTemplate屬性和HTTP中的Get操作。
WebInvoke 可以引發服務端操作,它可以被REST編程模型所調用。當它具有UriTemplate定義並且擁有傳輸方式(比如, HTTP POST, PUT, or DELETE),同時和OperationContact放在一塊的時候,就表明當前方法支持的是不同類型的HTTP傳輸方式。默認是POST方式。
下面 是具體說明:
Person CreatePerson(Person createPerson);
//It is basically insert operation, so WebInvoke HTTP POST is used
 
List<person> GetAllPerson();
Person GetAPerson(string id);
//These two methods retrieve the information, so WebGet (HTTP Get) is used
 
 Person UpdatePerson(Person updatePerson);
//This method updates the available information, so WebInvoke HTTP PUT is used
 
void DeletePerson(string id);
//This method deletes the available information, 
//so WebInvoke HTTP DELETE is used
 
步驟六:
實現在RESTService類中實現IRESTService接口:
public class RESTService:IRestSerivce
    {
        List<Person> persons = new List<Person>();
        private static int tCount = 0;
        public Person CreatePerson(Person person)
        {
            tCount++;
            person.ID = tCount.ToString();
            persons.Add(person);
            return person;
        }
        public List<Person> GetAllPerson()
        {
            return persons.ToList();
        }
        public Person GetAPerson(string id)
        {
            return persons.FirstOrDefault(e => e.ID.Equals(id));
        }
        public string UpdatePerson(Person updatePerson)
        {
            Person p = persons.FirstOrDefault(e => e.ID.Equals(updatePerson.ID));
            p.Name = updatePerson.Name+"[updated]";
            p.Age = updatePerson.Age+"[updated]";
            p.Address = updatePerson.Address+"[updated]";
            return "Updated success";
        }
        public string DeletePerson(string id)
        {
            persons.RemoveAll(e => e.ID.Equals(id));
            return "success";
        }
    }

 

步驟七:
為了讓這個服務能夠在ASP.NET兼容模式下運行,我們需要利用AspNetCompatibilityRequirements特性來修飾RESTService類:
 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class RESTService:IRestSerivce
    {
       //Code here
    }

 

所以目前的代碼如下:
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Activation;
using System.ServiceModel;
using System;

namespace RESTFulDaemon
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class RESTService:IRestSerivce
    {
        List<Person> persons = new List<Person>();
        private static int tCount = 0;
        public Person CreatePerson(Person person)
        {
            tCount++;
            person.ID = tCount.ToString();
            persons.Add(person);
            return person;
        }
        public List<Person> GetAllPerson()
        {
            return persons.ToList();
        }
        public Person GetAPerson(string id)
        {
            return persons.FirstOrDefault(e => e.ID.Equals(id));
        }
        public string UpdatePerson(Person updatePerson)
        {
            Person p = persons.FirstOrDefault(e => e.ID.Equals(updatePerson.ID));
            p.Name = updatePerson.Name+"[updated]";
            p.Age = updatePerson.Age+"[updated]";
            p.Address = updatePerson.Address+"[updated]";
            return "Updated success";
        }
        public string DeletePerson(string id)
        {
            persons.RemoveAll(e => e.ID.Equals(id));
            return "success";
        }
    }
}

 

在上述的類中,類前面被打上了ServiceBehavior特性,主要是用來說明這服務端只會存在這個類的一個實例.
 
步驟八:
如果要使用這個RESTFul Service應用,那么這個服務就必須得被寄宿.所以我們必須做如下的更改才能讓RESTService跑起來.
在Web.config文件中,移除掉<system.servicemodel>下面的所有代碼,並且加入如下的新的代碼:
<system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true">
    </serviceHostingEnvironment>
    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"></standardEndpoint>
      </webHttpEndpoint>
    </standardEndpoints>
  </system.serviceModel>

其中,<serviceHostingEnvironment>標記用於制定當前應用將運行在Asp.net兼容模式下.

<standardEndpoints> 標記用於為RESTFul應用獲取WebHelp.

然后,在Global.asax文件中,我們需要在Application_Start事件中添加如下代碼:

RouteTable.Routes.Add(new ServiceRoute("RestService", new WebServiceHostFactory(), typeof(RESTSerivce)));  
 
步驟九:
運行這個應用:
我們可以看到如下的頁面:
QQ截圖20131022144957
當運行 http://localhost:****/RestService/Help的時候,我們可以看到如下頁面:
QQ截圖20131022145015
 
如何使用RESTFul Service呢?

客戶端編程

這里我先貼出客戶端代碼:

using System.Text;
using System.Windows;
using System.Net;
using System.Xml.Serialization;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Diagnostics;
using System.Runtime.Serialization.Json;
using System.Collections.Generic;

namespace ConsumingDaemon
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnCreate_Click(object sender, RoutedEventArgs e)
        {
            //Client entity
            Person p = new Person();
            p.ID = "1";
            p.Name = "scy";
            p.Age = "25";
            p.Address = "China HeNan";


            HttpWebRequest request = WebRequest.Create("http://localhost:11126/RESTService/") as HttpWebRequest;
            request.KeepAlive = false;
            request.Method = "POST";

            StringWriter sw = new StringWriter();
            XmlSerializer xs = new XmlSerializer(p.GetType());
            xs.Serialize(sw,(object)p);
            string result = sw.ToString();

            //generate xml that required
            result = result.Replace("<?xml version=\"1.0\" encoding=\"utf-16\"?>\r\n",string.Empty);
            Regex regex = new Regex("<Person.*?>");
            Match m = regex.Match(result);
            if (m.Success)
            {
               result =  result.Replace(m.Value,"<Person xmlns=\"http://schemas.datacontract.org/2004/07/RESTFulDaemon\">");
            }

            AutoResetEvent autoResetEvent = new AutoResetEvent(false);

            byte[] buffer = Encoding.Default.GetBytes(result);
            request.ContentLength = buffer.Length;
            request.ContentType = "text/xml";
            request.BeginGetRequestStream((iar) =>
            {
                Stream PostData =  request.EndGetRequestStream(iar);
                PostData.Write(buffer, 0, buffer.Length);
                PostData.Close();
                autoResetEvent.Set();
            },null);

            autoResetEvent.WaitOne();

            HttpWebResponse resp = request.GetResponse() as HttpWebResponse;
            StreamReader loResponseStream = new StreamReader(resp.GetResponseStream(), Encoding.Default);

            string Response = loResponseStream.ReadToEnd();
            MessageBox.Show(Response);
            loResponseStream.Close();
            resp.Close();

        }

        private void btnGetAll_Click(object sender, RoutedEventArgs e)
        {
            WebClient client = new WebClient();
            client.Headers["Content-type"] = "application/json";

            // invoke the REST method
            byte[] data = client.DownloadData("http://localhost:11126/RESTService");

            // put the downloaded data in a memory stream
            MemoryStream ms = new MemoryStream();
            ms = new MemoryStream(data);

            // deserialize from json
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(List<Person>));

            List<Person> result = ser.ReadObject(ms) as List<Person>;

            string resultEx = string.Empty;
            foreach (var r in result)
            {
                resultEx += r.ID + "--";
            }
            MessageBox.Show(resultEx);
        }

        private void btnGetOne_Click(object sender, RoutedEventArgs e)
        {
            string id = txtGet.Text;
            Person result = GetOneByID(id);
            MessageBox.Show(result.ID + "|" + result.Name + "|" + result.Age + "|" + result.Address);
        }

        private Person GetOneByID(string id)
        {
            WebClient client = new WebClient();
            client.Headers["Content-type"] = "application/json";

            byte[] data = client.DownloadData("http://localhost:11126/RESTService/" + id);

            MemoryStream ms = new MemoryStream();
            ms = new MemoryStream(data);

            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Person));
            Person result = ser.ReadObject(ms) as Person;
            return result;
        }

        private void btnUpdateOne_Click(object sender, RoutedEventArgs e)
        {
            #region The XML way
            //string id = txtUpdate.Text;

            ////Client entity
            //Person p = new Person();
            //p.ID = "3";
            //p.Name = "scy1";
            //p.Age = "28";
            //p.Address = "Amearican Person";


            //HttpWebRequest request = WebRequest.Create("http://localhost:11126/RESTService/") as HttpWebRequest;
            //request.KeepAlive = false;
            //request.Method = "PUT";

            //StringWriter sw = new StringWriter();
            //XmlSerializer xs = new XmlSerializer(p.GetType());
            //xs.Serialize(sw, (object)p);
            //string result = sw.ToString();

            ////generate xml that required
            //result = result.Replace("<?xml version=\"1.0\" encoding=\"utf-16\"?>\r\n", string.Empty);
            //Regex regex = new Regex("<Person.*?>");
            //Match m = regex.Match(result);
            //if (m.Success)
            //{
            //    result = result.Replace(m.Value, "<Person xmlns=\"http://schemas.datacontract.org/2004/07/RESTFulDaemon\">");
            //}

            //AutoResetEvent autoResetEvent = new AutoResetEvent(false);

            //byte[] buffer = Encoding.Default.GetBytes(result);
            //request.ContentLength = buffer.Length;
            //request.ContentType = "text/xml";
            //request.BeginGetRequestStream((iar) =>
            //{
            //    Stream PostData = request.EndGetRequestStream(iar);
            //    PostData.Write(buffer, 0, buffer.Length);
            //    PostData.Close();
            //    autoResetEvent.Set();
            //}, null);

            //autoResetEvent.WaitOne();

            //HttpWebResponse resp = request.GetResponse() as HttpWebResponse;
            //StreamReader loResponseStream = new StreamReader(resp.GetResponseStream(), Encoding.Default);

            //string Response = loResponseStream.ReadToEnd();
            //MessageBox.Show(Response);
            //loResponseStream.Close();
            //resp.Close();
            #endregion

            #region The JSON way
            string id = txtUpdate.Text;

            Person p = GetOneByID(id);
            WebClient client = new WebClient();
            client.Headers["Content-type"] = "application/json";

            // serialize the object data in json format
            MemoryStream ms = new MemoryStream();
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Person));
            ser.WriteObject(ms, p);

            byte[] resultData = client.UploadData("http://localhost:11126/RESTService/", "PUT", ms.ToArray());
            
            MessageBox.Show(Encoding.Default.GetString(resultData));
            #endregion
        }

        private void btnDeleteOne_Click(object sender, RoutedEventArgs e)
        {
            string id = txtDelete.Text;
            WebClient client = new WebClient();
            client.Headers["Content-type"] = "application/json";
            // serialize the object data in json format
            MemoryStream ms = new MemoryStream();
            DataContractJsonSerializer ser =new DataContractJsonSerializer(typeof(string));
            ser.WriteObject(ms, id);

            // invoke the REST method
            byte[] data = client.UploadData("http://localhost:11126/RESTService/", "DELETE", ms.ToArray());

            MessageBox.Show(Encoding.Default.GetString(data));
        }
    }
}
View Code

下面是利用WPF寫的Client程序,當點擊“創建用戶”按鈕的時候,顯示的結果:

當點擊“獲取所有用戶”的時候,會把所有用戶的ID號給獲取到:
當點擊“獲取某個用戶”按鈕時的結果(2號用戶信息被獲取):
當點擊“更新某個用戶”按鈕時的結果(3號用戶信息被更新):

當點擊“刪除某個用戶”按鈕時候的顯示結果(4號用戶被刪除):

 

Client端的顯示效果如下:

源碼下載

 Click here to get source code

特別說明:本文翻譯自Create and Consume RESTFul Service in .NET Framework 4.0一文,但是文中的示例已經被我重寫了一遍,能夠充分展示在客戶端的CRUD的過程。
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM