AJAX enabled WCF Service and ASP.NET


A friend of mine has recently asked me some questions about ASP.NET AJAX and I decided that it would be a good idea to explore this, and publish it as a blog post. I whipped up a simple application that uses ASP.NET Ajax web form and it consumes services from a AJAX-enabled WCF Service. It’s fairly straightforward, so I’m going to dive straight into the implementation details, and some problems I encountered. I’ve made use of the data grid control from an open source library called Ajax Data Controls for this sample because it’s been designed for client side scripting and is more intuitive to use compared to the ASP.NET ones. Of course this could have been easily done with JQuery, but I wanted to try something different.

wcf_ajax

WCF Service

To configure WCF service to be AJAX enabled, you go about creating your Service Interface and Service implementations like you would normally do. The key difference is in the configuration, and using webHttpBinding.

<system.serviceModel>
 <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
   <behaviors>
     <serviceBehaviors>
       <behavior name="ajaxServiceBehavior">
         <serviceMetadata httpGetEnabled="true"/>
         <serviceDebug includeExceptionDetailInFaults="false"/>
       </behavior>
     </serviceBehaviors>
     <endpointBehaviors>
       <behavior name="ajaxWebHttpBehavior">
         <enableWebScript/>
       </behavior>
     </endpointBehaviors>
   </behaviors>
   <services>
     <service behaviorConfiguration="ajaxServiceBehavior" name="WebApplication1.AjaxService">
       <endpoint address="" binding="webHttpBinding" contract="WebApplication1.IAjaxService"
                   behaviorConfiguration="ajaxWebHttpBehavior">
         <identity>
           <dns value="localhost"/>
         </identity>
       </endpoint>
       <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
     </service>
   </services>
</system.serviceModel>

In your endpointBehavior, you need to specify <enableWebScript/> to enable AJAX communication in your service. You can also enable aspNetCompatibilityEnabled to “true” to enable ASP.NET Session state management accessibility in WCF.

Service Interface

[ServiceContract(Namespace="CodeBlitz")]
public interface IAjaxService
{
    [OperationContract]
    [WebGet(ResponseFormat=WebMessageFormat.Json)]
    Person[] GetPeople();

    [OperationContract]
    [WebInvoke(Method="POST", RequestFormat=WebMessageFormat.Json)]
    bool UpdatePerson(Person person);

    [OperationContract]
    [WebInvoke(Method="POST")]
    bool DeletePerson(int personId);
}

One important thing to note in your service implementation is that you need to specify a Namespace via the ServiceContract attribute. This is required because you will need this namespace to “access” your service later from your javascript  in your ajax web form. We also decorate our operation contracts using the WebGet and WebInvoke attributes to specify how we call these methods from a HTTP GET or HTTP POST, as well as the request/response format. We set the format to JSON, which is the format we use for communications in AJAX.

Service Implementation

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class AjaxService : IAjaxService
{
    private static List
<person> people = new List</person>
<person> {
                        new Person { Id=1, Name="Dave", Age=22 },
                        new Person { Id=2, Name="Bob", Age=34 }
                        };

    public Person[] GetPeople()
    {
        WebOperationContext.Current.OutgoingResponse.Headers.Add("Cache-Control", "no-cache");
        return people.ToArray();
    }

    public bool UpdatePerson(Person person)
    {
        if (people.Contains(person))
        {
            int index = people.IndexOf(person);
            people[index].Name = person.Name;
            people[index].Age = person.Age;
            return true;
        }
        return false;
    }

    public bool DeletePerson(int personId)
    {
        Person person = people.Find(p => p.Id == personId);
        if (person != null)
        {
            people.Remove(person);
            return true;
        }
        return false;
    }
}

We need to add a header value of “Cache-Control” to “no-cache” in our HTTP GET operation (GetPeople method) because HTTP GET automatically caches our response. In this example, we always want to get the latest values and not the ones from the cache, so we add this header value using WebOperationContext.Current.OutgoingResponse in the System.Servicemodel.Web namespace. If you don’t add this, you will always get the data from the cache.

Javascript in Web Form

<asp:ScriptManager ID="ScriptManager1" runat="server" >
 <Services>
   <asp:ServiceReference Path="~/AjaxService.svc" />
 </Services>
</asp:ScriptManager>

To add your AJAX-enabled WCF Service, you use a ScriptManager, like so in the snippet above, somewhere in your <form> tag. If your WCF service is hosted on a different server, then use the HTTP address instead.

