这可能是一个利基问题,但也许有人可以帮助我。我将我的Web服务从ASMX移植到WCF,但是我完全基于Castle ActiveRecord。为了确保我的配置中没有某些奇怪的问题,我使用NuGet中所有最新的Castle和NHibernate库从头开始构建了一个独立的repro。
我要做的第一件事就是在Application_Start
中初始化ActiveRecord。通常我会使用web.config
实例,但这可能无关紧要:
protected void Application_Start(object sender, EventArgs e)
{
//NHibernate.Driver.OracleClientDriver
IDictionary<string, string> properties = new Dictionary<string, string>();
properties.Add("connection.driver_class", "NHibernate.Driver.OracleClientDriver");
properties.Add("dialect", "NHibernate.Dialect.Oracle10gDialect");
properties.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
properties.Add("connection.connection_string", "user Id=xxx;password=xxx;server=localhost;persist security info=True");
properties.Add("proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle");
InPlaceConfigurationSource source = new InPlaceConfigurationSource();
source.IsRunningInWebApp = true;
source.ThreadScopeInfoImplementation = typeof(Castle.ActiveRecord.Framework.Scopes.HybridWebThreadScopeInfo);
source.Add(typeof(ActiveRecordBase), properties);
ActiveRecordStarter.Initialize(source, typeof(Task), typeof(Project));
}
注意我也在使用HybridWebThreadScopeInfo
实现,因为HttpContext.Current
在WCF中将为null。
接下来,我实现了我的网络服务:
public class Service1 : IService1
{
public string GetData(int value)
{
Project p;
p = Project.Find(123M);
var count = p.Tasks.Count(); //Force count query
return p.GoldDate.ToString();
}
}
当我致电Project.Find()
时,它运作正常。接下来,我调用p.Tasks.Count()
将强制执行新查询,因为Tasks
属性是惰性的。当我这样做时,我得到了例外:
初始化[NHTest.Project#123] - 无法初始化角色集合:NHTest.Project.Tasks,没有关闭会话或会话
发生这种情况的原因是因为没有会话范围。我想内部ActiveRecordBase
方法会创建一个会话,如果它不存在或什么的。现在,我可以手动创建一个:
public string GetData(int value)
{
Project p;
using (new SessionScope())
{
p = Project.Find(123M);
var count = p.Tasks.Count(); //Force count query
return p.GoldDate.ToString();
}
}
这将很有效。但是,我不想在我的所有代码中执行此操作,因为这在ASP.NET Web服务中完美运行。
那为什么它在ASP.NET中有用?
原因是作品是因为ActiveRecord附带了一个名为Castle.ActiveRecord.Framework.SessionScopeWebModule
的httpModule。此模块在每个HTTP请求之前运行,并在ActiveRecord中创建默认会话。但是,在WCF HTTP请求之前,此模块未被 调用。
ASP.NET兼容模式怎么样?
您可以使用以下方式启用ASP.NET兼容模式:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" ... />
这也将解决问题,并提供对WCF内HTTP请求管道的其他访问。这将是一个解决方案。但是,虽然它适用于Visual Studio测试Web服务器,但我从未能够在IIS7上使用兼容模式。另外,我觉得最好的设计是完全在WCF基础设施中工作。
我的问题:
Castle ActiveRecord是否提供在WCF请求中创建会话范围的功能?如果是这样,这是如何配置的?
答案 0 :(得分:1)
在浏览Castle ActiveRecord源代码后,我发现答案是否定的; ActiveRecord 不具有任何类型的本机WCF支持。但是,处理并不是非常复杂,因为ActiveRecord会处理所有混乱的NHibernate细节,例如配置和会话工厂。实际上,为每个WCF操作自动创建会话范围几乎与实现自定义IDisplayMessageInspector
一样简单。此消息检查器只需在收到请求时新建SessionScope
对象,并在请求结束时将其丢弃:
public class MyInspector : IDispatchMessageInspector
{
public MyInspector()
{
}
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
return new SessionScope();
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
SessionScope scope = correlationState as SessionScope;
if (scope != null)
{
scope.Flush();
scope.Dispose();
}
}
}
SessionScope
的构造函数将设置当前会话范围,然后可以在该线程中的任何其他位置访问该范围。这使得延迟加载和传递数据读取器等工作变得简单。您还会注意到,我在请求期间使用correlationState
状态跟踪SessionScope
的引用,类似于SessionScopeWebModule
如何使用HttpContext项集合。
此IDispatchInspector
可以在web.config中配置,或通过Attribute
类附加到WCF服务:
public class MyServiceBehaviorAttribute : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cDispatcher in serviceHostBase.ChannelDispatchers)
foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
eDispatcher.DispatchRuntime.MessageInspectors.Add(new MyInspector());
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
然后当然用[MyServiceBehavior]
标记服务类。
Ack主题!
啊,是的,这一切都可能在理论上有效,但实际上,IDispatchMessageInspector
可能不会与操作在同一个线程中运行。要解决此问题,我们需要实现一个ActiveRecord IWebThreadScopeInfo
类。当ActiveRecord查找当前会话范围时,将使用此对象。这通常在使用ASP.NET时使用HttpContext
键控,但HybridThreadScopeInfo
也能够按线程键入每个会话范围堆栈。这两个都不适用于WCF,所以我们需要一个支持ASP.NET(当HttpContext.Current
存在时),WCF(当OperationContext.Current
存在时)和每个线程的超级duper-hybrid实现。 '只是在某个任意线程中运行。
我的实施(未经过高度测试)基于HybridThreadScopeInfo implementation并进行了一些调整:
public class WcfThreadScopeInfo : AbstractThreadScopeInfo, IWebThreadScopeInfo
{
const string ActiveRecordCurrentStack = "activerecord.currentstack";
[ThreadStatic]
static Stack stack;
public override Stack CurrentStack
{
[MethodImpl(MethodImplOptions.Synchronized)]
get
{
Stack contextstack;
if (HttpContext.Current != null) //We're running in an ASP.NET context
{
contextstack = HttpContext.Current.Items[ActiveRecordCurrentStack] as Stack;
if (contextstack == null)
{
contextstack = new Stack();
HttpContext.Current.Items[ActiveRecordCurrentStack] = contextstack;
}
return contextstack;
}
if (OperationContext.Current != null) //We're running in a WCF context
{
NHibernateContextManager ctxMgr = OperationContext.Current.InstanceContext.Extensions.Find<NHibernateContextManager>();
if (ctxMgr == null)
{
ctxMgr = new NHibernateContextManager();
OperationContext.Current.InstanceContext.Extensions.Add(ctxMgr);
}
return ctxMgr.ContextStack;
}
//Working in some random thread
if (stack == null)
{
stack = new Stack();
}
return stack;
}
}
}
您还需要一个IExtension<InstanceContext>
派生类,WCF用它来在操作上下文中存储数据:
public class NHibernateContextManager : IExtension<InstanceContext>
{
public Stack ContextStack { get; private set; }
public NHibernateContextManager()
{
this.ContextStack = new Stack();
}
public void Attach(InstanceContext owner)
{
}
public void Detach(InstanceContext owner)
{
}
}
然后,您可以通过在IWebThreadScopeInfo
配置节点或web.config
属性上设置threadinfotype
属性,将ActiveRecord配置为在<activerecord>
中使用此ThreadScopeInfoImplementation
类如果您使用的是InPlaceConfigurationSource
对象。
希望这有助于某人!