Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function worker's service provider resolves scoped service from the root as singleton #97091

Closed
aventus13 opened this issue Jan 17, 2024 · 7 comments

Comments

@aventus13
Copy link

System version

Framework version: .Net 8.0
Package causing the problem: Microsoft.Azure.Functions.Worker, v1.20.1

Description

In ASP Core web application runtime, trying to resolve a scoped service from a non-scoped (root) service provider throws an error with the following message:

Cannot resolve scoped service from root provider

However, using an instance of IHost built by HostBuilder from Microsoft.Extensions.Hosting doesn't seem to respect service lifetime registration. This can be easily reproduced with the below code. It seems like a bug because the service resolution behavior isn't consistent and can lead to tricky bugs with dependencies relying on scoped context.

Expected behavior

An exception is thrown when resolving a scoped service from a root provider.

Actual behavior

The service is resolved as a singleton.

Code to reproduce

csproj file

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.20.1"/>
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0"/>
    </ItemGroup>

</Project>

code

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults();

builder.ConfigureServices((_, services) => services.AddScoped<IDependency, DependencyImplementation>());

var host = builder.Build();
var depOne = host.Services.GetRequiredService<IDependency>();// Expected to get exception here but it resolved the service
var depTwo = host.Services.GetRequiredService<IDependency>();
var areEqual = depOne == depTwo;// true, received same instance

var scopedDepOne = host.Services.CreateScope().ServiceProvider.GetRequiredService<IDependency>();
var scopedDepTwo = host.Services.CreateScope().ServiceProvider.GetRequiredService<IDependency>();
areEqual = scopedDepOne == scopedDepTwo;// false, received scoped instances as expected

Console.ReadKey();

interface IDependency
{ }

class DependencyImplementation : IDependency
{ }
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 17, 2024
@ghost
Copy link

ghost commented Jan 17, 2024

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

System version

Framework version: .Net 8.0
Package causing the problem: Microsoft.Azure.Functions.Worker, v1.20.1

Description

In ASP Core web application runtime, trying to resolve a scoped service from a non-scoped (root) service provider throws an error with the following message:

Cannot resolve scoped service from root provider

However, using an instance of IHost built by HostBuilder from Microsoft.Extensions.Hosting doesn't seem to respect service lifetime registration. This can be easily reproduced with the below code. It seems like a bug because the service resolution behavior isn't consistent and can lead to tricky bugs with dependencies relying on scoped context.

Expected behavior

An exception is thrown when resolving a scoped service from a root provider.

Actual behavior

The service is resolved as a singleton.

Code to reproduce

csproj file

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.20.1"/>
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0"/>
    </ItemGroup>

</Project>

code

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults();

builder.ConfigureServices((_, services) => services.AddScoped<IDependency, DependencyImplementation>());

var host = builder.Build();
var depOne = host.Services.GetRequiredService<IDependency>();// Expected to get exception here but it resolved the service
var depTwo = host.Services.GetRequiredService<IDependency>();
var areEqual = depOne == depTwo;// true, received same instance

var scopedDepOne = host.Services.CreateScope().ServiceProvider.GetRequiredService<IDependency>();
var scopedDepTwo = host.Services.CreateScope().ServiceProvider.GetRequiredService<IDependency>();
areEqual = scopedDepOne == scopedDepTwo;// false, received scoped instances as expected

Console.ReadKey();

interface IDependency
{ }

class DependencyImplementation : IDependency
{ }
Author: aventus13
Assignees: -
Labels:

area-Extensions-DependencyInjection

Milestone: -

@steveharter steveharter self-assigned this Jan 17, 2024
@steveharter steveharter added this to the 9.0.0 milestone Jan 17, 2024
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Jan 17, 2024
@wcsanders1
Copy link
Contributor

@aventus13 It looks like this failure to validate the scope is a result of calling the HostBuilder constructor rather than the CreateDefaultBuilder method, which enables scope validation when the environment is Development: https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L327

@aventus13
Copy link
Author

Thanks for your response, @wcsanders1. However- apologies if it's slightly off topic- isn't this a symptom of a rather wider problem stemming from the design of the DI's default behaviour? Given that scope validation is not enabled by default, it's possible to run into a tricky and potentially dangerous scenario, i.e. resolving a singleton service that takes in a scoped dependency.

var services = new ServiceCollection();
services.AddSingleton<ISingletonDependency, SingletonDependencyImplementation>()
  .AddScoped<IScopedDependency, ScopedDependencyImplementation>();

var provider = services.BuildServiceProvider()
  .CreateScope()
  .ServiceProvider;

var singleton = provider.GetRequiredService<ISingletonDependency>(); // Resolves successfully with an injected scoped dependency

class SingletonDependencyImplementation : ISingletonDependency
{
  // This should never happen
  public SingletonDependencyImplementation(IScopedDependency scoped)
  {
    // ...
  }
}

@wcsanders1
Copy link
Contributor

I agree @aventus13 that it seems like such behavior would never be wanted. I plan to submit a PR to fix it within this week.

@wcsanders1
Copy link
Contributor

I think #99199 should resolve this.

@steveharter steveharter removed their assignment Mar 12, 2024
@steveharter
Copy link
Member

It seems like a bug because the service resolution behavior isn't consistent and can lead to tricky bugs with dependencies relying on scoped context.

Yes thanks. This is a common issue; work done here to prevent scoped services from being used with a singleton constructor is good as long as it doesn't significantly affect perf in non-development runtime scenarios. As mentioned above, we do try to do error checking but only in development mode: https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L330-L331

@buyaa-n
Copy link
Contributor

buyaa-n commented Jul 26, 2024

I think #99199 should resolve this.

Closing

@buyaa-n buyaa-n closed this as completed Jul 26, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Aug 27, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants