Friday, 31 May 2013

How to Consume a WCF in a silverlight Mscrm

Create a Class Library
Create a class library project named DataAccessLayar and add a class named Customer.cs with following public properties.
namespace DataAccessLayer
{
    [DataContract]
    public class Customer
    {
        [DataMember]
        public string ContactName { get; set; }
        [DataMember]
        public string CustomerID { get; set; }
        [DataMember]
        public string Address { get; set; }
        [DataMember]
        public string City { get; set; }
        [DataMember]
        public string Hiredate { get; set; }
        [DataMember]
        public string Country { get; set; }
    }
}
In the above code snippet, you can notice that Customer class definition has an attribute named DataContract and properties have DataMember. These attributes are necessary to work with Wcf Services. If we will not keep them, WCF service will not be able to transfer the object and its properties. In order to add these attribute you may need to add reference of System.Runtime.Serialization (Right click and Add Reference … from .NET Tab)  to your class library.
Add another class called DataAccessLayer.cs in this class library that has a method called GetCustomers. This method is responsible for connecting to the database and returning the results as Generic lists. Here is the code for this.
NOTE: To avoid complications in this tutorial, I have created Customer class and DataAccessLayer class in the same class library project. In real scenario, you should separate them out.
string NorthwindConnStr = ConfigurationSettings.AppSettings.Get("NorthwindConnStr").ToString();
        /// <summary>
        /// Get customers
        /// </summary>
        /// <returns></returns>
        public List<Customer> GetCustomers(string startsWith)
        {
            List<Customer> list = new List<Customer>();
            using (SqlConnection conn = new SqlConnection(NorthwindConnStr))
            {
                conn.Open();
                using (SqlCommand dCmd = new SqlCommand("vs_GetCustomersStartingWith", conn))
                {
                    SqlParameter prms = new SqlParameter("@startsWith", SqlDbType.VarChar, 5);
                    prms.Value = startsWith + "%";
                    dCmd.Parameters.Add(prms);
                    dCmd.CommandType = CommandType.StoredProcedure;
                    using (SqlDataReader reader = dCmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            list.Add(new Customer
                            {
                                CustomerID = reader["CustomerID"].ToString(),
                                Address = reader["Address"].ToString(),
                                City = reader["City"].ToString(),
                                Country = reader["Country"].ToString(),
                                ContactName = reader["ContactName"].ToString()
                            });
                        }
                    }
                }
                conn.Close(); // lets close explicitely
            }
            return list;
        }
In the above method, I have created a generic type variable that will accept Customer object, I have a stored procedure named vs_GetCustomersStartsWith that will take a string as a parameter and return all records whose name starts with parameter value.

Finally I am forming the Customer object inside while (reader.Read()) loop and adding it into the generic list collection.
Get solutions of the .NET problems with video explanations, .pdf and source code in .NET How to's.
Create a WCF Service
Let’s create the WCF Service now. Right click the solution and click Add > New Proejct … Select Visual C# > Web from the Project Type and WCF Service Application from Templates, name it as WcfService. This will by default create following files:
  1. IService1.cs
  2. Service1.svc
  3. Service1.svc.cs
  4. Web.config
Now, let’s Add Reference to our Class Library project into our WCFService, right click the WcfService project and click Add Reference … Select Projects tab and select the DataAccessLayer project.
In the IService1 interface remove all code and add a method definition named GetCustomers. Notice that the interface declaration must have an attribute ServiceContract and its method declaration must have an attribute OperationContract otherwise this class and its member will not be exposed as services. My sample IService1 interface code looks like this.
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Collections.Generic;
using DataAccessLayer;
namespace WcfService
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        List<Customer> GetCustomers(string startsWith);
    }
}
Now let’s implement the above method in Service1.svc.cs file. If your project names are same as mine, your code for this file looks like below.
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using DataAccessLayer;
namespace WcfService
{
    public class Service1 : IService1
    {
        public List<Customer> GetCustomers(string startsWith)
        {
            DataAccessLayer.DataAccessLayer dal = new DataAccessLayer.DataAccessLayer();
            return dal.GetCustomers(startsWith);
        }
    }
}
Now we need a little configuration change in web.config file. So open the config file of WcfService project and change following.
In the system.serviceModel tag
  1. For endpoint – change the value of binding to basicHttpBinding.
  2. For identity > dns change the value to “localhost:3637” //as we are going to fix the port in the project properties later on
