This project has moved. For the latest updates, please go here.

DIY: Simple Scenario

This tutorial helps you to get your feet wet with the MVMMapper. It guides you in downloading the latest version and in starting your first Silverlight project using the mapper. The application you will make is very basic as it only shows the reading of data. For more advanced scenarios see: DIY: More complex scenarios Make Silverlight Project.png
  • Create the web that will host the Silverlight application and the web service
Make Silverlight Application and Web.png
  • Add a web service to the PersonLookup.Web
add web service.png
  • Change the properties of the PersonLookup.Web project to make sure that the web service will always be located at the same address
Fix web address.png
  • Add an ASP.NET Folder (App_Data) to the PersonLookup.Web project.
  • Add a SQL Database to the App_Data folder:
Add DB.png
  • Double click the database file so the Server Explorer will show the database. Expand the PersonsDB.mdf node and right-click the Table node.
  • Select Add new Table.
  • Add three columns to the table, right-click the ID column and mark it as primary key, configure the ID column to be an identity:
Configure Primary Key Person Table.png
  • Save the table as Person
  • Open the web.config file and add a connection string:
<configuration>
  <configSections>
  </configSections>
  <connectionStrings>
    <add name="PersonsDB"
         connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\PersonsDB.mdf;Integrated Security=True;User Instance=True;"/>
  </connectionStrings>

  <system.web>
  • Change this connection string depending on your setup. E.g., your instance name of SQL Server might be different.
  • Implement the web service
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;

namespace PersonLookup.Web
{
    [DataContract]
    public class Person
    {
        [DataMember]
        public int ID { get; set; }
        [DataMember]
        public string FirstName { get; set; }
        [DataMember]
        public string LastName { get; set; }
    }



    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(
        RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class PersonService
    {
        private string ConnectionString
        {
            get
            {
                return ConfigurationManager.ConnectionStrings["PersonsDB"].ConnectionString;
            }
        }

        [OperationContract]
        public List<Person> GetPersons()
        {
            var persons = new List<Person>();
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();
                using (var command = new SqlCommand("select ID, FirstName, LastName from Person", connection))
                {
                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            persons.Add(
                                new Person
                                {
                                    ID = reader.GetInt32(0),
                                    FirstName = reader.GetString(1),
                                    LastName = reader.GetString(2)
                                }
                            );
                        }
                    }
                }
            }
            return persons;
        }

        [OperationContract]
        public Person AddPerson(Person newPerson)
        {
            object result;
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();
                using (var command = new SqlCommand("insert into Person(FirstName, LastName) values (@firstName, @lastName); select SCOPE_IDENTITY()", connection))
                {
                    command.Parameters.Add("@firstName", System.Data.SqlDbType.NVarChar).Value = newPerson.FirstName;
                    command.Parameters.Add("@lastName", System.Data.SqlDbType.NVarChar).Value = newPerson.LastName;
                    result = command.ExecuteScalar();
                }
            }
            newPerson.ID = Convert.ToInt32((decimal)result);
            return newPerson;
        }

        [OperationContract]
        public int DeletePerson(Person person)
        {
            object result = null;
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();
                using (var command = new SqlCommand("delete from Person where ID = @id;select @@rowcount", connection))
                {
                    command.Parameters.Add("@id", System.Data.SqlDbType.Int).Value = person.ID;
                    result = command.ExecuteScalar();
                }
            }
            return (int)result;
        }

        [OperationContract]
        public int UpdatePerson(Person person)
        {
            object result;
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();
                using (var command = new SqlCommand("update Person set FirstName=@firstName, LastName=@lastName where ID=@id; select @@rowcount", connection))
                {
                    command.Parameters.Add("@id", System.Data.SqlDbType.Int).Value = person.ID;
                    command.Parameters.Add("@firstName", System.Data.SqlDbType.NVarChar).Value = person.FirstName;
                    command.Parameters.Add("@lastName", System.Data.SqlDbType.NVarChar).Value = person.LastName;
                    result = command.ExecuteScalar();
                }
            }
            return (int)result;
        }
    }
}
  • This is not a tutorial on database and web service programming and most of this code should be easy to read. Not all web service methods will be used in this tutorial but they will be used in others.
  • Build the solution and fix any errors before continuing.
  • Set a service reference to the PersonService in the PersonLookup Silverlight application please not the namespace that was specified and the collection type change in the service reference configuration.
