In general, I would not place business logic in the view model layer. But the term "Business Logic" is misleading.
Eric Evans uses a model where business logic is divided into two categories
- Domain logic - Logic related to the actual problem domain you are solving
- Application logic - Logic related to the fact, that you are building an application
He mentions the example of an accounting application. Rules about accounts, posts, tax accounts, etc. are domain rules, rules pertaining to the domain of accounting. Logic about CSV import/export has nothing to do with the domain of accounting. These rules exists purely because we are building a software application. These are examples of application logic.
Domain rules should NEVER go into the view model layer. If you are following the MVVM pattern, then the domain rules go, without question, in the model layer.
Application rules, like CSV import/export, could go in the view model layer. But personally, I would prefer to separate that out into a separate application logic layer.
The View Model should be very simple. Looking up the data needed by the view in the corresponding model, updating the model when the view changes, listening to events in the model, and propagating those events to the view, allowing the view to be updated when the model is updated behind the scenes (if applicable).
Personally I would make sure that the view model layer contains only one type of logic, presentation logic.
My perspective is from years of experience working with Winforms, the "old fashioned way," with events and code-behind. So I can tell you with absolute certainty that, once you get beyond the simplest of applications, your code quickly becomes a big ball of mud. It was inevitable, because that's the way applications were written back then.
Webforms is just the same, except that it throws in the additional complication of being a stateful abstraction over a stateless system (the web). As Rob Conery put it:
WebForms is a lie. It’s abstraction wrapped in deception covered in
lie sauce presented on a plate full of diversion and sleight of hand.
Nothing you do with Webforms has anything to do with the web – you let
it do the work for you.
This, from the guy that wrote a fully functional object-relational mapper using the dynamic
keyword in C# and only four hundred lines of code. When he speaks authoritatively about something, I listen.
The application I'm currently working on is a Winforms application, with several tabs in the main form. I can dynamically load forms into each of the tabs. While I didn't follow MVVM or MVP (you can, with libraries like this one), I did aggressively push every bit of code I could out to a separate assembly, leaving only that code that is absolutely required to run the form. There's still several hundred lines of code in there, not counting the partial class containing the form's control definitions, properties and event handler assignments, but I should never have to touch it again, unless I need to add something new to the form.
I have a static method that I can hand a form and a collections of Key/Value pairs, and it will (using Reflection) automatically match up the keys to the fields in the form, and populate the form with the collection's values. I can also do the reverse, getting a collection of Key/Value pairs from the form's fields. That whole thing is about twenty lines of code, but it pays for itself every time I use it, because I don't have to write forty custom assignment statements for forty controls on a form.
I can take that Key/Value list, and serialize it to XML, allowing me to persist it to a file. I have other forms that I can hand a conventional DTO, and map its fields to the form. None of this would be practical in the "big ball of mud" style of Winforms. It's almost trivial when adequate decoupling is utilized.
Does any of this sound familiar? It should; it's essentially a poor-man's form of data binding.
Admittedly, I really like the databinding of WPF. To take advantage of
it, I set the data context to the window itself, where it has access
to the business class as well as any other observable properties.
Good for you. It doesn't have to be MVVM compliant. But remember, patterns like MVVM were created to help developers build big systems. If you just need to display a dialog, you may not need all of that plumbing.
Part of the complexity of systems like WPF is inherent in all programmer libraries that seek to solve a specific problem in a generalized way. To do that, you have to account for every practical way a programmer might use your library. That adds complexity, but for those who design their libraries well, it also brings to the table a philosophy of doing things in a uniform and consistent way.
Consider John Resig's jQuery library: it is the very essence of a coherent design, and it hides a lot of weird details about the DOM, and variations in the way browsers handle it. Is it simpler just to write a few lines of Javascript? Sometimes. But the benefit of writing jQuery code in a coherent, uniform API makes it easier for the next person who comes along to understand what you did, and maintain that code if needed.
My real experience with "big application" models is in using ASP.NET MVC. ASP.NET is to ASP.NET MVC as Winforms is to WPF. When I worked in ASP.NET, I always struggled to bend it to my will. ASP.NET MVC bends to you. It is a joy to use; it produces a system structure that is clear and organized, and gives you full control over your application and markup. You can use Javascript, jQuery and CSS liberally, and ASP.NET MVC stays out of your way.
My only hurdle was getting used to it, and getting to know it on its own terms.
Further Reading
You Should Learn MVC by Rob Conery
Best Answer
It's the same approach as with any other major refactoring.
Built unit tests to validate the behavior of the existing code.
Identify and isolate small sections of code that can be refactored.
Refactor one section at a time.
Verify that all unit tests (see step 1) continue to pass. If they fail, fix your refactoring.
Rinse and repeat until you regret the day you ever decided to refactor the code base.
Eventually you'll complete the refactoring. Swear off code-behind and promise yourself that you'll never, ever make those mistakes again.
As far as how to separate out business logic from UI logic within MVVM. For the most part, all of your business logic should live at your Model layer or lower. All UI related logic, including properties to be bound to, will live at the View and ViewModel layer.
A good rule of thumb on "Is it business logic or UI logic" is the following. If the behavior of the code should persist regardless of what the UI is, that's business logic. If the code's behavior is tied to the presentation of the data, that's UI logic.