76
votes

My understanding is that when using the built in the dependency injection, a .NET Core console app will require you to create and manage all scopes yourself whereas a ASP.NET Core app will create and manage the HttpRequest scope by default through defined middleware(s).

With ASP.NET Core, you can optionally create and manage your own scopes that by calling CreateScope() for when you need services that live outside of a HttpRequest.

It is clear that calling IServiceScopeFactory.CreateScope() will create a new IServiceScope every time; however, does calling the IServiceProvider.CreateScope() extension method also create a new IServiceScope every time?

Basically, is there a meaningful difference between the following ways to create scope in both ASP.NET Core and .NET Core console apps:

public class Foo()
{
    public Foo(IServiceProvider serviceProvider)
    {
        using(var scope = serviceProvider.CreateScope())
        {   
            scope.ServiceProvider.GetServices<>();           
        }
    }
}

and

public class Bar()
{
    public Bar(IServiceScopeFactory scopeFactory)
    {
        using(var scope = scopeFactory.CreateScope())
        {   
            scope.ServiceProvider.GetServices<>();           
        }
    }
}
2
IServiceProvider.CreateScope will resolve IServiceScopeFactory and call CreateScope on it. So functionally both methods are identical, since one calls the other.Evk
One line of code less yea. fwiw: You shouldn't ever have to inject neither scope factory nor service provider into your classes, except for a few rare infrastructure casesTseng
That makes sense--to avoid the service locator pattern. In my case, the injection is for a IHostedService/BackgroundService which requires it's own scope separate from ASP.NET default. It would be just a single injection of the Service and all dependencies would be resolved from the registrations in StartUp.cs. I was struggling with injecting the ApplicationDbContext (scoped going into singleton = exception). Solved this by going with registring a singleton contextFactory. Any thoughts on that approach? Is it right?Connie King
CreateScope also is used for any root application containers like webhost service provider.Root application providers and all objects it create will live for the life of the application.If we resolve scoped or transient services from root container instance they will be created as singletons.Instead by using scope we can ensure that their intended lifetime is respected. Example var host = CreateWebHostBuilder(args).Build(); using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var ds = services.GetService<CatalogDataSource>();erhan355
@CK - I had a similar scenario - needed async Background service. So, I registered my service as HostedService and then used injected IServiceProvider to GetRequiredService<MyDbContext>(). Good article on a subject: docs.microsoft.com/en-us/dotnet/architecture/microservices/…lentyai

2 Answers

73
votes

CreateScope from IServiceProvider resolve IServiceScopeFactory and call CreateScope() on it:

public static IServiceScope CreateScope(this IServiceProvider provider)
{
    return provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}

So, as said @Evk

functionally both methods are identical

IServiceProvider just wrapped call CreateScope() from IServiceScopeFactory

18
votes

From what I tested

In ASP.NET Core 5 the following code works:

[HttpGet("/Echo/{word}")]
public IActionResult EchoAndLog(string word, [FromServices] IServiceScopeFactory serviceScopeFactory)
{
    var ipAddress = HttpContext.Connection.RemoteIpAddress;

    // No need to wait for logging, just fire and forget
    Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<LogDbContext>();

            var log = new ActivityLog
            {
                IpAddress = ipAddress,
                Endpoint = "Echo",
                Parameter = word
            };

            context.Add(log);
            await context.SaveChangesAsync();                                        
        }
    });

    return Ok(word);
}

Now if you change the IServiceScopeFactory to IServiceProvider it will NOT work:

[HttpGet("/Echo/{word}")]
public IActionResult EchoAndLog(string word, [FromServices] IServiceProvider serviceProvider)
{
    var ipAddress = HttpContext.Connection.RemoteIpAddress;

    // No need to wait for logging, just fire and forget
    Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceProvider.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<LogDbContext>();

            var log = new ActivityLog
            {
                IpAddress = ipAddress,
                Endpoint = "Echo",
                Parameter = word
            };

            context.Add(log);
            await context.SaveChangesAsync();                                        
        }
    });

    return Ok(word);
}

You will get the System.ObjectDisposedException exception:

Cannot access a disposed object.

Object name: 'IServiceProvider'.

Which tells me the IServiceProvider will live as long as the request's lifetime (scoped), but this is not the case with IServiceScopeFactory.