Set service reference.png
Configure service reference.png
  • So far all you have done is not very special, all is very basic. Now onwards with MVVM and the MVMMapper!
  • Add three folders to the Silverlight Application. Please note that in this simple scenarion the web service proxy classes are being used as the model and since they will not be modified or extended in any way there is no explicit need for a Model folder.
Pattern folders.png
  • The View folder will contain the user controls. The ViewModel folder will contain the classes and their generation configuration. The MVVM folder will contain all classes that support the the implementation of the MVVM pattern and the MVMMapper.
  • After downloading the MVMMapper you unpacked the zip file. Now find the unpacked MVMMapper in a Windows Explorer and drag-drop the MVMMapper folder in Visual Studio on the MVVM folder in the Silverlight solution. This will make a copy of the MVMMapper.
Adding the MVMMapper.png
  • Visual Studio recognizes the .tt files in the MVMMapper folder and will associate a custom tool that will try to run the text transformations.
TT custom tool.png
  • Simply remove the custom tool from ALL four .tt files
TT custom tool reset.png
  • Add an xml file to the ViewModel folder. This xml file is the mapping file that will contain all information for the MVMMapper to be able to generate the actual Person ViewModel class.
Add mapping file.png
  • Add the Mappingfile.xsd as a schema to the Person.xml.
MappingFile schema set.png
  • Make sure you set the green check mark. It is easier to find the MappingFile.xsd by sorting the second column by clicking on the header.
Select schema.png
  • Setting the schema like this makes the following step much easier: authoring the mapping file. A word of caution is appropriate here: typing errors in the mapping file can (and will) cause errors that are not easy to track. Follow this tutorial closely and make sure (later on) that you understand all the settings so when trying to solve (compilation) errors it will be easier to pin down the bug. Here goes:
<?xml version="1.0" encoding="utf-8" ?>
<ViewModelMapping xmlns="urn:mvmmapper:mappingfile">
  <ViewModelClass name="Person"
                  namespace="PersonLookup.ViewModel"
                  collectionClassName="PersonCollection"
                  modelClass="PersonLookup.PersonServiceReference.Person">
    <Properties>

      <Property name="ID"
                type="int">
        <ModelMapping propertyPath="ID"/>
      </Property>

      <Property name="FirstName"
                type="string">
        <ModelMapping propertyPath="FirstName"/>
      </Property>

      <Property name="LastName"
                type="string">
        <ModelMapping propertyPath="LastName"/>
      </Property>

    </Properties>
  </ViewModelClass>
</ViewModelMapping>
  • The ViewModelClass node describes the ViewModel class that will be generated.
    • name is the name of the generated ViewModel class.
    • namespace is the namespace of the generated ViewModel class.
    • collectionClassName (when present) is the name of a class that will be generated that will contain an ObservableCollection of the ViewModel class
    • modelClass is the class from the Model that the ViewModel class maps to. In this case it is the Person class from the web service proxy.
    • Properties is the collection of properties that will be generated on the ViewModel class. In this case all the properties of the web service Person class are reflected in the ViewModel but it is very well possible to leave some out or add more properties.
    • Property every property element describes a single property of the ViewModel class. All that is needed is the name and the type and the generator will create the private backing field, the public property and the implementation of the PropertyChanged event.
    • ModelMapping (when present) describes the property that this property of the ViewModel should map to on the Model class.
  • The next step is instructing the MVMMapper to use this MappingFile to generate the Person class.
  • Add a text file to the ViewModel folder
Add tt file.png
  • Add the following code to the Person.tt file:
<#
	MVMMapperFolder = @"..\MVVM\MVMMapper\";
	MappingFile="Person.xml";
#>
<#@ include file="..\MVVM\MVMMapper\GenerateViewModel.tt" #>
  • The MVMMapper folder tells the generator where the generator is based relative to the .tt file itself. The MappingFile points to the xml file that we want the generator to use as input. The last line starts the generator. In general you only have to adjust the MVMMapperfolder and the MappingFile variables.
  • If you did everything right you should be able to run the generation now by pressing the Transform All Templates button.
