.NET Blog

Tony Cavaliere

 
My Favourite Albums
  And the Grappa wins.
E-mail me Send mail
Add to Technorati Favorites AddThis Feed Button

Subscribe to Cynot Why Not


Recent posts

Disclaimer

Hey unlike other bloggers I stand by what I say but just in case. The opinions expressed herein are my own except on Tuesday when the second card is not turned up otherwise it ain't worth squat.

© Copyright 2010

Membership Ajax enabled WCF services: Where is CreateUser?

ASP.NET 2.0 introduced the Membership, Roles and Profile services. Developers could leverage these powerful services and rapidly add membership functionality to their sites. This was a great leap forward but did not address Web2.0, that is, there was no out of the box support for these services from an Ajax perspective. If you wanted to add Ajax type membership functionality the developer had to write ASMX or WCF services that wrapped these API calls. Then came along ASP.NET 3.5. In this release Microsoft shipped a set of Ajax enabled WCF services for the Membership, Roles and Profile services. Adding Ajax support was as simple as adding a few lines of XML to the web.config.

I just recently added Ajax support for the membership services to a local community web site. As advertised, just add a few lines of XML to the web.config and magically you can asynchronously log in or log out. This was wonderful. Then I decided to add support for registering users and then discovered there was no asynchronous equivalent to the CreateUser API. Why was this API not included? Are there security issues exposing this API?

The Ajax Membership API's are located in the assembly C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Web.Extensions.dll. Using every .NET developers trusted friend, reflector, we see that AuthenticationService class contains the following methods;

AuthenticationService     

Where is the CreateUser method? It appears as though it was not included. Why I ask, why was it not included? I guess we still have to resort to writing custom Ajax enabled Membership WCF services!

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: ASP.NET | WCF
Posted by CynotWhyNot on Tuesday, February 17, 2009 12:23 PM
Permalink | Comments (12) | Post RSSRSS comment feed

WCF: The Good the Bad and the Ugly

 

I make it a point to watch DNRTV every week. Carl Franklin the host of this excellent video cast program has a new presentation just about every week. The speakers are top notch. Last week a regular, Miguel Castro, gave a presentation on WCF. This was not your typical WCF talk with the usual demos where WCF services are created using the Visual Studio project templates. Instead, Miguel, created a complete WCF application client and host from scratch without the help of any project templates. In addition, the contract, implementation and host code were separated into individual assemblies. On the client side, a separate assembly was generated to house the proxies. The client application consumed the service by referencing this proxy assembly. This approach makes complete sense. If you are doing any work with WCF or plan to please make sure to catch this great presentation.

Miguel's presentation was done using the C# language and the code can be downloaded from the DNRTV site. I have duplicated the work in VB and if you are interested source can be download here.

Why the title you may ask? Well, Miguel's presentation first talked about the typical WCF presentation that uses the built-in Visual Studio project templates. He named the projects BadClient and BadService. He then went on to demo the 'Good' way of creating a WCF service and consuming it. Naturally, I thought of the great Sergio Leone classic. Hey, if you really want to get into the mood then drag the mouse over the poster to the left and sit back and enjoy.

Guess the movie (This one should be easy)

There are two kinds of people in the world, my friend. Those who have a rope around their neck and those who have the job of cutting.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: WCF
Posted by CynotWhyNot on Tuesday, September 16, 2008 12:20 PM
Permalink | Comments (75) | Post RSSRSS comment feed

Calling a WCF Service from Silverlight 2.0: Part Two

In part one of this post series, I showed the working Silverlight application that calls a WCF service. If you would like to see this application then go to part one.

In this post we will show how one goes about creating this application. This post is divided into fout main sections;

  1. Creating the WCF service.
  2. Creating the Silverlight application that consumes the service.
  3. Deployment Issues
  4. Deploying the WCF service and Silverlight control.

Initial Setup

Let's start by creating a Silverlight application. Start VS2008 and then select File->New Project menu option. This will bring up the New Project dialog. This dialog is shown in Figure 1. I have decided to name this project WCF.

New Project 

Figure 1: Creating a Silverlight Application.

In my case I have chosen the to create a Silverlight application using the VB.NET language. After clicking the OK button, the Add Silverlight Application dialog appears. As shown in Figure 2, accept the defaults and click the OK button.

Add Silverlight Application

Figure 2: The Add Silverlight Application Dialog.

VS2008 should have created a solution with two projects; the first an ASP.NET application used to host the Silverlight control and a second which is the Silverlight control. Figure 3 shows the solution window with these two projects.

Initial Silverlight Solution 

Figure 3: The Initial Silverlight Solution.

We are now ready to create the WCF service.

 

WCF Service

The service we about to create will return a list of person objects. Begin by adding a new class to the web site project. To do this right click on the WCFWeb project and select the Add New Item menu option. This will result in the Add New Item dialog appearing as shown in Figure 4.

Add New Item Person Class

Figure 4: Adding the Person class

Name the class Person and select the OK button. This will cause an additional dialog to appear asking whether you want to add the code to the App_Code directory, select OK. Modify the generated Person class as per Listing 1.

Imports System.ServiceModel

Imports System.Runtime.Serialization

Imports Microsoft.VisualBasic

 

'This class needs to be serializable. The DataContract attribute is the WCF way

'of doing this.

<DataContract()> _

Public Class Person

 

    'First name of the person. You must opt in to make a property serializable,

    'hence the DataMember attribute.

    Private _First As String

    <DataMember()> _

    Public Property First() As String

        Get

            Return _First

        End Get

        Set(ByVal value As String)

            _First = value

        End Set

    End Property

 

    'Last name of the person. You must opt in to make a property serializable,

    'hence the DataMember attribute.

    Private _Last As String

    <DataMember()> _

    Public Property Last() As String

        Get

            Return _Last

        End Get

        Set(ByVal value As String)

            _Last = value

        End Set

    End Property

 

    'Simple constructor.

    Public Sub New(ByVal first As String, ByVal last As String)

        Me.First = first

        Me.Last = last

    End Sub

 

End Class

Listing 1: Person Class

The Person class is rather simple containing just two properties and one constructor. The class is decorated with the <DataContract()> attribute which indicates it is serializable. The <DataMember()> attributes that decorate the two properties indicates that the members are part of the contract and are serializable. Unlike the <Serializable()> attribute, where by default properties are automatically serialized, with the <DataContract()> attribute you must explicitly chose which properties are included during serialization.

Next add a WCF Service template to the web project. Right click on the web project and select the Add New Item menu option. This will bring up the Add New Item dialog as shown in Figure 5. Call the service PersonService.

Add New Item - WCF Service

Figure 5: Add the WCF Service

After selecting the Add button, three new files are added to the web project; IPersonService.vb, PersonService.vb and PersonService.svc. In addition, the web.config file is modified with the addition of the <system.serviceModel> section.

Let's investigate each file.

As previously mentioned the web.config is modified whenever a WCF service is added to the project. Specifically, the section <system.serviceModel> is added. It is here that the configuration of the newly added WCF service exists. Two changes are required in order to get the service to properly working. Firstly, the binding wsHttpBinding needs to be changed to basicHttpBinding as Silverlight currently only supports the basicHttpBinding. The second change, although not strictly required, is to hard code the port for the dns. I like to do this to ensure that the application will always work regardless of which port the development web server decides to chose. An alternative approach would be to create a web application which automatically configures an IIS virtual directory. In order to hard code a port number select the web site project and go to the Properties window. Next set Use dynamic ports to False and then chose some port number. I like to use a port number of 666. Now you can update the web.config and hard code the dns value to localhost:666. Figure 6 shows the property window with the hard code port number and Listing 2 shows the <system.serviceModel> section with the binding and port number changes.

Property Window Port Number

Figure 6: The Web Site Project Properties Window with the hard coded port number

 

 

    <system.serviceModel>

        <behaviors>

            <serviceBehaviors>

                <behavior name="PersonServiceBehavior">

                    <serviceMetadata httpGetEnabled="true" />

                    <serviceDebug includeExceptionDetailInFaults="false" />

                </behavior>

            </serviceBehaviors>

        </behaviors>

        <services>

            <service behaviorConfiguration="PersonServiceBehavior" name="PersonService">

                <endpoint address="" binding="basicHttpBinding" contract="IPersonService">

                    <identity>

                        <dns value="localhost:666" />

                    </identity>

                </endpoint>

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

            </service>

        </services>

    </system.serviceModel>

Listing 2: web.config

The change to the IPersonService.vb interface is small as all we need to do is change the generated function name to something more meaningful. Listing 3 shows the necessary changes to IPersonService.vb.

 

Imports System.ServiceModel

 

<ServiceContract()> _

Public Interface IPersonService

 

    <OperationContract()> _

    Function GetPeople() As List(Of Person)

 

End Interface

Listing 3: IPersonService.vb

Note the only change to the templated interface is the function name and that it returns a list of person objects. The interface is decorated with the <ServiceContract()> attribute indicating the interface defines a service contract in a WCF application. The <OperationContract()> attribute indicates that the GetPeople function defines an operation that is part of a service contract. The bottom line is that a client (Silverlight in our case) can call the function GetPeople across process boundaries.

The PersonService class needs to implement the IPersonService interface. Listing 4 contains the modifications to the class.

 

Imports System.ServiceModel

 

'Implementation of IPersonService.

<ServiceBehavior(IncludeExceptionDetailInFaults:=True)> _

Public Class PersonService

    Implements IPersonService

 

    'Return an in memory list of people.

    Public Function GetPeople() As List(Of Person) Implements IPersonService.GetPeople

        Dim people As New List(Of Person)

        people.Add(New Person("John", "Smith"))

        people.Add(New Person("Jane", "Summers"))

        Return people

    End Function

 

End Class

Listing 4: PersonService.vb

The implementation of the GetPeople function is rather simple. Firstly, we create a reference to a List of Person objects. Next two person objects are added to the list collection and finally this list of persons is returned.

The PersonService.svc represents the end point to the service. Provided we run the service within the development environment, this file does not require any modifications. Later, in the this post we will discuss deployment issues and we will see that modifications to this file and additional code is required when hosting the service on a web server that uses multiple host headers (i.e., more than one address). More on that later.

Right click on the PersonService.svc and select the  View in Browser menu option. If all is well you should see the default service web page appearing (see Figure 7).

Browse to PersionService

Figure 7: Navigating to the PersonService.svc service.

That's it we have created a WCF service. Now let's move on to consuming this service from within a Silverlight application.

 

