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

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

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

因此,我试图按呼叫(异步,wcf生活方式)进行注册,但是由于这是Windows服务,因此无法关闭并且容器的作用域不正确。

在这种情况下,我该怎么做才能使其正常工作?

c# - Windows Service中托管的WCF中的IoC和每个调用的容器生存时间-LMLPHP

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

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;

最佳答案

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


这是一个坏主意。集成页面states


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


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

环境状态意味着您以某种可变状态存储数据,该状态可用于特定上下文。以下是几个选项:


全局静态。这些字段可由应用程序中的所有线程和请求访问。这不是很适合您的方案。
线程静态。从单个线程访问该字段的任何地方,都将返回相同的值,但其他线程将获得自己的值。由于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值。

09-25 17:54