Your question is odd. You're question implies that you would write your business layer for a Winforms app in a different way than you would in a web application, but if you apply layering correctly, the business layer should be completely independent of the used technology. So in that sense, if you would apply the Dependency Injection pattern in the business layer of a web app, you should also apply it in the business layer of a desktop application.
I'm currently working on a Winforms project myself and using the Dependency Injection pattern (and an IoC container) extensively. For me there's not a question whether DI should be used; it follows naturally from applying the SOLID principles. Whether or not you should use a IoC container however, is a totally different question, although for the types of application's I write and the type's of architectures I use, I can't imagine life without it.
Although desktop applications are very different in nature than web applications are, I use the same patterns on both types of applications. For instance, I use constructor injection in my Windows Forms classes and those forms mainly depend on a few generic interfaces, namely:
IRepository<TEntity>
(the repository pattern) for loading an entities.
IQueryHandler<TQuery, TResult>
for doing complex or custom queries of all sorts.
ICommandHandler<TCommand>
for execution of use cases (processing user actions).
I use the same abstractions in the web applications I build.
Those interfaces helped me a few months back to change this desktop application from a 2-tier application (all business logic ran in the desktop application) to a 3-tier application (where all business logic is now moved to a WCF service). We were able to do this without having to change any code in the Forms.
In the 2-tier model we didn't inject ICommandHandler<TCommand>
implementations directly, but injected a (singleton) proxy class that would create a new implementation each time it was called. For instance, when the form called the injected ICommandHandler<ProcessOrder>
, the actual CommandHandlerProxy<ProcessOrder>
would start a lifetime scope (a lifestyle that mimics the per-request lifestyle of a web application) and would create the real ProcessOrderCommandHandler
class that would do the actual logic. By doing this we ensured that a single unit of work (Entity Framework's DbContext
in our case) would be injected in all classes within this 'request'. All of course with dependency injection all the way down the call graph.
In the new 3-tier model, the forms are injected with an WcfProxyCommandHandler<TCommand>
which will serialize the given command to JSON and send it over to the WCF service, which will pick it up, deserializes the command, creates the ProcessOrderCommandHandler
and executes the command.
But bear in mind that this model is probably very different than what you're probably used to do. For instance:
- The real entities are hidden behind the WCF service. The desktop application knows nothing about them.
- Instead DTOs are returned from the WCF service when data is requested through the
IQueryHandler<TQuery, TResult>
abstraction.
- We use Entity Framework 5 (POCO classes with T4 and designer).
- Those DTOs are (mostly) used for reading; they're not sent back to the server for updating.
- Any request for a change of state (the execution of a use case) is done by sending a command message to the server (through the
ICommandHandler<TCommand>
abstraction).
- One single use case is encapsulated in single class that implements the
ICommandHandler<TCommand>
interface.
And as I said, it's Dependency Injection all the way down and this and the described design gives us much flexibility. For instance:
- We find it very easy to add new functionality.
- We find it very easy to add new cross-cutting concerns.
- It lowers the mental barrier; it makes the application easier to maintain and much less likely to break in unexpected ways.
There is however one thing I found out in the process:
- Binding in winforms is optimized to work with DataSets. If you try anything else (Poco's, Entity Framework entities, etc) you will get some very frustrating moments where you find out that the support for anything else but DataSets is minimal. It's clear that Microsoft hasn't invested in this area and won't investing in this area anymore. To workaround these limitations we wrote our own
BindingList<T>
implementation and found it very hard to create an implementation that works correctly with sorting and filtering (especially since our DTOs don't implement INotifyPropertyChanged
). We also wrote our own infrastructure to add DataAnnotations validation support to Winforms.
If you want to read more about the designs I use, please read these articles:
- Meanwhile... on the command side of my architecture
- Meanwhile... on the query side of my architecture
- Writing Highly Maintainable WCF Services