Consuming the WCF Service from within Silverlight

We will start by adding a reference to the service. Right click on the Silverlight project and select the Add Service Reference menu item. This will bring up the Add Service Reference dialog. Select the Discover button. This will cause the IDE to search the solution for all services, displaying them in the list box. Since there is only one service in the solution the Services list box will only contain a single service, namely, PersonService.svc. Change the namespace to PersonProxy. After doing this the dialog should appear similar to Figure 8.

Add Service Reference

Figure 8: Adding a Service Reference to the Silverlight Project

Select OK. This will add the PersonProxy reference to the project and in addition will add the ServiceReferences.ClientConfig configuration file. This configuration file requires modification as it does not contain the fully qualified name of the service. It is missing the namespace, WCF (the name of the project). I'm not sure if this is a Beta 2 bug and perhaps it will be fixed for RTM. After this change the ServiceReferences.ClientConfig file should appear the same as in Listing 5.

<configuration>

    <system.serviceModel>

        <bindings>

            <basicHttpBinding>

                <binding name="BasicHttpBinding_IPersonService"

                    maxBufferSize="65536"

                    maxReceivedMessageSize="65536">

                    <security mode="None" />

                </binding>

            </basicHttpBinding>

        </bindings>

        <client>

            <endpoint address="http://localhost:666/WCFWeb/PersonService.svc"

                binding="basicHttpBinding"

                bindingConfiguration="BasicHttpBinding_IPersonService"

                contract="WCF.PersonProxy.IPersonService"

                name="BasicHttpBinding_IPersonService" />

        </client>

    </system.serviceModel>

</configuration>

Listing 5: ServiceReferences.ClientConfig

This configuration file is included as content into the Silverlight XAP deployment file. If you need to change the end point, as you would need to do so if you are changing the location of the WCF service, then you can rename the XAP extension to ZIP, unzip the contents, modify the endpoint, re-zip the file and rename the extension back to XAP.

Before we can write any code to call the service, we need to add some UI to the Page.xaml file. I have chosen to display the data in a DataGrid. To initiate the call to the sercice I have added a button control. Finally, in case there were any errors generated during the call to the service, I have included a TextBlock. Let's proceed with the UI.

Listing 6 contains the complete XAML for the UI. The default generated Grid has been replaced with a StackPanel. Within the StackPanel are the Button, DataGrid and TextBlock controls. It is best not to paste the code from this listing into your copy of Page.xaml as the DataGrid requires an addition reference which is automatically included when you drag and drop the control.

<UserControl

   xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 

   x:Class="WCF.Page"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="500" Height="250">

 

    <StackPanel x:Name="LayoutRoot" Background="Bisque">

        <Button Content="Call WCF Service" FontSize="24" Width="220" Height="50"

               Margin="15" Click="btnWCF_Click" />

        <my:DataGrid x:Name="grdPeople" AutoGenerateColumns="False"

                    FontSize="24" Width="500" RowHeight="50" 

                    Visibility="Collapsed">

            <my:DataGrid.Columns>

                <my:DataGridTextColumn Header="First Name" FontSize="24" Width="230"

                   DisplayMemberBinding="{Binding First}" />

                <my:DataGridTextColumn Header="Last Name" FontSize="24" Width="237"

                   DisplayMemberBinding="{Binding Last}" />

            </my:DataGrid.Columns>

        </my:DataGrid>

        <TextBlock x:Name="txtError" TextWrapping="Wrap" Width="500" Height="350"

                  ScrollViewer.VerticalScrollBarVisibility="Auto" />

    </StackPanel>

 

</UserControl>

Listing 6: Page.xaml

A couple of notes on the DataGrid. The AutoGenerateColumns is set to false so that we can control which items are data bound. We are declaratively binding the first and last name using the DisplayMemberBinding attribute. Later in the code behind we will call the service and set the ItemSource of the DataGrid.

That's it for the UI now let's concentrate on the code behind.

The code behind for Page.xaml is responsible for calling the WCF service and then setting the ItemSource of the DataGrid to the list of person objects. Listing 7 contains the complete code listing for page.xaml.vb.

Partial Public Class Page

    Inherits UserControl

 

    Public Sub New()

        InitializeComponent()

    End Sub

 

    Private Sub btnWCF_Click( _

        ByVal sender As System.Object, _

        ByVal e As System.Windows.RoutedEventArgs)

 

        Dim proxy As New PersonProxy.PersonServiceClient()

        AddHandler proxy.GetPeopleCompleted, AddressOf onGetPeopleCompleted

        proxy.GetPeopleAsync()

 

        grdPeople.Visibility = Windows.Visibility.Collapsed

        grdPeople.ItemsSource = Nothing

 

    End Sub

 

    Public Sub onGetPeopleCompleted( _

        ByVal sender As Object, _

        ByVal e As PersonProxy.GetPeopleCompletedEventArgs)

 

        If e.Error Is Nothing Then

            grdPeople.Visibility = Windows.Visibility.Visible

            grdPeople.ItemsSource = e.Result

        Else

            txtError.Text = e.Error.ToString

        End If

 

    End Sub

 

End Class

Listing 7: Page.xaml.vb

