WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效
2015-07-10 14:24
671 查看
原文:WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效本篇文章来源于几天前一个朋友向我咨询的问题。问题是这样的,他说他采用ASP.NET应用程序的方式对定义的WCF服务进行寄宿(Hosting),并使用配置的方式对服务的BaseAddress进行了设置,但是在创建ServiceHost的时候却抛出InvalidOperationException,并提示相应Address Scheme的BaseAddress找不到。我意识到这可能和WCF中用于判断服务寄宿方式的逻辑有关,于是我让这位朋友将相同的服务寄宿代码和配置迁移到GUI程序或者Console应用中,看看是否正常。结果如我所想,一切正常,个人觉得这应该是WCF的一个Bug。今天撰文与大家讨论,看看大家对这个问题有何见解。
一、问题重现
问题很容易重现,假设我们通过ASP.NET应用对服务CalculatorService进行寄宿,为了简单起见,我将服务契约和服务实现定义在一起。CalculatorService的定义如下面的代码片断所示:
.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;}
[/code]
下面是服务寄宿相关的配置,在<host>/<baseAddresses>配置节中为服务添加了一个Scheme为http的BaseAddress:http://127.0.0.1:3721/services,那么终结点的地址就可以定义为基于该BaseAddress的相对地址了:calculatorservice。
[/code]
我们把服务寄宿的代码定义在一个Web Page的Load事件中。但程序执行到到创建ServiceHost的时候,抛出如下图所示的InvalidOperationException异常。
[/code]
[/code]
二、问题分析
通过上面提供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的服务寄宿,这显然是不对的。
[/code]
WCF对BaseAddress配置的加载和添加的逻辑定义在ServiceHostBase的LoadHostConfig方法中,大致的逻辑如下面的代码所示:
.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;}
[/code]
[/code]
[/code]
三、解决方式
其实这种情况也没有什么好的解决方案,不外乎就是避免通过配置的方式设置服务的BaseAddress,可以通过代码的方式来设置。如下面的代码所示:
.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;}
[/code]
另一种方式就是采用绝对地址的方式定义终结点:
[/code]
一、问题重现
问题很容易重现,假设我们通过ASP.NET应用对服务CalculatorService进行寄宿,为了简单起见,我将服务契约和服务实现定义在一起。CalculatorService的定义如下面的代码片断所示:
.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;}
[code] using System.ServiceModel;
namespace Artech.AspnetHostingDemo
{
[ServiceContract(Namespace = "urn:artech.com")]
public class CalculatorService
{
[OperationContract]
public double Add(double x, double y){ return x + y;}
}
}
[/code]
下面是服务寄宿相关的配置,在<host>/<baseAddresses>配置节中为服务添加了一个Scheme为http的BaseAddress:http://127.0.0.1:3721/services,那么终结点的地址就可以定义为基于该BaseAddress的相对地址了:calculatorservice。
[code] <?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service name="Artech.AspnetHostingDemo.CalculatorService">
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1:3721/services"/>
</baseAddresses>
</host>
<endpoint address="calculatorservice" binding="wsHttpBinding" contract="Artech.AspnetHostingDemo.CalculatorService"/>
</service>
</services>
</system.serviceModel>
<system.web>
<compilation debug="true"/>
</system.web>
</configuration>
[/code]
我们把服务寄宿的代码定义在一个Web Page的Load事件中。但程序执行到到创建ServiceHost的时候,抛出如下图所示的InvalidOperationException异常。
[code] Could not find a base address that matches scheme http for the endpoint with binding WSHttpBinding. Registered base address schemes are [].
[/code]
[code] at System.ServiceModel.ServiceHostBase.MakeAbsoluteUri(Uri relativeOrAbsoluteUri, Binding binding, UriSchemeKeyedCollection baseAddresses)
at System.ServiceModel.Description.ConfigLoader.LoadServiceDescription(ServiceHostBase host, ServiceDescription description, ServiceElement serviceElement, Action`1 addBaseAddress)
at System.ServiceModel.ServiceHostBase.LoadConfigurationSectionInternal(ConfigLoader configLoader, ServiceDescription description, ServiceElement serviceSection)
at System.ServiceModel.ServiceHostBase.LoadConfigurationSectionInternal(ConfigLoader configLoader, ServiceDescription description, String configurationName)
at System.ServiceModel.ServiceHostBase.ApplyConfiguration()
at System.ServiceModel.ServiceHostBase.InitializeDescription(UriSchemeKeyedCollection baseAddresses)
at System.ServiceModel.ServiceHost.InitializeDescription(Type serviceType, UriSchemeKeyedCollection baseAddresses)
at System.ServiceModel.ServiceHost..ctor(Type serviceType, Uri[] baseAddresses)
at Artech.AspnetHostingDemo._Default.Page_Load(Object sender, EventArgs e) in e:\WCF Projects\AspnetHostingDemo\AspnetHostingDemo\Default.aspx.cs:line 16
at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
at System.Web.UI.Control.OnLoad(EventArgs e) at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
[/code]
二、问题分析
通过上面提供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的服务寄宿,这显然是不对的。
[code] public sealed class HostingEnvironment : MarshalByRefObject
{ //其他成员
public static bool IsHosted{ get;}
}
[/code]
WCF对BaseAddress配置的加载和添加的逻辑定义在ServiceHostBase的LoadHostConfig方法中,大致的逻辑如下面的代码所示:
.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;}
[code] public abstract class ServiceHostBase : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
{
[SecurityTreatAsSafe, SecurityCritical]
private void LoadHostConfig(ServiceElement serviceElement, ServiceHostBase host, Action<Uri> addBaseAddress)
{
HostElement element = serviceElement.Host; if (element != null)
{
if (!ServiceHostingEnvironment.IsHosted)
{ //BaseAddress配置加载与添加
}
}
}
}
[/code]
[code] public static class ServiceHostingEnvironment
{
private static bool isHosted; internal static bool IsHosted{ get{ return isHosted;}}
internal static void EnsureInitialized()
{
if (hostingManager == null)
{
lock (ThisLock)
{
if (hostingManager == null)
{
if (!HostingEnvironmentWrapper.IsHosted)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString("Hosting_ProcessNotExecutingUnderHostedContext", new object[]{ "ServiceHostingEnvironment.EnsureServiceAvailable"})));
}
HostingManager manager = new HostingManager();
HookADUnhandledExceptionEvent();
Thread.MemoryBarrier();
isSimpleApplicationHost = GetIsSimpleApplicationHost();
hostingManager = manager;
isHosted = true;
}
}
}
}
}
[/code]
[code] internal static class HostingEnvironmentWrapper
{
public static bool IsHosted
{
get{ return HostingEnvironment.IsHosted;}
}
}
[/code]
三、解决方式
其实这种情况也没有什么好的解决方案,不外乎就是避免通过配置的方式设置服务的BaseAddress,可以通过代码的方式来设置。如下面的代码所示:
.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;}
[code]namespace Artech.AspnetHostingDemo
{
public partial class _Default : System.Web.UI.Page
{
private ServiceHost _serviceHost;
protected void Page_Load(object sender, EventArgs e)
{
this._serviceHost = new ServiceHost(typeof(CalculatorService), new Uri("http://127.0.0.1:3721/services"));
this._serviceHost.Open();
}
}
}
[/code]
另一种方式就是采用绝对地址的方式定义终结点:
[code] <?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service name="Artech.AspnetHostingDemo.CalculatorService">
<endpoint address="http://127.0.0.1:3721/services/calculatorservice" binding="wsHttpBinding" contract="Artech.AspnetHostingDemo.CalculatorService"/>
</service>
</services>
</system.serviceModel>
<system.web>
<compilation debug="true"/>
</system.web>
</configuration>
[/code]
相关文章推荐
- WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务
- asp.net 去重复验证
- 【Metasploit魔鬼训练营--实践笔记】4.2.3 SQL 注入漏洞探测
- ASP.NET性能优化之减少请求
- asp.net 接入微信公众平台,回复消息,解决乱码问题
- WCF技术剖析之二:再谈IIS与ASP.NET管道
- WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构
- ASP.NET控件组(lable1、label2、label3.....labeln)的赋值
- asp.net实现三层架构的例子
- 介绍“Razor”— ASP.NET的一个新视图引擎
- [每日刷题(2015/7/10)]简述ASP.NET的页面运行机制
- ASP.NET操作Word的IIS权限配置
- ASP.NET简单实现注销功能
- 怎样学好ASP.NET技术
- ASP.NET | WebForm 处理机
- ASP.NET简单实现注销功能
- asp.net实现三层架构的例子
- SharePoint 2013 表单认证使用ASP.Net配置工具添加用户
- spring mvc + JasperReports
- Asp.net mvc5 系列笔记