【问题标题】:IoC in WCF hosted in Windows Service and container lifetime per call托管在 Windows 服务中的 WCF 中的 IoC 和每次调用的容器生命周期
【发布时间】:2018-09-29 21:21:37
【问题描述】:

我有一些客户端(Web 和桌面应用程序)必须连接到一些服务,这些服务是托管在 Windows 服务中的 WCF 服务。这些服务必须从一个或多个数据库中获取数据,但由客户端决定从哪个数据库中可以定义一个或多个相同类型的数据库。

我在服务中设置了数据上下文、存储库、业务管理器和服务实现。 Datacontext 由 SimpleInjector 容器注入(我也尝试使用 Unity),当然,容器中的注册发生在创建 ServiceHosts 之前,它们是在上下文模式 Single 下创建的(也尝试每次调用和每次会话)。

我编写了 IDispatchMessageInspector 的实现,它将拦截所有 SOAP 消息并读取消息头并根据消息头中的值设置数据上下文的数据库连接字符串。 但这会导致问题,因为它不是“线程安全的”,或者至少当一个调用尚未完成时,下一个调用可能会将另一个连接字符串设置为相同的数据上下文,从而把它搞砸。

所以,我正在尝试根据调用(异步、wcf 生活方式)注册它,但因为这是 Windows 服务,它不会关闭并且容器的范围不正确。

在这种情况下我可以做些什么来让它发挥作用?

容器创建和服务主机启动:

var container = new Container();
//container.Options.DefaultLifestyle = new AsyncScopedLifestyle();
DIContainer.SetDI(container);

serviceHosts.Add(new ServiceHost(typeof(LoginService)));
serviceHosts.Add(new ServiceHost(typeof(IdentityService)));

foreach (var serviceHost in serviceHosts)
{
    serviceHost.Open();
}

DbContext 的注册:

container.RegisterInstance<CSI.AuthServices.DataAccess.EF.Interfaces.ISecurityContext>(new CSI.AuthServices.DataAccess.EF.SecurityContext());

用于读取 SOAP 消息头并设置数据库连接字符串的拦截器:

ISecurityContext securityContext = m_Container.GetInstance<ISecurityContext>();
var sqlConn = new SqlConnectionStringBuilder
{
    DataSource = @"DEV_TEST_SERVER\SQL2017",
    InitialCatalog = "COMMON",
    IntegratedSecurity = true,
    ConnectTimeout = 30
};
securityContext.Database.Connection.ConnectionString = sqlConn.ConnectionString;

【问题讨论】:

    标签: c# wcf unity-container soa simple-injector


    【解决方案1】:

    在上下文模式 Single 中创建的 ServiceHosts

    这是个坏主意。整合页面states:

    提示:对所有 WCF 服务使用 InstanceContextMode.PerCall。这可以防止由于 WCF 服务超过单个请求而导致的任何难以检测的问题。

    在将上下文数据(例如受请求影响的连接字符串)提供给应用程序的某些部分时,有两种选择。您可以使用 环境状态,也可以使用 流动 对象图的数据。

    环境状态意味着您将数据存储在某些可用于特定上下文的可变状态中。这里有几个选项:

    • 全局静态状态。应用程序中的所有线程和请求都可以访问这些字段。这不太适合您的场景。
    • 线程静态状态。从单个线程访问该字段的任何地方,都会返回相同的值,但其他线程会获得自己的值。由于 WCF 请求可以在多个线程上异步执行,因此此选项也不适合。
    • 异步范围的状态。这允许单个逻辑异步操作流使用相同的数据来限定范围。数据在该范围内随处可用。此选项最适合您的需求。

    注意:对于这个答案,我假设 ISecurityContext 定义如下:

    public interface ISecurityContext
    {
        public Database Database { get; }
    }
    

    使用第三个选项,您可以按如下方式实现您的SecurityContext:

    public sealed class SecurityContext : ISecurityContext
    {
        private static readonly AsyncLocal<Database> db =
            new AsyncLocal<Database>();
    
        Database ISecurityContext.Database => db.Value;
    
        internal void SetDatabase(Database database) => db.Value = database;
    }
    

    由于使用了System.Threading.AsyncLocal,您可以在整个应用程序中重复使用同一个SecurityContext 实例,注册为Singleton。

    在您的IDispatchMessageInspector 中,您可以通过调用SecurityContext.SetDatabase(db) 来设置数据库。

    另一个选项是使用对象图来流动数据。在这种情况下,您将可变状态存储为上下文类中的私有字段(例如您的SecurityContext 并将它们注册为Scoped。这样您可以在请求开始时设置它们的值,并且这些值可以在任何地方重用请求,其中ISecuriryContext 被注入,而另一个请求获得不同的SecurityContext 实例。

    您可以将SecurityContext 更改为以下内容:

    public sealed class SecurityContext : ISecurityContext
    {
        // Just get/set with a private backing field. No ambient state
        public Database Database { get; set; }
    }
    

    如下注册:

    container.Register<SecurityContext>(Lifestyle.Scoped);
    container.Register<ISecurityContext, SecurityContext>(Lifestyle.Scoped);
    

    在您的IDispatchMessageInspector 中,您可以解析SecurityContext 并设置Database:

    container.GetInstance<SecurityContext>().Database = db;
    

    您的应用程序的其余部分可以简单地依赖ISecurityContext 并检索其Database 值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-13
      • 2013-04-25
      相关资源
      最近更新 更多