The code consists of two methods. The btnWCF_Click method is wired to the button click event. This is where we instantiate the PersonProxy. Next an event handler for the GetPeopleCompleted event is added specifying the method to be called when the event is fired. We then initiate the call to the service by invoking GetPeopleAsync. Silverlight will only allow asynchronous calls to services.

The method onGetPeopleCompleted is responsible for binding the data to the DataGrid. As a precaution, we first check to see if an error has occurred and if so the error is displayed in a TextBlock. I have found that exceptions that happen during the call to the WCF service are by default hidden, i.e., they appear as (404) Not Found exception. This is a security feature as you don't necessarily want the consumers of the service to have knowledge of your code. Further investigation is  required to know how to best handle these kinds of exceptions.

If no error is generated during the call to the WCF service then the returned data (e.Result) is is bound to the DataGrid via the ItemSource property.

That's it we are done. Let's try running the application. Right click the WCFTestPage.aspx file in the web site project and select View in Browser menu option. This should bring up the browser and hopefully the Silverlight control will appear. Click the Call WCF Service button and after a short period of time a DataGrid should appear, containing a list of people. Figure 9 shows the DataGrid with the list of people.

I also have a working copy of this Silverlight control embedded in my previous post.

WCF Test Page

Figure 9: Running the Silverlight Test Page. 

 

Deployment Issues

I wanted to share with you some challenges I came across when I deployed this WCF service to my ISP. Hopefully this will save you some time.

The first problem I came across was using Silverlight for cross-domain communication, that is, Silverlight by default can only call services that are hosted on the same domain. This prevents cross-site request forgery and prevents a Silverlight control from making unauthorized calls to a third party service. In order for a Silverlight control to access a service in another domain the service must grant access. This can be done by installing one of two files on the web server. I will discuss only one of these files, the ClientAccessPolicy.xml. The contents of this file is shown in Listing 8.

<?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 include-subpaths="true" path="/" />

      </grant-to>

    </policy>

  </cross-domain-access>

</access-policy>

Listing 8: ClientAccessPolicy.xml

To add this file to the web site project, right click the project and select the Add New Item menu option. From the Add New Item dialog box select the XML file template and name it ClientAccessPolicy.xml. Finally, cut and paste the contents of the XML in listing 8 into this XML file.

As indicated by the <domain uri="*" /> node, this file allows requests from any domain. For further details on allowing cross-domain access visit MSDN Site. This file must be deployed to the root of the domain where the service is installed.

The second problem was a little more difficult to resolve. After I installed the the ClientAccessPolicy.xml file to the web server I received the following error:

This collection already contains an address with scheme http: There can be at most one address per scheme in the collection.

After a bit of searching I found a few posts that explained this multiple bindings issue. Out of the box .NET does not support multiple bindings per site and since I am hosting this service on my ISP the likelihood that it has multiple bindings is great. Thankfully there is a way around this that involves creating your own custom service host factory. This factory is responsible for choosing the appropriate base address. Listing 9 shown this custom service host factory.

 

Imports Microsoft.VisualBasic

Imports System.ServiceModel.Activation

Imports System.ServiceModel

 

Public Class CustomHostFactory

    Inherits ServiceHostFactory

 

    Protected Overrides Function CreateServiceHost( _

        ByVal serviceType As System.Type, _

        ByVal baseAddresses() As System.Uri) _

        As System.ServiceModel.ServiceHost

 

        Return New ServiceHost(serviceType, baseAddresses(0))

 

    End Function

 

End Class

Listing 9: Custom Service Host Factory

To add this code right click the App_Code folder in the web site project and select the Add New Item menu option. Then from the Add New Item dialog select the Class template and name it CustomService.vb. Finally cut and paste the code from Listing 9 into CustomService.vb.

The code is rather simple. The class CustomHostFactory inherits from ServiceHostFactory and overrides the CreateServiceHost function. The implementation creates an instance of a ServiceHost class and chooses the first base address from the collection of addresses.

Next the PersonService.svc file must be changed so that the service is created using this custom service host factory. Listing 10 shows this modified PersonService.svc file. For further information please visit the following blog post.

 

<%@ ServiceHost Language="VB" Debug="true" Factory="CustomHostFactory"

Service="PersonService" CodeBehind="~/App_Code/PersonService.vb" %>

Listing 10: Adding Custom Service Host Factory to PersonService.svc

 

Deploying the WCF Service and Silverlight Application to a Web Server

WCF Service:

Copy the following files to the virtual directory where the service is to be hosted:

  • App_Code/CustomService.vb
  • App_Code/IPersonService.vb
  • App_Code/Person.vb
  • App_Code/PersonService
  • PersonService.svc
  • web.config (modify the DNS value and set it to the domain where the service is to hosted)

Silverlight Application:

Add the Silverlight control to whatever page you desire. If you need to change the location of the WCF Service then you can rename the XAP extension to ZIP, unzip the contents, modify the endpoint in the ServicesReferences.ClientConfig file, re-zip the file and rename the extension back to XAP. 

Guess the movie

See that clock on the wall? In five minutes you are not going to believe what I've told you.

Currently rated 3.5 by 6 people

  • Currently 3.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: Silverlight | WCF
Posted by CynotWhyNot on Friday, August 15, 2008 6:29 AM
Permalink | Comments (83) | Post RSSRSS comment feed

Postback, UpdatePanel or JSON which one?

