Switching to MVVM

1. Introduction

Writing an application with good practices and proven architecture design is comparatively easy if we are going to write a new application. But most of the time we are working on existing applications, where it is not easy to make changes. If we ever get the chance to rewrite the existing application, we probably won't repeat the same mistakes that we or someone else made due to time constraints, technology limitation, scope creep etc. It would be better if we could refactor the existing codebase for betterment at the same time, with minimal or no risk effect.

Model-View-ViewModel (MVVM) is a proven design pattern used heavily in many WPF / Silverlight applications. But it might be possible that we already have a lot of code base that is not taking an advantage of it. This article focuses on implementing MVVM on existing applications rather than starting a new application. In this article we are going to see how we can take small steps towards MVVM. In the first part of this article, we are going to study the evaluation of MVVM and how we came to this point. The second part will examine the different steps to achieve MVVM. Although some steps have a major rework with the potential of high risk, there are still few things that have little or no effect on the project.

2. Evaluation of MVVM

Let's look at MVVM from a higher level and take a step-by-step approach to understanding it. Here our discussion is based on the complexity of the architecture from simple to complex, not from an historical order.

Probably the simplest design principle to separate the data from its presentation is Observer design pattern [1]. In Observer design pattern, we have two different classes for data (subject/model) or its presentation (observer/view). Subject classes contain the instance of all of the observers and send notification to all of observers when there is any change in the data. Here is a simple block diagram of observer design pattern.

Observer Desian Pattern
Figure 1: Observer Desian Pattern

The next step is to introduce the middle layer in between the data and its presentation. The main purpose of this layer is to communicate between these two components. This is a main concept of Model View Controller (MVC) [2]. It is shown by this block diagram.

MVC
Figure 2: MVC

This approach has some advantages and disadvantages. The main disadvantage is that our view is not totally independent of our model. Model View Presenter (MVP) [3] handles exactly the same problem. In the MVP model there is no relation between View and Model.

MVP
Figure 3: MVP

MVVM is very similar to MVP pattern. Or it is some sort of specialized form of MVP pattern. In MVVM, Presentator is known as ViewModel. Model communicates with ViewModel with notification and ViewModel communicates with View with data binding and command binding as shown by this block diagram.

MVVM
Figure 4: MVVM

Now let's take a more detailed look at MVVM. What is the biggest advantage of this? Its first advantage is that our presentation is totally unaware of our model. We don't write any user interface specific code in ViewModel and all the communication is based on data binding and command binding, which means we can easily write a unit tests for it. We can easily change any user interface or even change the data model easily. Here is a detail block diagram of MVVM.

MVVM
Figure 5: MVVM

This diagram explains how we can take advantage of MVVM. The most important thing in this pattern is to properly design the ViewModel. WPF has very orthogonal design. It means we can customize or enhanced different parts of the library without affecting others. Most of the WPF reusability is based on composition rather than inheritance; therefore we can take maximum advantage of it, even at run time, because we can easily change the composition behavior at run time but not the inherited components. In this block diagram we see the major components of the WPF class library that we developed, enhanced or customized in most of the WPF application.

ViewModel
Figure 6: ViewModel

Switching to MVVM

3. Switching to MVVM

We are going to start with a simple application, that is not written in WPF and gradually make step-by-step changes in it to introduce MVVM pattern. Our starting point is a loan amortization application written in VC++ and using WPF. The reason to pick that application is that by definition we can't use MVVM with VC++; XAML has very limited support in VC++ (The only support as of now is to load XAML at runtime and use it).

It is possible that we have some properties in our class that store data, but we usually have fields to store information. In our starting project, we have a class to store information about each payment. Here is our class.

public class PaymentInfo
{
	public int PaymentNo
	{ get; set; }

	public double Payment
	{ get; set; }

	public double Principle
	{ get; set; }

	public double Interest
	{ get; set; }

	public double Balance
	{ get; set; }
}

We have field variables to store information from the user interface and display it back to the user interface.

private double principle;
private double interestRate;
private int duration;
private double payment;