Start transform.png
  • The output view should show something like this:
Transform output.png
  • If you are interested in what is generated you can now open the Person.g.cs file which is underneath the Person.tt file in the Solution Explorer. If the file is empty you have to double check the xml mapping file for mistakes.
  • You should be able to compile the project now without any errors. If there are any errors, again, check the xml mapping file.
  • Next up is retrieving the data from the web service. We could add the logic to the Person class in several way but it might be better to and a new ViewModel class named Persons. This class will be responsible for managing collections of Person. It is more appropriate to have this new class retrieve the Persons from the web service.
  • So here we go again. Add an xml file to the ViewModel folder named Persons.xml, add the schema (see the same step earlier with the Person.xml file) and write this xml:
<?xml version="1.0" encoding="utf-8" ?>
<ViewModelMapping xmlns="urn:mvmmapper:mappingfile">
  <ViewModelClass name="Persons"
                  namespace="PersonLookup.ViewModel">
    <Properties>

      <Property name="Collection"
                type="PersonLookup.ViewModel.PersonCollection"/>
    </Properties>
  </ViewModelClass>
  <ViewModelProviderClass name="PersonsProvider"
                          namespace="ViewModel.Providers"/>
</ViewModelMapping>
  • The Collection property on this class does not map to a class in the Model so there is no ModelMapping element in the property definition.
  • The ViewModelProviderClass element describes a class that will be generated. This class is called a ViewModel provider class; it will contain the logic to call methods on existing classes in the solution. E.g., a method on a web service proxy class or a method on a Model provider class.
  • Add a Persons.tt file that is very similar to the Person.tt file. The only difference is that it refers to the Persons.xml mapping file.
  • Hit the Transform All Templates button and feel free to examine the generated Persons.g.cs
  • Next step is adding a method to the ViewModel provider class that calls a method on the web service proxy.
  • Add the Methods elelement and its child nodes to the ViewModelProviderClass as shown below:
  <ViewModelProviderClass name="PersonsProvider"
                          namespace="PersonLookup.ViewModel.Providers">
    <Methods>
      <Method name="GetAll">
        <Result name="persons"
                type="PersonLookup.ViewModel.PersonCollection"
                isViewModel="true"/>
        <WrappedMethod name="GetPersonsAsync"
                       providerName="PersonServiceClient"
                       providerNamespace="PersonLookup.PersonServiceReference">
          <Result>
            <Asynchronous methodCompletedEventName="GetPersonsCompleted"
                          eventArgsType="PersonLookup.PersonServiceReference.GetPersonsCompletedEventArgs"/>
          </Result>
        </WrappedMethod>
      </Method>
    </Methods>
  </ViewModelProviderClass>
  • The Methods element contains the Method elements that describe the methods that will be added to the provider class.
  • The name is the name of the method that will be generated
  • The Result element describes the name and type of the result of the method that will be generated. The name is needed because the result is not returned in the 'normal' fashion but instead as a parameter of a partial method as we will see later.
  • The WrappedMethod element describes the method that will be called by the generated method.
  • The name specifies the name of the method that will be called.
  • The providerName and the providerNamespace specify the class and its namespace that contain the method that will be called. In this case the web service's proxy class.
  • The Result element (child element of the WrappedMethod element) describes the return type of the wrapped method. In this case the method is a asynchronous method so we need to specify the Completed eventhandler that will be called when the wrapped method has finished.
  • Hit the Transform All Templates button and feel free to have a look at the generated code in Persons.g.cs
  • The code should still compile without errors. If you encounter any errors, chances are that a namespace, class or type in the mapping file was spelled wrongly. A quick way to find out what is wrong, look at the generated code and pin down what names are wrong.
  • Next step is adding a Command to the ViewModel class Persons so the View classes can bind to it.
  • First we need to create a Command class that will used for all ViewModel commands. You are free to use any command class; the implementation here is the minimal version of what is needed, feel free to extend it.
  • Add a class to the MVVM folder
Add ActionCommand.png
  • Write/copy this code:
using System;
using System.Windows.Input;

