Home / AJAX

HOW TO Send Data to a WCF Web Service

RSS
Modified on 2009/12/04 17:55 by Stephen Walther Categorized as Uncategorized
Windows Communication Foundation (WCF) provides a robust Web Service layer that can be used to expose data to Ajax-enabled applications and even handle changes that may occur in an application. In situations where data will only be retrieved from a WCF service the DataView component can be used on its own. When data changes need to be pushed back from the client to a WCF service another ASP.NET Ajax Library component called the DataContext can be used in conjunction with the DataView. The DataContext component tracks objects and the types of changes that are made to them. When objects are changed and then submitted to a WCF service the DataContext automatically classifies the type of change so that the service operation knows what action to perform for a given object. Three types of changes are tracked including insert, update and remove.

This tutorial will demonstrate how to use the DataContext and DataView objects together to send data changes to a WCF service. The steps that are discussed include:

  1. Creating a WCF Service Operation to Save Changes
  2. Loading Required Scripts
  3. Using the DataContext Component to Send Data to a WCF Service

Step 1: Creating a WCF Service Operation to Save Changes

To create an Ajax-enabled WCF service, right-click a Web Application Project or Website in Visual Studio and select Add New Item. Select the AJAX-enabled WCF Service template as shown in Figure 1.

Image

Figure 1. Adding an Ajax-enabled service into a Visual Studio Web Application Project or Website.

Once the Ajax-enabled WCF service is created a custom operation can be added that returns data to the Ajax application. The following code sample shows how a list of Customer objects can be returned from a service operation:

[ServiceContract(Namespace = "http://www.mycompany.com/MyAjaxService")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyAjaxService
{
    [OperationContract]
    public List<Customer> GetCustomers(int numberToFetch)
    {
        using (NorthwindDataContext context = new NorthwindDataContext())
        {
            return context.Customers.Take(numberToFetch).ToList();
        }
    }
}

To work with data that is modified on the client-side and then sent to the WCF service for processing, an operation that handles inserting, updating or deleting data must be created. The following code contains an operation named SaveCustomer that calls additional methods to handle change actions. A LINQ to SQL object named NorthwindDataContext is shown in this example but any type of code that interacts with a data store can be used.

[OperationContract]
public void SaveCustomer(List<Change<Customer>> changeSet)
{
    foreach (var c in changeSet)
    {
        switch (c.action)
        {
            case ChangeOperationType.insert:
                InsertCustomer(c.item);
                break;
            case ChangeOperationType.update:
                UpdateCustomer(c.item);
                break;
            case ChangeOperationType.remove:
                DeleteCustomer(c.item);
                break;
        }
    }
}

public void UpdateCustomer(Customer cust) { using (NorthwindDataContext context = new NorthwindDataContext()) { try { context.Customers.Attach(cust); context.Refresh(System.Data.Linq.RefreshMode.KeepCurrentValues, cust);

context.SubmitChanges(); } catch (Exception exp) { //Log error } } }

public void DeleteCustomer(Customer cust) { using (NorthwindDataContext context = new NorthwindDataContext()) { try { context.Customers.Attach(cust); context.Customers.DeleteOnSubmit(cust); context.SubmitChanges(); } catch (Exception exp) { //Log error } } }

public void InsertCustomer(Customer cust) { using (NorthwindDataContext context = new NorthwindDataContext()) { try { context.Customers.InsertOnSubmit(cust); context.SubmitChanges(); } catch (Exception exp) { //Log error } } }

Looking at the code for the SaveCustomer operation you'll see a List<Change<Customer>> object is accepted by the method. The Change object is a custom class capable of holding an object and the type of change that should occur. The types of changes sent by the client-side DataContext component include "insert", "update" and "remove" so those same values are defined using an enumeration:

public class Change<T>
{
    public ChangeOperationType action;
    public T item;
}

public enum ChangeOperationType { insert, update, remove }

Step 2: Loading Required Scripts

The ASP.NET Ajax Library provides DataContext and DataView components that can be used to fetch data from a WCF service and also send changes back to the service. The Script Loader can be used to load the scripts required by these components:

<script type="text/javascript" src="http://ajax.microsoft.com/ajax/beta/0911/Start.js"></script>
<script type="text/javascript">

Sys.require([Sys.components.dataView, Sys.components.dataContext, Sys.scripts.jQuery], function() { //Scripts loaded });

</script>


Step 3: Using the DataContext Component to Send Data to a WCF Service

The DataContext component can be used to fetch data from a Web Service and hand it off to a DataView for binding. It can also be used to send changes to a service as mentioned earlier. In cases where changes will be sent to a service a save operation must be defined using the DataContext component's saveOperation property:



var dataContext = null; Sys.require([Sys.components.dataView, Sys.components.dataContext, Sys.scripts.jQuery], function() { dataContext = Sys.create.dataContext({ serviceUri: "/Services/CustomerService.svc", saveOperation: "SaveCustomer" }); } );


Once the DataContext component is created it can be assigned as the data provider for a DataView component so that data can be bound to a client-side template. The following code demonstrates how to create a DataView declaratively and bind its dataProvider property to the DataContext component created earlier:

<body xmlns:sys="javascript:Sys" xmlns:dataview="javascript:Sys.UI.DataView">
    <h2>Modifying data using the ASP.NET Ajax Library</h2>
    <table id="CustomerView" style="width:800px;">
        <thead>
            <tr>
                <td>&nbsp;</td>
                <td>Contact</td>
                <td>Company</td>
                <td>Title</td>
                <td>Region</td>
            </tr>
        </thead>
        <tbody class="sys-template"
            sys:attach="dataview"
            dataview:oncommand="{{ onCommand }}"
            dataview:onrendering="{{ onRendering }}"
            dataview:autofetch="true"
            dataview:dataProvider="{{ dataContext }}"
            dataview:fetchOperation="GetCustomers"
            dataview:fetchParameters="{{ {numberToFetch: 10} }}">
            <tr>
                <td style="width:50px;" sys:class-odd="{{ $index % 2 !== 0 }}">
                    <span sys:command="edit" style="cursor:pointer;">Edit</span>
                    <span sys:command="update" style="cursor:pointer;display:none;">Update</span>
                    <span sys:command="delete" style="cursor:pointer;display:none;">Delete</span>
                    <span sys:command="cancel" style="cursor:pointer;display:none;">Cancel</span>
                </td>
                <td sys:class-odd="{{ $index % 2 !== 0 }}"><input type="text" sys:value="{binding ContactName}" /></td>
                <td sys:class-odd="{{ $index % 2 !== 0 }}"><input type="text" sys:value="{binding CompanyName}" /></td>
                <td sys:class-odd="{{ $index % 2 !== 0 }}"><input type="text" sys:value="{binding ContactTitle}" /></td>
                <td sys:class-odd="{{ $index % 2 !== 0 }}">
                    <input type="text" 
                      sys:value="{binding Region, convert=ConvertNullToText, convertBack=ConvertRegionValue}" />
                </td>
            </tr> 
        </tbody>       
    </table>
</body>


Looking through the code you can see that the DataView is initially defined on the body element using xmlns:dataview="javascript:Sys.UI.DataView". It is then attached to a <tbody> element (the template) using the sys:attach="dataview" syntax. From there the DataView's dataProvider property is bound to the DataContext object and additional properties are set to define what service operation to call to fetch data from, fetch parameters to use and the item rendering function to call. As a user interacts with the different data an onCommand function is defined to handle events that are raised as the end user interacts with the page. In this example four different types of commands are defined including edit, update, delete and cancel (note the sys:command attributes on the <span> elements).

As a user edits data and then saves it the "update" command will be called which causes the onCommand function to be invoked. onCommand accepts the source of the command and the command name as parameters and then handles hiding the different edit links. Once those steps are completed the function calls the DataContext component's saveChanges method to send the changed data to the server for processing:

function onCommand(sender, args) {
    var commandName = args.get_commandName();
    var source = args.get_commandSource();
    var ctx = getDataViewContainer(source);
    var parent = $(source.parentNode);
    
    if (commandName === 'edit') {
        ShowHideLinks(parent, 'none', 'block');               
        return;
    }

if (commandName === 'cancel' || commandName === 'update') { ShowHideLinks(parent, 'block', 'none'); parent.parent().find(':input').removeClass("highlight"); }

if (commandName === 'delete') { if (confirm('Are you sure you want to delete this item?')) { dataContext.removeEntity(ctx.dataItem); ctx.data.remove(ctx.dataItem); ShowHideLinks(parent, 'block', 'none'); } }

dataContext.saveChanges(); }

function getDataViewContainer(source) { var dv = source; while (!dv.control || Object.getType(dv.control) !== Sys.UI.DataView) { dv = dv.parentNode; } return dv.control.findContext(source); }

function ShowHideLinks(parent, editDisplay, othersDisplay) { var tr = parent.parent(); parent.children().each(function() { if ($(this).html() == 'Edit') { $(this).css('display', editDisplay); tr.find(':input').addClass("highlight"); } else { $(this).css('display', othersDisplay); } }); }

Using a tool such as Fiddler you can inspect the message that is sent by the DataContext component to the WCF service. An example of submitting an update request is shown next. The action property defines the type of change that the WCF service should perform on the object (insert = 0, update = 1, remove = 2).

{"changeSet":[{"action":1,"item":{"__type":"Customer:#Entities","CustomerID":"ALFKI","CompanyName":"Alfreds Futterkiste","ContactName":"Maria Johnson","ContactTitle":"Sales Representative","Address":"Obere Str. 57","City":"Berlin","Region":null,"PostalCode":"12209","Country":"Germany","Phone":"030-0074321","Fax":"030-0076545","Image":"buchanan.jpg"}}]}

The complete code for the client-side page is shown next. Figure 2 shows what the page looks like once rendered.

Image

Figure 2. The ASP.NET Ajax Library provides built-in components such as the DataContext that can be used to send changes to a service.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Master Detail</title>
    <link type="text/css" rel="Stylesheet" href="Styles/Styles.css" />
    <style type="text/css">
        input {border:1px solid transparent;background-color:Transparent;}
        .odd { background-color: white; }
        .highlight {border:1px solid black;background-color:LightYellow;}
    </style>
    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/beta/0911/Start.js"></script>
    <script type="text/javascript">
        var visibleCustID = null;
        var dataContext = null;
        Sys.require([Sys.scripts.jQuery,
                     Sys.components.dataView,
                     Sys.components.dataContext],                      
             function() {
                dataContext = Sys.create.dataContext({
                    serviceUri: "/Services/CustomerService.svc",
                    saveOperation: "SaveCustomer"
                });
             }
        );

function onRendering(sender, args) { Sys.Observer.makeObservable(sender.get_data()); }

function onCommand(sender, args) { var commandName = args.get_commandName(); var source = args.get_commandSource(); var ctx = getDataViewContainer(source); var parent = $(source.parentNode); if (commandName === 'edit') { ShowHideLinks(parent, 'none', 'block'); return; }

if (commandName === 'cancel' || commandName === 'update') { ShowHideLinks(parent, 'block', 'none'); parent.parent().find(':input').removeClass("highlight"); }

if (commandName === 'delete') { if (confirm('Are you sure you want to delete this item?')) { dataContext.removeEntity(ctx.dataItem); ctx.data.remove(ctx.dataItem); ShowHideLinks(parent, 'block', 'none'); } }

dataContext.saveChanges(); }

function getDataViewContainer(source) { var dv = source; while (!dv.control || Object.getType(dv.control) !== Sys.UI.DataView) { dv = dv.parentNode; } return dv.control.findContext(source); }

function ShowHideLinks(parent, editDisplay, othersDisplay) { var tr = parent.parent(); parent.children().each(function() { if ($(this).html() == 'Edit') { $(this).css('display', editDisplay); tr.find(':input').addClass("highlight"); } else { $(this).css('display', othersDisplay); } }); }

Sys.converters.ConvertNullToText = function(value, binding) { if (value == '' || value == null) { return "No Region"; } return value; }

Sys.converters.ConvertRegionValue = function(value, binding) { if (value == '') return null; return value; }

</script> </head> <body xmlns:sys="javascript:Sys" xmlns:dataview="javascript:Sys.UI.DataView"> <h2>Modifying data using the ASP.NET Ajax Library</h2> <table id="CustomerView" style="width:800px;"> <thead> <tr> <td>&nbsp;</td> <td>Contact</td> <td>Company</td> <td>Title</td> <td>Region</td> </tr> </thead> <tbody class="sys-template" sys:attach="dataview" dataview:oncommand="{{ onCommand }}" dataview:onrendering="{{ onRendering }}" dataview:autofetch="true" dataview:dataprovider="{{ dataContext }}" dataview:fetchoperation="GetCustomers" dataview:fetchparameters="{{ {numberToFetch: 10} }}"> <tr> <td style="width:50px;" sys:class-odd="{{ $index % 2 !== 0 }}"> <span sys:command="edit" style="cursor:pointer;">Edit</span> <span sys:command="update" style="cursor:pointer;display:none;">Update</span> <span sys:command="delete" style="cursor:pointer;display:none;">Delete</span> <span sys:command="cancel" style="cursor:pointer;display:none;">Cancel</span> </td> <td sys:class-odd="{{ $index % 2 !== 0 }}"><input type="text" sys:value="{binding ContactName}" /></td> <td sys:class-odd="{{ $index % 2 !== 0 }}"><input type="text" sys:value="{binding CompanyName}" /></td> <td sys:class-odd="{{ $index % 2 !== 0 }}"><input type="text" sys:value="{binding ContactTitle}" /></td> <td sys:class-odd="{{ $index % 2 !== 0 }}"> <input type="text" sys:value="{binding Region, convert=ConvertNullToText, convertBack=ConvertRegionValue}" /> </td> </tr> </tbody> </table> </body> </html>

  Name Size
- AjaxEnabledWCFService.jpg 36.53 KB
- DataContextDemoPage.jpg 84.77 KB