使用ASP.NET web API建立REST服務(二)

石曼迪發表於2013-10-27

Creating a REST service using ASP.NET Web API

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.

REST (REpresentational State Transfer,表述性狀態轉移) 。REST 指的是一組架構約束條件和原則。滿足這些約束條件和原則的應用程式或設計就是RESTful。

REST 定義了一組體系架構原則,您可以根據這些原則設計以系統資源為中心的 Web 服務,包括使用不同語言編寫的客戶端如何通過 HTTP 處理和傳輸資源狀態。 如果考慮使用它的 Web 服務的數量,REST 近年來已經成為最主要的 Web 服務設計模式。 事實上,REST 對 Web 的影響非常大,由於其使用相當方便,已經普遍地取代了基於 SOAP 和 WSDL 的介面設計。

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.

ASP.NET MVC beta 4附帶了API開發功能,同時可以呼叫ASP.NET Web API來建立和使用 REST 服務。建立其他服務時很重要的是要遵循標準的協議 (HTTP)。

在這篇文章我們來看看如何建立一個簡單的ASP.NET Web API REST服務,並且呼叫它。

開發工具:Visual Studio 2013

建立步驟:

1. 模型化資料

  建立一個新的ASP.NET WEB專案,在如下圖介面選擇Empty模板並且勾上Web API選項。(這裡我們不使用Asp.net Mvc4的方式去建立,我認為應該採用獨立的方式去建立比較好)

然後再Model中建立一個類Person

 

public class Person

 { 

public int Id { get; set; } 

public string FirstName { get; set; } 

public string LastName { get; set; } 

}

 

在這裡您可能會在您認為合理的框架中使用Web API,所以我們只從原理上來描述它,不拘泥於具體的實現方式和設計模式。

接著在Model中建立一個介面IPersonRepository

 

public interface IPersonRepository

 { 

IEnumerable<Person> GetAll(); 

Person Get(int id); 

Person Add(Person person); 

void Remove(int id); 

bool Update(Person person); 

 }

 

然後,讓我們去實現它。同樣在Model中建立一個實現類PersonRepository

 

    public class PersonRepository : IPersonRepository
    {

        private List<Person> _people = new List<Person>();
        private int num = 1;

        public PersonRepository()
        {
            this.Add(new Person { LastName = "三", FirstName = "張" });
            this.Add(new Person { LastName = "四", FirstName = "李" });
            this.Add(new Person { LastName = "五", FirstName = "王" });
            this.Add(new Person { LastName = "六", FirstName = "老" });
        }

        public IEnumerable<Person> GetAll()
        {
            return _people;
        }

        public Person Get(int id)
        {
            return _people.Find(p => p.Id == id);
        }

        public Person Add(Person person)
        {
            if (person == null)
                throw new ArgumentNullException("person");
            person.Id = num++;
            _people.Add(person);
            return person;
        }

        public void Remove(int id)
        {
            _people.RemoveAll(p => p.Id == id);
        }

        public bool Update(Person person)
        {
            if (person == null)
                throw new ArgumentNullException("person");

            int index = _people.FindIndex(p => p.Id == person.Id);
            if (index == -1)
                return false;
            _people.RemoveAt(index);
            _people.Add(person);
            return true;
        }
    }

 

 在這個實現中不管您將採取任何資料來源XML,檔案或者其他持久化儲存,我們都可以返回一個實現了IEnumerable介面的列表,從而消除資料來源的差異性。

2. 使用Api Controllers 訪問 API 服務

Unlike an ASP.NET/MVC Project, which generally uses Controllers derived from System.Web.MVC.Controller, a WebApi project will generally utilize controllers derived from System.Web.Http.WebApiController. Like the standard MVC Controller, The WebApi Controller accepts incoming HTTP requests and processes accordingly. However, the standard MVC Controller returns a View, while the WebApi controller returns data.

特別值得注意的是,WebApi controller會檢查接受HTTP請求頭部。然後將NET物件序列化為適當的返回資料型別(XML或JSON)。

在Controllers資料夾上右鍵選擇新增—>控制器,然後如下圖選擇

命名為PersonController

現在您可以方便的在這裡新增任何GURD程式碼。我們一起來分析一個具體實現:

 

static readonly IPersonRepository databasePlaceholder = new PersonRepository(); 

public Person GetPersonByID(int id) 

 { 

Person person = databasePlaceholder.Get(id); 

if (person == null) 

 { 

//返回一個自定義HTTP狀態碼

throw new HttpResponseException(HttpStatusCode.NotFound); 

 } 

return person; 

 }

 

第一句程式碼可以說是例項化一個資料訪問類,而我們要做的是根據ID返回要查詢的Person物件,通過呼叫對應方法可以實現。注意觀察這個方法是以Get方式開頭,而這個方法包含一個id引數,這個方法會匹配/api/PersonController/id的請求,而請求中的引數id會自動轉換為int型別

 