namespace MVVM
{
    public class ActionCommand : ICommand
    {
        private Action<object> _handler;

        public ActionCommand(Action<object> handler)
        {
            _handler = handler;
        }

        private bool _isEnabled = true;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                if (value != _isEnabled)
                {
                    _isEnabled = value;
                    if (CanExecuteChanged != null)
                    {
                        CanExecuteChanged(this, EventArgs.Empty);
                    }
                }
            }
        }

        public bool CanExecute(object parameter)
        {
            return IsEnabled;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            _handler(parameter);
        }
    }
}
  • Now all you need to do is specify in the mapping file that you want to use this class as the Command class:
  <ViewModelClass name="Persons"
                  namespace="PersonLookup.ViewModel"
                  commandClass="MVVM.ActionCommand">
  • and mark the method on the ViewModelProviderClass so it will cause the addition of a command on the ViewModel class that will call the method on the provider.
  <ViewModelProviderClass name="PersonsProvider"
                          namespace="PersonLookup.ViewModel.Providers">
    <Methods>
      <Method name="GetAll" generateCommand="true">
  • Hit Transform All Templates again and then compile the code (fix any errors). If you want have a look at the generated code in the Persons class that implements the GetAllCommand.
  • We're nearly done, all that is left is adding a View.
  • Add a Custom Control to the View folder
Add PersonsOverview.png
  • Change the Xaml code:
<UserControl x:Class="PersonLookup.View.PersonsOverview"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="400">

    <Grid x:Name="LayoutRoot"
          Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="auto"
                              MinWidth="100" />
        </Grid.ColumnDefinitions>
        <ListBox ItemsSource="{Binding Collection}">

        </ListBox>
        <StackPanel Grid.Column="1">
            <Button  Content="Get All"
                     Command="{Binding GetAllCommand}"
                     Margin="2" />
        </StackPanel>
    </Grid>
</UserControl>
  • The ListBox is bound to the Collection property and the Button is bound to the GetAllCommand, both on the Persons ViewModel class.
  • In the App.xaml.cs add the following code to create the view and bind it to an instance of the view model:
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            View.PersonsOverview view = new View.PersonsOverview();
            view.DataContext = new ViewModel.Persons();
            this.RootVisual = view;
        }
  • You can now run the application (make sure the web application is the startup project and one of the web pages is set as the start page. You can click the button but nothing will show up.
  • What is missing is a little code that takes the result of the GetAll method and puts it in the Collection property of the Persons ViewModel class. Adding that little code should be done in a partial class.
  • Add a C# class named Persons to the ViewModel folder and make it partial:
using System;

namespace PersonLookup.ViewModel
{
    public partial class Persons
    {
        partial void GetAllCompleted(PersonCollection persons, Exception error, object userState)
        {
            if (error == null)
            {
                this.Collection = persons;
            }
        }
    }
}
  • This partial method is called when the asynchronous method completes so this is where you handle errors and manipulate the results.
  • Running the application and clicking the button should result in this:
First View.png
  • We need to make a View for a single person.
  • Add a user control to the View folder:
<UserControl x:Class="PersonLookup.View.PersonSummary"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="400">

    <Grid x:Name="LayoutRoot"
          Background="White">
        <Border BorderBrush="Blue"
                Margin="2"
                BorderThickness="1"
                CornerRadius="3"
                Padding="3">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto" />
                    <ColumnDefinition Width="auto" />
                    <ColumnDefinition Width="auto" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding LastName}"
                           Grid.Column="0" />
                <TextBlock Text=", "
                           Grid.Column="1" />
                <TextBlock Text="{Binding FirstName}"
                           Grid.Column="2" />
            </Grid>
        </Border>
    </Grid>
</UserControl>
  • and add set the ItemTemplate of the ListBox in the PersonsOverview:
        <ListBox ItemsSource="{Binding Collection}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <my:PersonSummary />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
  • Running the application wil now give you this result:
Final View.png
  • In this tutorial you have seen the basics of the MVMMapper. There are a lot more features and more complex scenarios; these are described in the advanced tutorials.

Last edited Jun 15, 2010 at 4:17 PM by Erno_de_Weerd, version 9

Comments

No comments yet.