ASP.NET-自定義HttpModule與HttpHandler

    在以前的ASP.NET是如何在IIS下工做的這篇文章中介紹了ASP.NET與IIS配合工做的機制,在http請求通過一系列處理後,最後到達ASP.NET管道中,這時,就是Http Modules和HttpHandler出場的時候了。css

  再來擺出管道工做時序圖來一看:html

管道

   

HttpModuleweb

HttpModule是相似於過濾器的做用,能夠沒有,也能夠有任意個,每個均可以訂閱管道事件中的任意個事件,在每一個訂閱的事件中可自定義功能實現。c#

HttpModule是實現IHttpModule接口的類。接口以下:api

public interface IHttpModule
    {
        // 摘要: 
        //     處置由實現 System.Web.IHttpModule 的模塊使用的資源(內存除外)。
        void Dispose();
        //
        // 摘要: 
        //     初始化模塊,並使其爲處理請求作好準備。
        //
        // 參數: 
        //  context:
        //  一個 System.Web.HttpApplication,它提供對 ASP.NET 應用程序內全部應用程序對象的公用的方法、屬性和事件的訪問
        void Init(HttpApplication context);
    }

下面實現一個HttpModule,並訂閱管道中的一系列事件,訂閱事件就是在Init方法中綁定EventHandler的過程:瀏覽器

代碼有點長,由於我把每個事件都訂閱了,這樣一來能夠清楚的看出哪些事件執行了,這些事件執行的前後順序是什麼。代碼以下:緩存

public class MyModule : IHttpModule
    {
        #region IHttpModule Members

        public void Dispose()
        {
            //此處放置清除代碼。
        }

        public void Init(HttpApplication context)
        {
            // 下面是如何處理 LogRequest 事件併爲其 
            // 提供自定義日誌記錄實現的示例
            context.LogRequest += new EventHandler(OnLogRequest);
            context.BeginRequest += new EventHandler(context_BeginRequest);
            context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
            context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
            context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest);
            context.Disposed += new EventHandler(context_Disposed);
            context.Error += new EventHandler(context_Error);
            context.EndRequest += new EventHandler(context_EndRequest);
            context.MapRequestHandler += new EventHandler(context_MapRequestHandler);
            context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
            context.PostAuthenticateRequest += new EventHandler(context_PostAuthenticateRequest);
            context.PostAuthorizeRequest += new EventHandler(context_PostAuthorizeRequest);
            context.PostLogRequest += new EventHandler(context_PostLogRequest);
            context.PostReleaseRequestState += new EventHandler(context_PostReleaseRequestState);
            context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
            context.PostResolveRequestCache += new EventHandler(context_PostResolveRequestCache);
            context.PostUpdateRequestCache += new EventHandler(context_PostUpdateRequestCache);
            context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);
            context.RequestCompleted += new EventHandler(context_RequestCompleted);
            context.ResolveRequestCache += new EventHandler(context_ResolveRequestCache);
            context.UpdateRequestCache += new EventHandler(context_UpdateRequestCache);
            context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
            context.PreSendRequestContent += new EventHandler(context_PreSendRequestContent);
            context.PreSendRequestHeaders += new EventHandler(context_PreSendRequestHeaders);
            context.PostMapRequestHandler += new EventHandler(context_PostMapRequestHandler);


        }

        void context_Error(object sender, EventArgs e)
        {
            WriteLog("Error");
            //HttpContext.Current.Response.Write("Error<br />");
        }

        void context_UpdateRequestCache(object sender, EventArgs e)
        {
            WriteLog("UpdateRequestCache");
            //HttpContext.Current.Response.Write("UpdateRequestCache<br />");
        }

        void context_ResolveRequestCache(object sender, EventArgs e)
        {
            WriteLog("ResolveRequestCache");
            // HttpContext.Current.Response.Write("ResolveRequestCache<br />");
        }

        void context_RequestCompleted(object sender, EventArgs e)
        {
            WriteLog("RequestCompleted");
            // HttpContext.Current.Response.Write("RequestCompleted<br />");
        }

        void context_ReleaseRequestState(object sender, EventArgs e)
        {
            WriteLog("ReleaseRequestState");
            //HttpContext.Current.Response.Write("ReleaseRequestState<br />");
        }

        void context_PostUpdateRequestCache(object sender, EventArgs e)
        {
            WriteLog("PostUpdateRequestCache");
            //HttpContext.Current.Response.Write("PostUpdateRequestCache<br />");
        }

        void context_PostResolveRequestCache(object sender, EventArgs e)
        {
            WriteLog("PostResolveRequestCache");
            //HttpContext.Current.Response.Write("PostResolveRequestCache<br />");
        }

        void context_PostRequestHandlerExecute(object sender, EventArgs e)
        {
            WriteLog("PostRequestHandlerExecute");
            //HttpContext.Current.Response.Write("PostRequestHandlerExecute<br />");
        }

        void context_PostReleaseRequestState(object sender, EventArgs e)
        {
            WriteLog("PostReleaseRequestState");
            //HttpContext.Current.Response.Write("PostReleaseRequestState<br />");
        }

        void context_PostLogRequest(object sender, EventArgs e)
        {
            WriteLog("PostLogRequest");
            //HttpContext.Current.Response.Write("PostLogRequest<br />");
        }

        void context_PostAuthorizeRequest(object sender, EventArgs e)
        {
            WriteLog("PostAuthorizeRequest");
            //HttpContext.Current.Response.Write("PostAuthorizeRequest<br />");
        }

        void context_PostAuthenticateRequest(object sender, EventArgs e)
        {
            WriteLog("PostAuthenticateRequest");
            //HttpContext.Current.Response.Write("PostAuthenticateRequest<br />");
        }

        void context_PostAcquireRequestState(object sender, EventArgs e)
        {
            WriteLog("PostAcquireRequestState");
            //HttpContext.Current.Response.Write("PostAcquireRequestState<br />");
        }

        void context_MapRequestHandler(object sender, EventArgs e)
        {
            WriteLog("MapRequestHandler");
            //HttpContext.Current.Response.Write("MapRequestHandler<br />");
        }

        void context_Disposed(object sender, EventArgs e)
        {
            WriteLog("Disposed");
            //HttpContext.Current.Response.Write("Disposed<br />");
        }

        void context_AuthorizeRequest(object sender, EventArgs e)
        {
            WriteLog("AuthorizeRequest");
            //HttpContext.Current.Response.Write("AuthorizeRequest<br />");
        }

        void context_AcquireRequestState(object sender, EventArgs e)
        {
            WriteLog("AcquireRequestState");
            //HttpContext.Current.Response.Write("AcquireRequestState<br />");
        }


        void context_PreSendRequestHeaders(object sender, EventArgs e)
        {
            WriteLog("PreSendRequestHeaders");
            //HttpContext.Current.Response.Write("PreSendRequestHeaders<br />");
        }

        void context_PreSendRequestContent(object sender, EventArgs e)
        {
            WriteLog("PreSendRequestContent");
            //HttpContext.Current.Response.Write("PreSendRequestContent<br />");
        }

        void context_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            WriteLog("PreRequestHandlerExecute");
            //HttpContext.Current.Response.Write("PreRequestHandlerExecute<br />");
        }

        void context_EndRequest(object sender, EventArgs e)
        {
            WriteLog("EndRequest");
            //HttpContext.Current.Response.Write("EndRequest<br />");
        }

        void context_BeginRequest(object sender, EventArgs e)
        {
            WriteLog("*******************************************************************************");
            HttpApplication app = sender as HttpApplication;
            WriteLog(app.Context.Request.Path);
            WriteLog("BeginRequest");
            //HttpContext.Current.Response.Write("BeginRequest<br />");
        }

        void context_AuthenticateRequest(object sender, EventArgs e)
        {
            WriteLog("AuthenticateRequest");
            //HttpContext.Current.Response.Write("AuthenticateRequest<br />");
        }
        #endregion

        public void OnLogRequest(Object source, EventArgs e)
        {
            //能夠在此處放置自定義日誌記錄邏輯
            WriteLog("OnLogRequest");
            //HttpContext.Current.Response.Write("OnLogRequest<br />");
        }

        public void context_PostMapRequestHandler(object sender, EventArgs e)
        {
            WriteLog("PostMapRequestHandler");
            //HttpContext.Current.Response.Write("PostMapRequestHandler<br />");
        }

        public void WriteLog(string message)
        {
            string path = @"d:\aspnet\httpmodule.txt";
            StreamWriter writer = null;
            if (!File.Exists(path))
            {
                writer = File.CreateText(path);
            }
            else
            {
                FileInfo info = new FileInfo(path);
                writer = info.AppendText();

            }
            writer.WriteLine(message);

            writer.Flush();
            writer.Close();
        }
    }