Microsoft introduced AJAX as an add on to .NET 2.0. Today, AJAX is now included as part of the core release of .NET 3.5. With this technology it is significantly easier for developers to implement Web 2.0 type applications. Rather than posting the entire form and receiving the entire page, the browser can send a partial request and receive a partial response. This is known as partial rendering. In some cases, all that is required is adding a ScriptManager and an UpdatePanel control and you are done. If you wish to reduce the payload even further you can use JavaScript Object Notation (JSON) and reduce the request and response footprint even further. In this post, I will investigate each of these technics, that is, the postback, UpdatePanel and JSON and hopefully give some insight on when each is appropriate.

Prerequisites

  1. Visual Studio 2008. Visual Web Developer Express 2008 should be fine. This post will be using VS2008.
  2. SQL Server with the Northwind database. The express ver1sion should be fine.

Initial Setup

Start Visual Studio 2008 and create a new web site. Delete the default.aspx web form that was added. When done your solution should be similar to the following:

Figure 1: Initial Solution

F1 Initial Solution 

Next add a LINQ To SQL class to the web site. But before you do this make sure you have a data connection to the Northwind database. Right click on the web site and select Add New Item. Select LINQ To SQL Class and name it Northwind.dbml. The Add New Item dialog is shown in Figure 2.

Figure 2: Add LINQ To SQL Class

F2 Add LINQ To SQL Class

