How to populate a combobox in a MVVM design pattern
This post just explains how to populate a dropdown box in your Silverlight application. It's almost the same as in the previous post, but I will just repeat it here, as I will be using this multiple times. One difference between this and the former post, is that we work with an ObservableCollection
instead of normal strings
, and another difference lies in having to populate a dropbox, which requires a partial class
that makes sure that the data from the database is printed, and not the name of the object.
As in the previous post, this post will not be so thorough, and should anybody want to retrieve data from a MS SQL database trough a MVVM pattern with WCF, they should start by reading and following the steps on these two posts: MVVM structure and Deploying WCF Services.
Step 1: Model project -> Model folder -> New class.
I have called the new class ParentQuestionnaires.cs, and it will contain the data that I eventually want to retrieve from the database and insert into the application.
* using System.Runtime.Serialization; namespace Model.Model { [DataContract(Name = "ParentQuestionnaire")] public class ParentQuestionnaires { string _parentQuestionnaireName; [DataMember] public string ParentQuestionnaireName { get { return _parentQuestionnaireName; } set { _parentQuestionnaireName = value; } } } }
Step 2: Model project -> Data_access folder -> New class.
I have called my class ParentQuestionnairesDA.cs, and it will contain the connection to the database and the query.
*using Model.Model; using System.Data.SqlClient; namespace Model.Data_access { public class ParentQuestionnairesDA: DBConnection { public List<ParentQuestionnaires> GetParentQuestionnaire(int UID) { List<ParentQuestionnaires> PQlist = new List<ParentQuestionnaires>(); SqlConnection dbConn = connectToDB(); string _selectQuery = string.Format("select blablbalbla from blabla where blabla = " + UID + ";"); try { dbConn.Open(); SqlCommand cmd = new SqlCommand(_selectQuery, dbConn); SqlDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()) { ParentQuestionnaires PQ = new ParentQuestionnaires(); PQ.ParentQuestionnaireName = (string)rdr[0]; PQlist.Add(PQ); } } catch(Exception e) { Console.Write(e); } return PQlist; } } }
Step 3: Model project ->> IService1.cs
Connects client and server-side.
** using Model.Model; namespace Model { [ServiceContract] public interface IService1 { [OperationContract] List<ParentQuestionnaires> GetParentQuestionnaire(int UID); } }
Step 4: Model project -> Service1.svc.cs
Creates new instances of the two classes created above, calls the GetParentQuestionnaire(UID); method
** using System.Data.SqlClient; using Model.Model; using Model.Data_access; namespace Model { public class Service1 : IService1 { public List<ParentQuestionnaires> GetParentQuestionnaire(int UID) { List<ParentQuestionnaires> allQuestionnaires; ParentQuestionnairesDA AllDA = new ParentQuestionnairesDA(); allQuestionnaires = AllDA.GetParentQuestionnaire(UID); return allQuestionnaires; } } }
Step 5: This is the step that differentiates this guide from the rest: We need to make a partial class, that will make sure that we data from the database is printed in dropdown box - not the name of the object. First, create a new folder in your ViewModel, where you will put this class. I have named mine Model.
ViewModel project -> Model folder -> create class. I have called mine ParentQuestionnaire.cs
*using System.Windows.Shapes; namespace ViewModel.QMServiceReference // name of your Service Reference! { public partial class ParentQuestionnaire //will be called in the next step. { public override string ToString() { return this.ParentQuestionnaireName; //created in step 1. } } }
After this step, you need to publish the Model and update your ServiceReference.
Step 6: ViewModel -> Create new class. I always name mine after the .xaml page where I want to insert the data, followeb by "_ViewModel". Hence, I have called mine
* using ViewModel.QMServiceReference; using System.ComponentModel; using System.Collections.ObjectModel; namespace ViewModel { public class Client_Questionnaire_ViewModel : INotifyPropertyChanged //inportant! { private ObservableCollection<ParentQuestionnaire> parentQuest; //ParentQuestionnaire, created in the previous step. public ObservableCollection<ParentQuestionnaire> ParentQuest { get { return parentQuest; } set { parentQuest = value; RaisePropertyChanged("ParentQuest"); } } public Client_Questionnaire_ViewModel() { int UID = 2; //for now, hardcoding. QMServiceReference.Service1Client WebService = new Service1Client(); WebService.GetParentQuestionnaireCompleted += new EventHandler<GetParentQuestionnaireCompletedEventArgs>(WebService_GetParentQuestionnaireCompleted); WebService.GetParentQuestionnaireAsync(UID); } void WebService_GetParentQuestionnaireCompleted(object sender, GetParentQuestionnaireCompletedEventArgs e) { ParentQuest = e.Result; } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyname) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyname)); } } } }
And lastly: Insert into the combo box
into your .xaml page:
<ComboBox x:Name="ParentQuestionnairej" ItemsSource="{Binding ParentQuest}" HorizontalAlignment="Left" Width="150" Height="20" Style="{StaticResource ComboBox2}" Margin="0,2,0,0" Grid.Column="1" Grid.Row="0" />
MS SQL Connection – Deploying WCF Services
So, I found a host for my WCF Service, which uses a IIS 7 server that can run my .svc WebService file. I could connect to my MySQL database hosted another place, but given that my new host uses MS SQL, I will be migrating to that.
This post explains how you can deploy your WCF Service and connect to the underlying database, and how to retrieve data all the way through the MVVM Design Pattern. The prerequisites for this post is that you have 1) created a project with a design pattern that somewhat resembles the MVVM covered in my previous post (see MVVM Structure), and 2) that you have created a table in a MS SQL database called 'Suppliers', and inserted three attributes called ID
, name
and country
, respectively.
First, make sure that you have 3 projects in your solution, fx. called Model (a WCF Service Application), View (a Silverlight application), and ViewModel (a Silverlight class). You have connected the projects View
and ViewModel
together like it was described in step 4 of the previous post.
Step 1: Just to make sure that you have all the right information in order to deploy to your server, right-click on your Model
project >> Publish, and enter all necessary data. Notice that you have to enter ftp://yourdomain.net
and not just ftp.yourdomain.net
.
If you have entered the correct data, the following output will be shown, and the WCF Service will have been added to the root of your server:
Step 2: Go to your Model
project and chose Web.Config
. Find the row that starts with < system.serviceModel>
. It will look something like this:
<system.serviceModel> <!--Add code here --> <services> <service name="Model.Service1" behaviorConfiguration="Model.Service1Behavior"> <!-- Service Endpoints --> <endpoint address="" binding="wsHttpBinding" contract="Model.IService1"> (...)
Step 3: Right below the < system.serviceModel>
and before < services>
starts, you'll need to add the following code snippet:
<serviceHostingEnvironment> <baseAddressPrefixFilters> <add prefix="http://yourDomain.net"/> </baseAddressPrefixFilters> </serviceHostingEnvironment>
Step 4: Then, a small change needs to be implemented to the endpoint binding
. Shown in the code below step 2, you can see that the standard binding is wsHttpBinding
. This needs to be changed to BasicHttpBinding
.
After having completed step 3 and 4, you will thus end up with the code below:
<system.serviceModel> <serviceHostingEnvironment> <baseAddressPrefixFilters> <add prefix="http://yourDomain.net"/> </baseAddressPrefixFilters> </serviceHostingEnvironment> <services> <service name="Model.Service1" behaviorConfiguration="Model.Service1Behavior"> <!-- Service Endpoints --> <endpoint address="" binding="BasicHttpBinding" contract="Model.IService1"> <!--
Step 5: Repeat step 1., ie. right-click on your WebSevice >> Publish. You will then notice that two files and folder have been added to the root of your server: A Service1.svc
and a Web.Config
file, and a Bin
folder.
Step 6: Still in your Model
project, open your IService1.cs
. Here you will create a class that will contain the attributes and properties for the Suppliers
object that we create. Also notice the code inside the brackets [], fx. [OperationContract] and [DataMember], which make the methods and properties available to the other projects.
namespace Model { [DataContract]//Makes the class available public class Suppliers //creates the objectet Suppliers, and defines its attributtes (properties). { int _ID; string _name; string _country; [DataMember]//makes the property available public int ID { get { return _ID; } set { _ID = value; } } [DataMember] public string Name { get { return _name; } set { _name = value; } } [DataMember] public string Country { get { return _country; } set { _country = value; } } }
Step 7: Open your Service1.svc.cs file in the Model
project. In this file we will connect to the database and run our queries. When you open the file you'll notice that a lot of code has already been created for you. Below is the standard content of the Service1.svc
file:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace Model { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together. public class Service1 : IService1 { public string GetData(int value) { return string.Format("You entered: {0}", value); } public CompositeType GetDataUsingDataContract(CompositeType composite) { if (composite == null) { throw new ArgumentNullException("composite"); } if (composite.BoolValue) { composite.StringValue += "Suffix"; } return composite; } } }
You just need to delete everything inside the class, and instead insert what is explained in the following steps.
Step 8: Add a reference to using System.Data.SqlClient;
at the top.
Step 9: Create a SqlConnection ConnectToDB()
method, that sets the connections string and returns the Sql Connection. If you have Winhost, you will find what you need to insert into your Connection String
by logging in >> Site >> Site Tools >> MS SQL 2008 >> Manage. The ConnectToDB
method looks something like this:
public SqlConnection connectToDB() { string connstring = "Data Source=************;Initial Catalog=*****;User ID=******;Password=*******;Integrated Security=False;"; SqlConnection dbConn = new SqlConnection(connstring); return dbConn; }
Step 8: Now we will add the method that will query the database and return a List
of data that ultimately will be inserted into the Silverlight application. Notice that we create a method called getSupplierList()
of type List< Suppliers>
, where Suppliers
is the name of the class created in step 6.
public List<Suppliers> getSupplierList() { List<Suppliers> SupplierList = new List<Suppliers>(); //list that will containt all data retrieved from the database. SqlConnection dbConn = connectToDB();//Method above string _selectQuery = string.Format("SELECT * FROM suppliers");//Query try { dbConn.Open(); //Creates the comand object and sets the query of the command. SqlCommand cmd = new SqlCommand(_selectQuery, dbConn); //Executes SqlDataReader rdr = cmd.ExecuteReader(); //loop, read data while (rdr.Read())//for hver række der bliver hentet ind, gør vi følgende { Suppliers Sup = new Suppliers(); Sup.ID = (int)rdr[0]; Sup.Name = (string)rdr[1]; Sup.Country = (string)rdr[2]; SupplierList.Add(Sup); //row is added } } catch (Exception e) { Console.WriteLine(e.ToString()); } return SupplierList; } }
Step 9: Open IService1.cs
again. Before the public class Suppliers
that you created earlier, add the OperationContract
shown below, that exposes your getSupplierList()
method and makes it accessable to the ViewModel
.
[ServiceContract] public interface IService1 { [OperationContract] List<Suppliers> getSupplierList(); //Name of method in Service1 }
Step 10: Repeat step 1 (publish).
Now, we need to connect the ViewModel
to the Model
.
Step 11: On your ViewModel
, right-click on References
, and chose Add new Webservice
. Here you will want to find the webService you have uploaded to the server. In the address field, enter http://yourDomain.com/Service1.svc, like shown below. Name your WebService (I have named mine QMWebService) and click 'OK'.
Now, you have connected the ViewModel
to the Model
.
If your service is not hosted locally and your Silverlight is, then you will get a warning about CrossDomain policy. To enable your service to receive calls from your locally hosted application, follow step 12:
Step 12: Create a new .xml file that contains the following code (source).
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="SOAPAction"> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
Copy the file to the root of your server.
Step 13: This step is necessary in order for the View
to reflect the changes made to it when the property in the ViewModel
is set (updated). You will have to add a class to your ViewModel
, and just copy-paste the code from below. It is taken from the MVVM demo application by Josh Smith, and can be also be found here. Name the class ViewModelBase.cs.
Name the class ViewModelBase, as all classes in the ViewModel are inherited from it.
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ComponentModel; using System.Diagnostics; namespace SilverLightViewModel { /// <summary> /// Base class for all ViewModel classes in the application. /// It provides support for property change notifications /// and has a DisplayName property. This class is abstract. /// </summary> public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable { #region Constructor protected ViewModelBase() { } #endregion // Constructor #region DisplayName /// <summary> /// Returns the user-friendly name of this object. /// Child classes can set this property to a new value, /// or override it to determine the value on-demand. /// </summary> public virtual string DisplayName { get; protected set; } #endregion // DisplayName #region Debugging Aides /// <summary> /// Warns the developer if this object does not have /// a public property with the specified name. This /// method does not exist in a Release build. /// </summary> //[Conditional("DEBUG")] //[DebuggerStepThrough] //public void VerifyPropertyName(string propertyName) //{ // // Verify that the property name matches a real, // // public, instance property on this object. // if (TypeDescriptor.GetProperties(this)[propertyName] == null) // { // string msg = "Invalid property name: " + propertyName; // if (this.ThrowOnInvalidPropertyName) // throw new Exception(msg); // else // Debug.Fail(msg); // } //} /// <summary> /// Returns whether an exception is thrown, or if a Debug.Fail() is used /// when an invalid property name is passed to the VerifyPropertyName method. /// The default value is false, but subclasses used by unit tests might /// override this property's getter to return true. /// </summary> protected virtual bool ThrowOnInvalidPropertyName { get; private set; } #endregion // Debugging Aides #region INotifyPropertyChanged Members /// <summary> /// Raised when a property on this object has a new value. /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Raises this object's PropertyChanged event. /// </summary> /// <param name="propertyName">The property that has a new value.</param> protected virtual void OnPropertyChanged(string propertyName) { //this.VerifyPropertyName(propertyName); PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { var e = new PropertyChangedEventArgs(propertyName); handler(this, e); } } #endregion // INotifyPropertyChanged Members #region IDisposable Members /// <summary> /// Invoked when this object is being removed from the application /// and will be subject to garbage collection. /// </summary> public void Dispose() { this.OnDispose(); } /// <summary> /// Child classes can override this method to perform /// clean-up logic, such as removing event handlers. /// </summary> protected virtual void OnDispose() { } #if DEBUG /// <summary> /// Useful for ensuring that ViewModel objects are properly garbage collected. /// </summary> ~ViewModelBase() { string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode()); System.Diagnostics.Debug.WriteLine(msg); } #endif #endregion // IDisposable Members } }
Step 14: The next step is to go to your MainPage_ViewModel
, that will contain the necessary properties and eventhandlers. The aim with this class is to register changes, call the WebService, and retrieve the data that will be inserted into the .xaml Grid
, that will be created in the next step.
MainPage_ViewModel:
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using SilverLightViewModel;//new using System.Collections.ObjectModel;//new using ViewModel.QMServiceReference; //new namespace ViewModel { public class MainPage_ViewModel : ViewModelBase { private ObservableCollection<Suppliers> _suppliers; //Collection registres changes. Updates run. //list of all supplies public MainPage_ViewModel() { QMServiceReference.Service1Client WebService = new Service1Client(); WebService.getSupplierListCompleted += new EventHandler<getSupplierListCompletedEventArgs>(WebService_getSupplierListCompleted);//Eventhandler is trigged when the webservice call has been run, and data han been retrieved WebService.getSupplierListAsync(); //calls webservice. } void WebService_getSupplierListCompleted(object sender, getSupplierListCompletedEventArgs e) { //Is called when the webservice returnes a list Suppliers = e.Result; } # region Properties public ObservableCollection<Suppliers> Suppliers { get { return _suppliers; } //instance of Suppliers set { _suppliers = value; OnPropertyChanged("Suppliers"); }//property name } # endregion } }
Step 15 (last step!): Create a Grid
in your MainPage.xaml, like the one shown below. Notice that the name of the binding attribute is not named after the name you have created in your database, but rather in what you declared them in your Model
, in the Suppliers
class (see step 6).
MainPage.xaml:
<data:DataGrid x:Name="SupplerGrid" MinHeight="100" IsReadOnly="True" AutoGenerateColumns="False" ItemsSource="{Binding Suppliers}"> <data:DataGrid.Columns> <data:DataGridTextColumn x:Name="ID" Header="ID" Binding="{Binding ID}" /> <data:DataGridTextColumn x:Name="ID2" Header="Name" Binding="{Binding Name}" /> <data:DataGridTextColumn x:Name="testData" Header="Value" Binding="{Binding Country}" /> </data:DataGrid.Columns> </data:DataGrid>
And no... this time I won't end the post by stating "And that's it!", because this task is horrible, frustrating and endlessly time-consuming. But it works. I hope this post will help somebody get through the process costing only a limited amount of blood, sweat and tears.
Enjoy!