简体   繁体   中英

ASP.Net Core: DI and Events

I have a ASP.NET Core project in which I have one class DataDistaptcher that dispatches data based on an event implementation, and another class LocationFilter that is listening to this event to do some stuff based on the dispatched data.

Inside the DataDispatcher there is a method:

public void UpdateData(string path)
{   
        //Upload Data
        ...

        //Fire  event
        OnDataUpdated(EventArgs.Empty);

}

The LocationFilter constructor is like:

public LocationFilter(IDataDispatcher dispatcher)
{
        dispatcher.DataUpdated += new EventHandler((o,e) => UpdateData());
}

I'm using dependency injection in my project, and I want to update data at the start of the app, so I get DataDispatcher from IServiceProvider and update after app.UseMvc()

 // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {   
        services.AddSingleton<ILocationFilter, LocationFilter>();
        services.AddSingleton<IDataDispatcher, DataDispatcher>();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider provider)
    {   

        app.UseMvc();

        var dispatcher = provider.GetRequiredService<IDataDispatcher>();

        dispatcher.UpdateData("File path");
    }

Now I have a controller where LocationFilter is injected:

public Controller(ILocationFilter filter)
{
     //Filter dosen't contain data from dispatcher
}

If I move dispatcher update to Controller , the event fires and LocationFilter do have the data dispatched.

So I don't want to fire updates with every request, I want update only at start, so where should I put the dispatcher.update() method?

I think the best solution here is to implement the IHostedService interface and add your call to the StartAsync method. When you run an asp.net core app, the app takes all the IhostedServices from the DI container and executes the start method. When the application shuts down, the StopAsync method will be called. See this documentation for more info: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio

code is based on ASP.NET Core 3.1
You might change your LocationFilter like this:

public class LocationFilter
{
    public LocationFilter()
    { } //constructor without dataDispatcher

    public void SubscribeToDataDispatcher(DataDispatcher instance)
    {
        // attach your event
        instance.DataUpdated += new EventHandler((o,e) => UpdateData());
    }
}

Now your Hosted Service would look like this:

public class UpdateDataService : IHostedService
{
    private readonly DataDispatcher _dataDispatcher;
    private readonly LocationFilter _locationFilter;

    public UpdateDataService(DataDispatcher dataDispatcher, LocationFilter locationFilter)
    {
        _dataDispatcher = dataDispatcher;
        _locationFilter = locationFilter;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // connect the locationfilter to the data dispatcher and update the data.
        _locationFilter.SubscribeToDataDispatcher(_dataDispatcher);
        _dataDispatcher.UpdateData();
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        // detach your event?
        return Task.CompletedTask;
    }
}

As the last step, you have to add your hosted service to the dependency injection container in Startup.cs so that it is executed in your app.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHostedService<UpdateDataService>();
}

Extra note:

The code written above only demonstrates how to attach events on application startup. I do not recommend using events like this if you only fire the event on application startup. The better approach would be to turn your DataDispatcher into a hosted service and give it a reference to your LocationFilter instance. Something like this:

public interface IDataDispatcherEventListener
{
    void OnEvent(DataModel data);
}

public class LocationFilter : IDataDispatcherEventListener
{
    public void OnEvent(DataModel data)
    {
        // do whatever with your data
    }
}

public class DataDispatcher : IHostedService
{
    private readonly IEnumerable<IDataDispatcherEventListener> _eventListeners;

    public DataDispatcher(IEnumerable<IDataDispatcherEventListener> eventListeners)
    {
        _eventListeners = eventListeners;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        var workResult = DoWork();
        foreach(var listener in _eventListeners)
        {
            listener.OnEvent(workResult);
        }
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    { }
}

Now your work is done on startup and your location filter will be notified of the work. By implementing this interface, you remove the dependency between DataDispatcher and LocationFilter. By using an IEnumerable<IDataDispatcherEventListener> , the asp.net core environment will inject all instances of IDataDispatcherEventListener that you registered in the constructor, so if you want to notify a different service as well, you only need to register it in the DI container.

From what I see your singleton LocationFilter object is only created on the first call to your controller (its constructor to be specific). Which is why the data is missing.

While you are explicitly creating the dispatcher in the Configure startup method, the LocationFilter singleton object is not yet created. It is only created the "first" time it is requested (ie the first time your Controller's constructor is called - after which the same Singleton object would be used).

"Singleton lifetime services are created the first time they're requested (or when ConfigureServices is run and an instance is specified with the service registration)."

You can explicitly create your LocationFilter singleton object in your ConfigureServices method itself while adding the Singleton Service. Depending on how you are creating it, you may have to take care of disposing it as well.

Look at the complete documentation here .. Read the Singleton and Service lifetime sections.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM