程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF技術剖析之六

WCF技術剖析之六

編輯:關於.NET

為什麼在基於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:http://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="http://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異常。

下面是錯誤信息和異常的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("http://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="http://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>
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved