How to register Windows Forms with Simple Injector
Ideally, you would want to register your forms as Singleton
. In my experience, however, this will result in hard to debug errors, especially when you use a BindingSource
for binding your data to whatever.
A second problem with using Singleton
as the lifestyle is that if your application uses modeless windows, this windows will throw an ObjectDisposedException
when opened a second time, because the Windows Forms Application framework will dispose the Form on the first close, while Simple Injector should be in charge of that. So Simple Injector will create one–and exactly one–instance, if registered as Singleton. If somebody else (e.g. your application, the windows forms framework) will dispose the object, it won't be recreated.
The most easy solution, which is also easy to understand, is to register your forms as Transient
. And yes, you need to suppress the diagnostic warnings. The reason for this diagnostic warning according to the documentation:
A component that implements
IDisposable
would usually need deterministic clean-up but Simple Injector does not implicitly track and dispose components registered with the transient lifestyle.
Simple Injector is unable to dispose a transient component because it is unable to determine when the object should be disposed. This means, however, that forms that are opened in a modal fashion with a call to .ShowDialog()
will never be disposed! And because a windows forms application typically runs for a long time, maybe even a week or month, this will eventually result in a 'Win32Exception' with a message: "Error Creating Window Handle". Which essentially means you exhausted all resources of the computer.
Disposing of the forms is therefore important. And although Simple Injector is able to do this job if you would use a Scope, this is with Windows Forms not so easy to implement. So you yourself have to take care of disposing the closed Forms which have been shown using ShowDialog()
.
Depending on your specific use case there are several ways to implement a FormOpener
or NavigationService
. One way to do it:
public interface IFormOpener
{
void ShowModelessForm<TForm>() where TForm : Form;
DialogResult ShowModalForm<TForm>() where TForm : Form;
}
public class FormOpener : IFormOpener
{
private readonly Container container;
private readonly Dictionary<Type, Form> openedForms;
public FormOpener(Container container)
{
this.container = container;
this.openedForms = new Dictionary<Type, Form>();
}
public void ShowModelessForm<TForm>() where TForm : Form
{
Form form;
if (this.openedForms.ContainsKey(typeof(TForm)))
{
// a form can be held open in the background, somewhat like
// singleton behavior, and reopened/reshown this way
// when a form is 'closed' using form.Hide()
form = this.openedForms[typeof(TForm)];
}
else
{
form = this.GetForm<TForm>();
this.openedForms.Add(form.GetType(), form);
// the form will be closed and disposed when form.Closed is called
// Remove it from the cached instances so it can be recreated
form.Closed += (s, e) => this.openedForms.Remove(form.GetType());
}
form.Show();
}
public DialogResult ShowModalForm<TForm>() where TForm : Form
{
using (var form = this.GetForm<TForm>())
{
return form.ShowDialog();
}
}
private Form GetForm<TForm>() where TForm : Form
{
return this.container.GetInstance<TForm>();
}
}
This class must be registered as Singleton
:
container.RegisterSingleton<IFormOpener, FormOpener>();
And can be used by injecting this service in for example your root form of the application:
public partial class RootForm : Form
{
private readonly IFormOpener formOpener;
public RootForm(IFormOpener formOpener)
{
this.formOpener = formOpener;
this.InitializeComponent();
}
private void ShowCustomers_Click(object sender, EventArgs e)
{
this.formOpener.ShowModelessForm<AllCustomersForm>();
}
private void EditCustomer_Click(object sender, EventArgs e)
{
var result = this.formOpener.ShowModalForm<EditCustomerForm>();
// do something with result
}
}
Simple Injector Property Injection in User Control Windows Forms
My problem is when using drag and drop to design UI, the user control is automatically generated using the new operator, and property injection doesn't work.
This is exactly why the documentation refers to property injection. Property injection allows to be applied on externally created instances (as user controls are created by the UI framework, not the DI Container).
What the documentation fails to mention, however, is how to initialize a created instance with dependencies. Simple Injector contains an InitializeInstance
for this. The following code allows initializing user controls that use explicit property injection:
private static Container container;
public static void InitializeControl(UserControl control)
{
var producer = container.GetRegistration(control.GetType(), true);
producer.Registration.InitializeInstance(control);
}
This, unfortunately, is far from perfect because it requires forms and user controls to depend on the DI Container. To prevent this, you can pass on dependencies from the containing form to the user control through Method Injection instead, but the downside of this is that you'd easily have to pass many dependencies, causing the consuming form's constructor to explode.
Alternatively, contrary to what the documentation states, it is possible to resolve user controls from the DI Container and circumvent using the designer as stated here by Ron. This might have consequences of its own, however, and I have no experience with this.
Using Simple Injector with Unit Of Work & Repository Pattern in Windows Form
The problem you have is the difference in lifestyles between your service, repository, unitofwork and dbcontext.
Because the MemberRepository
has a Singleton lifestyle, Simple Injector will create one instance which will be reused for the duration of the application, which could be days, even weeks or months with a WinForms application. The direct consequence from registering the MemberRepository
as Singleton is that all dependencies of this class will become Singletons as well, no matter what lifestyle is used in the registration. This is a common problem called Captive Dependency.
As a side note: The diagnostic services of Simple Injector are able to spot this configuration mistake and will show/throw a Potential Lifestyle Mismatch warning.
So the MemberRepository
is Singleton and has one and the same DbContext
throughout the application lifetime. But the UnitOfWork
, which has a dependency also on DbContext
will receive a different instance of the DbContext
, because the registration for DbContext
is Transient. This context will, in your example, never save the newly created Member
because this DbContext
does not have any newly created Member
, the member is created in a different DbContext
.
When you change the registration of DbContext
to RegisterSingleton
it will start working, because now every service, class or whatever depending on DbContext
will get the same instance.
But this is certainly not the solution because having one DbContext
for the lifetime of the application will get you into trouble, as you probably already know. This is explained in great detail in this post.
The solution you need is using a Scoped instance of the DbContext
, which you already tried. You are missing some information on how to use the lifetime scope feature of Simple Injector (and most of the other containers out there). When using a Scoped lifestyle there must be an active scope as the exception message clearly states. Starting a lifetime scope is pretty simple:
using (ThreadScopedLifestyle.BeginScope(container))
{
// all instances resolved within this scope
// with a ThreadScopedLifestyleLifestyle
// will be the same instance
}
You can read in detail here.
Changing the registrations to:
var container = new Container();
container.Options.DefaultScopedLifestyle = new ThreadScopedLifestyle();
container.Register<IMemberRepository, MemberRepository>(Lifestyle.Scoped);
container.Register<IMemberService, MemberService>(Lifestyle.Scoped);
container.Register<DbContext, MemberContext>(Lifestyle.Scoped);
container.Register<IUnitOfWork, UnitOfWork>(Lifestyle.Scoped);
and changing the code from btnSaveClick()
to:
private void btnSave_Click(object sender, EventArgs e)
{
Member member = new Member();
member.Name = txtName.Text;
using (ThreadScopedLifestyle.BeginScope(container))
{
var memberService = container.GetInstance<IMemberService>();
memberService.Save(member);
}
}
is basically what you need.
But we have now introduced a new problem. We are now using the Service Locator anti pattern to get a Scoped instance of the IMemberService
implementation. Therefore we need some infrastructural object which will handle this for us as a Cross-Cutting Concern in the application. A Decorator is a perfect way to implement this. See also here. This will look like:
public class ThreadScopedMemberServiceDecorator : IMemberService
{
private readonly Func<IMemberService> decorateeFactory;
private readonly Container container;
public ThreadScopedMemberServiceDecorator(Func<IMemberService> decorateeFactory,
Container container)
{
this.decorateeFactory = decorateeFactory;
this.container = container;
}
public void Save(List<Member> members)
{
using (ThreadScopedLifestyle.BeginScope(container))
{
IMemberService service = this.decorateeFactory.Invoke();
service.Save(members);
}
}
}
You now register this as a (Singleton) Decorator in the Simple Injector Container
like this:
container.RegisterDecorator(
typeof(IMemberService),
typeof(ThreadScopedMemberServiceDecorator),
Lifestyle.Singleton);
The container will provide a class which depends on IMemberService
with this ThreadScopedMemberServiceDecorator
. In this the container will inject a Func<IMemberService>
which, when invoked, will return an instance from the container using the configured lifestyle.
Adding this Decorator (and its registration) and changing the lifestyles will fix the issue from your example.
I expect however that your application will in the end have an IMemberService
, IUserService
, ICustomerService
, etc... So you need a decorator for each and every IXXXService
, not very DRY if you ask me. If all services will implement Save(List<T> items)
you could consider creating an open generic interface:
public interface IService<T>
{
void Save(List<T> items);
}
public class MemberService : IService<Member>
{
// same code as before
}
You register all implementations in one line using Batch-Registration:
container.Register(typeof(IService<>),
new[] { Assembly.GetExecutingAssembly() },
Lifestyle.Scoped);
And you can wrap all these instances into a single open generic implementation of the above mentioned ThreadScopedServiceDecorator
.
It would IMO even be better to use the command / handler pattern (you should really read the link!) for this type of work. In very short: In this pattern every use case is translated to a message object (a command) which is handled by a single command handler, which can be decorated by e.g. a SaveChangesCommandHandlerDecorator
and a ThreadScopedCommandHandlerDecorator
and LoggingDecorator
and so on.
Your example would then look like:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
public class CreateMemberCommand
{
public string MemberName { get; set; }
}
With the following handlers:
public class CreateMemberCommandHandler : ICommandHandler<CreateMemberCommand>
{
//notice that the need for MemberRepository is zero IMO
private readonly IGenericRepository<Member> memberRepository;
public CreateMemberCommandHandler(IGenericRepository<Member> memberRepository)
{
this.memberRepository = memberRepository;
}
public void Handle(CreateMemberCommand command)
{
var member = new Member { Name = command.MemberName };
this.memberRepository.Insert(member);
}
}
public class SaveChangesCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private ICommandHandler<TCommand> decoratee;
private DbContext db;
public SaveChangesCommandHandlerDecorator(
ICommandHandler<TCommand> decoratee, DbContext db)
{
this.decoratee = decoratee;
this.db = db;
}
public void Handle(TCommand command)
{
this.decoratee.Handle(command);
this.db.SaveChanges();
}
}
And the form can now depend on ICommandHandler<T>
:
public partial class frmMember : Form
{
private readonly ICommandHandler<CreateMemberCommand> commandHandler;
public frmMember(ICommandHandler<CreateMemberCommand> commandHandler)
{
InitializeComponent();
this.commandHandler = commandHandler;
}
private void btnSave_Click(object sender, EventArgs e)
{
this.commandHandler.Handle(
new CreateMemberCommand { MemberName = txtName.Text });
}
}
This can all be registered as follows:
container.Register(typeof(IGenericRepository<>),
typeof(GenericRepository<>));
container.Register(typeof(ICommandHandler<>),
new[] { Assembly.GetExecutingAssembly() });
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(SaveChangesCommandHandlerDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(ThreadScopedCommandHandlerDecorator<>),
Lifestyle.Singleton);
This design will remove the need for UnitOfWork
and a (specific) service completely.
Simple injector how to register /resolve collection of singletons against same interface
You are calling the wrong Register<T>
overload. You are actually calling Register<T>(params T[] singletons)
, instead of calling Register<T>(IEnumerable<Registration> registrations)
. This causes the registrations to be made as a collection of Registration
instances, instead of a collection of IGlobalExchangeRateLimitProvider
instances, as can be seen when hovering over the verified container:
Instead, include the type of the collection when calling Collection.Register
var container = new Container();
container.Collection.Register<IGlobalExchangeRateLimitProvider>(new[]
{
Lifestyle.Singleton.CreateRegistration<IGlobalExchangeRateLimitProvider>(
() => new GlobalExchangeRateLimitProvider("A"), container),
Lifestyle.Singleton.CreateRegistration<IGlobalExchangeRateLimitProvider>(
() => new GlobalExchangeRateLimitProvider("B"), container)
});
container.Verify();
set child winform properties from a parent form when using simple injector c#
First of all, you copied the FormOpener probably from this answer. But you missed the part about Forms needing to be transient. Don't register your forms as Singleton
. Especially because you dispose them, this will work one and exactly one time. The next time you would want to show a Form
you will get an ObjectDisposedException
.
When you register the Forms as Transient
Simple Injector will tell you that the forms implement IDisposable
and this is (of course) correct. But because you take care of disposing in the FormOpener
you can safely suppress this warning. Register your forms like this:
private static void RegisterWindowsForms(
this Container container, IEnumerable<Assembly> assemblies)
{
var formTypes =
from assembly in assemblies
from type in assembly.GetTypes()
where type.IsSubclassOf(typeof(Form))
where !type.IsAbstract
select type;
foreach (var type in formTypes)
{
var registration = Lifestyle.Transient.CreateRegistration(type, container);
registration.SuppressDiagnosticWarning(DiagnosticType.DisposableTransientComponent,
"Forms are disposed by application code. Letting Simple Injector do this " +
"is problematic because that would need a scope, which is impossible to do.");
container.AddRegistration(type, registration);
}
}
To come to your question:
What you need is some extra infrastructure to initialize the Form
.
By letting your forms implement an interface IFormInit<T>
you can pass data to the form and directly show it.
public interface IFormInit<T> : IDisposable
{
DialogResult InitAndShowForm(T data);
}
To let Simple Injector create the forms based on this interface we need to register them in the container. We can let Simple Injector search for all closed implementations by supplying a list of assemblies, like this:
container.Register(typeof(IFormInit<>), assemblies, Lifestyle.Transient);
Notice that Simple Injector will automatically merge these registrations with the ones from RegisterWindowsForms
. So you can now get an instance of Form
by calling:
container.GetInstance<PopupForm>();
or
container.GetInstance<IFormInit<SomeDataClass>>();
You can now add this code to your FormOpener
class:
public DialogResult ShowModalForm<TData>(TData data)
{
Type formType = typeof(IFormInit<>).MakeGenericType(typeof(TData));
dynamic initForm = this.container.GetInstance(formType);
DialogResult result = (DialogResult) initForm.InitAndShowForm(data);
initForm.Dispose();
return result;
}
This will get the Form
from the container based on the IFormInit<T>
type that it implements. When you get the form, you call the function on the interface instead of directly call Form.ShowDialog()
. When the form is closed you dispose of the Form.
Note: The use of dynamic
typing maybe needs clarification. Why it is needed is inspired by the QueryHandler
pattern described here.
Usage is as follows:
// Add a specific class to pass to the form
public class LocationDataWrapper
{
public DataTable LocationData { get; set; }
}
public partial class PopUpForm : Form, IFormInit<LocationDataWrapper>
{
public PopUpForm() => InitializeComponent();
// Implement the interface, the loaded event can be removed
public DialogResult InitAndShowForm(LocationDataWrapper data)
{
dgvNearestLocations.DataSource = data.LocationData;
return this.ShowDialog();
}
}
On button click event
DialogResult result = this._formOpener.ShowModalForm(new LocationDataWrapper
{
LocationData = locationDataTable,
});
You can create wrapper or data classes for each form and it will automatically show the correct form, when you let this Form
implement IFormInit<ThisSpecificDataClass>
.
Simple Injector - Registering ASP.NET Identity
The amount of error information given by Simple Injector can be a bit overwhelming, but you can distill the information down to two separate problems:
ApplicationUserManager
is registered as Transient- Types incorrectly depend on unregistered type
ApplicationSignInManager
(Transient) instead ofSignInManager<ApplicationUser, String>
(Web Request).
You should certainly read the Diagnostic Services documentation page in the Simple Injector documentation and especially read about Lifestyle Mismatches, Disposable Transient Components and Short-Circuited Dependencies to understand what Simple Injector is warning about and how to fix the problem.
In short, the fix is two-fold:
- Change the registration of
ApplicationUserManager
fromTransient
toScoped
to prevent it from having a too-short lifetime and to ensure it is disposed of. Also read this part of the documentation to learn the difference between Transient and Scoped from the context of Simple Injector. - Make sure that types (i.e.
AccountController
andManageController
) don't depend onApplicationSignInManager
(in their constructor), but instead depend on its base typeSignInManager<ApplicationUser, String>
.
Form1 is registered as transient, but implements IDisposable
I changed my Code in this line
container.Register<Form1>();
to
container.Register<Form1>(Lifestyle.Singleton);
and it works fine .You can see full code in below
private static void Bootstrap()
{
// Create the container as usual.
container = new Container();
// container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
// Register your types, for instance:
container.Register<IStudentService, StudentService>(Lifestyle.Singleton);
//container.Register<IUserContext, WinFormsUserContext>();
container.Register<IStudentRepository, StudentRepository>(Lifestyle.Singleton);
container.Register<Form1>(Lifestyle.Singleton);
// Optionally verify the container.
container.Verify();
}
Related Topics
Find If Point Lies on Line Segment
Output Console.Writeline from Wpf Windows Applications to Actual Console
Linq: from a List of Type T, Retrieve Only Objects of a Certain Subclass S
Very Simple Definition of Initializecomponent(); Method
How to Write a Comment to an Xml File When Using the Xmlserializer
How to Access Gmail API Using Service Account
How to Set a Proxy for Webbrowser Control Without Effecting the System/Ie Proxy
Opening Process and Changing Window Position
Use "Real" Cultureinfo.Currentculture in Wpf Binding, Not Cultureinfo from Ietflanguagetag
Windows Authentication in ASP.NET 5
Kestrel Shutdown Function in Startup.Cs in ASP.NET Core
Dynamically Create a Generic Type for Template
How to Get Dpi Scale for All Screens