訂閱的事件實現中,將事件名稱保存到我本地D盤的一個文本文件中。服務器

代碼實現完畢了,下一步就是要代碼起做用了,很簡單,只須要在web.config中簡單配置就能夠了。配置中注意IIS7集成模式和IIS7經典模式(包括IIS6)的區別,配置以下:app

<!--IIS6或者IIS7經典模式-->
<system.web>
    <httpModules>
      <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
    </httpModules>
  </system.web>
<!--IIS7集成模式-->
<system.webServer>
    <modules>
      <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
    </modules>
</system.webServer>

如此一來,一個HttpModule及其配置工做就完成了,接下來,發佈網站到IIS或者直接在VS中運行,隨便訪問項目中的一個文件(任何文件類型均可以),個人項目中有一個WebForm2.aspx的頁面,我在瀏覽器中訪問這個頁面,發現頁面是空白的,由於頁面中我什麼都沒寫,上面的Module實現中,我把輸出所有放到本地D盤的一個文本文件中了,ok,打開那個文本文件。如圖:模塊化

image

咱們看到輸出內容,第2行是訪問的頁面地址,下面依次爲訂閱的事件輸出,咱們清楚的看到了事件的執行順序。

BeginRequest          		#發出信號表示建立任何給定的新請求。 此事件始終被引起,而且始終是請求處理期間發生的第一個事件
AuthenticateRequest         #發出信號表示配置的身份驗證機制已對當前請求進行了身份驗證。 訂閱 AuthenticateRequest 事件可確保在處理附加模塊或事件處理程序以前對請求進行身份驗證
PostAuthenticateRequest     #預訂 PostAuthenticateRequest 事件的功能能夠訪問由 PostAuthenticateRequest 處理的任何數據
AuthorizeRequest			#發出信號表示 ASP.NET 已對當前請求進行了受權。 訂閱 AuthorizeRequest 事件可確保在處理附加的模塊或事件處理程序以前對請求進行身份驗證和受權
PostAuthorizeRequest		#發出信號表示 ASP.NET 已對當前請求進行了受權。 訂閱 PostAuthorizeRequest 事件可確保在處理附加的模塊或處理程序以前對請求進行身份驗證和受權
ResolveRequestCache			#引起這個事件來決定是否可使用從輸出緩衝返回的內容來結束請求。這依賴於Web應用程序的輸出緩衝時怎樣設置的
PostResolveRequestCache		#在 ASP.NET 跳過當前事件處理程序的執行並容許緩存模塊知足來自緩存的請求時發生
MapRequestHandler			#ASP.NET 基礎結構使用 MapRequestHandler 事件來肯定用於當前請求的請求處理程序
PostMapRequestHandler		#在 ASP.NET 已將當前請求映射到相應的事件處理程序時發生
AcquireRequestState			#當 ASP.NET 獲取與當前請求關聯的當前狀態(如會話狀態)時發生
PostAcquireRequestState		#預訂 AcquireRequestState 事件的功能能夠訪問由 PostAcquireRequestState 處理的任何數據
PreRequestHandlerExecute	#在ASP.NET開始執行HTTP請求的處理程序以前引起這個事件。在這個事件以後,ASP.NET 把該請求轉發給適當的HTTP處理程序
PostRequestHandlerExecute   #在 ASP.NET 事件處理程序(例如,某頁或某個 XML Web service)執行完畢時發生
ReleaseRequestState			#在 ASP.NET 執行完全部請求事件處理程序後發生。 該事件將使狀態模塊保存當前狀態數據
PostReleaseRequestState		#在 ASP.NET 已完成全部請求事件處理程序的執行而且請求狀態數據已存儲時發生
UpdateRequestCache			#當 ASP.NET 執行完事件處理程序以使緩存模塊存儲將用於從緩存爲後續請求提供服務的響應時發生
PostUpdateRequestCache		#在 ASP.NET 完成緩存模塊的更新並存儲了用於從緩存中爲後續請求提供服務的響應後,發生此事件
OnLogRequest				#剛好在 ASP.NET 爲當前請求執行任何記錄以前發生,即便發生錯誤,也會引起 LogRequest 事件
PostLogRequest				#在 ASP.NET 處理完 LogRequest 事件的全部事件處理程序後發生
EndRequest					#在 ASP.NET 響應請求時做爲 HTTP 執行管線鏈中的最後一個事件發生
PreSendRequestContent		#剛好在 ASP.NET 向客戶端發送內容以前發生,可能發生屢次
PreSendRequestHeaders		#剛好在 ASP.NET 向客戶端發送 HTTP 標頭以前發生
RequestCompleted			#在任何託管模塊和處理程序執行後,它使模塊清理資源

 

