閱讀28 返回首頁    go 阿裏雲 go 技術社區[雲棲]


WCF技術剖析之六:為什麼在基於ASP.NET應用寄宿(Hosting)下配置的BaseAddress無效

本篇文章來源於幾天前一個朋友向我谘詢的問題。問題是這樣的,他說他采用ASP.NET應用程序的方式對定義的WCF服務進行寄宿(Hosting),並使用配置的方式對服務的BaseAddress進行了設置,但是在創建ServiceHost的時候卻拋出InvalidOperationException,並提示相應Address Scheme的BaseAddress找不到。我意識到這可能和WCF中用於判斷服務寄宿方式的邏輯有關,於是我讓這位朋友將相同的服務寄宿代碼和配置遷移到GUI程序或者Console應用中,看看是否正常。結果如我所想,一切正常,個人覺得這應該是WCF的一個Bug。今天撰文與大家討論,看看大家對這個問題有何見解。

一、問題重現

問題很容易重現,假設我們通過ASP.NET應用對服務CalculatorService進行寄宿,為了簡單起見,我將服務契約和服務實現定義在一起。CalculatorService的定義如下麵的代碼片斷所示:

   1: using System.ServiceModel;
   2: namespace Artech.AspnetHostingDemo
   3: {
   4:     [ServiceContract(Namespace = "urn:artech.com")]
   5:     public class CalculatorService
   6:     {
   7:         [OperationContract]
   8:         public double Add(double x, double y) { return x + y; }
   9:     }
  10: }

下麵是服務寄宿相關的配置,在<host>/<baseAddresses>配置節中為服務添加了一個Scheme為http的BaseAddress:https://127.0.0.1:3721/services,那麼終結點的地址就可以定義為基於該BaseAddress的相對地址了:calculatorservice。

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <services>
   5:       <service name="Artech.AspnetHostingDemo.CalculatorService">
   6:         <host>
   7:           <baseAddresses>
   8:             <add baseAddress="https://127.0.0.1:3721/services"/>
   9:           </baseAddresses>
  10:         </host>
  11:         <endpoint address="calculatorservice" binding="wsHttpBinding" contract="Artech.AspnetHostingDemo.CalculatorService"/>
  12:       </service>
  13:     </services>
  14:   </system.serviceModel>
  15:   <system.web>
  16:     <compilation debug="true"/>
  17:   </system.web>
  18: </configuration>

我們把服務寄宿的代碼定義在一個Web Page的Load事件中。但程序執行到到創建ServiceHost的時候,拋出如下圖所示的InvalidOperationException異常。

image

 

下麵是錯誤信息和異常的StackTrace:

   1: Could not find a base address that matches scheme http for the endpoint with binding WSHttpBinding. Registered base address schemes are [].

 

   1: at System.ServiceModel.ServiceHostBase.MakeAbsoluteUri(Uri relativeOrAbsoluteUri, Binding binding, UriSchemeKeyedCollection baseAddresses)   
   2: at System.ServiceModel.Description.ConfigLoader.LoadServiceDescription(ServiceHostBase host, ServiceDescription description, ServiceElement serviceElement, Action`1 addBaseAddress)   
   3: at System.ServiceModel.ServiceHostBase.LoadConfigurationSectionInternal(ConfigLoader configLoader, ServiceDescription description, ServiceElement serviceSection)   
   4: at System.ServiceModel.ServiceHostBase.LoadConfigurationSectionInternal(ConfigLoader configLoader, ServiceDescription description, String configurationName)   
   5: at System.ServiceModel.ServiceHostBase.ApplyConfiguration()   
   6: at System.ServiceModel.ServiceHostBase.InitializeDescription(UriSchemeKeyedCollection baseAddresses)   
   7: at System.ServiceModel.ServiceHost.InitializeDescription(Type serviceType, UriSchemeKeyedCollection baseAddresses)   
   8: at System.ServiceModel.ServiceHost..ctor(Type serviceType, Uri[] baseAddresses)   
   9: at Artech.AspnetHostingDemo._Default.Page_Load(Object sender, EventArgs e) in e:\WCF Projects\AspnetHostingDemo\AspnetHostingDemo\Default.aspx.cs:line 16   
  10: at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)   
  11: at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)   
  12: at System.Web.UI.Control.OnLoad(EventArgs e)   at System.Web.UI.Control.LoadRecursive()   
  13: at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

二、問題分析

通過上麵提供StackTrace,我們可以看到錯誤發生在WCF試圖將BaseAddress和RelativeAddress進行組合生成AbsoluteAddress的時候。從錯誤消息可以看出,在進行地址的組合時,由於沒有找到適合綁定類型(WsHttpBinding)Scheme(http)的BaseAddress,導致了異常的拋出。

要解答這個問題,首先要解釋一下WCF的BaseAddress在不同服務寄宿(Service Hosting)方式下的定義方式。對於WCF服務的自我寄宿(Self Hosting)或者采用Windows Service進行服務寄宿,我們可以通過代碼或者形如上麵的配置為服務指定一係列的BaseAddress(對於一個既定的URI Scheme,隻能由唯一的BaseAddress)。但是對於采用IIS或者WAS進行服務寄宿,我們需要為相應的服務定義一個.svc文件,我們通過訪問.svc文件的方式來調用相應的服務。對於後者,.svc文件得地址就是WCF服務的BaseAddress,所以WCF會忽略BaseAddress的配置。

那麼WCF采用怎樣的方式來判斷當前服務寄宿的方式是基於IIS呢,還是其他呢?答案是通過System.Web.Hosting.HostingEnvironment的靜態屬性IsHosted。對於ASP.NET有一定了解的人應該很清楚,在一個ASP.NET應用下,該屬性永遠返回為True。也就是說,WCF會把基於ASP.NET應用的服務寄宿,看成是基於IIS的服務寄宿,這顯然是不對的。

   1: public sealed class HostingEnvironment : MarshalByRefObject
   2: {     //其他成員
   3:     public static bool IsHosted { get; }
   4: }

WCF對BaseAddress配置的加載和添加的邏輯定義在ServiceHostBase的LoadHostConfig方法中,大致的邏輯如下麵的代碼所示:

   1: public abstract class ServiceHostBase : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
   2: {
   3:     [SecurityTreatAsSafe, SecurityCritical]
   4:     private void LoadHostConfig(ServiceElement serviceElement, ServiceHostBase host, Action<Uri> addBaseAddress)
   5:     {
   6:         HostElement element = serviceElement.Host; if (element != null)
   7:         {
   8:             if (!ServiceHostingEnvironment.IsHosted)
   9:             {                //BaseAddress配置加載與添加
  10:             }
  11:         }
  12:     }
  13: }

 

   1: public static class ServiceHostingEnvironment
   2: {
   3:     private static bool isHosted; internal static bool IsHosted { get { return isHosted; } }
   4:     internal static void EnsureInitialized()
   5:     {
   6:         if (hostingManager == null)
   7:         {
   8:             lock (ThisLock)
   9:             {
  10:                 if (hostingManager == null)
  11:                 {
  12:                     if (!HostingEnvironmentWrapper.IsHosted)
  13:                     {
  14:                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString("Hosting_ProcessNotExecutingUnderHostedContext", new object[] { "ServiceHostingEnvironment.EnsureServiceAvailable" })));
  15:                     }
  16:                     HostingManager manager = new HostingManager();
  17:                     HookADUnhandledExceptionEvent(); 
  18:                     Thread.MemoryBarrier(); 
  19:                     isSimpleApplicationHost = GetIsSimpleApplicationHost();
  20:                     hostingManager = manager; 
  21:                     isHosted = true;
  22:                 }
  23:             }
  24:         }
  25:     }
  26: }
  27:  
   1: internal static class HostingEnvironmentWrapper
   2: {
   3:     public static bool IsHosted
   4:     {
   5:         get { return HostingEnvironment.IsHosted; }
   6:     }
   7: }

其實這種情況也沒有什麼好的解決方案,不外乎就是避免通過配置的方式設置服務的BaseAddress,可以通過代碼的方式來設置。如下麵的代碼所示:

   1: namespace Artech.AspnetHostingDemo
   2: {
   3:     public partial class _Default : System.Web.UI.Page
   4:     {
   5:         private ServiceHost _serviceHost;
   6:         protected void Page_Load(object sender, EventArgs e)
   7:         {
   8:             this._serviceHost = new ServiceHost(typeof(CalculatorService), new Uri("https://127.0.0.1:3721/services")); 
   9:             this._serviceHost.Open();
  10:         }
  11:     }
  12: }

另一種方式就是采用絕對地址的方式定義終結點:

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <services>
   5:       <service name="Artech.AspnetHostingDemo.CalculatorService">
   6:         <endpoint address="https://127.0.0.1:3721/services/calculatorservice" binding="wsHttpBinding" contract="Artech.AspnetHostingDemo.CalculatorService"/>
   7:       </service>
   8:     </services>
   9:   </system.serviceModel>
  10:   <system.web>
  11:     <compilation debug="true"/>
  12:   </system.web>
  13: </configuration>

 


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 14:34:16

  上一篇:go  WCF技術剖析之五:利用ASP.NET兼容模式創建支持會話(Session)的WCF服務
  下一篇:go  嵌入式X86、ARM、MIPS架構對比 arm芯片將成主流?