How to combine designable components with dependen

2020-06-13 03:48发布

问题:

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?


edit: I have removed "(e.g. a WinForms UserControl)" since I intended the question to be about components in general. Components are all about inversion of control (see section 8.3.1 of the UMLv2 specification) so I don't think that "you shouldn't inject any services" is a good answer.


edit 2: It took some playing with WPF and the MVVM pattern to finally "get" Mark's answer. I see now that visual controls are indeed a special case. As for using non-visual components on designer surfaces, I think the .NET component model is fundamentally incompatible with dependency injection. It appears to be designed around the service locator pattern instead. Maybe this will start to change with the infrastructure that was added in .NET 4.0 in the System.ComponentModel.Composition namespace.

回答1:

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.