訪問一個頁面的過程當中,依次觸發了23個事件,而HttpModule可訂閱的事件個數爲25個,觀察發現,Error和Disposed這兩個事件沒有觸發。Error事件在發生錯誤的狀況下執行,而Disposed事件,當咱們關閉剛纔打開的頁面,再到文本文件裏查看,發現Disposed事件出現了,因此Disposed在會話結束後觸發。

因爲HttpModule的個數能夠有多個,咱們能夠按照上面的方式定義HttpModule實現類,而後再web.config中增長配置項,就能夠實現多個HttpModule同時訂閱管道事件了。

 

介紹完HttpModule,那麼HttpHandler又是什麼呢,它又在什麼何時執行呢?接下來看一下HttpHandler。

HttpHandler

HttpHandler是HTTP請求的處理中心,真正地對客戶端請求的服務器頁面作出編譯和執行,並將處理事後的信息附加在HTTP請求信息流中再次返回到HttpModule中。 
HttpHandler與HttpModule不一樣,一旦定義了本身的HttpHandler類,那麼它對系統的HttpHandler的關係將是「覆蓋」關係。

HttpHandler是實IHttpHandler接口的類,IHttpHandler接口定義以下:

    public interface IHttpHandler
    {
        // 摘要: 
        //     獲取一個值,該值指示其餘請求是否可使用 System.Web.IHttpHandler 實例。
        //
        // 返回結果: 
        //     若是 System.Web.IHttpHandler 實例可再次使用,則爲 true;不然爲 false。
        bool IsReusable { get; }

        // 摘要: 
        //     經過實現 System.Web.IHttpHandler 接口的自定義 HttpHandler 啓用 HTTP Web 請求的處理。
        //
        // 參數: 
        //   context:
        //     System.Web.HttpContext 對象,它提供對用於爲 HTTP 請求提供服務的內部服務器對象(如 Request、Response、Session
        //     和 Server)的引用。
        void ProcessRequest(HttpContext context);
    }

接口中只有一個屬性和一個方法,因此實現一個HttpHandler也很簡單,下面實現一個簡單的HttpHandler,代碼以下:

public class MyIISHandler : IHttpHandler
    {
        /// <summary>
        /// 您將須要在網站的 Web.config 文件中配置此處理程序 
        /// 並向 IIS 註冊它,而後才能使用它。有關詳細信息,
        /// 請參見下面的連接: http://go.microsoft.com/?linkid=8101007
        /// </summary>
        #region IHttpHandler Members

        public bool IsReusable
        {
            // 若是沒法爲其餘請求重用託管處理程序,則返回 false。
            // 若是按請求保留某些狀態信息,則一般這將爲 false。
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            //在此處寫入您的處理程序實現。
            WriteLog("請求一個asox頁面");
        }

        #endregion
    }

上面我實現了一個很簡單的HttpHandler類,在ProcessRequest方法中,調用上面的HttpModule類中寫文本文件的方法,在文本文件中寫入「請求一個asox頁面」,沒錯,是一個asox頁面,我本身定義的文件格式,下面我會在web.config中添加配置項:

<!--IIS6或者IIS7經典模式-->
  <system.web>
    <httpHandlers>
      <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
    </httpHandlers>
  </system.web>
 
<!--IIS7集成模式-->
  <system.webServer>
    <handlers>
       <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
    </handlers>
  </system.webServer>

配置項屬性的解釋:

path:指定了須要調用處理程序的路徑和文件名(能夠包含通配符)。「*」、「*.aspx」、「booklist.aspx」、「test1.aspx,test2.aspx」、「*.asox」、「*.txt」。

verb:指定了處理程序支持的HTTP動做。*-支持全部的HTTP動做;「GET」-支持Get操做;「POST」-支持Post操做;「GET, POST」-支持兩種操做。

type:用名字空間、類名稱和程序集名稱的組合形式指定處理程序或處理程序工廠的實際類型。ASP.NET運行時首先搜索bin目錄中的DLL,接着在GAC中搜索。

 

接着,發佈站點到IIS。打開IIS,找到當前站點的「處理程序映射」,會發現多了剛剛配置的HttpHandler,如圖:

2014-04-15_181203

沒錯,關於對*.asox這種類型的文件,就能夠映射到上面建立的HttpHandler來進行處理,觀察其它條目發現,像*.aspx、*.ashx的處理程序是System.Web.UI.PageHandlerFactory和System.Web.UI.SimpleHandlerFactory這樣的工廠類型。沒錯,能夠指定處理程序爲一個HttpHandler,也能夠指定爲一個抽象工廠類型。先不說工廠類型的事兒,訪問一下網站中的asox頁面,看一下文本文件的記錄狀況。

image

起做用了,在HttpModule輸出的一堆信息中,夾雜着HttpHandler的輸出,固然這僅限於訪問asox類型的頁面,由於我只對路徑爲*.asox的文件格式作了設置,修改下配置文件,例如將path=」*.asox」改成path=」*.aspx」,那麼ASP.NET對*.aspx頁面原有的解析機制將被咱們設置的處理程序所覆蓋。

 

回到上面的輸出內容,咱們觀察HttpHandler輸出內容所在的位置,位於PreRequestHandlerExecute和PostRequestHandlerExecute這兩個事件之間,這與HttpApplication類中的管道事件的建立過程有關。

 

前面說到了,處理處理程序能夠指定爲一個工廠類型,下面,我就建立一個工廠類型的處理程序。

這裏所說的工廠類型的處理程序,就是實現了IHttpHandlerFactory接口的類,IHttpHandlerFactory接口定義以下:

