这个问题是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对象的哈希码。我的假设是:
当测试应用程序仅生成一个客户端时,这是真的。然后一切都按预期工作。当两个(或更多)客户端同时运行时,会发生奇怪的事情。来自具有两个客户端的测试运行的Here is the log,代理和 gredrm 。
在每个客户端成功请求后, gredrm 似乎劫持了代理的OperationContext - 导致设置了错误的LoggedInPersonID。
发生了什么事,我做错了什么?
编辑:
我创建了一个重现行为的示例应用程序: https://github.com/ningenang/NHibernateInterceptorDebugging