Refine:带有 PerSession InstanceContext 的 WCF Castle ActiveRecord SessionScope 实现
为了促进使用 AR 的 WCF 服务的延迟加载,我为 WCF 创建了“Session Scope PerRequest”解决方案。
如果您想在网站或 Web 服务中使用 ActiveRecord,您必须通过它在 Web 环境中运行的配置来告诉它。
它无论如何假设它将有一个 WCF 中不存在的 HttpContext.Current 。
因此,我们告诉 AR 使用我们自己的 AbstractThreadScopeInfo 实现,该实现还实现了 IWebThreadScopeInfo,以告诉它它与“每请求会话”模式兼容,该模式如今已成为“每会话会话”模式。
添加了一些对我遇到的延迟加载异常的修复 这里
public class WCFThreadScopeInfo : AbstractThreadScopeInfo, IWebThreadScopeInfo
{
public static readonly ILog Logger = LogManager.GetLogger (typeof (WCFThreadScopeInfo));
private readonly object _syncLock;
public WCFThreadScopeInfo()
{
_syncLock = new object ();
}
public new void RegisterScope (ISessionScope scope)
{
CurrentStack.Push (scope);
}
public new ISessionScope GetRegisteredScope ()
{
if (CurrentStack.Count == 0)
{
//Instead of returning a "null" stack as is in the original ActiveRecord code,
//instantiate a new one which adds itself to the stack immediately
lock (_syncLock)
{
if (CurrentStack.Count == 0)
{
new SessionScope ();
}
}
}
return CurrentStack.Peek () as ISessionScope;
}
public new void UnRegisterScope (ISessionScope scope)
{
if (GetRegisteredScope () != scope)
{
throw new ScopeMachineryException ("Tried to unregister a scope that is not the active one");
}
CurrentStack.Pop ();
}
public new bool HasInitializedScope
{
get { return GetRegisteredScope () != null; }
}
#region Overrides of AbstractThreadScopeInfo
public override Stack CurrentStack
{
[MethodImpl(MethodImplOptions.Synchronized)]
get
{
//Lets use the OperationContext instead of the HttpContext
OperationContext current = OperationContext.Current;
//Which offcourse can't be null
if (current == null)
throw new ScopeMachineryException ("Could not access OperationContext.Current");
//Get the first WCF StackContainer from the OperationContext or null
WCFStackContainer stackContainer = (WCFStackContainer)current.Extensions.FirstOrDefault (ex => ex is WCFStackContainer);
//If the previous statement didn't find any add a new one to the OperationContext
if (stackContainer == null)
{
Logger.Debug ("Creating new WCFStackContainer");
stackContainer = new WCFStackContainer ();
current.Extensions.Add (stackContainer);
}
//In the end return the stack in the container
return stackContainer.Stack;
}
}
#endregion
}
正如你在上面看到的,我们需要一个可以添加到当前OperationContext.Extensions中的WCFStackContainer。为了促进这一点,它需要实现 IExtension。请参阅此处:
public class WCFStackContainer : IExtension<OperationContext>
{
private Stack _stack;
public Stack Stack
{
get { return _stack; }
set { _stack = value; }
}
#region Implementation of IExtension<OperationContext>
public void Attach (OperationContext owner)
{
//On Attachment to the OperationContext create a new stack.
_stack = new Stack();
}
public void Detach (OperationContext owner)
{
_stack = null;
}
#endregion
}
现在我们为具有 HttpContext 的 Web 应用程序替换了 IsWebApp 功能。现在我们需要识别会话。
因此,如果我们有一个 HttpContext,我们会执行类似的操作
.ctor
{
HttpContext.Current.Items.Add ("ar.sessionscope", new SessionScope());
}
public void Dispose ()
{
try
{
SessionScope scope = HttpContext.Current.Items["ar.sessionscope"] as SessionScope;
if (scope != null)
{
scope.Dispose ();
}
}
catch (Exception ex)
{
CTMLogger.Fatal("Error " + "EndRequest: " + ex.Message, ex);
}
}
这在 WCF 中无法完成 所以您需要类似的操作:
public class ARSessionWCFExtension : IExtension<OperationContext>
{
private static readonly ILog Logger = LogManager.GetLogger (typeof(ARSessionWCFExtention));
private SessionScope _session;
#region IExtension<OperationContext> Members
public void Attach (OperationContext owner)
{
Logger.Debug ("Attachig ARSessionScope to WCFSession");
_session = new SessionScope();
}
public void Detach (OperationContext owner)
{
try
{
Logger.Debug ("Detaching ARSessionScope from WCFSession");
if (_session != null)
_session.Dispose ();
}
catch(Exception ex)
{
Logger.Fatal ("Exception: " + ex.Message + " Stacktrace: " + ex.StackTrace);
}
}
#endregion
}
我已经明白了这一点!还有更多:)
因此,在 WCFService 中,我们这样做:
.ctor
{
OperationContext.Current.Extensions.Add(new ARSessionWCFExtension());
}
在 IDisposable 实现中,我们添加了这个。我们很高兴:)
public void Dispose()
{
try
{
foreach (var extension in OperationContext.Current.Extensions.Where (ex => ex is ARSessionWCFExtention).ToList ())
OperationContext.Current.Extensions.Remove (extension);
Logger.Debug ("Session disposed ClinicID: " + _currentClinic.ClinicID);
}
catch (Exception ex)
{
Logger.Fatal ("Exception message: " + ex.Message + " StackTrace: " + ex.StackTrace);
}
}
但是它不起作用,OperationContext.Current 为空!。我在 MSDN 上进行了数小时毫无意义的搜索后发现没有 OperationContext 因为“OperationContext 仅在代码由客户端启动时才可用”
我现在通过将当前的 OperationContext 和 SessionID 存储在构造函数中解决了这个问题并在解构器中比较它们,然后使用它们来处理会话。
所以我现在有:
.ctor
{
_current = OperationContext.Current;
_sessionID = OperationContext.Current.SessionId;
OperationContext.Current.Extensions.Add (new ARSessionWCFExtention ());
}
public void Dispose()
{
try
{
OperationContext.Current = _current;
if (OperationContext.Current.SessionId != _sessionID)
throw new Exception("v weird!");
foreach (var extension in OperationContext.Current.Extensions.Where (ex => ex is ARSessionWCFExtention).ToList ())
OperationContext.Current.Extensions.Remove (extension);
Logger.Debug ("Session disposed ClinicID: " + _currentClinic.ClinicID);
}
catch (Exception ex)
{
Logger.Fatal ("Exception message: " + ex.Message + " StackTrace: " + ex.StackTrace);
}
}
有人知道我如何才能更强大/更好/更好地解决这个问题吗?
我尝试处理 OperationContext.Channel.Close 事件,但这不仅仅在客户端触发。每次完成调用后,OperationContext.OperationComplete 事件都会触发。这不是我们想要的,我们希望 AR 会话持续 WCFSession 的长度。
To facilitate lazy loading on our WCF Services that use AR I created a "Session Scope PerRequest" solution for WCF.
If you want to use ActiveRecord in a website or webservice you have to tell it via the configuration it is running in a web envirnoment.
How ever it assumes it will have a HttpContext.Current which doesn't exsist in WCF.
So we tell AR to use our own implementation of AbstractThreadScopeInfo which also implements IWebThreadScopeInfo to tell it it is compatible with the Session per Request pattern which becomes a Session per Session pattern today.
Added some fixes to lazy loading exception I ran into from here
public class WCFThreadScopeInfo : AbstractThreadScopeInfo, IWebThreadScopeInfo
{
public static readonly ILog Logger = LogManager.GetLogger (typeof (WCFThreadScopeInfo));
private readonly object _syncLock;
public WCFThreadScopeInfo()
{
_syncLock = new object ();
}
public new void RegisterScope (ISessionScope scope)
{
CurrentStack.Push (scope);
}
public new ISessionScope GetRegisteredScope ()
{
if (CurrentStack.Count == 0)
{
//Instead of returning a "null" stack as is in the original ActiveRecord code,
//instantiate a new one which adds itself to the stack immediately
lock (_syncLock)
{
if (CurrentStack.Count == 0)
{
new SessionScope ();
}
}
}
return CurrentStack.Peek () as ISessionScope;
}
public new void UnRegisterScope (ISessionScope scope)
{
if (GetRegisteredScope () != scope)
{
throw new ScopeMachineryException ("Tried to unregister a scope that is not the active one");
}
CurrentStack.Pop ();
}
public new bool HasInitializedScope
{
get { return GetRegisteredScope () != null; }
}
#region Overrides of AbstractThreadScopeInfo
public override Stack CurrentStack
{
[MethodImpl(MethodImplOptions.Synchronized)]
get
{
//Lets use the OperationContext instead of the HttpContext
OperationContext current = OperationContext.Current;
//Which offcourse can't be null
if (current == null)
throw new ScopeMachineryException ("Could not access OperationContext.Current");
//Get the first WCF StackContainer from the OperationContext or null
WCFStackContainer stackContainer = (WCFStackContainer)current.Extensions.FirstOrDefault (ex => ex is WCFStackContainer);
//If the previous statement didn't find any add a new one to the OperationContext
if (stackContainer == null)
{
Logger.Debug ("Creating new WCFStackContainer");
stackContainer = new WCFStackContainer ();
current.Extensions.Add (stackContainer);
}
//In the end return the stack in the container
return stackContainer.Stack;
}
}
#endregion
}
As you can see above we need a WCFStackContainer which can be added to the current OperationContext.Extensions. To facilitate this it needs to implement IExtension. See here:
public class WCFStackContainer : IExtension<OperationContext>
{
private Stack _stack;
public Stack Stack
{
get { return _stack; }
set { _stack = value; }
}
#region Implementation of IExtension<OperationContext>
public void Attach (OperationContext owner)
{
//On Attachment to the OperationContext create a new stack.
_stack = new Stack();
}
public void Detach (OperationContext owner)
{
_stack = null;
}
#endregion
}
So now we replaced the IsWebApp functionality for web applications that have a HttpContext. Now we need to identify the session.
So if we have a HttpContext we do something like this
.ctor
{
HttpContext.Current.Items.Add ("ar.sessionscope", new SessionScope());
}
public void Dispose ()
{
try
{
SessionScope scope = HttpContext.Current.Items["ar.sessionscope"] as SessionScope;
if (scope != null)
{
scope.Dispose ();
}
}
catch (Exception ex)
{
CTMLogger.Fatal("Error " + "EndRequest: " + ex.Message, ex);
}
}
This can't be done in WCF So you need something like:
public class ARSessionWCFExtension : IExtension<OperationContext>
{
private static readonly ILog Logger = LogManager.GetLogger (typeof(ARSessionWCFExtention));
private SessionScope _session;
#region IExtension<OperationContext> Members
public void Attach (OperationContext owner)
{
Logger.Debug ("Attachig ARSessionScope to WCFSession");
_session = new SessionScope();
}
public void Detach (OperationContext owner)
{
try
{
Logger.Debug ("Detaching ARSessionScope from WCFSession");
if (_session != null)
_session.Dispose ();
}
catch(Exception ex)
{
Logger.Fatal ("Exception: " + ex.Message + " Stacktrace: " + ex.StackTrace);
}
}
#endregion
}
I allready see the point ! A little more to come :)
So in the WCFService we do:
.ctor
{
OperationContext.Current.Extensions.Add(new ARSessionWCFExtension());
}
And here we go in the IDisposable implementation we add this. And we're happy :)
public void Dispose()
{
try
{
foreach (var extension in OperationContext.Current.Extensions.Where (ex => ex is ARSessionWCFExtention).ToList ())
OperationContext.Current.Extensions.Remove (extension);
Logger.Debug ("Session disposed ClinicID: " + _currentClinic.ClinicID);
}
catch (Exception ex)
{
Logger.Fatal ("Exception message: " + ex.Message + " StackTrace: " + ex.StackTrace);
}
}
But then it doesn't work OperationContext.Current is null!. I found out after hours of pointless searching on MSDN I found out that there's no OperationContext because "The OperationContext is only available when the code is initiated by the Client"
I've solved this now by storing the current OperationContext and SessionID in the constructor and and compairing them in the Deconsturctor and then using them to dispose of the session.
So I now have:
.ctor
{
_current = OperationContext.Current;
_sessionID = OperationContext.Current.SessionId;
OperationContext.Current.Extensions.Add (new ARSessionWCFExtention ());
}
public void Dispose()
{
try
{
OperationContext.Current = _current;
if (OperationContext.Current.SessionId != _sessionID)
throw new Exception("v weird!");
foreach (var extension in OperationContext.Current.Extensions.Where (ex => ex is ARSessionWCFExtention).ToList ())
OperationContext.Current.Extensions.Remove (extension);
Logger.Debug ("Session disposed ClinicID: " + _currentClinic.ClinicID);
}
catch (Exception ex)
{
Logger.Fatal ("Exception message: " + ex.Message + " StackTrace: " + ex.StackTrace);
}
}
Does anybody have any idea how I could solve this a little robuster/better/nicer?
I tried handling the OperationContext.Channel.Close event but that doesn't fire only at the client side. And the event OperationContext.OperationComplete fire's after every completed call. And that's not what we want we want the AR Session to last the length of a WCFSession.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论