En este punto, estoy inyectando cosas en mis Controllers con facilidad, en algunos casos construyendo mi propia clase ResolverServices. La vida es buena .
Lo que no puedo averiguar cómo hacer es hacer que el marco se inyecte automáticamente en las clases que no son controladores. Lo que funciona es que el marco se inyecte automáticamente en mi controlador IOptions
, que es efectivamente la configuración para mi proyecto:
public class MessageCenterController : Controller
{
private readonly MyOptions _options;
public MessageCenterController(IOptions<MyOptions> options)
{
_options = options.Value;
}
}
Estoy pensando si puedo hacer lo mismo con mis propias clases. Supongo que estoy cerca cuando imito el controlador, así:
public class MyHelper
{
private readonly ProfileOptions _options;
public MyHelper(IOptions<ProfileOptions> options)
{
_options = options.Value;
}
public bool CheckIt()
{
return _options.SomeBoolValue;
}
}
Creo que estoy fallando cuando lo llamo así:
public void DoSomething()
{
var helper = new MyHelper(??????);
if (helper.CheckIt())
{
// Do Something
}
}
El problema que tengo para rastrear esto es que prácticamente todo lo que habla de DI lo está hablando a nivel del controlador. Intenté buscar dónde sucede en el Controller
código fuente del objeto, pero se vuelve un poco loco allí.
Sé que puedo crear manualmente una instancia de IOptions y pasarla al MyHelper
constructor, pero parece que debería poder hacer que el marco lo haga, ya que funciona Controllers
.
fuente
new
. Nunca para objetos que deberían resolverseIServiceProvider
en su fábrica y llamandoIMyHelper helper = services.RequestService<IMyHelper>()
Respuestas:
A continuación se muestra un ejemplo práctico del uso de DI sin nada que involucre controladores MVC. Esto es lo que necesitaba hacer para comprender el proceso, así que quizás ayude a alguien más.
El objeto ShoppingCart obtiene, a través de DI, una instancia de INotifier (que notifica al cliente de su pedido).
using Microsoft.Extensions.DependencyInjection; using System; namespace DiSample { // STEP 1: Define an interface. /// <summary> /// Defines how a user is notified. /// </summary> public interface INotifier { void Send(string from, string to, string subject, string body); } // STEP 2: Implement the interface /// <summary> /// Implementation of INotifier that notifies users by email. /// </summary> public class EmailNotifier : INotifier { public void Send(string from, string to, string subject, string body) { // TODO: Connect to something that will send an email. } } // STEP 3: Create a class that requires an implementation of the interface. public class ShoppingCart { INotifier _notifier; public ShoppingCart(INotifier notifier) { _notifier = notifier; } public void PlaceOrder(string customerEmail, string orderInfo) { _notifier.Send("[email protected]", customerEmail, $"Order Placed", $"Thank you for your order of {orderInfo}"); } } public class Program { // STEP 4: Create console app to setup DI static void Main(string[] args) { // create service collection var serviceCollection = new ServiceCollection(); // ConfigureServices(serviceCollection) serviceCollection.AddTransient<INotifier, EmailNotifier>(); // create service provider var serviceProvider = serviceCollection.BuildServiceProvider(); // This is where DI magic happens: var myCart = ActivatorUtilities.CreateInstance<ShoppingCart>(serviceProvider); myCart.PlaceOrder("[email protected]", "2 Widgets"); System.Console.Write("Press any key to end."); System.Console.ReadLine(); } } }
fuente
ShoppingCart
en otra clase o método al que no accedemos alserviceProvider
objeto?Digamos que
MyHelper
es usado por loMyService
que a su vez es usado por su controlador.La forma de resolver esta situación es:
Regístrese tanto
MyService
comoMyHelper
enStartup.ConfigureServices
.El controlador recibe una instancia de
MyService
en su constructor.public HomeController(MyService service) { ... }
MyService
a su vez, el constructor recibirá una instancia deMyHelper
.public MyService(MyHelper helper) { ... }
El marco DI podrá resolver todo el gráfico de objetos sin problemas. Si le preocupa que se creen nuevas instancias cada vez que se resuelve un objeto, puede leer acerca de las diferentes opciones de vida y registro, como la vida útil única o de solicitud.
Debería sospechar mucho cuando crea que tiene que crear manualmente una instancia de algún servicio, ya que podría terminar en el antipatrón del localizador de servicios . Mejor dejar la creación de los objetos en el contenedor DI. Si realmente te encuentras en esa situación (digamos que creas una fábrica abstracta), entonces podrías usar el
IServiceProvider
directamente (O solicita unIServiceProvider
en tu constructor o usa el expuesto en el httpContext ).var foo = serviceProvider.GetRequiredService<MyHelper>();
Recomendaría leer la documentación específica sobre el marco ASP.Net 5 DI y sobre la inyección de dependencias en general.
fuente
Desafortunadamente, no existe una forma directa. La única forma en que logré hacerlo funcionar es creando una clase estática y usándola en cualquier otro lugar como se muestra a continuación:
public static class SiteUtils { public static string AppName { get; set; } public static string strConnection { get; set; } }
Luego, en su clase de inicio, complételo de la siguiente manera:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { //normal as detauls , removed for space // set my variables all over the site SiteUtils.strConnection = Configuration.GetConnectionString("DefaultConnection"); SiteUtils.AppName = Configuration.GetValue<string>("AppName"); }
Aunque este es un patrón incorrecto, ya que se mantendrá durante todo el ciclo de vida de la aplicación y no pude encontrar una mejor manera de usarlo fuera del controlador.
fuente
Aquí hay un ejemplo más completo para responder directamente a la pregunta del OP, basado en la documentación actual de .NET Core 2.2 DI aquí . Agregar esta respuesta ya que puede ayudar a alguien que es nuevo en .NET Core DI, y porque esta pregunta es el resultado de búsqueda principal de Google.
Primero, agregue una interfaz para MyHelper:
public interface IMyHelper { bool CheckIt(); }
En segundo lugar, actualice la clase MyHelper para implementar la interfaz (en Visual Studio, presione ctrl-. Para implementar la interfaz):
public class MyHelper : IMyHelper { private readonly ProfileOptions _options; public MyHelper(IOptions<ProfileOptions> options) { _options = options.Value; { public bool CheckIt() { return _options.SomeBoolValue; } }
En tercer lugar, registre la interfaz como un servicio proporcionado por el marco en el contenedor de servicios DI. Haga esto registrando el servicio IMyHelper con el tipo concreto MyHelper en el método ConfigureServices en Startup.cs.
public void ConfigureServices(IServiceCollection services) { ... services.AddScoped<IMyHelper, MyHelper>(); ... }
Cuarto, cree una variable privada para hacer referencia a una instancia del servicio. Pase el servicio como argumento en el constructor (a través de la inyección del constructor) y luego inicialice la variable con la instancia del servicio. Haga referencia a cualquier propiedad o método de llamada en esta instancia de la clase personalizada a través de la variable privada.
public class MessageCenterController : Controller { private readonly MyOptions _options; private readonly IMyHelper _myHelper; public MessageCenterController( IOptions<MyOptions> options, IMyHelper myHelper ) { _options = options.value; _myHelper = myHelper; } public void DoSomething() { if (_myHelper.CheckIt()) { // Do Something } } }
fuente
Puede utilizar Activator.CreateInstance (). Aquí hay una función contenedora para ello. La forma en que usa esto es la siguiente.
var determinedProgrammatically = "My.NameSpace.DemoClass1"; // implements IDemo interface var obj = CreateInstance<My.NameSpace.IDemo, string>(determinedProgrammatically, "This goes into the parameter of the constructor.", "Omit this parameter if your class lives in the current assembly");
Ahora tiene una instancia de obj que se crea a partir del tipo determinado mediante programación. Este obj se puede inyectar en clases no controladoras.
public TInterface CreateInstance<TInterface, TParameter>(string typeName, TParameter constructorParam, string dllName = null) { var type = dllName == null ? System.Type.GetType(typeName) : System.AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.StartsWith(dllName, System.StringComparison.OrdinalIgnoreCase)).GetType(typeName); return (TInterface)System.Activator.CreateInstance(type, constructorParam); }
PD: puede iterar a través de System.AppDomain.CurrentDomain.GetAssemblies () para determinar el nombre del ensamblado que alberga su clase. Este nombre se utiliza en el tercer parámetro de la función contenedora.
fuente