Moh*_*rak 39 .net c# dependency-injection winforms
如何在 Winforms C# 中定义依赖注入?
接口IC类别:
public interface ICategory
{
void Save();
}
Run Code Online (Sandbox Code Playgroud)
类类别存储库:
public class CategoryRepository : ICategory
{
private readonly ApplicationDbContext _context;
public CategoryRepository(ApplicationDbContext contex)
{
_context = contex;
}
public void Save()
{
_context.SaveChanges();
}
}
Run Code Online (Sandbox Code Playgroud)
表格一:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
表格2:
public partial class Form2 : Form
{
private readonly ICategury _ic;
public Form2(ICategury ic)
{
InitializeComponent();
_ic=ic
}
}
Run Code Online (Sandbox Code Playgroud)
问题?
Program.cs中依赖注入的定义
Application.Run(new Form1());
Run Code Online (Sandbox Code Playgroud)
Form 2 调用时依赖注入的定义
Form2 frm= new Form2();
frm.show();
Run Code Online (Sandbox Code Playgroud)
Rez*_*aei 71
要在 WinForms .NET 5 或 6 中使用 DI,您可以执行以下步骤:
创建 WinForms .NET 应用程序
安装 Microsoft.Extensions.Hosting 包(它为您提供了一系列有用的功能,例如 DI、日志记录、配置等)
添加新接口IHelloService.cs:
public interface IHelloService
{
string SayHello();
}
Run Code Online (Sandbox Code Playgroud)
为您的服务添加新的实现HelloService.cs:
public class HelloService : IHelloService
{
public string SayHello()
{
return "Hello, world!";
}
}
Run Code Online (Sandbox Code Playgroud)
修改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>();
});
}
}
Run Code Online (Sandbox Code Playgroud)
现在您可以注入IHelloService并Form1使用它:
//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());
}
}
Run Code Online (Sandbox Code Playgroud)
如果您想Form2使用DI进行显示,您首先需要注册它 services.AddTransient<Form2>();,然后根据Form2的用途,您可以使用以下选项之一:
Form2如果您在 的整个生命周期中只需要一个 实例Form1,那么您可以将其作为依赖项注入到 的构造函数中Form1,并存储该实例并在需要时显示它。
但请注意:它只会初始化一次,打开时Form1不会再次初始化。您也不应该处置它,因为它是传递给 的唯一实例Form1。
public Form1(IHelloService helloService, Form2 form2)
{
InitializeComponent();
form2.ShowDialog();
}
Run Code Online (Sandbox Code Playgroud)
如果您需要多个实例Form2或者需要多次初始化它,那么您可能会像这样获得它的实例:
using (var form2 = Program.ServiceProvider.GetRequiredService<Form2>())
form2.ShowDialog();
Run Code Online (Sandbox Code Playgroud)
Wik*_*hla 12
除了 Reza 在他的回答中描述的方法之外,还有另一种方法。我的这个答案最初发布在我的博客上,但是,由于 Stack Overflow 是我们大多数人的主要信息来源,因此 Reza 答案下面的评论中链接的博客文章很容易被错过。
那么就这样吧。该解决方案基于本地工厂模式。
我们将从表单工厂开始
public interface IFormFactory
{
Form1 CreateForm1();
Form2 CreateForm2();
}
public class FormFactory : IFormFactory
{
static IFormFactory _provider;
public static void SetProvider( IFormFactory provider )
{
_provider = provider;
}
public Form1 CreateForm1()
{
return _provider.CreateForm1();
}
public Form2 CreateForm2()
{
return _provider.CreateForm2();
}
}
Run Code Online (Sandbox Code Playgroud)
从现在开始,该工厂是创建表单的主要客户端界面。客户端代码不再应该只调用
var form1 = new Form1();
Run Code Online (Sandbox Code Playgroud)
不,这是禁止的。相反,客户端应该始终调用
var form1 = new FormFactory().CreateForm1();
Run Code Online (Sandbox Code Playgroud)
(对于其他形式也类似)。
请注意,虽然工厂已实现,但它本身并不执行任何操作!相反,它将创建委托给一个神秘的提供者,该提供者必须注入到工厂中。这背后的想法是,提供程序将被注入到组合根中一次,组合根是代码中靠近启动的位置,并且在应用程序堆栈中位于非常高的位置,以便可以在那里解决所有依赖项。因此,表单工厂不需要知道最终将注入哪个提供程序。
这种方法有一个显着的优势 - 根据实际需求,可以注入不同的提供程序,例如,您可以为实际应用程序提供一个基于 DI 的提供程序(我们稍后会编写它),并为单元测试提供一个存根提供程序。
无论如何,让我们有一个具有依赖关系的表单:
public partial class Form1 : Form
{
private IHelloWorldService _service;
public Form1(IHelloWorldService service)
{
InitializeComponent();
this._service = service;
}
}
Run Code Online (Sandbox Code Playgroud)
这种形式依赖于一个服务,该服务将由构造函数提供。如果 Form1 需要创建另一个表单 Form2,它会按照我们已经讨论过的方式进行操作:
var form2 = new FormFactory().CreateForm2();
Run Code Online (Sandbox Code Playgroud)
然而,当表单不仅需要依赖服务还需要一些自由参数(字符串、整数等)时,事情就会变得更加复杂。通常,你会有一个构造函数
public Form2( string something, int somethingElse ) ...
Run Code Online (Sandbox Code Playgroud)
但现在你需要更多类似的东西
public Form2( ISomeService service1, IAnotherService service2,
string something, int somethingElse ) ...
Run Code Online (Sandbox Code Playgroud)
这是我们真正应该考虑的事情。再看一遍,现实生活中的形式可能需要
我们该如何处理?
为了获得完整的示例,我们来修改表单工厂
public interface IFormFactory
{
Form1 CreateForm1();
Form2 CreateForm2(string something);
}
public class FormFactory : IFormFactory
{
static IFormFactory _provider;
public static void SetProvider( IFormFactory provider )
{
_provider = provider;
}
public Form1 CreateForm1()
{
return _provider.CreateForm1();
}
public Form2 CreateForm2(string something)
{
return _provider.CreateForm2(something);
}
}
Run Code Online (Sandbox Code Playgroud)
让我们看看表单是如何定义的
public partial class Form1 : Form
{
private IHelloWorldService _service;
public Form1(IHelloWorldService service)
{
InitializeComponent();
this._service = service;
}
private void button1_Click( object sender, EventArgs e )
{
var form2 = new FormFactory().CreateForm2("foo");
form2.Show();
}
}
public partial class Form2 : Form
{
private IHelloWorldService _service;
private string _something;
public Form2(IHelloWorldService service, string something)
{
InitializeComponent();
this._service = service;
this._something = something;
this.Text = something;
}
}
Run Code Online (Sandbox Code Playgroud)
你能在这里看到一个模式吗?
现在终于到了组合根。我们先从服务开始
public interface IHelloWorldService
{
string DoWork();
}
public class HelloWorldServiceImpl : IHelloWorldService
{
public string DoWork()
{
return "hello world service::do work";
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,虽然接口应该位于堆栈中的某个位置(每个人都可以识别),但实现可以在任何地方自由提供(表单不需要引用实现!)。然后,遵循起始代码,最终提供表单工厂并设置容器
internal static class Program
{
[STAThread]
static void Main()
{
var formFactory = CompositionRoot();
ApplicationConfiguration.Initialize();
Application.Run(formFactory.CreateForm1());
}
static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
services.AddTransient<IHelloWorldService, HelloWorldServiceImpl>();
services.AddTransient<Form1>();
services.AddTransient<Func<string,Form2>>(
container =>
something =>
{
var helloWorldService =
container.GetRequiredService<IHelloWorldService>();
return new Form2(helloWorldService, something);
});
});
}
static IFormFactory CompositionRoot()
{
// host
var hostBuilder = CreateHostBuilder();
var host = hostBuilder.Build();
// container
var serviceProvider = host.Services;
// form factory
var formFactory = new FormFactoryImpl(serviceProvider);
FormFactory.SetProvider(formFactory);
return formFactory;
}
}
public class FormFactoryImpl : IFormFactory
{
private IServiceProvider _serviceProvider;
public FormFactoryImpl(IServiceProvider serviceProvider)
{
this._serviceProvider = serviceProvider;
}
public Form1 CreateForm1()
{
return _serviceProvider.GetRequiredService<Form1>();
}
public Form2 CreateForm2(string something)
{
var _form2Factory = _serviceProvider.GetRequiredService<Func<string, Form2>>();
return _form2Factory( something );
}
}
Run Code Online (Sandbox Code Playgroud)
首先注意如何使用 Host.CreateDefaultBuilder 创建容器,这是一项简单的任务。然后注意如何注册服务以及如何在其他服务中注册表单。
这对于没有任何依赖项的表单来说很简单,只是
services.AddTransient<Form1>();
Run Code Online (Sandbox Code Playgroud)
但是,如果表单需要服务和自由参数,则将其注册为...表单创建函数,即返回实际表单的任何自由参数的 Func。看看这个
services.AddTransient<Func<string,Form2>>(
container =>
something =>
{
var helloWorldService = container.GetRequiredService<IHelloWorldService>();
return new Form2(helloWorldService, something);
});
Run Code Online (Sandbox Code Playgroud)
这很聪明。我们使用一种注册机制来注册表单工厂函数,该注册机制本身使用工厂函数(是的,一个工厂使用另一个工厂,Factception。如果您在这里感到迷茫,请随意休息一下)。我们注册的函数 Func<string, Form2> 有一个参数,即 some(对应于表单构造函数的自由参数),但它的其他依赖项由容器解析(这就是我们想要的)。
这就是为什么实际的表单工厂需要注意它所解析的内容。一个简单的表格解析如下
return _serviceProvider.GetRequiredService<Form1>();
Run Code Online (Sandbox Code Playgroud)
其中另一个分两步解决。我们首先解析工厂函数,然后使用创建的方法参数将其提供给函数:
var _form2Factory = _serviceProvider.GetRequiredService<Func<string, Form2>>();
return _form2Factory( something );
Run Code Online (Sandbox Code Playgroud)
就是这样。每当创建表单时,要么
new FormFactory().CreateForm1();
Run Code Online (Sandbox Code Playgroud)
对于“简单”形式(仅具有服务依赖性)或只是
new FormFactory().CreateForm2("foo");
Run Code Online (Sandbox Code Playgroud)
适用于同时需要服务依赖项和其他自由参数的表单。
小智 6
只是想将其添加到此处作为 IFormFactory 的替代模式。这就是我通常的处理方式。
好处是您不需要为添加的每个表单和每组参数不断更改 IFormFactory 接口。
应用程序启动时加载所有表单,并将参数传递给 show 方法或您可以在表单上定义的其他基本方法。
internal static class Program
{
public static IServiceProvider ServiceProvider { get; private set; }
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
ServiceProvider = CreateHostBuilder().Build().Services;
Application.Run(ServiceProvider.GetService<Form1>());
}
static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
services.AddSingleton<IFormFactory,FormFactory>();
services.AddSingleton<IProductRepository, ProductRepository>();
//Add all forms
var forms = typeof(Program).Assembly
.GetTypes()
.Where(t => t.BaseType == typeof(Form))
.ToList();
forms.ForEach(form =>
{
services.AddTransient(form);
});
});
}
}
Run Code Online (Sandbox Code Playgroud)
表格工厂
public interface IFormFactory
{
T? Create<T>() where T : Form;
}
public class FormFactory : IFormFactory
{
private readonly IServiceScope _scope;
public FormFactory(IServiceScopeFactory scopeFactory)
{
_scope = scopeFactory.CreateScope();
}
public T? Create<T>() where T : Form
{
return _scope.ServiceProvider.GetService<T>();
}
}
Run Code Online (Sandbox Code Playgroud)
表1
public partial class Form1 : Form
{
private readonly IFormFactory _formFactory;
public Form1(IFormFactory formFactory)
{
InitializeComponent();
_formFactory = formFactory;
}
private void button_Click(object sender, EventArgs e)
{
var form2 = _formFactory.Create<Form2>();
form2?.Show(99);
}
}
Run Code Online (Sandbox Code Playgroud)
表2
public partial class Form2 : Form
{
private readonly IProductRepository _productRepository;
public Form2(IProductRepository productRepository)
{
InitializeComponent();
_productRepository = productRepository;
}
public void Show(int recordId)
{
var product = _productRepository.GetProduct(recordId);
//Bind your controls etc
this.Show();
}
}
Run Code Online (Sandbox Code Playgroud)