在ASP.NET Core中,我如何从我的类内部解析出IServiceProvider?

39
我开始学习ASP.NET 5(vNext)中的变化,但是找不到如何获取IServiceProvider,例如在“Model”的方法中。
public class Entity 
{
     public void DoSomething()
     { 
           // This next line doesn't compile.
           // Where is ServiceContainer or something like that?
           var dbContext = ServiceContainer.GetService<DataContext>();
     }
}

我知道,我们在启动时配置服务,但是所有的服务集合都在哪里或者IServiceProvider?
7个回答

70

你需要引入Microsoft.Extensions.DependencyInjection命名空间才能访问该泛型。

GetService<T>();

应该在其上使用的扩展方法

IServiceProvider 

请注意,您可以直接将服务注入到ASP.NET 5控制器中。请参见下面的示例。

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

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

    public string ServiceValue { get; set; }
}

启动.cs

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

主控制器

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

任何一种方法都可以用于访问该服务。Startup.cs的其他服务扩展。

AddInstance<IService>(new Service())

每次只有一个实例。您负责初始对象的创建。

AddSingleton<IService, Service>()
一个单例实例被创建并且其行为像一个单例。
AddTransient<IService, Service>()
每次注入时都会创建一个新的实例。
AddScoped<IService, Service>()

一个单例实例被创建在当前的HTTP请求范围内。它在当前范围上下文中等同于Singleton。

更新于2018年10月18日

详见:aspnet GitHub - ServiceCollectionServiceExtensions.cs


1
这似乎在 ASP.NET 5 中不起作用,而这正是 OP 关注的。也许我漏掉了什么? - Serj Sagan
自从这篇文章发布以来,情况已经发生了变化。如果我没记错的话,这是在Beta 7之后?这绝对是关于ASP.NET 5的。他们将命名空间从Microsoft.Framework.DependencyInjection改为Microsoft.Extensions.DependencyInjection。这里是定义GetService<T>扩展的地方:https://github.com/aspnet/DependencyInjection/blob/dev/src/Microsoft.Extensions.DependencyInjection.Abstractions/ServiceProviderExtensions.cs - Jaime Still
与Angular相同的概念 :) - mokth
1
将 IServiceProvider 传递给控制器而不是传递依赖项本身,这不是一种不好的做法吗? - Simple Fellow
1
@SimpleFellow - 我也是这么想的。通过传递 IServiceProvider,那么模式就只是使用 DI“传递定位器对象”。但是在使用这种模式编码一段时间后,我开始认为传递 IServiceProvider 的实用性值得重新考虑,至少对于工作服务(也许不适用于 REST API)。 - user3053247
显示剩余4条评论

4

我认为让一个实体(或模型)可以访问任何服务并不是一个好主意。

然而,控制器在其构造函数中确实可以访问任何已注册的服务,您无需担心此问题。

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

现在我觉得它太棒了:) - Sam

4
请使用GetRequiredService替代GetService,例如ASP.NET Core教程中的示例(https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/working-with-sql)。
方法的文档请查看: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>>()))

4

不要使用GetService()

GetService和GetRequiredService的区别与异常相关。

如果服务不存在,GetService()会返回null。而GetRequiredService()会抛出异常。

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

3

一般来说,你希望DI去自动注入它给你的内容:

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

然而,Entity必须像ControllerViewComponent一样自动实例化给你。如果你需要在dbContext不可用的地方手动实例化它,则可以执行以下操作:
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();
     }
}

但是需要强调的是,这被认为是一种反模式,除非绝对必要,否则应该避免使用。而且...冒着让某些模式人员非常不满意的风险...如果其他所有方法都失败了,您可以在辅助类中添加一个static IContainer并在StartUp类的ConfigureServices方法中进行赋值:MyHelper.DIContainer = builder.Build();这是一种非常丑陋的做法,但有时您只需要让它工作。


1
这是一个完美的例子,说明“如何不进行依赖注入”的多种方式。 - Robert Perry

2
我认为问题提出者有些困惑了。实体应该尽可能“轻量级”。它们应该尝试不包含逻辑,也不包含除导航属性以外的外部引用。查找一些常见的模式,如仓储库模式,可以帮助将逻辑抽象化,使其与实体本身分离。"最初的回答"

0

不要在代码中直接获取服务,试着将其注入到构造函数中。

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
    }
}

我还建议阅读一下AddTransient的含义,因为它将对应用程序共享DbContext实例的方式产生重大影响。这是一种称为依赖注入的模式。需要一些时间来适应,但一旦你适应了,就再也不想回去了。

1
谢谢您的回答,但如果我有许多实体,我需要重写构造函数,并且无法使用Activator.CreateInstance。在方法参数中使用注入看起来很丑,您能告诉我如何遵循YAGNI原则并使用良好的模式吗? - Sam
您是否试图使用Entity Framework?注入应在构造函数中使用,而不是方法。我不确定您在这个上下文中指的是YAGNI。 - Anton
是的,我正在使用Entity Framework,但看看这个情况: 我有一个实体,例如 - Catalog, Catalog有属性 - Url, 在旧的MVC中,我可以创建httpcontext包装器并在属性内部使用url helper, 在新的MVC中,我无法这样做,并且除了在方法中包含IUrlHelper(在属性路径上添加新的方法包装器)以进行DRY之外,没有其他选择, 但这仍然很奇怪。 - Sam
我相信@Sam所提到的练习来自以下链接:http://docs.asp.net/projects/mvc/en/latest/tutorials/mvc-with-entity-framework.html在这个练习中,他们明确使用了Microsoft.Framework.DependencyInjection中定义的通用GetService扩展方法。 - Jaime Still
构造函数注入没问题,但是对于EF来说呢?在从数据库中实例化时,它们会注入对象吗?@Anton - Sam
一个实体为什么需要引用它的上下文? - Leonardo Herrera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接