¿Cómo definir la inyección de dependencia en Winforms C#?
Interfaz ICategoría:
public interface ICategory { void Save(); }
Repositorio de categoría de clase:
public class CategoryRepository : ICategory { private readonly ApplicationDbContext _context; public CategoryRepository(ApplicationDbContext contex) { _context = contex; } public void Save() { _context.SaveChanges(); } }
Formulario 1:
public partial class Form1 : Form { private readonly ICategury _ic; public Form1(ICategury ic) { InitializeComponent(); _ic=ic } private void button1_Click(object sender, EventArgs e) { Form2 frm= new Form2(); frm.show(); } }
Forma2:
public partial class Form2 : Form { private readonly ICategury _ic; public Form2(ICategury ic) { InitializeComponent(); _ic=ic } }
¿Problema?
Definición de inyección de dependencia en Program.cs
Application.Run(new Form1());
Definición de inyección de dependencia en el momento de la llamada del Formulario 2
Form2 frm= new Form2(); frm.show();
Para usar DI en un WinForms .NET 5 o 6 puedes hacer los siguientes pasos:
Crear una aplicación WinForms .NET
Instale el paquete Microsoft.Extensions.Hosting (que le brinda un montón de funciones útiles como DI, registro, configuraciones, etc.)
Agregue una nueva interfaz, IHelloService.cs
:
public interface IHelloService { string SayHello(); }
Agregue una nueva implementación para su servicio HelloService.cs
:
public class HelloService : IHelloService { public string SayHello() { return "Hello, world!"; } }
Modifique el Program.cs
:
//using Microsoft.Extensions.DependencyInjection; static class Program { [STAThread] static void Main() { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var host = CreateHostBuilder().Build(); ServiceProvider = host.Services; Application.Run(ServiceProvider.GetRequiredService<Form1>()); } public static IServiceProvider ServiceProvider { get; private set; } static IHostBuilder CreateHostBuilder() { return Host.CreateDefaultBuilder() .ConfigureServices((context, services)=>{ services.AddTransient<IHelloService, HelloService>(); services.AddTransient<Form1>(); }); } }
Ahora puede inyectar IHelloService
en Form1
y usarlo:
//using Microsoft.Extensions.DependencyInjection; public partial class Form1 : Form { private readonly IHelloService helloService; public Form1(IHelloService helloService) { InitializeComponent(); this.helloService = helloService; MessageBox.Show(helloService.SayHello()); } }
Si desea mostrar Form2
usando DI, primero debe registrarlo services.AddTransient<Form2>();
, luego, dependiendo del uso de Form2, puede usar cualquiera de las siguientes opciones:
Si solo necesita una única instancia de Form2
en toda la vida útil de Form1
, puede inyectarla como una dependencia al constructor de Form1
y almacenar la instancia y mostrarla cuando lo desee.
Pero preste atención: se inicializará solo una vez, cuando abra Form1
y no se inicializará nuevamente. Tampoco debe desecharlo, porque es la única instancia que se pasa a Form1
.
public Form1(IHelloService helloService, Form2 form2) { InitializeComponent(); form2.ShowDialog(); }
Si necesita varias instancias de Form2
o necesita inicializarlo varias veces, puede obtener una instancia como esta:
using (var form2 = Program.ServiceProvider.GetRequiredService<Form2>()) form2.ShowDialog();
En winforms, los constructores de formularios deben ser constructores predeterminados. Debe establecer los valores que desea pasar en el constructor como una propiedad. Durante la carga del formulario, puede verificar si la propiedad se ha establecido y, si no, actuar como desee: usar un valor predeterminado, advertir al operador o cerrar el formulario.
Por supuesto, al menos uno de los formularios debe decidir qué ICategoría debe inyectarse. Por ejemplo, el formulario principal:
public class MainForm { private ICategory Category {get; } public MainForm() { InitializeComponent(); CategoryFactory factory = new CategoryFactory(); this.Category = factory.Create(); }
Más tarde, MainForm debe mostrar Form1, por ejemplo, después de hacer clic en un botón:
private void ShowForm1() { using (Form1 form = new Form1()) { form.Category = this.Category; var dlgResult = form.ShowDialog(this); if (dlgResult == DialogResult.Ok) { this.ProcessDialogResult(...); } } }
Formulario 1:
class Form1 : ... { public Form1() { InitializeComponent(); // subscribe to event form load } public ICategory Category {get; } public void FormLoading(object sender, ...) { // check if Category set, and report problems if (this.Category == null) { this.LogMissingCategory(); this.WarnOperatorMissingCategory(); this.Close(); } ... } private void ShowForm2() { using (Form2 form = new Form2()) { form.Category = this.Category; var dlgResult = form.ShowDialog(this); if (dlgResult == DialogResult.Ok) { ... } } } }
Si tiene muchos formularios que necesitan una categoría, aparentemente hay algo como un formulario de categoría:
class CategoryForm : Form { public ICategory Category {get; } protected override void OnFormLoading(...) { // TODO check Category base.OnFormLoading(...); } } classs Form3 : CategoryForm {...}