Following is the code snippet related with endpoint and dns of my web.config file
<!-- Service Endpoints -->


<endpoint address="" binding="basicHttpBinding" contract="WcfService.IService1">


<!-- 

Upon deployment, the following identity element should be removed or replaced to reflect the 

identity under which the deployed service runs. If removed, WCF will infer an appropriate identity 


automatically.

-->

<identity>

<dns value="localhost:3637"/>


</identity>

</endpoint>


<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>


Now right click the project, click Properties and go to Web tab (left side) and select Specific port and enter 3637 in the textbox and save it. This is to avoid automatically generation of random ports while we are working on our sample application.
As Silverlight doesn’t support cross domain access by default so we need create a ClientAccessPolicy.xml file that is read when any request is sent to the server to access wcf service.  In order to work for cross domain, write following code in this file
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>
This ensures that your wcf service will be consumed by Silverlight application even if both do not fall in the same domain domain.
Save this project and build it.
Now, right click the WcfService project and go to Debug and click start new Instance. This will open up a browser with http://localhost:3637/Service1.svc address. Click Stop button from Visual Studio and return to normal mode, you will notice that your localhost:3637 port is still active and is able to listen the request (See bottom-right of your screen and you will see that ASP.NET Development Server – Port 3637 is still active).  Keep it as it is.
Consume WCF Service in Silverlight
Right click the Silverlight application and click Add Service Reference... . Write the url of the wcf service you got above in the address box, click Go button. In the Namespace box, write WcfServiceReference and click OK. This should add a reference of the wcf service and also create ServiceReferences.ClientConfig file in the root of your Silverlight application.
Double click the xaml file and drag a TextBlock, TextBox, Button and DataGrid to your xaml file and ensure that they are looking similar to the below code snippet. This will ensure that your Search form is looking good.
<Grid x:Name="LayoutRoot" Background="White">
        <Canvas>
            <TextBlock Text="Search" Canvas.Left="10" Canvas.Top="12"></TextBlock>
            <TextBox x:Name="txtSearch" Width="75" Height="22" Canvas.Left="55" Canvas.Top="10"></TextBox>
            <Button Content="  Get Data from WCF Service  " Click="Button_Click" Canvas.Top="9" Canvas.Left="140"></Button>
            <TextBlock x:Name="lblWaiting" Canvas.Left="375" Canvas.Top="10" Text="Status: Idle"></TextBlock>
            <data:DataGrid x:Name="dataGrid1" Canvas.Left="10" Canvas.Top="35" Width="550" Height="300" IsReadOnly="True"
                           ItemsSource="{Binding Mode=OneWay}" AutoGenerateColumns="True" >
            </data:DataGrid>
        </Canvas>
    </Grid>
As you can see that we have specified Button_Click event handler that will fire when user clicks button after entering keyword in the textbox. Let us see the code for this event.
private void Button_Click(object sender, RoutedEventArgs e)
        {
            // call data web service
            var proxy = new WcfServiceReference.Service1Client("BasicHttpBinding_IService1");
            proxy.GetCustomersCompleted += new EventHandler<SilverlightApplication.WcfServiceReference.GetCustomersCompletedEventArgs>(proxy_GetCustomerCompleted);
            proxy.GetCustomersAsync(txtSearch.Text.Trim());
            // call simple web service
            lblWaiting.Text = "Status: Waiting ...";
        }
//-------------------------------------------------
        void proxy_GetCustomerCompleted(object sender, WcfServiceReference.GetCustomersCompletedEventArgs e)
        {
            System.Collections.ObjectModel.ObservableCollection<WcfServiceReference.Customer> list = e.Result;
            dataGrid1.ItemsSource = list;
            lblWaiting.Text = "Status: Done";
            if (list.Count.Equals(0))
                lblWaiting.Text = "No records found.";
            else
                lblWaiting.Text = list.Count + " records found.";
        }
You can see that in the Button_Click event I have specified a variable named proxy and instantiating it by passing the endpointConfigurationName as parameter. endpointConfigurationName is nothing but the bindingConfiguration value of the ServiceReferences.ClientConfig file.
Later on I have specified the event handler for GetCustomersCompleted and specified the parameter for GetCustomerAsync method as the keyword (textbox value).
When the GetCustomersCompleted will finish, it will raise an event named proxy_GetCustomerCompleted. In this event, write the code like above. Build your Silverlight project and ensure that it is built successfully.

No comments:

Post a Comment