Monday, 30 July 2012

Securing WCF Services exposed on Azure Service Bus Namespace - Part 5


In previous post, we had seen implementing the service application and exposing to public using Service Bus endpoint. In this post, we will see the implementation of client application which consumes the service endpoint.

The functionality of this client application is very simple, but the important point is to consume the service bus endpoint after authenticate and authorize completed by ACS.

This client application shows a customer entry screen which has the following use case –
  1. The screen must show a list of customer information in Grid View.
  2. The screen must also have an entry fields which are used to enter the customer information for adding, updating the data.
  3. The user can add a new record by providing the details of the customer in the entry fields and press Save button.
  4. The Grid View must also have two column Select, Delete for selecting/deleting a particular record.
  5. To edit a particular record, user must press the Select on that particular row. The system should show the details of the customer in the entry fields. The user can change the details and save the details by pressing Save button.
  6. The user can delete a particular record by pressing Delete button.
Steps for creating a client project.

Step 1: Open Visual Studio and create a new project using File -> Project (Ctrl + Shift + N). Select the Web -> ASP.NET Web Application from Installed Template and press OK.
Step 2: Once the solution created successfully, open the Default.aspx page and change the script as below.
<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>
Step 3: Open the Default.aspx code behind and add the below events.
public partial class _Default : System.Web.UI.Page
{
    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)
    {
    }
}
We will be coding the implementation after required helper class implemented.

Step 4: The next step is to create the Customer class which will be used for decrypting the customer message which are received from service to an object.

This entity class must be with proper declaration and attributes which align with the namespace and properties of Customer class used in the service application. This class can be created by running svcutil utility from the Visual Studio Command Prompt with localhost endpoint URI (in the same system where the service is published).

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/DNT.RelayServiceBus/CustomerService.svc
This statement refers the local endpoint exposed on the server. So this command must run in the local server where the service is running.

Remove classes and interfaces other then Customer class from the proxy.cs file and copy the same to the client project. Make sure the namespace of the class is what client project namespace (In this example - DNT.ServiceConsumer).
Step 5: To consume the service, we need to define the customer service interface in the client application. So add a new class file and name it as ICustomerContract.cs.
[ServiceContract]
public interface ICustomerContract
{
 [OperationContract]
 IList<Customer> GetAll();

 [OperationContract]
 Customer Get(string customerID);

 [OperationContract]
 int Create(Customer customer);

 [OperationContract]
 int Update(Customer customer);

 [OperationContract]
 int Delete(string customerID);

 [OperationContract]
 string SayHello();

 [OperationContract]
 string Echo(string text);
}
If you note, this is the same interface which was used in the service application.
Step 6: I am going to create an helper class which will have the logic to connect the Service Bus and call the methods.

So create a class CustomerServiceHelper.cs and add the following methods.
public class CustomerServiceHelper
{
    private ICustomerContract channel = null;

    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;

        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.TokenProvider = TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerSecret);

        // create the channel factory loading the configuration
        ChannelFactory<ICustomerContract> channelFactory = new ChannelFactory<ICustomerContract>(binding, new EndpointAddress(serviceUri));

        // apply the Service Bus credentials
        channelFactory.Endpoint.Behaviors.Add(sharedSecretServiceBusCredential);

        // create and open the client channel
        channel = channelFactory.CreateChannel();
        ((ICommunicationObject)channel).Open();
    }

    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;
        }
    }

    public void CloseChannel()
    {
        ((ICommunicationObject)channel).Close();
        //channelFactory.Close();
    }

    public IList<Customer> GetAll()
    {
        OpenChannel();

        IList<Customer> customers = channel.GetAll();

        CloseChannel();
        return customers;
    }

    public Customer Get(string customerID)
    {
        OpenChannel();

        Customer customer = channel.Get(customerID);

        CloseChannel();
        return customer;
    }

    public int Create(Customer customer)
    {
        OpenChannel();

        int intNOfRows = channel.Create(customer);

        CloseChannel();
        return intNOfRows;
    }

    public int Update(Customer customer)
    {
        OpenChannel();

        int intNOfRows = channel.Update(customer);

        CloseChannel();
        return intNOfRows;
    }

    public int Delete(string customerID)
    {
        OpenChannel();

        int intNOfRows = channel.Delete(customerID);

        CloseChannel();
        return intNOfRows;
    }
}
The OpenChannel() method will open Service Bus connection and creating a channel. The CloseChannel() method will close the channel.

