OperationContext被另一个WCF客户端劫持

时间:2017-05-09 09:03:35

标签: wcf nhibernate unity-container

这个问题是this one的延续。

NHibernate.SessionFactory现在配置了ISessionFactory-scoped interceptor

//Implemented as a lazy singleton
public class SessionFactory
{
    [...]
    public static IInterceptor SessionFactoryInterceptor;
    [...]
    private void Init()
    {
        try
        {
            var configuration = new Configuration();

            if (SessionFactoryInterceptor != null)
                configuration.SetInterceptor(SessionFactoryInterceptor);

            configuration.Configure();

            sessionFactory = configuration.BuildSessionFactory();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex.Message);
            while (ex.InnerException != null)
            {
                Console.Error.WriteLine(ex.Message);
                ex = ex.InnerException;
            }
            throw;
        }
    }
    [...]
    private static readonly object _lock = new object();

    public ISession OpenSession()
    {
        lock (_lock)
        {
            var session = sessionFactory.OpenSession();

            [...]

            return session;
        }
    }

    [...]
}

拦截器实现使用Unity解析LoggedInPersonID:

public class LoggedInPersonIDInterceptor : NHibernate.EmptyInterceptor
{

    private static readonly ILog log = LogManager.GetLogger(nameof(LoggedInPersonIDInterceptor));

    public int? LoggedInPersonID
    {
        get
        {
            try
            {
                return UnityWrapper.Resolve<HasLoggedInPersonID>().LoggedInPersonID;
            }
            catch (Exception ex)
            {
                log.Warn(ex);
                return null;
            }
        }
    }

    public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState,
                                        string[] propertyNames, NHibernate.Type.IType[] types)
    {
        return SetLoggedInPersonID(currentState, propertyNames);
    }

    public override bool OnSave(object entity, object id, object[] currentState,
                          string[] propertyNames, NHibernate.Type.IType[] types)
    {
        return SetLoggedInPersonID(currentState, propertyNames);
    }

    protected bool SetLoggedInPersonID(object[] currentState, string[] propertyNames)
    {
        int max = propertyNames.Length;

        var lipid = LoggedInPersonID;

        for (int i = 0; i < max; i++)
        {
            if (propertyNames[i].ToLower() == "loggedinpersonid" && currentState[i] == null && lipid.HasValue)
            {
                currentState[i] = lipid;

                return true;
            }
        }

        return false;
    }
}

拦截器类型使用Andrew Oakley's UnityOperationContextLifetimeManager在WCF方法的开头注册。该服务在AspNetCompatibilityRequirementsMode.Required:

中运行
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
class ServiceInQuestion : AbstractServiceInQuestion, IServiceInQuestion
{
    protected override ILog Log => LogFactory.CreateLogger(nameof(ServiceInQuestion));
    [...]

    public ResponseHeaderType ServiceMethod(ServiceMethodType arg)
    {
        var header = new ResponseHeaderType();

        UnityWrapper.ConfiguredContainer.RegisterType<ILoginInformation, WcfLoginInformation>(
            new UnityOperationContextLifetimeManager(),
            new InjectionFactory(
                container =>
                {
                    Log.Debug($"Operation context hash: {OperationContext.Current.GetHashCode()} Creating WCFLoginInformation {arg.Header.UserName}");
                    return ValidateLogin(arg.Header, header);
                })
        );

        LoggedInPersonIDInterceptorUtil.RegisterPersonIdProviderWcfContext(() => new HasLoggedInPersonID {
            LoggedInPersonID = UnityWrapper.Resolve<ILoginInformation>().Person.ID,
            UserIdentification = UnityWrapper.Resolve<ILoginInformation>().Person.UserIdentification,
        }); 

        [...]

        Log.Debug($"OperationContext hash: {OperationContext.Current.GetHashCode()} {arg.Header.UserName}");

        [...]
    }

    [...]
}

以下是拦截器实用程序类的代码:

