【问题标题】:How to subscribe to Events of Dependency Injected Services如何订阅依赖注入服务的事件
【发布时间】:2020-11-27 06:53:54
【问题描述】:

我正在使用实体框架核心在我的 asp.net 核心应用程序中创建 crud 服务。我的 crud 服务具有事件和处理程序,例如 OnCreated、OnUpdated。

这些事件也应该能够调用其他 crud 服务。

如何做到这一点?

以下是我注入 CRUD 服务的方式:

services.AddTransient<IRoleManager, RoleManager>();
        services.AddTransient<IOfficeManager, OfficeManager>();
        services.AddTransient<IProjectManager, ProjectManager>();

这是我的 CRUD 服务之一的示例:

public class ProjectManager : IProjectManager
{
    protected AppDbContext Db;

    public ProjectManager(AppDbContext db)
    {
        Db = db;
    }

    public IQueryable<Project> GetProjects()
    {
        return Db.Projects;
    }

    public Project CreateProject (Project project)
    {
        Project Project = Db.Projects.Add(project).Entity;
        Db.SaveChanges();
        OnProjectCreated(Project);
        return Project;
    }

    public Project UpdateProject (Project project)
    {
        Project Project = Db.Projects.Update(project).Entity;
        Db.SaveChanges();
        OnProjectUpdated(Project);
        return Project;
    }

    public Project DeleteProject (Project project)
    {
        Db.Projects.Remove(project);
        Db.SaveChanges();
        OnProjectDeleted(project);
        return project;
    }

    public event EventHandler<ProjectManagerEventArgs> ProjectCreated;
    public event EventHandler<ProjectManagerEventArgs> ProjectUpdated;
    public event EventHandler<ProjectManagerEventArgs> ProjectDeleted;

    protected virtual void OnProjectCreated(Project project)
    {
        ProjectCreated?.Invoke(this, new ProjectManagerEventArgs(project));
    }

    protected virtual void OnProjectUpdated(Project project)
    {
        ProjectUpdated?.Invoke(this, new ProjectManagerEventArgs(project));
    }

    protected virtual void OnProjectDeleted(Project project)
    {
        ProjectDeleted?.Invoke(this, new ProjectManagerEventArgs(project));
    }
}

我可以这样订阅...

services.AddTransient(sp =>
    {
        AppDbContext dependency = sp.GetService<AppDbContext>();
        ProjectManager target = new ProjectManager(dependency);
        target.ProjectCreated += new ProjectManagerListener().OnProjectCreated;
        return (IProjectManager)target;
    });

但这种方式感觉非常草率,并且阻止我的 Handler/Listener 访问其他 CRUD 服务。

如何在 DI 中使用事件?

监听器代码

public class ProjectManagerListener
{
    private readonly IClaimManager ClaimManager;
    private readonly IRoleManager RoleManager;

    public ProjectManagerListener(IClaimManager claimManager, IRoleManager roleManager)
    {
        ClaimManager = claimManager;
        RoleManager = roleManager;
    }

    public void OnProjectCreated(object source, ProjectManagerEventArgs ProjectEventArgs)
    {
        foreach (Role role in RoleManager.GetRoles())
        {
            ClaimManager.CreateClaim(new ProjectRoleClaim() { ProjectId = ProjectEventArgs.Project.Id, RoleId = role.Id });
        }
    }
}

监听器注入代码

services.AddTransient(sp =>
        {
            AppDbContext dependency = sp.GetService<AppDbContext>();
            IClaimManager ClaimManager = sp.GetService<IClaimManager>();
            IRoleManager RoleManager = sp.GetService<IRoleManager>();
            ProjectManager target = new ProjectManager(dependency);
            target.ProjectCreated += new ProjectManagerListener(ClaimManager, RoleManager).OnProjectCreated;
            return (IProjectManager)target;
        });

【问题讨论】:

  • 您能否详细说明您最终要达到的目标是什么?为什么需要在服务IProjectManager的DI注册内创建ProjectManagerListener的实例?
  • 也许我想在创建项目时发送电子邮件。当我的任何 crud 项目被创建、更新、删除等时,我希望发生的任何事件。
  • 您已经有一个事件,只需在使用IProjectManager 的服务的构造函数中订阅它。还是我错过了什么?
  • 如图所示,我可以在 startup.cs 中以非常草率的方式订阅事件。但我可能需要访问其他 crud 服务。假设我正在构建一封电子邮件,并且需要从其他与传递对象相关的 CRUD 服务中收集更多信息。
  • 嗯,没用 提供的信息不多……你用了多线程吗?

