Sunday 12 February 2012

Implementing Azure AppFabric Service Bus - Part 5


Till now we had seen developing WCF Service application and exposing to the public using Azure Service Bus and configuring Auto Start capability to the service as it required starting on IIS server restarts automatically.

In this post, I am going to provide a sample client application which consumes the CustomerService built using the WCF Application and exposed in previous posts. This application is a normal ASP.NET application which has the implementation to consume the service exposed on the Service Bus.

Before going to the actual implementation, below is the use case of this example.
  1. Required a screen to show list of customers in a GridView.
  2. The screen should also contain an entry form for adding/modifying customer records.
  3. The user can add a new customer by entering the customer information in the entry form and press Save button.
  4. The user can select an existing customer by selecting on the GridView using Select column hyperlink.
  5. On selection of a record, the information about the customer must be populated on the entry screen for updation.
  6. The user can update the customer information and Save again.
  7. The user can delete a particular customer using Delete hyperlink on the Delete column in GridView.
This is the functionalities of the screen. Now let’s get start the implementation of client application for consuming the service bus service.

Pre-requisite – As our project is going to be Windows Azure Project, make sure the latest Windows Azure SDK (v 6.0) installed.

Step 1: Open Visual Studio 2010 and select New Project from Start Page (or File => New => Project) for creating new Azure Project.
Step 2: Select the preferred language (here C#) and Windows Azure Project (from Cloud) from the Installed Templates. Enter the project name (DotNetTwitterSOPClient) and preferred Location then press OK to create.

Step 3: The Visual Studio will open New Windows Azure Project window. Select the ASP.NET Web Role from the .NET Framework 4 roles (left panel) and press > button to add to the Windows Azure Solution list. Press OK.

Step 4: Run the project once and verify is it working fine.
Step 5: Before going for functional implementation, let us first design the UI. So open the Default.aspx script source and add the below UI script in between the BodyContent.
<div>
    <table>
        <tr>
            <td>Customer Id</td>
            <td><asp:TextBox ID="txtCustomerId" runat="server" Width="120px"></asp:TextBox> </td>
            <td style="width:100px"></td>
            <td>Company Name</td>
            <td><asp:TextBox ID="txtCompanyName" runat="server" Width="250px"></asp:TextBox> </td>
        </tr>
        <tr>
            <td>Contact Name</td>
            <td><asp:TextBox ID="txtContactName" runat="server" Width="300px"></asp:TextBox> </td>
            <td></td>
            <td>Contact Title</td>
            <td><asp:TextBox ID="txtContactTitle" runat="server" Width="250px"></asp:TextBox> </td>
        </tr>
        <tr>
            <td rowspan="3">Address</td>
            <td rowspan="3"><asp:TextBox ID="txtAddress" runat="server" Width="300px" TextMode="MultiLine" Height="70px"></asp:TextBox> </td>
            <td rowspan="3"></td>
            <td>City</td>
            <td><asp:TextBox ID="txtCity" runat="server" Width="200px"></asp:TextBox> </td>
        </tr>
        <tr>
            <td>Region</td>
            <td><asp:TextBox ID="txtRegion" runat="server" Width="200px"></asp:TextBox> </td>
        </tr>
        <tr>
            <td>Country</td>
            <td><asp:TextBox ID="txtCountry" runat="server" Width="200px"></asp:TextBox> </td>
        </tr>
        <tr>
            <td>Phone No</td>
            <td><asp:TextBox ID="txtPhoneNo" runat="server" Width="200px"></asp:TextBox> </td>
            <td></td>
            <td>Fax No</td>
            <td><asp:TextBox ID="txtFax" runat="server" Width="200px"></asp:TextBox> </td>
        </tr>
        <tr>
            <td colspan="5" style="text-align:center">
                <asp:Button ID="btnSave" runat="server" Width="100px" Text="Save" onclick="btnSave_Click" />
                <asp:Button ID="btnClear" runat="server" Width="100px" Text="Clear" onclick="btnClear_Click" />
            </td>
        </tr>
        <tr>
            <td colspan="5">
                <asp:Label runat="server" ID="lblMessage" Text="" CssClass="Message"></asp:Label>
                <asp:HiddenField ID="hndCustomerId" runat="server" Value="" />
            </td>
        </tr>
    </table>
</div>
<div>
    <asp:GridView ID="grdViewCustomers" runat="server"
        AllowPaging="True" AutoGenerateColumns="False" TabIndex="1"
        DataKeyNames="CustomerID" Width="100%" BackColor="White" 
        CellPadding="3" BorderStyle="Solid" BorderWidth="1px" BorderColor="Black" 
        GridLines="Horizontal" OnRowDataBound="grdViewCustomers_RowDataBound" 
        OnPageIndexChanging="grdViewCustomers_PageIndexChanging"
        OnSelectedIndexChanging="grdViewCustomers_SelectedIndexChanging"
        OnRowDeleting="grdViewCustomers_RowDeleting">
        <Columns>
            <asp:CommandField ShowSelectButton="True" HeaderText="Select" />
            <asp:CommandField ShowDeleteButton="True" HeaderText="Delete" />
            <asp:BoundField DataField="CustomerID" HeaderText="Customer ID" />
            <asp:BoundField DataField="CompanyName" HeaderText="Company Name" />
            <asp:BoundField DataField="ContactName" HeaderText="Contact Name" />
            <asp:BoundField DataField="ContactTitle" HeaderText="Contact Title" />
            <asp:BoundField DataField="Address" HeaderText="Address" />
            <asp:BoundField DataField="City" HeaderText="City" />
            <asp:BoundField DataField="Region" HeaderText="Region" />
        </Columns>
        <RowStyle BackColor="White" ForeColor="#333333" />
        <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
        <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Right" />
        <SelectedRowStyle BackColor="#A5D1DE" Font-Bold="true" ForeColor="#333333" />
        <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
        <AlternatingRowStyle BackColor="#E2DED6" ForeColor="#284775" />
    </asp:GridView>
</div>
Here the HTML Script contains controls related entry form and a GridView to show list of customers.
Step 6: There are some events are added in the script file which are required to do the functionalities for the screen. Currently add empty event handler in code behind to make the screen work with no error.
public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    protected void grdViewCustomers_RowDataBound(object sender, GridViewRowEventArgs e)
    {
    }

    protected void grdViewCustomers_PageIndexChanging(object sender, GridViewPageEventArgs e)
    {
    }

    protected void btnClear_Click(object sender, EventArgs e)
    {
    }

    protected void grdViewCustomers_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
    {
    }

    protected void grdViewCustomers_RowDeleting(object sender, GridViewDeleteEventArgs e)
    {
    }

    protected void btnSave_Click(object sender, EventArgs e)
    {
    }
}
Now we are ready for implementation with Service Bus. You can run the project once and verify the screen works fine.
Step 7: We required the Customer entity class defined in WCF Service and exposed on Service Bus for desterilize the message comes from the service. To get the customer service model code, we have to use svcutil utility.
So, open the Visual Studio Command Prompt (2010), switch to required directory and run the below command.
svcutil /language:cs /out:proxy.cs /config:app.config http://localhost/DotNetTwitterSOPService/CustomerService.svc


Note: This command must be executed where the WCF Service runs. Because we are creating the code using the locally published url. The url will be specified in Web.Config on WCF Service Project. So you can open the project and get to know the url (or by running the WCF project also can know from the address bar).
Step 8: Add the Customer.cs file created using above command (or create a new Customer.cs file in the project and copy & paste from the Customer.cs file created).
Note:
  1. Make sure the namespace changed to WebRole namespace (WebRole).
  2. You required to add System.Runtime.Serialization assembly as reference to the project.
  3. Delete the code except Customer class such as ICustomerService, ICustomerServiceChannel interfaces and CustomerServiceClient class.


Step 9: As we are going to consume CustomerService from service bus, we required the ICustomerService interface to talk to the service. So, add a new class file to the Web Role (using right click -> Add -> Class) and name it as ICustomerService.cs.


Add the following code in the ICustomerService
[ServiceContract]
public interface ICustomerService
{

    [OperationContract]
    IList GetAll();

    [OperationContract]
    Customer Get(string customerID);

    [OperationContract]
    int Create(Customer customer);

    [OperationContract]
    int Update(Customer customer);

    [OperationContract]
    int Delete(string customerID);

    [OperationContract]
    string GetValue(string value);
}
This is the same interface defined in the WCF Service in Part 3.
Note: You required to add System.ServiceModel assembly as reference in the project and add System.ServiceModel namespace in the namespace section.