public static class LoggedInPersonIDInterceptorUtil
{

    //Called from Global.asax Application_Start
    public static void SetupSessionFactoryInterceptor()
    {
        SessionFactory.SessionFactoryInterceptor = new LoggedInPersonIDInterceptor();
    }

    [...]

    private static void RegisterPersonIdProvider(Func<HasLoggedInPersonID> loggedInPersonIDProvider, LifetimeManager lifeTimeManager)
    {
        UnityWrapper.ConfiguredContainer.RegisterType<HasLoggedInPersonID, HasLoggedInPersonID>(
            lifeTimeManager,
            new InjectionFactory(c =>
            {
                var value = loggedInPersonIDProvider();
                //For debugging purposes only
                log.Debug($"OperationContext hash: {OperationContext.Current?.GetHashCode()} Creating HasLoggedInPersonID ({value.LoggedInPersonID}, {value.UserIdentification})");

                return value;
            })
        );
    }


    [...]

    public static void RegisterPersonIdProviderWcfContext(Func<HasLoggedInPersonID> loggedInPersonIDProvider)
    {
        RegisterPersonIdProvider(loggedInPersonIDProvider, new UnityOperationContextLifetimeManager());
    }

    [...]
}

以下是UnityWrapper类的代码。 UnityInstanceProvider类来自Andrew Oakley的WCF终身经理库。

public static class UnityWrapper
{
    private static readonly Lazy<IUnityContainer> Container = new Lazy<IUnityContainer>(ValueFactory);

    [...]

    public static IUnityContainer ConfiguredContainer
    {
        get
        {
            //check if WCF context
            var context = OperationContext.Current;
            if (context != null)
            {
                var provider = context.EndpointDispatcher.DispatchRuntime.InstanceProvider as UnityInstanceProvider;
                if (provider != null)
                    return provider.GetContainer();

            }

            return (HttpContext.Current?.Application.GetContainer() ?? Container).Value;
        }
    }

    [...]

    public static T Resolve<T>()
    {
        var resolved = ConfiguredContainer.Resolve<T>();

        //This is for debugging purposes only
        var context = OperationContext.Current;
        if (context != null)
        {
            if (typeof(T).Name == nameof(HasLoggedInPersonID))
            {
                var value = resolved as HasLoggedInPersonID;
                log.Debug($"OperationContext hash: {context.GetHashCode()}. Resolving {nameof(HasLoggedInPersonID)} ({value.LoggedInPersonID}, {value.UserIdentification})");
            }

        }

        return resolved;
    }

    [...]

}

为了测试拦截器,我编写了一个简单的测试应用程序,可以生成可变数量的测试客户端。每个客户端调用一个Web服务方法,该方法使用随机数据更新数据库记录(以确保实体是脏的)。要更新的数据库记录对于每个客户端是不同的。每个客户端使用不同的应用程序用户帐户(不同的LoggedInPersonID)。每次更新后,都会检查数据库记录,看是否已由正确的LoggedInPersonID更新。每个客户端继续更新其数据库记录,直到该断言失败(由错误的LoggedInPersonID更新)。

作为一个完整性检查,我添加了Unity创建和解析对象的日志记录,包括WCF OperationContext.Current对象的哈希码。我的假设是:

  • 每个请求都应该获得一个新的OperationContext(不同的哈希)
  • 只应为每个请求创建一次WCFLoginInformation和HasLoggedInPersonID类型。

当测试应用程序仅生成一个客户端时,这是真的。然后一切都按预期工作。当两个(或更多)客户端同时运行时,会发生奇怪的事情。来自具有两个客户端的测试运行的Here is the log代理 gredrm

在每个客户端成功请求后, gredrm 似乎劫持了代理的OperationContext - 导致设置了错误的LoggedInPersonID。

发生了什么事,我做错了什么?

编辑:

我创建了一个重现行为的示例应用程序: https://github.com/ningenang/NHibernateInterceptorDebugging

0 个答案:

没有答案