优化:使用PerSession InstanceContext为WCF构建ActiveRecord SessionScope实现

时间:2009-11-24 09:33:46

标签: c# wcf nhibernate castle-activerecord

嗨大家帮助我们在使用AR的WCF服务上进行延迟加载我为WCF创建了一个“Session Scope PerRequest”解决方案。

[编辑] 好的,所以我把问题放在最后:)所以请耐心等待,最后开始阅读。 :) [/编辑]

如果您想在网站或网络服务中使用ActiveRecord,您必须通过其在网络环境中运行的配置告诉它。

它怎么会假设它有一个HttpContext.Current,它不会在WCF中存在。

因此,我们告诉AR使用我们自己的AbstractThreadScopeInfo实现,它也实现了IWebThreadScopeInfo,告诉它它与每个请求的会话模式兼容,该模式今天成为每会话会话模式。

为我从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
}

如上所示,我们需要一个可以添加到当前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是null!。我在MSDN上发现了几个小时无意义的搜索后发现没有OperationContext,因为“只有在客户端启动代码时才能使用OperationContext”

我现在通过在构造函数中存储当前的OperationContext和SessionID并在Deconsturctor中对它们进行比较然后使用它们来处理会话来解决这个问题。

所以我现在有:

.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 Session能够持续WCFSession的长度。

谢谢:)

0 个答案:

没有答案