如果沒有找到相應id的聯絡人,則會丟擲一個自定義HttpResponseMessage的異常,這個異常是指向的404異常,表示請求資源不存在。實際專案中,我們是非常有必要返回一些和操作對應的異常資訊的。

完整程式碼如:

    public class PersonController : ApiController
    {
        static readonly IPersonRepository databasePlaceholder = new PersonRepository();

        
        public Person GetPersonByID(int id)
        {
            Person person = databasePlaceholder.Get(id);
            if (person == null)
            {
                //返回一個自定義HTTP狀態碼
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return person;
        }

        public IEnumerable<Person> GetAllPeople()
        {
            return databasePlaceholder.GetAll();
        }

        public HttpResponseMessage PostPerson(Person person)
        {
            person = databasePlaceholder.Add(person);
            var response = this.Request.CreateResponse<Person>(HttpStatusCode.Created, person);
            string uri = Url.Link("Default", new { id = person.Id });
            response.Headers.Location = new Uri(uri);
            return response;
        }

        public bool PutPerson(Person person)
        {
            if (!databasePlaceholder.Update(person))
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return true;
        }

        public void DeletePerson(int id)
        {
            Person person = databasePlaceholder.Get(id);
            if (person == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            databasePlaceholder.Remove(id);
        }
    }

 

As we have alluded to previously, a core tenet of MVC is to favor Convention over Configuration. In terms of our controller class, what this means is that the ASP.NET/MVC runtime establishes certain default behaviors which require no additional configuration, unless you want to change them. Initially, for me anyway, this was also a source of confusion, as the runtime appeared to perform sufficient "magic" that I didn't know what was happening.

當然其中一個非常重要的Web API規範就是HTTP謂詞的對映,如GET、POST、PUT、DELETE。那麼正如我們上面所述,我們的動作也遵循了這個規範。原因是在 ASP.NET Web API,一個 controller 是一個 class(類) 以處理 HTTP 請求(requests)。在 controller 裡所有的公開方法(public methods)都稱為 Action方法 或簡稱 Action。當 Web API Framework 接收到一個請求,它路由請求到一個 Action。

每個實體(entry)在路由表裡都包含一個路由樣板(route template)。Web API 的路由樣板預設是 "api/{controller}/{id}",此樣板裡,"api" 是文字路徑片段,{controller} 和 {id} 是定位引數。

當 Web API Framework 接收到一個 HTTP 請求,它會去嘗試比對 URI 對路由表的路由樣板列表, 如果沒有符合的路由,Client 會收到一個 404 錯誤。例如,以下 URI 會符合預設路由:

· /api/Person

· /api/Person /1

路由表定義如:

config.Routes.MapHttpRoute( 

 name: "Default", 

 routeTemplate: "api/{controller}/{id}", 

 defaults: new { id = RouteParameter.Optional } 

 );

 

例如,一個 GET 請求,Web API 會檢視那一個 action 是以 "Get..." 開頭,比如 "GetContact" 或 "GetAllContacts"。同樣規則適用在 GET, POST, PUT, DELETE 方法。你也能在 controller 裡使用屬性(attributes)去啟用其他 HTTP 方法。

 以下是一些可能 HTTP 請求及其含義:

HTTP Method

URI路徑

Action

引數

GET

/api/Person

GetAllPeople

GET

/api/Person /1

GetPersonByID

1

DELETE

/api/Person /1

DeletePerson

1

POST

/api/Person

PostPerson

newPerson

當然,我們可以根據自己的實際需要修改這個路由表,如:

config.Routes.MapHttpRoute( 
 name: "Default", 
 routeTemplate: "api/{controller}/{action}/{id}", 
 defaults: new { id = RouteParameter.Optional } 
 );

 

那麼根據ID查詢某個人就需要這樣傳送請求:

/api/person/ GetPersonByID/1

也可以訪問到需要的資源。

3. 建立消費服務

       根據專案的需要,你可以在你的API的客戶端新增一個Person類。webapi懂得如何序列化和反序列化。NET類中的JSONXML,都是使用中最常見的兩種資料交換格式。

          We can re-write our client examples to utilize a Person class if it suits our needs, and pass instances of Person to and from our API project indirectly through serialization. Our core client methods, re-written, look like this:

         我們以控制檯程式為例,新建一個控制檯專案,假設命名為:RestApiClient

         新增第一個類Person:         

    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

        至於為什麼要在這裡還要新增一個Person類,我們可以想象WEB API機制,它是類似於Web Service的一種遠端資料呼叫訊息交換機制,伺服器上使用的類本地是沒有的,為了型別和序列化方便通常還要在消費端再次定義一次,加入您將消費放在服務端如以WCF的形式來進行,真正的客戶端通過同步/非同步請求來獲取資源,那麼另當別論。

然後新建一個類用來傳送請求給WEB API,我們再次來分析這個類裡面寫些什麼,列舉一個方法:

        // 傳送一個HTTP GET請求給Person Controller  API:
        static JArray getAllPeople()
        {
            HttpClient client = new HttpClient();
            HttpResponseMessage response =client.GetAsync("http://localhost:1296/api/person/").Result;
            return response.Content.ReadAsAsync<JArray>().Result;
        }

     可以看出返回的是JArray格式。說明如下:

使用JSON前,需要引用Newtonsoft.Jsondllusing Newtonsoft.Json.Linq的名稱空間。LINQ to JSON主要使用到JObject, JArray, JPropertyJValue這四個物件,JObject用來生成一個JSON物件,簡單來說就是生成"{}",JArray用來生成一個JSON陣列,也就是"[]",JProperty用來生成一個JSON資料,格式為key/value的值,而JValue則直接生成一個JSON值。下面我們就用LINQ to JSON返回上面分頁格式的資料。

下面就是定義一個HttpClient去傳送一個GET請求給指定WEB API,然後利用HttpResponseMessage接收返回的JSON資料。

完整程式碼如:

 

        // 傳送一個HTTP GET請求給Person Controller  API:
        static JArray getAllPeople()
        {
            HttpClient client = new HttpClient();
            HttpResponseMessage response =client.GetAsync("http://localhost:1296/api/person/").Result;
            return response.Content.ReadAsAsync<JArray>().Result;
        }

        // 傳送一個帶ID引數的HTTP GET請求給Person Controller API
        static JObject getPerson(int id)
        {
            HttpClient client = new HttpClient();
            HttpResponseMessage response = client.GetAsync("http://localhost:1296/api/person/GetPersonByID/" + id).Result;
            return response.Content.ReadAsAsync<JObject>().Result;
        }

        // 傳送一個匿名(類)物件HTTP POST給Person Controller API
        static JObject AddPerson(string newLastName, string newFirstName)
        {
            // 初始化一個匿名物件
            var newPerson = new { LastName = newLastName, FirstName = newFirstName };
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri("http://localhost:1296/");
            var response = client.PostAsJsonAsync("api/person", newPerson).Result;
            return response.Content.ReadAsAsync<JObject>().Result;

        }

        static bool UpdatePerson(int personId, string newLastName, string newFirstName)
        {
            var newPerson = new { id = personId, LastName = newLastName, FirstName = newFirstName };
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri("http://localhost:1296/");
            var response = client.PutAsJsonAsync("api/person/", newPerson).Result;
            return response.Content.ReadAsAsync<bool>().Result;
        }

        static void DeletePerson(int id)
        {
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri("http://localhost:1296/");
            var relativeUri = "api/person/" + id.ToString();
            var response = client.DeleteAsync(relativeUri).Result;
            client.Dispose();
        }

 

最不值錢的就是最終呼叫了,在Program類的Main方法中呼叫程式碼如下:

 

           Console.WriteLine("The Program will start......");

           JArray people = getAllPeople();
           foreach (var person in people)
           {
               Console.WriteLine(person);
           }


           Console.WriteLine(Environment.NewLine + "根據ID=2查詢:");
           JObject singlePerson = getPerson(2);
           Console.WriteLine(singlePerson);


           Console.WriteLine(Environment.NewLine + "新增一個新物件並返回");
           JObject newPerson = AddPerson("曼迪", "石");
           Console.WriteLine(newPerson);


           Console.WriteLine(Environment.NewLine + "更新一個已經存在的物件並返回成功與否");

           JObject personToUpdate = getPerson(2);
           string newLastName = "麻子";
           Console.WriteLine("Update Last Name of " + personToUpdate + "to " + newLastName);


           int id = personToUpdate.Value<int>("Id");
           string FirstName = personToUpdate.Value<string>("FirstName");
           string LastName = personToUpdate.Value<string>("LastName");


           if (UpdatePerson(id, newLastName, FirstName))
           {
               Console.WriteLine(Environment.NewLine + "更新person:");
               Console.WriteLine(getPerson(id));
           }


           Console.WriteLine(Environment.NewLine + "刪除ID為5的物件:");

           DeletePerson(5);

           Console.WriteLine("輸出所有物件");
           people = getAllPeople();
           foreach (var person in people)
           {
               Console.WriteLine(person);
           }
           Console.Read();

PS

  1. 這裡我偷了個懶,把他們寫成一個部分類了,實際應該怎麼寫你懂得
  2. 如果報每個方法最後一句出錯,請加引用:"System.Net.Http.Formatting",可以在這裡找到C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll真實的想法是實在不想加這個引用,原因請看下面。

    另外遺憾的一點是我們對服務的消費操作並沒有使用到純非同步操作也沒有使用配置,只因我們講述的是最簡單的原理,後面會逐漸構建起一個完整的框架。敬請期待!

原始碼下載:請點選

 

 

 

 

相關文章