标签: c# asp.net-core dependency-injection entity-framework-core


【解决方案1】:

完成这项工作的一种方法是为事件发布/侦听创建服务,而不是使用 C# 事件。然后,您可以将 DI 与这些服务一起使用。在其绝对粗略的形式中,您需要以下元素:

事件:

public class ProjectCreatedEvent
{
    public int ProjectId { get; }

    public ProjectCreatedEvent(int projectId)
    {
        ProjectId = projectId;
    }
}

这些很可能是不可变的 POCO。

听众:

public interface IListener
{
}

public interface IListener<T> : IListener
{
    void HandleMessage(T message);
}

public class ProjectCreatedEventListener : IListener<ProjectCreatedEvent>
{
    private readonly IClaimManager _claimManager;

    public ProjectCreatedEventListener(IClaimManager claimManager)
    {
        _claimManager = claimManager;
    }

    public void HandleMessage(ProjectCreatedEvent message)
    {
        _claimManager.CreateClaim(message.ProjectId);
    }
}

由于这些将在 DI 容器中注册,因此它们可以轻松注入依赖项。

调度员:

public interface IDispatcher
{
    void Publish<T>(T message);
}

public class Dispatcher : IDispatcher
{
    private readonly IEnumerable<IListener> _listeners;

    public Dispatcher(IEnumerable<IListener> listeners)
    {
        _listeners = listeners;
    }

    public void Publish<T>(T message)
    {
        foreach (var listener in _listeners.OfType<IListener<T>>())
        {
            listener.HandleMessage(message);
        }
    }
}

这只是注入所有侦听器,然后提供一种向其侦听器发布消息的方法。

然后您将发布如下事件:

public class ProjectManager : IProjectManager
{
    private readonly IDispatcher _dispatcher;

    public ProjectManager(IDispatcher dispatcher)
    {
        _dispatcher = dispatcher;
    }

    public void CreateProject(string name)
    {
        // Do the CRUD...
        Console.WriteLine($"Creating project '{name}'");
        _dispatcher.Publish(new ProjectCreatedEvent(43));
    }
}

这是一个在 DI 中连接的工作示例:

public static void Main(string[] args) 
{
    var serviceProvider = new ServiceCollection()
        .AddTransient<IProjectManager, ProjectManager>()
        .AddTransient<IClaimManager, ClaimManager>()
        .AddTransient<IListener<ProjectCreatedEvent>, ProjectCreatedEventListener>()
        .AddTransient<IListener>(sp => sp.GetService<IListener<ProjectCreatedEvent>>())
        .AddTransient<IDispatcher, Dispatcher>()
        .BuildServiceProvider();

    var service = serviceProvider.GetService<IProjectManager>();

    service.CreateProject("My project.");
}

结果如下:

【讨论】:

  • 这是一个可靠的答案 devNull,但它并没有完全解决我的问题。我认为这个问题是一个竞争条件,但很难说,因为浏览器刚刚关闭并且应用程序停止并且没有错误。 RoleManager 依赖于 ClaimManager、ProjectManager 和 OfficeManager。 ProjectManager 依赖于 ClaimManager 和 RoleManager。 OfficeManager 依赖于 ClaimManager 和 RoleManager。
【解决方案2】:

我使用 SQL 触发器解决了这个问题:

使用来自 Nuget 的 EntityFrameworkCore.Triggers

在我的模型中(示例角色):

static Role()
    {
        Triggers<Role>.Inserted += e =>
        {
            e.Context.Add(new RoleClaim() { RoleId = e.Entity.Id });
            IEnumerable<Office> offices = e.Context.Set<Office>();

            foreach(Office office in offices)
            {
                e.Context.Add(new OfficeRoleClaim() { Office = office, Role = e.Entity });
            }

            IEnumerable<Project> projects = e.Context.Set<Project>();

            foreach (Project project in projects)
            {
                e.Context.Add(new ProjectRoleClaim() { Project = project, Role = e.Entity });
            }

            e.Context.SaveChanges();
        };
    }

在 DbContext 中:

public override int SaveChanges()
    {
        return this.SaveChangesWithTriggers(base.SaveChanges, acceptAllChangesOnSuccess: true);
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-31
    • 2020-09-09
    • 2017-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-28
    相关资源
    最近更新 更多