.NET Core IServiceScopeFactory.CreateScope() vs IServiceProvider.CreateScope() extension

C#asp.net CoreDependency Injection.Net Core

C# Problem Overview


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<>();           
        }
    }
}

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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.

Solution 3 - C#

This seems to be interesting question. Also finding @Sasan is quite good and it really highlight few things and usage scenario.

I am taking @Sasan example code and providing few more details about behavior of those two.

Look at following two sample.

 Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceProvider.CreateScope())
        {

and

 Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {

What is happening in sample that code is running without waiting for task completion. In this scenario

  • Example with IServiceProvider gives error because serviceProvider that available in method using [FromServices] has lifetime of scope. In this case request. When controller method return serviceProvider object become disposable so exception is being caught.
  • Now in this case if following scenario is there.
 Task.Run(async () =>
    {
        using (var scope = serviceProvider.CreateScope())
        {
        await Task.Delay(1000);
  • In above case there is no error because as soon as task start it capture scope and that will be used but in case of delay by the time task wakeup again method return so object serviceProvider become disposed and it has no chance to create scope.

  • In case IScopedServiceFactory, this is not the issue. Reason is this is Singleton so that object is there for lifetime of entire application so it will not dispose even method return. So for IScopedServiceFactory scenario wait before capture or after capture same things.

Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {

and

Task.Run(async () =>
    {
        using (var scope = serviceScopeFactory.CreateScope())
        {
         await Task.Delay(1000);

Note:

  • For IServiceProvider or IScopedServiceFactory when call CreateScope, there is no different as internally it call same method. That is highlighted earlier.
  • Only difference is IServiceProvider and IScopedServiceFactory is lifetime, one is Scoped and another one is Singleton. It makes difference in few corner cases.

Solution 4 - C#

In short, IServiceProvider.CreateScope() and IServiceScopeFactory.CreateScope() are identical (in non-scoped context even instances of IServiceProvider and IServiceScopeFactory are identical).

But here is a little difference between these abstractions

IServiceProvider's lifetime can be Scoped. But IServiceScopeFactory's lifetime is always Singleton.

When IServiceProvider is scoped?

When we inject it into a scoped service or method. So, when we inject IServiceProvider into a controller for example, it is possible to resolve scoped dependencies without scope creating (because in this case scope is already created).

Example

So, in the example below you don't need to create scope to resolve (retrieve) a scoped service in any action of the controller:

public class SomeController : ControllerBase
{
    private readonly IServiceProvider _serviceProvider;

    public SomeController(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IActionResult SomeAction()
    {
        //Resolve a scoped service from IServiceProvider without creating a scope:
        var service = _serviceProvider.GetRequiredService<ScopedService>();
        return Ok(service.GetHashCode());
    }
}

But when we inject IServiceProvider into singleton it'll be a root provider (without ability to get scoped services).
Here is I've prepared more examples.

Note that you should avoid using IServiceProvider or even IServiceScopeFactory because they both implement an anti-pattern that called Service Locator.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionConnie KingView Question on Stackoverflow
Solution 1 - C#Roman MarusykView Answer on Stackoverflow
Solution 2 - C#SasanView Answer on Stackoverflow
Solution 3 - C#dotnetstepView Answer on Stackoverflow
Solution 4 - C#Rodion MostovoyView Answer on Stackoverflow