本篇文章来源于几天前一个朋友向我咨询的问题。问题是这样的,他说他采用ASP.NET应用程序的方式对定义的WCF服务进行寄宿(Hosting),并使用配置的方式对服务的BaseAddress进行了设置,但是在创建ServiceHost的时候却抛出InvalidOperationException,并提示相应Address Scheme的BaseAddress找不到。我意识到这可能和WCF中用于判断服务寄宿方式的逻辑有关,于是我让这位朋友将相同的服务寄宿代码和配置迁移到GUI程序或者Console应用中,看看是否正常。结果如我所想,一切正常,个人觉得这应该是WCF的一个Bug。今天撰文与大家讨论,看看大家对这个问题有何见解。
一、问题重现
问题很容易重现,假设我们通过ASP.NET应用对服务CalculatorService进行寄宿,为了简单起见,我将服务契约和服务实现定义在一起。CalculatorService的定义如下面的代码片断所示:
<style type="text/css">.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } </style>
1: using System.ServiceModel;<!--CRLF-->
2: namespace Artech.AspnetHostingDemo<!--CRLF-->
3: {
<!--CRLF-->
4: [ServiceContract(Namespace = "urn:artech.com")]<!--CRLF-->
5: public class CalculatorService<!--CRLF-->
6: {
<!--CRLF-->
7: [OperationContract]
<!--CRLF-->
8: public double Add(double x, double y) { return x + y; }<!--CRLF-->
9: }
<!--CRLF-->
10: }
<!--CRLF-->
下面是服务寄宿相关的配置,在<host>/<baseAddresses>配置节中为服务添加了一个Scheme为http的BaseAddress:http://127.0.0.1:3721/services,那么终结点的地址就可以定义为基于该BaseAddress的相对地址了:calculatorservice。
1: <?xml version="1.0"?><!--CRLF-->
2: <configuration><!--CRLF-->
3: <system.serviceModel><!--CRLF-->
4: <services><!--CRLF-->
5: <service name="Artech.AspnetHostingDemo.CalculatorService"><!--CRLF-->
6: <host><!--CRLF-->
7: <baseAddresses><!--CRLF-->
8: <add baseAddress="http://127.0.0.1:3721/services"/><!--CRLF-->
9: </baseAddresses><!--CRLF-->
10: </host><!--CRLF-->
11: <endpoint address="calculatorservice" binding="wsHttpBinding" contract="Artech.AspnetHostingDemo.CalculatorService"/><!--CRLF-->
12: </service><!--CRLF-->
13: </services><!--CRLF-->
14: </system.serviceModel><!--CRLF-->
15: <system.web><!--CRLF-->
16: <compilation debug="true"/><!--CRLF-->
17: </system.web><!--CRLF-->
18: </configuration><!--CRLF-->
我们把服务寄宿的代码定义在一个Web Page的Load事件中。但程序执行到到创建ServiceHost的时候,抛出如下图所示的InvalidOperationException异常。
下面是错误信息和异常的StackTrace:
<style type="text/css">.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } </style> 1: Could not find a base address that matches scheme http for the endpoint with binding WSHttpBinding. Registered base address schemes are [].
<!--CRLF-->
1: at System.ServiceModel.ServiceHostBase.MakeAbsoluteUri(Uri relativeOrAbsoluteUri, Binding binding, UriSchemeKeyedCollection baseAddresses)
<!--CRLF-->
2: at System.ServiceModel.Description.ConfigLoader.LoadServiceDescription(ServiceHostBase host, ServiceDescription description, ServiceElement serviceElement, Action`1 addBaseAddress)
<!--CRLF-->
3: at System.ServiceModel.ServiceHostBase.LoadConfigurationSectionInternal(ConfigLoader configLoader, ServiceDescription description, ServiceElement serviceSection)
<!--CRLF-->
4: at System.ServiceModel.ServiceHostBase.LoadConfigurationSectionInternal(ConfigLoader configLoader, ServiceDescription description, String configurationName)
<!--CRLF-->
5: at System.ServiceModel.ServiceHostBase.ApplyConfiguration()
<!--CRLF-->
6: at System.ServiceModel.ServiceHostBase.InitializeDescription(UriSchemeKeyedCollection baseAddresses)
<!--CRLF-->
7: at System.ServiceModel.ServiceHost.InitializeDescription(Type serviceType, UriSchemeKeyedCollection baseAddresses)
<!--CRLF-->
8: at System.ServiceModel.ServiceHost..ctor(Type serviceType, Uri[] baseAddresses)
<!--CRLF-->
9: at Artech.AspnetHostingDemo._Default.Page_Load(Object sender, EventArgs e) in e:\WCF Projects\AspnetHostingDemo\AspnetHostingDemo\Default.aspx.cs:line 16
<!--CRLF-->
10: at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
<!--CRLF-->
11: at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
<!--CRLF-->
12: at System.Web.UI.Control.OnLoad(EventArgs e) at System.Web.UI.Control.LoadRecursive()
<!--CRLF-->
13: at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
<!--CRLF-->
二、问题分析
通过上面提供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<!--CRLF-->
2: { //其他成员<!--CRLF-->
3: public static bool IsHosted { get; }<!--CRLF-->
4: }
<!--CRLF-->
WCF对BaseAddress配置的加载和添加的逻辑定义在ServiceHostBase的LoadHostConfig方法中,大致的逻辑如下面的代码所示:
<style type="text/css">.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } </style>1: public abstract class ServiceHostBase : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable<!--CRLF-->
2: {
<!--CRLF-->
3: [SecurityTreatAsSafe, SecurityCritical]
<!--CRLF-->
4: private void LoadHostConfig(ServiceElement serviceElement, ServiceHostBase host, Action<Uri> addBaseAddress)<!--CRLF-->
5: {
<!--CRLF-->
6: HostElement element = serviceElement.Host; if (element != null)<!--CRLF-->
7: {
<!--CRLF-->
8: if (!ServiceHostingEnvironment.IsHosted)<!--CRLF-->
9: { //BaseAddress配置加载与添加<!--CRLF-->
10: }
<!--CRLF-->
11: }
<!--CRLF-->
12: }
<!--CRLF-->
13: }
<!--CRLF-->
1: public static class ServiceHostingEnvironment<!--CRLF-->
2: {
<!--CRLF-->
3: private static bool isHosted; internal static bool IsHosted { get { return isHosted; } }<!--CRLF-->
4: internal static void EnsureInitialized()<!--CRLF-->
5: {
<!--CRLF-->
6: if (hostingManager == null)<!--CRLF-->
7: {
<!--CRLF-->
8: lock (ThisLock)<!--CRLF-->
9: {
<!--CRLF-->
10: if (hostingManager == null)<!--CRLF-->
11: {
<!--CRLF-->
12: if (!HostingEnvironmentWrapper.IsHosted)<!--CRLF-->
13: {
<!--CRLF-->
14: throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString("Hosting_ProcessNotExecutingUnderHostedContext", new object[] { "ServiceHostingEnvironment.EnsureServiceAvailable" })));<!--CRLF-->
15: }
<!--CRLF-->
16: HostingManager manager = new HostingManager();<!--CRLF-->
17: HookADUnhandledExceptionEvent();
<!--CRLF-->
18: Thread.MemoryBarrier();
<!--CRLF-->
19: isSimpleApplicationHost = GetIsSimpleApplicationHost();
<!--CRLF-->
20: hostingManager = manager;
<!--CRLF-->
21: isHosted = true;<!--CRLF-->
22: }
<!--CRLF-->
23: }
<!--CRLF-->
24: }
<!--CRLF-->
25: }
<!--CRLF-->
26: }
<!--CRLF-->
27:
<!--CRLF-->
1: internal static class HostingEnvironmentWrapper<!--CRLF-->
2: {
<!--CRLF-->
3: public static bool IsHosted<!--CRLF-->
4: {
<!--CRLF-->
5: get { return HostingEnvironment.IsHosted; }<!--CRLF-->
6: }
<!--CRLF-->