路由其實也可以很簡單-------Asp.net WebAPI學習筆記(一)

来源:https://www.cnblogs.com/devtester/archive/2018/04/23/8897302.html
-Advertisement-
Play Games

MVC也好,WebAPI也好,據我所知,有部分人是因為複雜的路由,而不想去學的。曾經見過一位程式猿,在他MVC程式中,一切皆路由,url中是完全拒絕"?"和“&”。對此,我也不好說什麼,搞不好是個人風格。路由雖然重要,但其實也只是實現MVC的一種手段,並非你用的路由越多,你的url完全不使用參數,你 ...


  MVC也好,WebAPI也好,據我所知,有部分人是因為複雜的路由,而不想去學的。曾經見過一位程式猿,在他MVC程式中,一切皆路由,url中是完全拒絕"?"和“&”。對此,我也不好說什麼,搞不好是個人風格。路由雖然重要,但其實也只是實現MVC的一種手段,並非你用的路由越多,你的url完全不使用參數,你的MVC就越純正。說實話,筆者一開始對路由也感到恐懼,但是閱讀了官方文檔後,發現路由其實也可以很簡單,關鍵在於我們如何使用。由於筆者也是初學者,有什麼錯漏的地方,歡迎大家指正。

  本系列文章使用的是vs2017,WebAPI版本是2。本系列大多數內容並非原創,而是來自官網的教程(https://docs.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/),如果你英文不好,可以將鏈接中的en-us改成zh-cn。中文版地址:https://docs.microsoft.com/zh-cn/aspnet/web-api/overview/getting-started-with-aspnet-web-api/。不過建議你可以的話,還是看英文版本,因為有些翻譯是完全走樣。

  廢話不多說,馬上來看看如何新建一個WebAPI項目。打開vs2017,文件-新建-項目

  

  選擇空模板,勾選webapi

  

  添加模型類,在右側資源管理器的Models文件夾上右鍵-添加-類

  類的代碼如下:

 1 namespace ProductsApp.Models
 2 {
 3     public class Product
 4     {
 5         public int Id { get; set; }
 6         public string Name { get; set; }
 7         public string Category { get; set; }
 8         public decimal Price { get; set; }
 9     }
10 }

  添加空的控制器,在Controllers文件夾上右鍵-添加-控制器,選擇Web API2 控制器 - 空

  控制器名稱為:ProductsController,代碼如下:

using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;

namespace ProductsApp.Controllers
{
    public class ProductsController : ApiController
    {
        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public IHttpActionResult GetProduct(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }
    }
}

   這樣,一個簡單的WebAPI就完成了。完成後,文件結構如下:

  調用的話,我們直接使用IIS新建一個網站,埠為1111

  

 

  打開edge瀏覽器,輸入地址:http://localhost:1111/api/Products,效果如下(註意,每次修改完代碼後,需要重新生成一下), 

  一切都很簡單,代碼也都不複雜,不過明顯有兩個問題,一個是,為什麼預設就調用了GetAllProducts()了,另一個是,我們明明返回一個列表的,怎麼到了客戶端就變成json了呢?

  第一個問題,就是本文所要研究的問題。說到路由,筆者想起一樁往事。筆者自從接觸asp.net以來,一直都在使用webform,即使在MVC大行其道的時候。有一次,接到一個外包項目,利用某開源社區框架做業務的擴展,由於該開源框架用的是MVC,於是就問對方的技術負責人,業務擴展項目是否也必須用MVC,對方答道,用MVC幹嘛,繞來繞去不是更麻煩嗎?他這句話讓我深以為然,大有惺惺相惜之感。我這樣說,並非要貶低MVC,更無意挑起MVC與WebForm之爭,而是在遍地MVCer的情況下,還能找到WebFormer而高興。實際上,只要能滿足客戶要求,誰會在意你用MVC還是WebForm呢。

  廢話不多說,回到我們的問題,為什麼我們輸入地址:http://localhost:1111/api/Products,就是在調用GetAllProducts()呢?首先,我們看看App_Start文件夾下的WebApiConfig.cs文件,這個文件是用來配置路由的,代碼如下: 

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服務
            // Web API 路由
            config.MapHttpAttributeRoutes();

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

  上面的代碼,實質上就是定義了一個預設的路由規則。

  我們再看看Global.asax

        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
        }

  很明顯,在程式第一次啟動的時候,我們的路由規則就被配置載入了。這個預設的規則就是 api/{controller}/{id},其中{controller}匹配一個控制類,例如ProductsController,{id}是可選的,匹配的是public方法(也就是action)的參數。那麼,Controller里的public方法,也就是action該如何匹配呢?

  從官方的文檔可以查到這麼幾句話:If you are familiar with ASP.NET MVC, Web API routing is very similar to MVC routing. The main difference is that Web API uses the HTTP method, not the URI path, to select the action。意思就是,如果你熟悉MVC,那麼API的路由是跟MVC的路由非常相似的。兩者之間的不同,是Web API使用http方法,而非URI路徑去選擇action。這裡的action,就是我們Controller裡面的public 方法。

  也就是說,預設路由api/{controller}/{id},首先匹配一個Controller類,然後用http請求方法匹配Action方法名,最後,用{id},匹配Action中的參數。

  http請求方法是什麼東西?如果你是傳統的asp開發者,或是php開發者,相信都會非常熟悉。例如我們以前寫表單html,通常都會這樣寫:

<form action="form_action.asp" method="get">
 ....
</form>

  裡面的method就是我們所說的http請求方法,最常見的就是get和post,get的話,就是將參數放到url上去提交,post的話,參數不會顯示在url中。更多的http方法,可以點擊這裡

  既然知道WebAPI的預設路由,是用http請求方法去匹配控制類中的action,那麼就好辦了,我們在地址欄輸入地址:http://localhost:1111/api/Products ,其實就是相當於在使用get方法與ProductsController中的Action進行匹配了。

  然而,上面代碼中,兩個Action方法都沒明確表明是用什麼http請求方法,那怎麼確定調用哪一個方法呢?get跟GetAllProducts()到底有什麼關係呢,以至於GetAllProducts()可以被預設調用?或許有的人已經看出來了,沒錯,調用的方法GetAllProducts()那麼巧,也是以Get開頭的。這就是我們匹配的其中一個條件。如果Controller中,public方法的名字(也就是action的名字),是以"Get", "Post", "Put", "Delete", "Head", "Options", 或 "Patch"開頭,那麼按照約定,該方法(action)匹配對應的http請求方法的調用。如果開頭沒有上述的關鍵字,預設表示該方法只支持Post。

  例如GetAllProducts()方法,就表示使用http的get方法調用。DeleteProduct(int id)就表示用http的deletel方法調用。由於我們調用的地址是:http://localhost:1111/api/Products,翻譯成匹配規則就是,匹配ProductsController中,一個使用get,同時沒有參數的Action(也就是public 方法),即GetAllProducts()。如果我們有另一個Get方法,同時也是沒有參數的話,就會報錯。例如,我們增加一個方法:

        public string GetTest()
        {
            return "GetTest is called";
        }

  該方法明顯也是匹配Get方法,同時沒有參數。重新生成下項目,然後用PostMan調用一下,會發現匹配多個的錯誤。(PostMan的安裝就不說了,很簡單,不斷下一步。)

  

 

  我們在原來的基礎上,修改一下ProductsController的代碼,增加一個方法(紅色字體部分),代碼如下:

using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;

namespace ProductsApp.Controllers
{
    public class ProductsController : ApiController
    {
        Product[] products = new Product[]
        {
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
        };
        
        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        } 

        public IHttpActionResult GetProduct(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }

        public string SayHello()
        {
            return "Hello,World";
        }
    }
}

  

  我們在PostMan中輸入地址:http://localhost:1111/Products,http請求方法,選擇Post,按照我們的規則,應該會調用SayHello方法,實際效果如下:

  

  如果我們將url改成:http://localhost:1111/api/Products/1,但方法依然是Post,那麼按照上面說的,先找Post的方法,而三個方法中,只有SayHello符合,雖然後面加了id,並且值為1,由於它是可選的,所以,在post下,調用的依然是SayHello,如下圖:

  

  假如,我們將Post方法改為Get,那麼就會選擇調用我們的GetProduct方法,效果如下:

  

  

  這個就是WebAPI預設的路由,主要使用Http請求方法來匹配Controller里的Action。而這個匹配的規則,就是使用首碼來決定哪一個最匹配,如果首碼都不是http方法,表示預設匹配Post。是不是感覺很簡單呢,如果這樣還覺得複雜,沒關係,下麵還有更簡單的方法,就是屬性路由。

   上面的這種路由匹配規則,其實是屬於約定的路由。在調用的時候,你還多多少少需要想一下,究竟url是怎樣,會調用哪個方法,會不會有多個方法同時匹配等等。但是使用屬性路由,你就可以完全的“精准定位”。屬性路由,就是利用特性,重新定義路由。例如:

        [HttpPost]
        [Route("aaa/bbb")]
        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

  HttpPost強制了這個方法是需要使用Post來調用,Route強制定義了這個方法的調用路徑。雖然這個方法是以Get開頭,但是[HttpPost]優先順序大於這個約定,我們用PostMan來測試下,我們依然先輸入之前的地址:http://localhost:1111/Products,方法為Get,可以看到拋出Not Found這個錯誤

  

  直到我們將地址改為:http://localhost:1111/aaa/bbb,http方法改為Post的時候,調用才成功。是不是太厲害了,我們可以隨便定義訪問這個方法的路由,什麼約定的規則完全可以置之不理,我們可以完全實現“精准定位”,路由變得不再複雜了,一切都在我們的掌握之中。

  那麼,我們怎樣才能使用這種屬性路由呢,首先,我們要打開App_Start文件夾中的WebApiConfig.cs文件,確保一下這句代碼存在:

        public static void Register(HttpConfiguration config)
        {
            // 確保開啟了屬性路由
            config.MapHttpAttributeRoutes();

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

  其次,要引入命名空間:using System.Web.Http;就是這麼簡單,我們就可以使用屬性路由。看到WebApiConfig.cs的代碼,有人擔心,會不會是因為config.MapHttpAttributeRoutes()代碼在前,所以優先順序才大於約定的路由呢,我們可以換一下次序,代碼改成這樣:

       public static void Register(HttpConfiguration config)
        {
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        //次序在後
            config.MapHttpAttributeRoutes();
        }

  結果發現,屬性路由的優先順序依然大於約定的路由,如果你還擔心,大可直接刪除約定的路由,將上述的代碼改成這樣:

        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
        }

  這樣,你就可以完全不用考慮約定的路由對屬性路由的影響,一切的映射都在你的牢牢掌握中。不過壞處就是,你的每個方法,都要顯式指定http方法和路由。但我覺得這個代價是值得的,因為我們再不用花時間去繞來繞去,再不用擔心增加了新方法會不會造成路由衝突等問題,我們只需定好命名規則,保證我們每個方法定義的路由不重覆即可。說著說著,我也覺得自己跟文章開頭說的那位程式猿一樣,在走向一個極端,他是在玩命的用路由,而我是在拼命的阻止路由的多匹配性,追求唯一確定,儘量不讓路由造成我的負擔,也許,這也是一種風格?

  剩下的都是很簡單,例如,路由首碼,還是用官方的例子:

public class BooksController : ApiController
{
    [Route("api/books")]
    public IEnumerable<Book> GetBooks() { ... }

    [Route("api/books/{id:int}")]
    public Book GetBook(int id) { ... }

    [Route("api/books")]
    [HttpPost]
    public HttpResponseMessage CreateBook(Book book) { ... }
}

  每個方法的路由首碼都是“api/books",是不是顯得很重覆,我們可以將首碼抽取,為整個控制器增加公共的首碼,代碼如下:

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET api/books
    [Route("")]
    public IEnumerable<Book> Get() { ... }

    // GET api/books/5
    [Route("{id:int}")]
    public Book Get(int id) { ... }

    // POST api/books
    [Route("")]
    public HttpResponseMessage Post(Book book) { ... }
}

  路由首碼的重寫,我們可以使用波浪符對首碼進行重寫,例如:

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET /api/authors/1/books
    [Route("~/api/authors/{authorId:int}/books")]
    public IEnumerable<Book> GetByAuthor(int authorId) { ... }

    // ...
}

   除此之外,還有路由約束,例如Route("api/books/{id:int}"),表示id是一個32位整數,如果是可選的,可以在後面加"?",例如Route("api/books/{id:int?}")

  更詳細的使用可以參考官網文檔:https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

    有了屬性路由,我們甚至可以極端的拋棄約定的路由,從而實現”精准定位“,一切定位都可以牢牢的掌握在自己手中。相信這樣,大家應該不會再害怕面對路由了吧。

 

 

  

  

 

 

  

 

 

 

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • AspNetCoreApi 跨域處理 如果咱們有處理過MV5 跨域問題這個問題也不大。 (1)為什麼會出現跨域問題: 瀏覽器安全限制了前端腳本跨站點的訪問資源,所以在調用WebApi 介面時不能成功訪問資源,原因“同源策略”的存在: 同源指以下幾點相同 (1) IP地址/功能變數名稱 (2) 埠號 (3) ...
  • 最近因為公司業務需要,又有機會擼winform了,這次的需求是因為公司有項目申報的這塊業務,項目申報前期需要關註政府發佈的相關動態信息,政府部門網站過多,人工需要一個一個網站去瀏覽和查閱,有時候還會遺漏掉,因此呢,我們打算用爬蟲+移動端web來做,我呢主要負責爬蟲和web Api。 爬蟲篇 爬蟲主要 ...
  • 在WPF里用MediaElement控制項,實現一個迴圈播放單一視頻的程式,同時可以控制視頻的播放、暫停、停止。 一種方式,使用MediaElement.MediaEnded事件,在視頻播放結束後,自動重新播放; 另一種方式,使用WPF定時器,在定時器事件里寫入視頻播放代碼。 後者優點是可以控制迴圈時 ...
  • 本文是一篇介紹net同步非同步的文章,是為張四火同學原創的。請張四活同學及廣大讀者指出,文章不通順的地方。後續應當還有兩篇文章敬請期待 ...
  • 最近有個統計分佈的需求,需要按統計本周,上周,本月,上月,本季度,上季度,本年度,上年度等時間統計分佈趨勢,所以這裡就涉及到計算周,月,季度,年度等的起止時間了,下麵總結一下C#中關於根據當前時間獲取周,月,季度,年度等時間段的起止時間的方法,廢話不多說,直接貼代碼,如果你覺得有用,請多多推薦。 ...
  • 本文的概念性內容來自深入淺出設計模式一書 項目需求 這是一個糖果機的需求圖. 它有四種狀態, 分別是圖中的四個圓圈: No Quarter: 無硬幣 Has Quater 有硬幣 Gumball Sold 糖果賣出 Out of Gumball 沒有糖果了 這個圖很像一個狀態圖. 每個圓圈就是一個狀 ...
  • 原文鏈接 一.Has方法: 二.With方法: ...
  • 基於Ubuntu安裝redis, 我找的一個很好的網站: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-redis-on-ubuntu-16-04設置redis密碼登錄, 編輯redis.c ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...