The OpenChannel() method creates the service bus endpoints (Refer: Line #17 and Line #22) based on the configuration setup in the Web.config file. The Web.Config file needs to provide the Service Bus namespace, binding method and service identity name and key.

Step 7: Change the Default.aspx code behind (Default.aspx.cs) code as below.
public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            ClearScreen();
            BindGrid();
        }
    }

    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 = "";

        txtCustomerId.ReadOnly = false;
    }

    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_PageIndexChanging(object sender, GridViewPageEventArgs e)
    {
        grdViewCustomers.PageIndex = e.NewPageIndex;
        grdViewCustomers.SelectedIndex = -1;
        BindGrid();
    }

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

    protected void grdViewCustomers_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
    {
        hndCustomerId.Value = grdViewCustomers.Rows[e.NewSelectedIndex].Cells[2].Text.Trim();
        if (hndCustomerId.Value.Length > 0)
        {
            CustomerServiceHelper helper = new CustomerServiceHelper();
            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;

            txtCustomerId.ReadOnly = true;
        }
    }

    protected void grdViewCustomers_RowDeleting(object sender, GridViewDeleteEventArgs e)
    {
        try
        {
            string strCustomerID = grdViewCustomers.Rows[e.RowIndex].Cells[2].Text.Trim();
            if (strCustomerID.Length > 0)
            {
                CustomerServiceHelper helper = new CustomerServiceHelper();
                helper.Delete(strCustomerID);

                ClearScreen();
                BindGrid();
                lblMessage.Text = "Record deleted successfully";
            }
        }
        catch (Exception ex)
        {
            lblMessage.Text = ex.Message;
        }
    }

    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;

            CustomerServiceHelper helper = new CustomerServiceHelper();
            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 = ex.Message;
        }
    }

    private void BindGrid()
    {

        try
        {
            CustomerServiceHelper helper = new CustomerServiceHelper();
            grdViewCustomers.DataSource = helper.GetAll();
            grdViewCustomers.DataBind();
        }
        catch (Exception ex)
        {
            lblMessage.Text = ex.Message;
        }
    }
}
Step 8: We also required to add the Service Bus Namespace, Endpoints and Service Identity name and key in the Web.Config file. These configurations will be referred in the Helper class.

So add the following configuration in the Web.Config file.
<appSettings>
 <add key="ServiceNamespace" value="dntsales" />
 <add key="IssuerName" value="thiru" />
 <add key="IssuerSecret" value="fBnxJPexOod7V96awTbnS2wjiMJV1Mo0J0xzr/tBr44=" />
 <add key="CustomerServiceEndPointNameTcp" value="Tcp/Order/Test/V0101" />
 <add key="CustomerServiceEndPointNameHttp" value="Http/Order/Test/V0101" />
 <add key="ServiceConsumingProtocol" value="Tcp"/>
</appSettings>
As you see in the configuration, I use thiru service identity for consuming the service. This identity is setup with Send claim in the Rule Group for the Relying Party application we are consuming here.

You can refer the following post for further information.
http://www.dotnettwitter.com/2012/06/securing-wcf-services-exposed-on.html

Step 9: Finally run the application and verify the output. If everything perfect, the application will show the following screen.
You can add, update or delete the customer records as per the use case provided before.

Note: Before running this code, you required to verify whether the endpoints are accessible on internet. It means, the service is running and exposing the service to public using Service Bus.

To verify you can browse the https://dntsales.servicebus.windows.net endpoint from the browser. The browser will show the next level hierarchy of endpoints in a list.

For more information on how to access the endpoints, please refer the following post in Step 15 and Step 16.
http://www.dotnettwitter.com/2012/01/implementing-azure-appfabric-service_11.html

download the client application source code here.


0 Responses to “Securing WCF Services exposed on Azure Service Bus Namespace - Part 5”

Post a Comment