[WEB API]使用owin與web form做整合,並解決post無法傳送cookie的問題

[WEB API]使用owin與web form做整合,並解決post無法傳送cookie的問題

前言

因為網路上大家都是直接在global.asax裡面直接跟web api做整合,並且有關cors或者一些細節都沒多著墨,但其實一個十幾年歷史的大專案,global.asax裡面的程式碼已經累積的很嚇人了,而且真的要實做的情境也不會那麼單純,所以我比較希望在startup.cs裡面直接使用web api,包括簡單整合了swagger,然後前端用vue.js來跟web api做溝通,因為原有網站還有membership的遺跡歷史,所以整個整合起來是坑洞一堆的,而這邊就先記錄下來我是怎麼完成整個需求,如果你目前身處的情境跟筆者相似的話,那這篇文章可能會對你很有幫助(拍拍)

導覽

  1. 安裝相關套件
  2. 起始一個web api
  3. 加入swagger
  4. 與前端的整合
  5. 解決post無法傳送cookie的問題
  6. 結論

安裝相關套件

先尋找owin,然後把圖示的相關都安裝起來(注意一下因為有些是舊有網站用到的,基本上這邊應該是安裝owin和web api.owin就好了,但如果效果不如預期的話,就全裝起來了吧)

再來是web api的部份

起始一個web api

首先為專案加入startup.cs,然後輸入以下的內容

public class Startup                                        
{                                                           
    public void Configuration(IAppBuilder app)              
    {                       
        HttpConfiguration config = new HttpConfiguration(); 
        WebApiConfig.Register(config);                      
        app.UseWebApi(config);                              
    }
}                                                       

接著為App_Start的WebApiConfig.cs,加上我們需要的部份

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

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}", //為了方便,直接使用預設mvc的方式,來使用web api
                defaults: new { id = RouteParameter.Optional }
            );

            var cors = new EnableCorsAttribute("*", "*", "*") //打開cors
            {
                SupportsCredentials = true //允許ajax可以傳送cookie
            };
            config.EnableCors(cors);
            config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));//永遠使用json
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();//預設直接把首字改小寫
            config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;
        }
    }

然後新增一支最簡單的web api,來測試是否已經成功了

 public class DefaultController : ApiController          
 {                                                       
     public IHttpActionResult Get(int Id)                
     {                                                   
         return Ok(Id);                                  
     }                                                   
 }                                                       

從下面圖示可以看到,只要網站登入過了,我們確實會自動幫忙傳送cookie

加入swagger

swagger其實不只是一個說明文件,有時候也是一個很方便測試的工具,而且因為我們有老舊session的問題,使用postman不一定能通,所以為了方便測試性的考量,直接在登入的瀏覽器使用swagger測試是必然的,接下來一樣在startup.cs加入swagger的功能吧

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
            HttpConfiguration config = new HttpConfiguration();
            WebApiConfig.Register(config);
            SwaggerRegister(config);
            app.UseWebApi(config);
        }

        private void SwaggerRegister(HttpConfiguration config)
        {
            config
             .EnableSwagger(c =>
             {
                 c.SingleApiVersion("v1", "My API");
                 c.IncludeXmlComments(GetXmlCommentsPath());
             })
            .EnableSwaggerUi();
        }

        internal static string GetXmlCommentsPath()
        {
            return string.Format(@"{0}\App_Data\XmlDocument.xml",
                System.AppDomain.CurrentDomain.BaseDirectory);
        }
    }                                            

成功之後我們一樣測試一下swagger是否能開啟成功

與前端的整合

前端我是使用vue.js與axios來跟現有的web form新增需求,或者是為一些效能不好甚至是很難維護的網頁一支一支的翻新,這邊先示例一下如何使用axios來跟web form做溝通,而且也確實可以使用cookie來跟現有的membership做互動,可以用最小的風險來為老舊網站換上新衣。

axios部份

axios.get(`${process.env.API_URL}/algo/getAlgoSettingFb?eventId=${eventId}`, { withCredentials: true })

實測ok,已回傳200並且也可以看到cookie的標頭有順利傳出去

解決post無法傳送cookie的問題

接下來實測試一下post的部份,卻發現為何沒有傳cookie呢?回傳我們302告知導頁的訊息,為何Get可以,但是Post卻不行呢?因為這個問題導致筆者卡了好幾小時,因為相關可以參考的資源太少了,我只能找好幾種方式去測試,到底什麼方式可行,但是每次老舊網站重新編譯到要可以run,都需要花費很多時間,這下子不記錄一下真枉費我浪費了那麼多時間,就只為了解決這個看似微不足道的問題了,其實因為筆者一直卡在web api安裝了cors,而且也在web api config打開了各項允許的設定,為了測試我乾脆把所有程式碼允許的cors全部註解,改用chrome的套件來allow cors。

結果一樣是Get能過,但Post過不了

即然過不了的話,不如就不要用外掛了,筆者思考了不能過的很多原因,甚至把owin又改成使用global的方式,反覆試了很多種方式,最後直接把解答記錄下來,就不多囉嗦了。

為Global.asax.cs新增 Application_BeginRequest()的程式碼

        protected void Application_BeginRequest()
        {
            if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
            {
                Response.Headers.Add("Access-Control-Allow-Origin", "http://localhost:8080"); //這裡一定要明確指定client端的ip
                Response.Headers.Add("Access-Control-Allow-Headers", "Origin, Content-Type, X-Auth-Token");
                Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS");
                Response.Headers.Add("Access-Control-Allow-Credentials", "true");
                Response.End();
            }
        }

結果

結論

其實以這個例子來說,就知道了其實cors有分兩個部份,也就是web form有web form的,web api有web api的,兩個地方都要打開,我們才可以順利的使用,而我們都知道已經使用web api了,就不要在使用session了,但是偏偏老舊網站的歷史包袱卻不是任何一個人說想怎樣就怎樣的,通常是利益在決定你能怎麼做,對股東或老闆來說維護困難效能不行就多請幾個人來想辦法,想要改版如果系統掛了,商業利益難道工程師要負責嗎?所以其實用最小的風險為極大商業價值的老舊網站換上新衣,是一項極巨艱難的任務,現在對筆者來說最困難的不是學習任何新技術,而是要如何學習舊有的技術,了解原有的domain問題,然後想出解決方案並與新的技術做整合,最後則是評估如何使用最大的成功機率來改造,為整個開發團隊和公司達到雙贏,而如果你對此篇有任何想法或覺得有好做法的話,再請多多指導。