A dialog will appear asking whether you would like to place the LINQ To SQL class into the App_Code folder, answer yes. For this scenario, we will be constructing a query that returns all the products for a customer using postal code to identify the customer. This requires that we add the Customers, Orders, Order Details and Products table to the designer surface. In the Server Explorer, navigate to the Northwind connection (if it doesn't already exist then add the connection) and expand the tables node. Select each table and drag them to the designer surface. Once completed the designer window should appear similar to the following:

Figure 3: LINQ To SQL Object Model

F3 LINQ To SQL Object Model

Behind the scenes LINQ To SQL is generating object classes that mirrors the tables that were added to the designer surface. To view the source for these classes use the Class View window. There you should see the Customer, Order, Order_Detail and Product classes. You may need to save the dbml file before these classes become visible in the Class View. One last point the dbml file is an XML file that describes schema for tables. This XML file is used to generate the object classes.

The next step is to add the code that will be responsible for retrieving products by postal code. Here we will use a LINQ query and the query will return a ProductsByPostalCode object that is serializable. This serialization will be important when we implement code that uses JSON. Right click the APP_Code folder and select the Add New Item menu item. Next select the class icon and choose Products.vb as the name for the file. Finally, add the code in Listing 1 to the Products class.

Listing 1: Code to Retrieve Products by Postal Code.

Imports System

Imports System.ServiceModel

Imports System.Runtime.Serialization

 

Public Class Products

 

    Public Function GetByPostalCode(ByVal pc As String) As List(Of ProductsByPostalCode)

 

        Dim db As New NorthwindDataContext()

        Dim products = From c In db.Customers _

                       Join o In db.Orders On o.CustomerID Equals c.CustomerID _

                       Join d In db.Order_Details On d.OrderID Equals o.OrderID _

                       Join p In db.Products On d.ProductID Equals p.ProductID _

                       Where c.PostalCode.Equals(pc) _

                       Select New ProductsByPostalCode With { _

                            .CompanyName = c.CompanyName, _

                            .Quantity = d.Quantity, _

                            .ProductName = p.ProductName, _

                            .PostalCode = c.PostalCode, _

                            .Phone = c.Phone _

                        }

        Return products.ToList

 

    End Function

 

End Class

 

<DataContract()> _

Public Class ProductsByPostalCode

 

    Private _CompanyName As String

    <DataMember()> _

    Public Property CompanyName() As String

        Get

            Return _CompanyName

        End Get

        Set(ByVal value As String)

            _CompanyName = value

        End Set

    End Property

 

    Private _ProductName As String

    <DataMember()> _

    Public Property ProductName() As String

        Get

            Return _ProductName

        End Get

        Set(ByVal value As String)

            _ProductName = value

        End Set

    End Property

 

    Private _Quantity As Short

    <DataMember()> _

    Public Property Quantity() As Short

        Get

            Return _Quantity

        End Get

        Set(ByVal value As Short)

            _Quantity = value

        End Set

    End Property

 

    Private _PostalCode As String

    <DataMember()> _

    Public Property PostalCode() As String

        Get

            Return _PostalCode

        End Get

        Set(ByVal value As String)

            _PostalCode = value

        End Set

    End Property

 

    Private _Phone As String

    <DataMember()> _

    Public Property Phone() As String

        Get

            Return _Phone

        End Get

        Set(ByVal value As String)

            _Phone = value

        End Set

    End Property

 

End Class

The method GetProductsByPostalCode creates a LINQ query that joins the Customers, Orders, Order_Details and Products objects and selects the CompanyName, ProductName, Quantity, PostalCode and Phone for the specified postal code. This data is returned in a custom object, ProductsByPostalCode. Using an anonymous type would not have been approp1riate here as serializing anonymous types is not easily done (not even sure if it is at all possible). The PoductsByPostalCode class is attributed with <DataContact> and the only method GetByPostalCode is attributed by <DataMember>. These attributes specify that the type defines or implements a data contract and is serializable. The <Serializable> attribute could have been used but <Data Contract> is the preferred WCF way.

That's it, the data access layer is complete.

 

Postback

Implementing the postback model is fairly straight forward. Add a web form to the web site and call the web form, Postback.aspx. Next add a TextBox, Button and GridView. Afterwards the markup should look like Listing 2.

List 2: Postback.aspx markup code. 

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Untitled Page</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

      <asp:TextBox ID="txtPostalCode" runat="server"></asp:TextBox>

      <asp:Button ID="btnGetProducts" runat="server" Text="Get Products"></asp:Button>

      <br/>

      <asp:GridView ID="gvProducts" runat="server"></asp:GridView>

    </div>

    </form>

</body>

</html>

Next in the code behind add the following code that gets executed when the button is clicked. This code calls the GetByPostalCode method that was created earlier.

List 3: Postback.aspx.vb code behind.

    Protected Sub btnGetProducts_Click(ByVal sender As Object, ByVal e As System.EventArgs) _

    Handles btnGetProducts.Click

        Dim prod As New Products

        gvProducts.DataSource = prod.GetByPostalCode(txtPostalCode.Text)

        gvProducts.DataBind()

    End Sub

Now lets take a look at the payload of this web application. To do this we will use a Fiddler a great tool for monitoring HTTP traffic. First start Fiddler and then start the Postback.aspx. Enter the postal code 12209 and click the Get Products button. You should see a table with 12 products, as shown in Figure 4.

Figure 4: Products returned for postal code 12209.

F4 Postabackaspx Products for 12209

In Fiddler, select the postback.aspx URL session and then select the Performance Statistics tab. The bytes sent are 1522 and bytes received are 5,295. If you would like to see the raw data sent to and from the browser, double click the session in Fiddler and select the Raw tab. It is apparent that the entire form is sent to the server and the server sends the entire page back to the browser.

Figure 5: Raw request and Response from Fiddler

F5 Fiddler Request and Response 

 

Partial Rendering with the UpdatePanel

This time we will use the AJAX UpdatePanel to fetch the products. Adding an UpdatePanel is extremely simple, all you need to do is add a ScriptManager tag and then surround the portion of the page you wish to render partially with an UpdatePanel control. Add a new web form to the site and give it a name UpdatePanel. Next copy the markup from Postback.aspx to the UpdatePanel.aspx and similarly copy the code behind from Postback.aspx.vb to UpdatePanel.aspx.vb.

Drag and drop the AJAX ScriptManager from the toolbox to the UpdatePanel.aspx making sure that the <ScriptManager> tag appears after the <form> tag. The next step is to add the AJAX UpdatePanel control to the UpdatePanel.aspx markup. Again this is done by dragging and dropping the AJAX UpdatePanel control from the toolbox. Add the <ContentTemplate> tags within the the <UpdatePanel> tags. Lastly, cut the markup for the Button, TextBox and GridView controls and paste them within the <ContentTemplate> tags. If all is well you should have code resembling Listing 4.

Listing 4: Markup with the UpdatePanel Control

<html xmlns="http://www.w3.org/1999/xhtml">

<head id="Head1" runat="server">

    <title>Untitled Page</title>

</head>

<body>

    <form id="form1" runat="server">

      <asp:ScriptManager ID="ScriptManager1" runat="server">

      </asp:ScriptManager>

    <div>

      <asp:UpdatePanel ID="UpdatePanel1" runat="server">

        <ContentTemplate>

          <asp:TextBox ID="txtPostalCode" runat="server"></asp:TextBox>

          <asp:Button ID="btnGetProducts" runat="server" Text="Get Products"></asp:Button>

          <br/>

          <asp:GridView ID="gvProducts" runat="server"></asp:GridView>       

        </ContentTemplate>

      </asp:UpdatePanel>

    </div>

    </form>

</body>

</html>

I have not included the button event handler code as it is identical to that of the Postback.aspx.vb.

Now let's look at the payload of this page. Start Fiddler and then navigate to the UpdatePanel.aspx page. You may need to set the UpdatePanel.aspx as the Start page. Next enter the postal code 12209 and select the Get Products button. The output from the page will be the same as in Figure 4. In Fiddler select the session that corresponds to the button click and then select the Performance Statistics tab. Fiddler reports that the bytes sent are 933 and bytes received are 5124. Not a huge saving, but let's not forget that the page consists almost entirely of the grid. What if the page contained other content that did not require refreshing then the payload difference between the postback model and the UpdatePanel would be significant. Figure 6 shows the request and response for the UpdatePanel. Note that the response only contains the markup that appears within the UpdatePanel. This is what is meant by partial rendering; only the markup that is requested is sent back in the HTTP response.  

Figure 6: UpdatePanel: Request and Response

F6 UpdatePanel Request and Response

This is great. By adding only a few lines of markup you can significantly reduce the payload of your page.

 

WCF and JSON

The last method I will demonstrate reduces the payload even more, but at a cost. JavaScript Object Notation (JSON) is an extremely terse way of serializing objects. Basically, JSON uses name-value pairs to serialize the object. XML serialization could be used, instead, but it would be more verbose and more importantly there is no built-in JavaScript support for XML serialization. Windows Communication Foundation (WCF) makes it extremely simple to create a service that uses JSON as it's transport mechanism.

To add an AJAX-enabled WCF Service, right click the web site and select Add New Item. The Add New Item dialog appears. Select the AJAX-enabled WCF Service template and name it ProductService.svc. Figure 7 shows the Add New Item dialog.

Figure 7: Add AJAX-enabled WCF Service.

F7 Add AJAX-enable WCF Service 

Adding the AJAX-enabled WCF Service causes three things to happen:

  1. ProductService.svc file is added to the web site. This is analogous to the asmx file generated file in web services.
  2. ProductService.vb file is added in the App_Code folder. This the code behind for the service. Remove the AspNetCompatibility attribute.
  3. The <system.serviceModel> node containing WCF configuration is added to the web.config file. Remove the line <serviceHostingEnvironment aspNetCompatibilityEnabled="true" /> from the web.config.

I've had issues with keeping the ASP.NET Compatibility setting. For some reason the client browser, at runtime, does not recognize the namespace (in this case CynotWhyNot). This is strange as intellisense recognizes the namespace.  

By default, the WCF service added will use JSON for serialization. Changing it to use XML requires a change to the <behaviors> section in the web.config. In order to use this service, changes are required to ProductService.vb, namely, we need to add the code to call the data access code. Listing 5 contains the necessary changes to call the Products GetByPostalCode method.

Listing 5: ProductService.vb

Imports System.ServiceModel

Imports System.ServiceModel.Activation

Imports System.ServiceModel.Web

 

<ServiceContract(Namespace:="CynotWhyNot.WhichOne")> _

Public Class ProductService

 

    <OperationContract()> _

    Public Function GetProducts(ByVal pc As String) As List(Of ProductsByPostalCode)

        Dim nwp As New Products

        Dim products As New List(Of ProductsByPostalCode)

        products = nwp.GetByPostalCode(pc)

        Return products

    End Function

 

End Class

Some points of interest:

  1. The <ServiceContract> attribute is added at the class level. All interfaces that are to be exposed as a service must have this attribute. Interestingly, when adding an AJAX-enabled WCF service no interface is generated. I have been looking for an explanation for why it behaves this way as I was under the impression that all WCF services must implement an interface that is attributed by the <ServiceContract> attribute. I have come across a few web casts that mention this as a convenience. Nonetheless it works fine.  It is good practise to add a namespace to avoid collisions. 
  2. The <OperationContract> attribute is added to the GetProducts method. The <OperationContract> attribute exposes the method as a service operation.

The WCF framework will automatically generate the JavaScript code required to invoke this service. To view the generated JavaScript, navigate the browser to the service and then  append /js to the URL. I am using FireFox here as it allows the view of the code directly in the browser. IE, on the other hand, r1equires that the JavaScript be saved to a file.

Figure 8: Generated JavaScript to Invoke the Service. 

F8 Javascript generated code

Note the function CynotWhyNot.WhichOne.ProductService.GetProducts. This will be the JavaScript function that will be called to asynchronously invoke the server side service.

With the WCF service in place we can now concentrate on the aspx page. This page will need to call the service, retrieve the product data from the call to the service and finally render the data.  Yes, dare I say it we need to write JavaScript. This is what I meant by there is a cost and that cost is coding HTML DOM using JavaScript.

Add a new web form to the web site and call it JSON.aspx. Next add the following code to JSON.aspx.

Listing 6: JSON.aspx Markup and Code

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Untitled Page</title>

</head>

 

<script type="text/javascript">

 

  function getProducts() {

    var postalCode = document.getElementById('txtPostalCode');

    CynotWhyNot.WhichOne.ProductService.GetProducts(postalCode.value,onGetProductsComplete);

  }

 

  function onGetProductsComplete(products) {

    clearRows();

    //If products are returned then go ahead and add rows to the table.

    if (products.length > 0)

    {

        for(var i = 0; i < products.length; i++)

        {         

            var product = products[i];

            addRow(

                product.CompanyName,

                product.Phone,

                product.PostalCode,

                product.ProductName,

                product.Quantity

            );

        }

    }

  }

 

  var c = "#EFF3FB";

  function addRow(company,phone,postal,product,quantity) {

    if (c == "#EFF3FB") { c = "white"; } else { c = "#EFF3FB"; }

    var table = document.getElementById('tblProducts');

    var row = table.insertRow(table.rows.length);

    row.bgColor = c;

    row.insertCell(0).appendChild(document.createTextNode(company));

    row.insertCell(1).appendChild(document.createTextNode(postal));

    row.insertCell(2).appendChild(document.createTextNode(phone));

    row.insertCell(3).appendChild(document.createTextNode(product));

    row.insertCell(4).appendChild(document.createTextNode(quantity));

  }

 

  function clearRows() {

    var table = document.getElementById('tblProducts');

    for(var i = table.rows.length - 1; i > 0 ; i--)

    {

      table.deleteRow(i);

    }

  }

 

</script>

 

<body>

    <form id="form1" runat="server">

    <div>

 

      <asp:ScriptManager ID="ScriptManager1" runat="server">

        <Services>

          <asp:ServiceReference Path="~/ProductService.svc" />

        </Services>

      </asp:ScriptManager>

 

      <asp:TextBox ID="txtPostalCode" runat="server"></asp:TextBox>

      <asp:Button ID="btnGetProducts" runat="server" Text="Get Products" OnClientClick="getProducts(); return false;"></asp:Button>

 

      <br/>

 

      <asp:Table ID="tblProducts" runat="server" EnableViewState="false" >

          <asp:TableHeaderRow BackColor="#DDDDDD" Font-Size="Small">

              <asp:TableHeaderCell HorizontalAlign="Left" Width="400px"><h2>Company</h2></asp:TableHeaderCell>

              <asp:TableHeaderCell HorizontalAlign="Left" Width="150px"><h2>Postal</h2></asp:TableHeaderCell>

              <asp:TableHeaderCell HorizontalAlign="Left" Width="150px"><h2>Phone</h2></asp:TableHeaderCell>

              <asp:TableHeaderCell HorizontalAlign="Left" Width="350px"><h2>Product</h2></asp:TableHeaderCell>

              <asp:TableHeaderCell HorizontalAlign="Left" Width="100px"><h2>Quantity</h2></asp:TableHeaderCell>

          </asp:TableHeaderRow>

      </asp:Table>

 

    </div>

    </form>

</body>

</html>

Let's breakdown this code.

The <ScriptManager> tag is added so that we can add a reference to ProductService.srv service. This will automatically cause the JavaScript generated code (see Figure 8) to be uploaded to the client browser. An added feature is that design time intellisense support for this JavaScript is included. I was impressed when I first saw this. The addition of the <ScriptManager> tag also causes the broser to upload the Microsoft Ajax library.

The button uses the OnClientClick attribute to call the custom function getProducts and then returns false. Returning false is needed otherwise the click action will cause a postback and we want to prevent this from happening.

The GridView has been replaced with a table and only the table header is added. The table rows containing the product information will be added using JavaScript.

Finally, we have four JavaScript functions contained within the script block. The function getProducts calls the JavaScript service proxy CynotWhyNot.WhichOne.ProductService.GetProducts passing two parameters; postalCode.value and onGetProductsComplete. postalCode.value is the postal code entered in the text box and  onGetProductsComplete is a function callback that is called when the service has completed. There are two optional parameters that have been omitted for brevity; a callback to a function in case an error occurred in the service and userContext which can be any data the developer wishes to pass to the callback function when the service successfully completed (in our case onGetProductsComplete).

The function onGetProductsComplete is called asynchronously when the service completes provided there were no errors. Any return value from the service call is returned as the first parameter to the callback. The great thing is this parameter is automatically de-serialized for you. That is, the parameter, products, in the callback to onGetProductsComplete  is a JavaScript object that parallels the server side object List(Of ProductsByPostalCode). Since JavaScript has no understanding of managed generics, the JavaScript object is returned as an array of objects of type ProductsByPostalCode.

The remaining JavaScript code is responsible for adding rows to the table. I  won't bore you with the details of it.

Run this page, enter a postal code of 12209 and then select the Get Products button. The resulting page is shown in figure 9.

Figure 9: Running JSON.aspx.

F9 Running JSON.aspx

The question is what kind of payload results when we use JSON. Start Fiddler and then browse to JSON.aspx, enter 12209 as the postal code and then select the Get Products button. Select the session in Fiddler and then click the Performance Statistics tab. The bytes sent is now only 546 and bytes received is 2408. Down by a factor of 2 from the previous ways. Now let's take a look at the actual data sent across the wire. Double click on the session in Fiddler and select the raw tab. The results are shown in Figure 10.

Figure 10: Data transmitted using JSON

F10 Data JSON

The request is reduced to {"pc":"12209"} and the response is reduced to a collection of name-value pairs. Now that's compact!

 

Summary

We started off by asking the question, Which One?

The traditional post back method is straight forward to code but has the largest payload as it always sends the entire form to the server and the server responds by sending the complete markup. The UpdatePanel is great way of reducing the payload. Implementation can be as simple as adding a few lines to the aspx file. Even though the payload is reduced, the server processing remains unaltered as the complete page life cycle process is run. Nonetheless, it is great way to improve performance. Lastly, JSON with WCF is by far the best way of minimizing payload. But this comes at a cost, the developer is required to write JavaScript code.

What I typically suggest:

  • Use postback if you are saving data, e.g., an entry form.
  • Use UpdatePanel when you need to update a portion of the screen that was initiated by the user.
  • Use JSON when you want to periodically update a portion of the screen. An example of this might be a stock price updated every minute.

 

Guess the movie

Out of order, I show you out of order. You don't know what out of order is, Mr. Trask. I'd show you, but I'm too old, I'm too tired, I'm too fuckin' blind. If I were the man I was five years ago, I'd take a FLAMETHROWER to this place! Out of order? Who the hell do you think you're talkin' to? I've been around, you know? There was a time I could see. And I have seen. Boys like these, younger than these, their arms torn out, their legs ripped off. But there isn't nothin' like the sight of an amputated spirit. There is no prosthetic for that. You think you're merely sending this splendid foot soldier back home to Oregon with his tail between his legs, but I say you are... executin' his soul! And why? Because he's not a Bairdman. Bairdmen. You hurt this boy, you're gonna be Baird bums, the lot of ya. And Harry, Jimmy, Trent, wherever you are out there, FUCK YOU TOO!

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: ASP.NET | JSON | WCF
Posted by CynotWhyNot on Wednesday, April 23, 2008 11:26 AM
Permalink | Comments (72) | Post RSSRSS comment feed