<script type="text/javascript">
    var _people;
    var _loadMessageDiv;
    var _gridView;

    function pageLoad() {
        _loadMessageDiv = $get('loadMessageDiv');
        _gridView = $find('< %= gridView.ClientID %>');
        GetPeople();
    }

    function OnError(result) {
        alert("Error: " + result.get_message());
    }

    function GetPeople() {
        _loadMessageDiv.style.display = '';
        CodeBlitz.IAjaxService.GetPeople(OnGetPeopleComplete);
    }

    function OnGetPeopleComplete(result) {
        _people = result;
        DataBindPeopleGridView();
        _loadMessageDiv.style.display = 'none';
    }

    function DataBindPeopleGridView() {
        _gridView.set_dataSource(_people);
        _gridView.dataBind();
    }

    function OnEditPerson(sender, e) {
        var rowIndex = e.get_row().get_rowIndex();
        _gridView.set_editIndex(rowIndex);
        DataBindPeopleGridView();
    }

    function OnUpdatePerson(sender, e) {
        var row = e.get_row();
        var personId = _gridView.get_dataKeys()[e.get_row().get_rowIndex()];
        var name = row.get_container().childNodes[0].childNodes[0];
        var age = row.get_container().childNodes[1].childNodes[0];

        var person = { "Id": personId, "Name": name.value, "Age": age.value };

        CodeBlitz.IAjaxService.UpdatePerson(person, OnUpdatePersonSuccess);
    }

    function OnUpdatePersonSuccess(result) {
        if (result == true) {
            _gridView.set_editIndex(-1);
            GetPeople();
        }
    }

    function OnCancelUpdate(sender, e) {
        _gridView.set_editIndex(-1);
        DataBindPeopleGridView();
    }

    function OnDeletePerson(sender, e) {
        var row = e.get_row();
        var personId = _gridView.get_dataKeys()[e.get_row().get_rowIndex()];
        CodeBlitz.IAjaxService.DeletePerson(personId, OnDeletePersonSuccess);
    }

    function OnDeletePersonSuccess(result) {
        if (result == true) {
            _gridView.set_editIndex(-1);
            GetPeople();
        }
    }
</script>

Let go through various important portions of the Javascript. The pageLoad function is called automatically when the page is first loaded, there’s no need to wire that up explicitly. With the ScriptManager, you can now call your service using the Namespace you specified earlier in the ServiceContract attribute. In this case, we can access the service operations using CodeBlitz.<ServiceInterface>.<MethodName>. Every method you call will require the input parameters first (if there’s any), followed by a OnSucess and OnFailed script methods as the last two parameters. In your OnSuccess javascript, you specify what happens with the result you collect back, e.g. OnGetPeopleComplete databinds the gridview with the result.

Ajax_Intellisense

In my UpdatePerson method, I need to send a Person object back to the service. This would need to be in JSON format, and this is how you create a JSON format object in javascript.

    var person = { "Id": personId, "Name": name.value, "Age": age.value };

It’s in the format of “PropertyName” : “Value”. To learn more about this, see this link. If you need serialization from .NET objects to JSON, you can find a few libraries out there that supports this. I have not done much investigation on this, but you can have a look at one such library called Jayrock.

If you’ve noticed, I have not specified any OnFailed script methods for any of my service calls. That’s because there’s another way to do this, if you want to handle all errors in a common way. You add this script at the end of your HTML layout (after your ScriptManager tag) within your <body> tag, like so.

<script type="text/javascript">
    CodeBlitz.IAjaxService.set_defaultFailedCallback(OnError);
</script>

This is a handy way to route all your failed calls to the OnError script method, and saves you the effort of specifying it everywhere. However if you want dedicated error handling code, then you need to do it explicitly for each service call. Lastly we wire up the javascript functions to the AjaxDataControl GridView like so.

<AjaxData:GridView ID="gridView" runat="server" CssClass="StdGrd" DataKeyName="Id"
 EditCommandEvent="OnEditPerson" UpdateCommandEvent="OnUpdatePerson"
 CancelCommandEvent="OnCancelUpdate" DeleteCommandEvent="OnDeletePerson" >
   <FooterStyle CssClass="StdGrdFooter" />
   <RowStyle CssClass="StdGrdRow" />
   <AlternatingRowStyle CssClass="StdGrdAltRow" />
   <SelectedRowStyle CssClass="StdGrdSelectedRow" />
   <HeaderStyle CssClass="StdGrdHeader" />
   <EmptyDataRowStyle CssClass="StdGrdEmptyRow" />
   <EditRowStyle CssClass="StdGrdEditRow" />
   <Columns>
     <AjaxData:GridViewBoundColumn HeaderText="Name" DataField="Name" />
     <AjaxData:GridViewBoundColumn HeaderText="Age" DataField="Age"  />
     <AjaxData:GridViewCommandColumn ButtonType="Button" ShowEditButton="true"
        ShowDeleteButton="true" ShowCancelButton="true" />
   </Columns>
</AjaxData:GridView>

I said at the beginning of this post that it’s relatively straightforward…well looking at the length of this post, it might not be that true after all 🙂

Download latest Ajax Data Control binaries/source here.

Download this code sample here.

Share this post:
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: