When creating a designable .NET component, you are required to provide a default constructor. From the IComponent documentation:
To be a component, a class must implement the IComponent interface and provide a basic constructor that requires no parameters or a single parameter of type IContainer.
This makes it impossible to do dependency injection via constructor arguments. (Extra constructors could be provided, but the designer would ignore them.) Some alternatives we're considering:
Service Locator
Don't use dependency injection, instead use the service locator pattern to acquire dependencies. This seems to be what IComponent.Site.GetService is for. I guess we could create a reusable ISite implementation (ConfigurableServiceLocator?) which can be configured with the necessary dependencies. But how does this work in a designer context?
Dependency Injection via properties
Inject dependencies via properties. Provide default instances if they are necessary to show the component in a designer. Document which properties need to be injected.
Inject dependencies with an Initialize method
This is much like injection via properties but it keeps the list of dependencies that need to be injected in one place. This way the list of required dependencies is documented implicitly, and the compiler will assists you with errors when the list changes.
Any idea what the best practice is here? How do you do it?
This same question bugged me for a long time until I realized that I was thinking about it in a wrong way. AFAIR, the only reason for creaing an IComponent implementation is to provide design-time features - there's no run-time effect of IComponent implementations.
By corrolary, this means that you should mostly create components to implement design-time features. Particularly for Controls, this means that you can configure the component to behave in a certain way. It is very important to realize that this is a completely different concern than how the component actually behaves or which data it displays. It should not have behavior at design-time, and nor should it contain data.
As such, the constraint on the constructor is actually a blessing, since it instructs you to rethink your design. A Control is a data-source agnostic piece of software than displays and interacts with data in a certain way. As long as that data conforms to certain interfaces, etc. the Control is happy. How that data arrives is of no concern to the Control, and neither should it be. It would be an error to let the Control control how data is loaded and modified.
In WPF, this is explicitly much clearer than in Windows Forms: You give a particular Control a DataContext and bind properties of the Control to members of that DataContext. The DataContext (which can be any object) originates from outside the Control; that's the responsibility or your Presentation Layer.
In Windows Forms, you can still do the same by assigning a data context to a Control. Essentially, this is Property Injection - just be aware that you should not inject services; you should inject data.
In summary, I would go with neither of your suggestions. Instead, let the Control have one or more properties that allow you to assign data to the Control, and use databinding to bind against this data. Inside the Control's implementation, be prepared to handle the situation where there's no data: This will occur every time the Control is hosted by VS at design-time. The Null Object pattern very useful for implementing such resilience.
Then, set up the data context from your Controllers. That's the MVC way of doing it: The Control is the View, but you should have a separate Controller that can instantiate and assign a Model to the View.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With