public interface IHttpHandlerFactory
    {
        // 摘要: 
        //     返回實現 System.Web.IHttpHandler 接口的類的實例。
        //
        // 參數: 
        //   context:
        //     System.Web.HttpContext 類的實例,它提供對用於爲 HTTP 請求提供服務的內部服務器對象(如 Request、Response、Session
        //     和 Server)的引用。
        //
        //   requestType:
        //     客戶端使用的 HTTP 數據傳輸方法(GET 或 POST)。
        //
        //   url:
        //     所請求資源的 System.Web.HttpRequest.RawUrl。
        //
        //   pathTranslated:
        //     所請求資源的 System.Web.HttpRequest.PhysicalApplicationPath。
        //
        // 返回結果: 
        //     處理請求的新的 System.Web.IHttpHandler 對象。
        IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
        //
        // 摘要: 
        //     使工廠能夠重用現有的處理程序實例。
        //
        // 參數: 
        //   handler:
        //     要重用的 System.Web.IHttpHandler 對象。
        void ReleaseHandler(IHttpHandler handler);
    }

一樣很簡單,也是隻有兩個接口方法,下面是實現這個接口的工廠,代碼以下:

public class MyHttpHandlerFactory:IHttpHandlerFactory
{
        public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
        {
			//寫日誌
            WriteLog(string.Format("requestType:{0}|url:{1}|pathTranslated:{2}", requestType, url, pathTranslated));
            
			//生成一個IHttpHandler
			Type t = typeof(MyIISHandler);
            IHttpHandler handler =  Activator.CreateInstance(t) as IHttpHandler;
            return handler;
        }

        public void ReleaseHandler(IHttpHandler handler)
        {
        }
}

方法中,返回了前面建立的那個HttpHander類,依然調用記錄文本文件的方法輸出內容,方便觀察執行的實際和具體內容。配置文件改改成這個工廠類。

<add name="mycustomFactoryHandler" path="*.asox" verb="*" type="fengzheng.MyHttpHandlerFactory,handler_modules"/>

配置完畢後,訪問網站中的asox頁面,打開文本文件,內容以下:

image

咱們發現,工廠類中構造IHttpHandler接口的方法發生在HttpModule的MapRequestHandler以後,這一樣與HttpApplication類中構造管道事件有關。

 

說了這麼多,那麼,HttpModule和HttpHandler究竟能幹什麼呢?

HttpModule很經常使用的一個做用就是Url重寫,URLRewriter就是基於HttpModule實現的。

另外,有經過HttpHandler對圖片加水印,防止盜鏈的。

具體的能夠參考這篇文章

 

部署網站注意事項:

網站採用.net 4.0集成模式部署,集成模式是一種統一的請求處理管道,它將ASP.NET請求管道與IIS核心管道組合在一塊兒,這種模式可以提供更好的性能,可以實現配置和治理的模塊化,並且增長了使用託管代碼模塊擴展IIS時的靈活性。IIS經典模式與集成模式的區別

集成模式和經典模式的配置文件稍有不一樣,部署時須要注意針對不一樣的部署模式,修改配置文件。在vs2013中新建的web應用程序,默認的web.config內容以下:

<?xml version="1.0" encoding="utf-8"?>
<!--
  有關如何配置 ASP.NET 應用程序的詳細信息,請訪問
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
</configuration>
  • 按照經典模式部署,配置文件應該以下:
<?xml version="1.0" encoding="utf-8"?>
<!--
  有關如何配置 ASP.NET 應用程序的詳細信息,請訪問
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <httpModules>
      <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
    </httpModules>
    <httpHandlers>
      <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
    </httpHandlers>
  </system.web>
</configuration>

經典模式經測試老是出現以下錯誤,500.21 - 模塊沒法識別:

HTTP 錯誤 500.21 - Internal Server Error
處理程序「PageHandlerFactory-ISAPI-4.0_64bit」在其模塊列表中有一個錯誤模塊「IsapiModule」

至於錯誤緣由:目前還不是很清楚。

 

  • 按照集成模式部署,配置文件應該以下,將配置信息放到system.webServer節點之下
<?xml version="1.0" encoding="utf-8"?>
<!--
  有關如何配置 ASP.NET 應用程序的詳細信息,請訪問
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules>
      <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
    </modules>
    <handlers>
      <add name="mycustomhandler" path="*" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
    </handlers>
  </system.webServer>
</configuration>
相關文章
相關標籤/搜索