Sam
Sam

Reputation: 539

How can I resolve from the IServiceProvider from inside my classes in ASP.NET Core?

I starting to learn changes in ASP.NET 5 (vNext) and cannot find how to get IServiceProvider, for example in "Model"'s method

public class Entity 
{
     public void DoSomething()
     { 
           // This next line doesn't compile.
           // Where is ServiceContainer or something like that?
           var dbContext = ServiceContainer.GetService<DataContext>();
     }
}

I know, we configuring services at startup, but where all service collection staying or IServiceProvider?

Upvotes: 41

Views: 124602

Answers (7)

Pit
Pit

Reputation: 65

Do not use GetService()

The difference between GetService and GetRequiredService is related with exception.

GetService() returns null if a service does not exist. GetRequiredService() will throw exception.

public static class ServiceProviderServiceExtensions
{
    public static T GetService<T>(this IServiceProvider provider)
    {
        return (T)provider.GetService(typeof(T));
    }

    public static T GetRequiredService<T>(this IServiceProvider provider)
    {
        return (T)provider.GetRequiredService(typeof(T));
    }
}

Upvotes: 4

Robert Perry
Robert Perry

Reputation: 1956

I think the OP is getting confused. Entities should be as “thin” as possible. They should try not to contain logic, and or external references other than navigation properties. Look up some common patterns like repository pattern which helps to abstract your logic away from the entities themselves

Upvotes: 2

Jaime Still
Jaime Still

Reputation: 1998

You have to bring in Microsoft.Extensions.DependencyInjection namespace to gain access to the generic

GetService<T>();

extension method that should be used on

IServiceProvider 

Also note that you can directly inject services into controllers in ASP.NET 5. See below example.

public interface ISomeService
{
    string ServiceValue { get; set; }
}

public class ServiceImplementation : ISomeService
{
    public ServiceImplementation()
    {
        ServiceValue = "Injected from Startup";
    }

    public string ServiceValue { get; set; }
}

Startup.cs

public void ConfigureService(IServiceCollection services)
{
    ...
    services.AddSingleton<ISomeService, ServiceImplementation>();
}

HomeController

using Microsoft.Extensions.DependencyInjection;
...
public IServiceProvider Provider { get; set; }
public ISomeService InjectedService { get; set; }

public HomeController(IServiceProvider provider, ISomeService injectedService)
{
    Provider = provider;
    InjectedService = Provider.GetService<ISomeService>();
}

Either approach can be used to get access to the service. Additional service extensions for Startup.cs

AddInstance<IService>(new Service())

A single instance is given all the time. You are responsible for initial object creation.

AddSingleton<IService, Service>()

A single instance is created and it acts like a singleton.

AddTransient<IService, Service>()

A new instance is created every time it is injected.

AddScoped<IService, Service>()

A single instance is created inside of the current HTTP Request scope. It is equivalent to Singleton in the current scope context.

Updated 18 October 2018

See: aspnet GitHub - ServiceCollectionServiceExtensions.cs

Upvotes: 71

juFo
juFo

Reputation: 18567

use GetRequiredService instead of GetService, like the example on ASP.NET Core tutorials ( https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/working-with-sql )

documentation on the method:

https://learn.microsoft.com/en-us/aspnet/core/api/microsoft.extensions.dependencyinjection.serviceproviderserviceextensions#Microsoft_Extensions_DependencyInjection_ServiceProviderServiceExtensions_GetRequiredService__1_System_IServiceProvider_

using Microsoft.Extensions.DependencyInjection;

      using (var context = new ApplicationDbContext(serviceProvicer.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))

Upvotes: 4

Leonardo Herrera
Leonardo Herrera

Reputation: 8406

I don't think it is a good idea for an entity (or a model) to have access to any service.

Controllers, on the other hand, do have access to any registered service in their constructors, and you don't have to worry about it.

public class NotifyController : Controller
{
    private static IEmailSender emailSender = null;
    protected static ISessionService session = null;
    protected static IMyContext dbContext = null;
    protected static IHostingEnvironment hostingEnvironment = null;

    public NotifyController(
                IEmailSender mailSenderService,
                IMyContext context,
                IHostingEnvironment env,
                ISessionService sessionContext)
    {
        emailSender = mailSenderService;
        dbContext = context;
        hostingEnvironment = env;
        session = sessionContext;
    }
}

Upvotes: 4

Serj Sagan
Serj Sagan

Reputation: 30198

Generally you want to have the DI do its thing and inject that for you:

public class Entity 
{
    private readonly IDataContext dbContext;

    // The DI will auto inject this for you
    public class Entity(IDataContext dbContext)
    {
        this.dbContext = dbContext;
    }

     public void DoSomething()
     {
         // dbContext is already populated for you
         var something = dbContext.Somethings.First();
     }
}

However, Entity would have to be automatically instantiated for you... like a Controller or a ViewComponent. If you need to manually instantiate this from a place where this dbContext is not available to you, then you can do this:

using Microsoft.Extensions.PlatformAbstractions;

public class Entity 
{
    private readonly IDataContext dbContext;

    public class Entity()
    {
        this.dbContext = (IDataContext)CallContextServiceLocator.Locator.ServiceProvider
                            .GetService(typeof(IDataContext));
    }

     public void DoSomething()
     {
         var something = dbContext.Somethings.First();
     }
}

But just to emphasize, this is considered an anti-pattern and should be avoided unless absolutely necessary. And... at the risk of making some pattern people really upset... if all else fails, you can add a static IContainer in a helper class or something and assign it in your StartUp class in the ConfigureServices method: MyHelper.DIContainer = builder.Build(); And this is a really ugly way to do it, but sometimes you just need to get it working.

Upvotes: 3

Anton
Anton

Reputation: 1386

Instead of getting your service inline, try injecting it into the constructor.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient(typeof(DataContext));
    }
}

public class Entity
{
    private DataContext _context;

    public Entity(DataContext context)
    {
        _context = context;
    }

    public void DoSomething()
    {
        // use _context here
    }
}

I also suggest reading up on what AddTransient means, as it will have a significant impact on how your application shares instances of DbContext. This is a pattern called Dependency Injection. It takes a while to get used to, but you will never want to go back once you do.

Upvotes: 0

Related Questions