Step 10: For connecting the service exposed on the Service Bus, we required the endpoint url (Service URI). But if we are consuming several services in a particular project, the service bus URI will differ for each service. So, we can define some important parameters in the Web.Config and generate the endpoint url in the code when required.
<appSettings>
  <add key="ServiceNamespace" value="ComDotNetTwitterSOP" />
  <add key="IssuerName" value="owner" />
  <add key="IssuerSecret" value="DpMUy110dpc+CO4Z9HVu1K0xkLRtpXGOfjJBTnwnF2U="/>
  <add key="CustomerServiceEndPointNameTcp" value="NetTcp/CustomerService/Test/V0100"/>
  <add key="CustomerServiceEndPointNameHttp" value="Http/CustomerService/Test/V0100"/>
  <add key="ServiceConsumingProtocol" value="Http"/>
</appSettings>
Change the Issuer Name and Key as you got from Management Portal and assigned to the service while exposting.

I have defined six important parameters in the appSettings node on Web.Config. Below are the configuration script.
  1. ServiceNamespace – This is the namespace created in the Management portal under Service Bus, Access Control & caching section. This namespace will be configured in the WCF Service project and exposed to Service Bus.
  2. IssuerName - IssuerSecret is again from Management portal for security purpose.
  3. CustomerServiceEndPointNameTcp – for defining the service path defined for TCP protocol (sb:// endpoint).
  4. CustomerServiceEndPointNameHttp - for defining the service path defined for HTTPS protocol (https:// endpoint).
  5. ServiceConsumingProtocol – defined the connectivity will be based on which protocol. So we can switch the consuming protocol based on the requirement and network boundaries.
The CustomerServiceEndPointNameTcp, CustomerServiceEndPointNameHttp defines a part of endpoint url. For example, let us take the two endpoint url exposed to public for CustomerService.

https://ComDotNetTwitterSOP.servicebus.windows.net/Http/CustomerService/Test/V0100/
sb://ComDotNetTwitterSOP.servicebus.windows.net/NetTcp/CustomerService/Test/V0100/

The text in blue color defines the service path for each endpoint url.

Step 11: Next, I am going to define a helper class (CustomerHelper) for doing operations with CustomerService deployed on the Service Bus.
The CustomerHelper class has various methods for doing various operations. So create a class CustomerHelper.cs and add the below methods.
  1. Defining a private variable to hold the channel object of ICustomerService at the scope of class.
    private ICustomerService channel = null;
  2. OpenChannel() –This method is used to open the channel for making any communication with the service. So this method will be called before any method called on the service.
    /// <summary>
    /// Method to open the Client Channel
    /// </summary>
    protected void OpenChannel()
    {
        string serviceNamespaceDomain = ConfigurationManager.AppSettings["ServiceNamespace"].ToString();
        string issuerName = ConfigurationManager.AppSettings["IssuerName"].ToString();
        string issuerSecret = ConfigurationManager.AppSettings["IssuerSecret"].ToString();
    
        ServiceBusEnvironment.SystemConnectivity.Mode = this.ConnectivityMode;
    
        // create the service URI based on the service namespace
        Uri serviceUri;
        Binding binding;
        if (this.ConnectivityMode == ConnectivityMode.Tcp)
        {
            serviceUri = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespaceDomain, ConfigurationManager.AppSettings["CustomerServiceEndPointNameTcp"].ToString());
            binding = new NetTcpRelayBinding();
        }
        else
        {
            serviceUri = ServiceBusEnvironment.CreateServiceUri("https", serviceNamespaceDomain, ConfigurationManager.AppSettings["CustomerServiceEndPointNameHttp"].ToString());
            binding = new BasicHttpRelayBinding();
        }
    
        // create the credentials object for the endpoint
        TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
        sharedSecretServiceBusCredential.CredentialType = TransportClientCredentialType.SharedSecret;
        sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerName = issuerName;
        sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerSecret = issuerSecret;
    
        // create the channel factory loading the configuration           
        EndpointAddress myEndpoint = new EndpointAddress(serviceUri);
        ChannelFactory<ICustomerService> channelFactory = new ChannelFactory<ICustomerService>(binding, myEndpoint);
    
        // apply the Service Bus credentials
        channelFactory.Endpoint.Behaviors.Add(sharedSecretServiceBusCredential);
    
        // create and open the client channel
        channel = channelFactory.CreateChannel();
    }
    
    /// <summary>
    /// Getting connectivity mode from configuration
    /// </summary>
    private ConnectivityMode ConnectivityMode
    {
        get
        {
            if (ConfigurationManager.AppSettings["ServiceConsumingProtocol"].ToString().Trim().ToUpper() == "TCP")
            {
                return ConnectivityMode.Tcp;
            }
            else if (ConfigurationManager.AppSettings["ServiceConsumingProtocol"].ToString().Trim().ToUpper() == "HTTP")
            {
                return ConnectivityMode.Http;
            }
    
            return ConnectivityMode.AutoDetect;
        }
    }
    As defined in the code, our first step is to get the service URI which is actually the published service endpoint url. We can consume the service on Service Bus in TCP or HTTP protocol. The endpoints with sb// exposing TCP protocol and https// exposing secure HTTPS protocol.
    Once we get the endpoint url, we can create a channel using normal WCF method i.e. ChannelFactory.
    This code required reference of Microsoft.ServiceBus assembly. So add reference of Microsoft.ServiceBus for the project and include the following references in the header section.
  3. CloseChannel() – Method for closing the channel opened in OpenChannel() method.
    /// <summary>
    /// Method to close the Client Channel
    /// </summary>
    public void CloseChannel()
    {
        ((ICommunicationObject)channel).Close();
    }
  4. GetAll() – Method to get all the customers from the database.
    /// <summary>
    /// Method to get all the Customers
    /// </summary>
    /// <returns>List of Customers</returns>
    public IList<Customer> GetAll()
    {
        OpenChannel();
    
        IList<Customer> customers = channel.GetAll();
    
        CloseChannel();
        return customers;
    }
    As defined previously, before calling the GetAll() method at the service, we required to open the channel. So the OpenChannel() used to open the channel and CloseChannel() method used to close the channel.
  5. Get(CustomerID) – Method to get the details of a particular Customer.
    /// <summary>
    /// Method to get a particular Customer based on Customer Id
    /// </summary>
    /// <param name="customerID">Customer Id</param>
    /// <returns>Customer Object</returns>
    public Customer Get(string customerID)
    {
        OpenChannel();
    
        Customer customer = channel.Get(customerID);
    
        CloseChannel();
        return customer;
    }
  6. Create(Customer) – For creating a new customer record in the database.
    /// <summary>
    /// Method to create a new Customer record in the system
    /// </summary>
    /// <param name="customer">Customer object</param>
    /// <returns>No of Row affected</returns>
    public int Create(Customer customer)
    {
        OpenChannel();
    
        int intNOfRows = channel.Create(customer);
    
        CloseChannel();
        return intNOfRows;
    }
  7. Update(Customer) – For updating an existing customer in the database.
    /// <summary>
    /// Method to Update existing Customer in the System
    /// </summary>
    /// <param name="customer">Customer object</param>
    /// <returns>No of Row affected</returns>
    public int Update(Customer customer)
    {
        OpenChannel();
    
        int intNOfRows = channel.Update(customer);
    
        CloseChannel();
        return intNOfRows;
    }
  8. Delete(Customer) – For Deleting an existing customer from the database.
    /// <summary>
    /// Method to Delete an existing Customer from the system
    /// </summary>
    /// <param name="customer">Customer object</param>
    /// <returns>No of Row affected</returns>
    public int Delete(string customerID)
    {
        OpenChannel();
    
        int intNOfRows = channel.Delete(customerID);
    
        CloseChannel();
        return intNOfRows;
    }
Step 12: Now we have completed the base work and can start integrating the code with UI. So let’s first start by binding the list of customer records to the GridView. Add below code in Page Load event and additional methods in code behind.
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        ClearScreen();
        BindGrid();
    }
}
/// <summary>
/// Clearing the screen
/// </summary>
private void ClearScreen()
{
    txtCustomerId.Text = "";
    txtCompanyName.Text = "";
    txtContactName.Text = "";
    txtContactTitle.Text = "";
    txtAddress.Text = "";
    txtCity.Text = "";
    txtRegion.Text = "";
    txtCountry.Text = "";
    txtPhoneNo.Text = "";
    txtFax.Text = "";
    lblMessage.Text = "";
    hndCustomerId.Value = "";
}
/// <summary>
/// Method which binds the data to the Grid
/// </summary>
private void BindGrid()
{

    try
    {
        CustomerHelper helper = new CustomerHelper();
        grdViewCustomers.DataSource = helper.GetAll();
        grdViewCustomers.DataBind();
    }
    catch (Exception e)
    {
    }
}
Step 13: Add the below code for getting the details of a customer record on click of Select hyperlink on a particular row.
protected void grdViewCustomers_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    hndCustomerId.Value = grdViewCustomers.Rows[e.NewSelectedIndex].Cells[2].Text.Trim();
    if (hndCustomerId.Value.Length > 0)
    {
        CustomerHelper helper = new CustomerHelper();
        Customer customer = helper.Get(hndCustomerId.Value);

        txtCustomerId.Text = customer.CustomerID;
        txtCompanyName.Text = customer.CompanyName;
        txtContactName.Text = customer.ContactName;
        txtContactTitle.Text = customer.ContactTitle;
        txtAddress.Text = customer.Address;
        txtCity.Text = customer.City;
        txtRegion.Text = customer.Region;
        txtCountry.Text = customer.Country;
        txtPhoneNo.Text = customer.Phone;
        txtFax.Text = customer.Fax;
    }
}
Step 14: Add below code for deleting a customer record on click of Delete hyperlink on a particular row.
protected void grdViewCustomers_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        e.Row.Cells[1].Attributes.Add("onclick", "return confirm('Are you sure want to delete?')");
    }
}
protected void grdViewCustomers_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    try
    {
        string strCustomerID = grdViewCustomers.Rows[e.RowIndex].Cells[2].Text.Trim();
        if (strCustomerID.Length > 0)
        {
            CustomerHelper helper = new CustomerHelper();
            helper.Delete(strCustomerID);

            ClearScreen();
            BindGrid();
            lblMessage.Text = "Record deleted successfully";
        }
    }
    catch (Exception ex)
    {
        lblMessage.Text = "There is an error occured while processing the request. Please verify the code!";
    }
}
Step 15: Add below code saving a customer record when click of Save button.
protected void btnSave_Click(object sender, EventArgs e)
{
    try
    {
        Customer customer = new Customer();
        customer.CustomerID = txtCustomerId.Text;
        customer.CompanyName = txtCompanyName.Text;
        customer.ContactName = txtContactName.Text;
        customer.ContactTitle = txtContactTitle.Text;
        customer.Address = txtAddress.Text;
        customer.City = txtCity.Text;
        customer.Region = txtRegion.Text;
        customer.Country = txtCountry.Text;
        customer.Phone = txtPhoneNo.Text;
        customer.Fax = txtFax.Text;

        CustomerHelper helper = new CustomerHelper();
        if (hndCustomerId.Value.Trim().Length > 0)
            helper.Update(customer);
        else
            helper.Create(customer);

        ClearScreen();
        BindGrid();
        lblMessage.Text = "Record saved successfully";
    }
    catch (Exception ex)
    {
        lblMessage.Text = "There is an error occured while processing the request. Please verify the code!";
    }
}
Step 16: Below code when page changing the page index.
protected void grdViewCustomers_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    grdViewCustomers.PageIndex = e.NewPageIndex;
    grdViewCustomers.SelectedIndex = -1;
    BindGrid();
}
Now you can run the project and verify the screen output. Below are the screen output from my client.

download the working example of the source in C# here.

Note:
  1. In some enterprise the firewall stops any communication with public directly. Even my enterprise network boundaries also won’t allow this. I get below error when I consume the service from the app.

    The token provider was unable to provide a security token while accessing 'https://comdotnettwittersop-sb.accesscontrol.windows.net/WRAPv0.9/'. Token provider returned message: 'Unable to connect to the remote server'.
    In that time, get the proxy address and configure the in the Web.Config settings as below and try again.
    <system.net>
        <defaultProxy useDefaultCredentials="true">
          <proxy proxyaddress="http://158.45.335.80:8080"/>
        </defaultProxy>
      </system.net>
  2. Deleting the customer may not work as the record may referred in other tables.
Download the working source code in C# here.

0 Responses to “Implementing Azure AppFabric Service Bus - Part 5”

Post a Comment