Here is a piece of code to get user input and store it in field variables

if (txtPrincipleAmount.Text.Length > 0)
{
	principle = Convert.ToDouble(txtPrincipleAmount.Text);
}
else
{
	MessageBox.Show("Please enter principle amount", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
	return;
}

if (txtInterestRate.Text.Length > 0)
{
	interestRate = Convert.ToDouble(txtInterestRate.Text);
	interestRate /= 100;
	interestRate /= 12;
}
else
{
	MessageBox.Show("Please enter interest", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
	return;
}

if (txtDuration.Text.Length > 0)
{
	duration = Convert.ToInt32(txtDuration.Text);
}
else
{
	MessageBox.Show("Please enter duration", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
	return;
}

We have some utility methods to perform our calculations, which we call from our event handler.

// Calculate the remaining balance at particular payment
private double CalculateBalance(int month)
{
	double interestTerm = Math.Pow((1 + interestRate), month);
	double totalInterest = principle * interestTerm;
	double totalPaid = payment * (interestTerm - 1) / interestRate;
	return totalInterest - totalPaid;
}

// Calculate the Interest part of any particular payment
private double CalculateInterestPart(int month)
{
	double interestTerm = Math.Pow((1 + interestRate), (month - 1));
	double totalInterest = principle * interestTerm;
	double totalPaid = payment * (interestTerm - 1) / interestRate;
	return (totalInterest - totalPaid) * interestRate;
}

// Calculate the principle part of any particular payment
private double CalculatePrinciple(int month)
{
	return payment - CalculateInterestPart(month);

We have one method to calculate the amortization schedule for the complete loan period and add those values in DataGrid.

// Calculate the complete amortization schedule and fill the data grid control
private void CalculatePayment()
{
	int totalpayments = duration * 12;

	Title = "Amortization Schedule for " + 
		Convert.ToString(totalpayments) + " Payments";

	// calculate interest term
	double interestTerm = Math.Pow((1 + interestRate), totalpayments);

	// calculate payment
	payment = (principle * interestRate) / (1 - (1 / interestTerm));

	payments.Clear();

	for (int iIndex = 1; iIndex <= totalpayments; ++iIndex)
	{
		PaymentInfo paymentInfo = new PaymentInfo();
		paymentInfo.PaymentNo = iIndex;
		paymentInfo.Balance = CalculateBalance(iIndex);
		paymentInfo.Payment = payment;
		paymentInfo.Interest = CalculateInterestPart(iIndex);
		paymentInfo.Principle = CalculatePrinciple(iIndex);

		payments.Add(paymentInfo);
	}

	lstAmortization.ItemsSource = payments;
}

Note that the user interface code is heavily coupled inside the business logic, in this case calculating the loan amortization. There is no simple way to write test cases to check this calculation without involving the user interface.

The XAML of our project is very simple. Here is the complete XAML code of our program.

<Window x:Class="MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="400" Width="600">
    <Grid Background="Beige">
        <Grid.RowDefinitions>
            <RowDefinition Height="2*"/>
            <RowDefinition Height="2*"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" FontSize="18" FontWeight="Bold"
                       HorizontalAlignment="Center" VerticalAlignment="Center">Loan Amortization</TextBlock>
            <TextBlock Grid.Column="0" Grid.Row="1" VerticalAlignment="Center" Margin="5">Principle Amount</TextBlock>
            <TextBlock Grid.Column="0" Grid.Row="2" VerticalAlignment="Center" Margin="5">Interest Rate</TextBlock>
            <TextBlock Grid.Column="0" Grid.Row="3" VerticalAlignment="Center" Margin="5">Duration</TextBlock>
            <TextBox Grid.Column="1" Grid.Row="1" Margin="5" VerticalAlignment="Center" Name="txtPrincipleAmount"/>
            <TextBox Grid.Column="1" Grid.Row="2" Margin="5" VerticalAlignment="Center" Name="txtInterestRate"/>
            <TextBox Grid.Column="1" Grid.Row="3" Margin="5" VerticalAlignment="Center" Name="txtDuration"/>
        </Grid>
        <DataGrid Grid.Row="1" Name="lstAmortization" Margin="5">
        </DataGrid>       
        <Grid Grid.Row="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Grid.Column="0" Name="btnCalculate" Width="75" Height="45" Click="btnCalculate_Click">Calculate</Button>
            <Button Grid.Column="1" Name="btnExit" Width="75" Height="45" Click="btnExit_Click">Exit</Button>
        </Grid>
    </Grid>
</Window>

Also note that like other programming styles, here we have the name of every control.

Switching to MVVM

3.1. Step 1: Use Properties

If code is using a field not properties, then convert those from field to properties. (This should have no or minimal effect on existing source code, because I chose property names very similar to (or the same) as fields. We simply change the uses the properties instead of fields in this step. Here is an updated version of our program.

public double Principle
{ get; set; }

public double InterestRate
{ get; set; }

public int Duration
{ get; set; }

public double Payment
{ get; set; }

There is no change in XAML of the program. If we use the same property name as the previously used field, then we don't have to change anything in the code either. In our example, I used the letter case for properties and camel case for fields, so we updated the source code accordingly and use letter case there.

3.2. Step 2: Implement INotifyPropertyChanged Interface / Dependency Property

My first step is to change the data model to make it WPF friendly. To do this I will either implement the INotifyPropertyChange interface or make them dependency property. If we already have properties, then this should have no effect at all for the rest of the project. The rest of the code should be the same and should work the same, but at least now you have your model ready.

In this example we are going to change our properties to dependency properties. In the coming steps we will see the example of the INotifyPropertyChanged interface too, to see both variants. Here is our updated code.

public static readonly DependencyProperty PrincipleProperty =
	DependencyProperty.Register("Principle", typeof(double), typeof(MainWindow));

public double Principle
{
	get { return (double)GetValue(PrincipleProperty); }
	set { SetValue(PrincipleProperty, value); }
}

public static readonly DependencyProperty InterestRateProperty =
	DependencyProperty.Register("InterestRate", typeof(double), typeof(MainWindow));

public double InterestRate
{
	get { return (double)GetValue(InterestRateProperty); }
	set { SetValue(InterestRateProperty, value); }
}

public static readonly DependencyProperty DurationProperty =
	DependencyProperty.Register("Duration", typeof(int), typeof(MainWindow));

public int Duration
{
	get { return (int)GetValue(DurationProperty); }
	set { SetValue(DurationProperty, value); }
}

public static readonly DependencyProperty PaymentProperty =
	DependencyProperty.Register("Payment", typeof(double), typeof(MainWindow));

public double Payment
{
	get { return (double)GetValue(PaymentProperty); }
	set { SetValue(PaymentProperty, value); }
}

There is no change in the XAML file.

3.3. Step 3: Use Data Binding

The next step is using Data Binding. This will reduce lots of thecodebase. Now it shouldn't have any code like this:

txtName.Text = firstName;

This is a major change in the code base. But at this step I focus only on data, not on behavior. My code still has event handlers and should work fine, but now I don't have to worry about updating the data into control or getting data from control to my variable. (No more control to variable interaction). Now we only check the invalid input directly from the properties (they automatically get values from the user interface). For validation purposes, we are going to introduce a new class inherited by ValidationRule class. Here is the code of our new class.

public class MyValidation : ValidationRule
{
	public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
	{
		int number = Int32.Parse((string)value);

		if (number < 0)
		{
			return new ValidationResult(false, "Please enter values greater than zero.");
		}

		return new ValidationResult(true, null);
	}
}

Now we have to update our XAML and use the Data binding there. Here is an updated version of our XAML. In this version we also include our validation class to perform validation.

<TextBox Grid.Column="1" Grid.Row="1" Margin="5" VerticalAlignment="Center">
	<TextBox.Text>
		<Binding Path="Principle">
			<Binding.ValidationRules>
				<local:MyValidation/>
			</Binding.ValidationRules>
		</Binding>
	</TextBox.Text>
</TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Margin="5" VerticalAlignment="Center">
	<TextBox.Text>
		<Binding Path="InterestRate">
			<Binding.ValidationRules>
				<local:MyValidation/>
			</Binding.ValidationRules>
		</Binding>
	</TextBox.Text>
</TextBox>
<TextBox Grid.Column="1" Grid.Row="3" Margin="5" VerticalAlignment="Center">
	<TextBox.Text>
		<Binding Path="Duration">
			<Binding.ValidationRules>
				<local:MyValidation/>
			</Binding.ValidationRules>
		</Binding>
	</TextBox.Text>
</TextBox>

We also define one style to display the error message when validation fails. Here is the code of our style.

<Style TargetType="{x:Type TextBox}">
	<Style.Triggers>
		<Trigger Property="Validation.HasError" Value="true">
			<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
				Path=(Validation.Errors)[0].ErrorContent}"/>
		</Trigger>
	</Style.Triggers>
</Style>

Also note that now we no longer need to specify the name of control; with the help of data binding we will automatically get the values in the correct property (dependency property in this example).

3.4. Step 4: Refactor Event Handler

This is an intermediate step. Now I already have all the data handling in the form of data binding with no control/variable code in it. I am going to iterate through all event handlers and whatever code is written there, move them into a method and call that method from the event handler. This is the updated version of our event handler now.

private void btnCalculate_Click(object sender, RoutedEventArgs e)
{
	CalculatePayment();
}

3.5. Step 5: Implement ICommand Interface

In the next step I am going to introduce the ICommand interface and implement it. There is no change for the existing codebase. Here is one example of it.

public class MyCommand : ICommand
{
	public Action Function
	{ get; set; }

	public MyCommand()
	{
	}

	public MyCommand(Action function)
	{
		Function = function;
	}

	public bool CanExecute(object parameter)
	{
		if (Function != null)
		{
			return true;
		}

		return false;
	}

	public void Execute(object parameter)
	{
		if (Function != null)
		{
			Function();
		}
	}

	public event EventHandler CanExecuteChanged
	{
		add { CommandManager.RequerySuggested += value; }
		remove { CommandManager.RequerySuggested -= value; }
	}
}

There is no change in XAML of our project.

Switching to MVVM

3.6. Step 6: Add ICommand Properties

Now I am going to add properties in my class (which has event handler earlier). The type of that property is ICommand. This again should not have any effect on the rest of the code. The number of properties should be at least the same as the method I called inside the event handler and I chose a similar name.

public ICommand ExitCommand
{ get; set; }

public ICommand CalculateAmortizationCommand
{ get; set; }

There is no change in XAML of our project.

3.7. Step 7: Assign Methods to ICommand Type Properties

I have already created the method to calculate amortization (or do other work). Create object of MyCommand class and assign it to our ICommand properties in constructor.

ExitCommand = new MyCommand(Close);
CalculateAmortizationCommand = new MyCommand(CalculatePayment);

There is no change in XAML of our project.

3.8. Step 8: Use Command Binding

Remove the event handler from the code as well as from XAML and instead use the command binding. This is again a major change in the code (both code and XAML).

<Button Grid.Column="0" Name="btnCalculate" Width="75" Height="45" Command="{Binding CalculateAmortizationCommand}" >Calculate</Button>
<Button Grid.Column="1" Name="btnExit" Width="75" Height="45" Command="{Binding ExitCommand}" >Exit</Button>

Also note that now all of our business logic is in method, not in event handler, which means now we can somehow perform unit testing on those methods without any user interface involvement.

3.9. Step 9: Define ViewModel Class

Define a class to ViewModel and move all ICommand interface properties and methods assigned to it in this class. We introduced one event type property in our ViewModel class to handle the close event.

public event EventHandler RequestClose;

private void CloseWindow()
{
	EventHandler handler = this.RequestClose;

	if (handler != null)
	{
		handler(this, EventArgs.Empty);
	}
}

We set this event handler from our window (view) class. There are multiple ways to do this, but here we are doing this with unnamed delegate.

public partial class MainWindow : Window
{
	private MyViewModel vm = new MyViewModel();

	public MainWindow()
	{
		InitializeComponent();

		vm.RequestClose += delegate
		{
			Close();
		};

		DataContext = vm;
	}
}

Here is the complete code of our ViewModel class.

public class MyViewModel : DependencyObject
{
	public static DependencyProperty PrincipleProperty =
		DependencyProperty.Register("Principle", typeof(double), typeof(MainWindow));

	public double Principle
	{
		get { return (double)GetValue(PrincipleProperty); }
		set { SetValue(PrincipleProperty, value); }
	}

	public static DependencyProperty InterestRateProperty =
		DependencyProperty.Register("InterestRate", typeof(double), typeof(MainWindow));

	public double InterestRate
	{
		get { return (double)GetValue(InterestRateProperty); }
		set { SetValue(InterestRateProperty, value); }
	}

	public static DependencyProperty DurationProperty =
		DependencyProperty.Register("Duration", typeof(int), typeof(MainWindow));

	public int Duration
	{
		get { return (int)GetValue(DurationProperty); }
		set { SetValue(DurationProperty, value); }
	}

	public static DependencyProperty PaymentProperty =
		DependencyProperty.Register("Payment", typeof(double), typeof(MainWindow));

	public double Payment
	{
		get { return (double)GetValue(PaymentProperty); }
		set { SetValue(PaymentProperty, value); }
	}

	public event EventHandler RequestClose;

	public ObservableCollection Payments
	{ get; set; }

	public ICommand CalculateAmortizationCommand
	{ get; set; }

	public ICommand ExitCommand
	{ get; set; }

	public MyViewModel()
	{            
		CalculateAmortizationCommand = new MyCommand(CalculatePayment);
		ExitCommand = new MyCommand(CloseWindow);

		Payments = new ObservableCollection();
	}

	private void CloseWindow()
	{
		EventHandler handler = this.RequestClose;

		if (handler != null)
		{
			handler(this, EventArgs.Empty);
		}
	}

	// Calculate the complete amortization schedule and fill the list control
	private void CalculatePayment()
	{
		int totalpayments = Duration * 12;

		double monthlyInterest = CalculateMonthlyInterest(InterestRate);

		// calculate interest term
		double interestTerm = Math.Pow((1 + monthlyInterest), totalpayments);

		// calculate payment
		Payment = (Principle * monthlyInterest) / (1 - (1 / interestTerm));

		Payments.Clear();

		for (int iIndex = 1; iIndex <= totalpayments; ++iIndex)
		{
			PaymentInfo paymentInfo = new PaymentInfo();
			paymentInfo.PaymentNo = iIndex;
			paymentInfo.Balance = CalculateBalance(iIndex);
			paymentInfo.Payment = Payment;
			paymentInfo.Interest = CalculateInterestPart(iIndex);
			paymentInfo.Principle = CalculatePrinciple(iIndex);

			Payments.Add(paymentInfo);
		}

		//lstAmortization.ItemsSource = payments;
	}

	// Calculate the remaining balance at particular payment
	private double CalculateBalance(int month)
	{
		double monthlyInterest = CalculateMonthlyInterest(InterestRate);

		double interestTerm = Math.Pow((1 + monthlyInterest), month);
		double totalInterest = Principle * interestTerm;
		double totalPaid = Payment * (interestTerm - 1) / monthlyInterest;
		return totalInterest - totalPaid;
	}

	// Calculate the Interest part of any particular payment
	private double CalculateInterestPart(int month)
	{
		double monthlyInterest = CalculateMonthlyInterest(InterestRate);

		double interestTerm = Math.Pow((1 + monthlyInterest), (month - 1));
		double totalInterest = Principle * interestTerm;
		double totalPaid = Payment * (interestTerm - 1) / monthlyInterest;
		return (totalInterest - totalPaid) * monthlyInterest;
	}

	// Calculate the principle part of any particular payment
	private double CalculatePrinciple(int month)
	{
		return Payment - CalculateInterestPart(month);
	}

	// Calculate the monthly interest rate
	private double CalculateMonthlyInterest(double InterestRate)
	{
		double monthlyInterest = InterestRate;
		monthlyInterest /= 100;
		monthlyInterest /= 12;

		return monthlyInterest;
	}
}

Note this class doesn't have any user interface element so we can easily write test cases on it. In addition to this, if we want to change the presentation of this, such as WPF to Silverlight or a Console based application, we can do it very easily. This class contains all the business logic and now it is up to the user of this class how to present that information.

Switching to MVVM

3.10. Step 10: Define ViewModelBase Class

Let's do one more step. We might want to do the same thing again and again, so why not create one base class to have minimum ViewModel functionality to reuse it in other ViewMode classes. One more reason to create a ViewModelBase class is to show how to implement the INotifyPropertyChanged interface. Here is the code of our base class.

public abstract class ViewModelBase : INotifyPropertyChanged
{
	public event EventHandler RequestClose;

	public ICommand ExitCommand
	{ get; set; }

	public void Close()
	{
		EventHandler handler = this.RequestClose;

		if (handler != null)
		{
			handler(this, EventArgs.Empty);
		}        
	}

	public void RaisePropertyChanged(string propertyName)
	{
		PropertyChangedEventHandler handler = PropertyChanged;

		if (handler != null)
		{
			handler(this, new PropertyChangedEventArgs(propertyName));
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;
}

Also note that we made our ViewModelBase class abstract to avoid creating an object of this class. Here is part of our ViewModel class inherited by ViewModelBase.

public class MyViewModel : ViewModelBase
{
	public double principle;
	public double interestRate;
	public int duration;
	public double payment;

	public double Principle
	{
		get { return principle; }
		set
		{
			principle = value;
			RaisePropertyChanged("Principle");
		}
	}

	public double InterestRate
	{
		get { return interestRate; }
		set
		{
			interestRate = value;
			RaisePropertyChanged("InterestRate");
		}
	}

	public int Duration
	{
		get { return duration; }
		set
		{
			duration = value;
			RaisePropertyChanged("Duration");
		}
	}

	public double Payment
	{
		get { return payment; }
		set
		{
			payment = value;
			RaisePropertyChanged("Payment");
		}
	}

}

In this class we raise the property change event whenever there is a change in the property. There are lots of other things we can do to improve our code base, but this is just a first step in the right direction. We can easily include more steps in the following guidelines, but after following these steps we will have a reasonably good design of our program towards MVVM.

Here is the output of this program.

[MVVM_07.gif]
Figure 7: Loan Amortization Output

4. References

1. Design Pattern, Elements of Reusable Object Oriented Software
Erich Gamm, Richard Helm, Ralph Johnson, John Vlissides

2. A Cookbook for Using the Model-View-Controller User Interface Paradigm in Smalltalk-80
Glenn E. Krasner, Stephen T. Pope
Journal of Object Oriented Programming, August/September 1988
http://www.ics.uci.edu/~redmiles/ics227-SQ04/papers/KrasnerPope88.pdf

3, GUI Architectures
Martin Fowler
http://www.martinfowler.com/eaaDev/uiArchs.html

4. Loan Amortization Application in WPF using C++
Zeeshan Amjad
http://www.codeproject.com/KB/WPF/LoanAmortizationWPF.aspx
http://www.codeguru.com/cpp/cpp/cpp_managed/general/print.php/c16355/



About the Author

Zeeshan Amjad

C++ Developer at Bechtel Corporation. zamjad.wordpress.com

Related Articles

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Hybrid cloud platforms need to think in terms of sweet spots when it comes to application platform interface (API) integration. Cloud Velocity has taken a unique approach to tight integration with the API sweet spot; enough to support the agility of physical and virtual apps, including multi-tier environments and databases, while reducing capital and operating costs. Read this case study to learn how a global-level Fortune 1000 company was able to deploy an entire 6+ TB Oracle eCommerce stack in Amazon Web …

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds