Chapter 7

Designing and Building Data Services

What's In This Chapter?

  • Understanding web services
  • Choosing between SOAP and REST
  • Designing services for mobile applications
  • Optimizing services for mobile data access
  • Creating logical resource endpoints
  • Extending your services into the enterprise

Now that you've written your shared application, and built one or more containers for your targeted platforms, you need a way to get information from both your enterprise databases and other back-end systems for consumption on your mobile devices. In this chapter you learn how to build web services that are optimized for mobile applications. You explore examples of both traditional SOAP and RESTful techniques and learn to build services that mirror your application design to best serve data to your application. Finally, you examine techniques for customizing and limiting the volume of data returned to your application according to mobile best practices.

Understanding Web Services Principles

Web services and Service Oriented Architectures (SOAs) have become ubiquitous in most large companies. The proliferation of web applications over the past decade or two has resulted in the need for integration between heterogeneous systems in most enterprises. Because SOAs are based upon clear standards with implementations available for the most popular enterprise development frameworks and technologies, they have become the standard for moving information around most organizations.

Generally, two major classes of web services are present in most organizations: SOAP and REST. Each has its strengths and weaknesses, and using either is a viable option for your mobile application. But you should consider some things when deciding what technique is best for your situation.

Using SOAP Services

SOAP-based services are usually easy to find in any organization. This technique of writing web services has been around for a long time and is well established in most companies. As a result, you usually have the benefit of a seasoned team of developers that can help you expose and publish new services easily. Many application frameworks, such as Microsoft .NET, include robust SOAP support in both implementations of the protocol, such as Windows Communication Framework (WCF), or in middleware services, such as Web References in Visual Studio or Language Integrated Query (LINQ), specifically LINQ to XML.

For all these reasons, SOAP can be an attractive option, but you must consider some of the drawbacks of this approach before using it for all your mobile services. First, SOAP is by definition an XML standard. XML has rich support on the server, and in Mono that support is extended to most major mobile platforms. However, XML is also quite verbose, and streaming large blocks of character-based information over limited 2G and 3G connections can result in serious performance issues on a mobile device. SOAP also requires the use of header and body tags in the XML that increase the weight of these services even more. Existing enterprise SOAP services are also often coarse-grained, providing large amounts of information that may or may not be needed in a mobile application. Finally, the Remote Procedure Call (RPC) style used by most SOAP services can be difficult to consume in a mobile context.

Using REST Services

Representational State Transfer (REST) is a technique pioneered by the World Wide Web and adopted to provide a simple way to exchange information between systems, which has become increasingly popular in recent years. RESTful services use the standard HTTP verbs (GET, POST, PUT, and DELETE) to specify actions performed on a representation of a specific resource — for example, a customer. The services themselves are defined as resource endpoints that use parameter substitution to convey pertinent information for server-side processing. Most development frameworks, including WCF, now offer REST support, so implementation is becoming more and more widespread.

REST is often the best fit for mobile data services for several reasons. First, it uses HTTP for all interactions. Each request is a standard HTTP Request, and the data is delivered in a standard HTTP Response. As a result, the service response can contain data in any of a number of data formats supported by HTTP, including XML and JSON, as well as other resource formats such as documents, images, and applications. Although resource endpoint definitions can become somewhat complex, they are more easily tailored to mobile application needs. The resource-centric design is often more easily translated into the object-oriented designs used in the sample applications. If you are faced with writing or rewriting existing enterprise services to support your application, put RESTful services in place, rather than extending existing SOAP architectures. The flexibility and ease of use of RESTful services have most often made it worth the cost of training and development for the organizations we have worked with.

Defining a Mobile Services API

Consider several things when building data services for your mobile application. First and foremost, you should always define an interface that reflects your mobile application use case. As mentioned previously, although SOAs and web services are a part of most organizations' overall data management strategy, existing enterprise services are usually not suitable for mobile applications.

Most organizations tend to take an “inside-out” perspective of data management and services design. That is to say, there is a tendency to start from the data structures defined in the back-end and expose them outward. Most Object-to-Relational Mapping frameworks (ORMs) make it easy to bring relational data constructs, such as tables and joins, into the services architecture. As a result, a single service request often returns a large amount of data pertaining to multiple entities in the problem domain. For example, a customer request may return the basic customer information, along with all addresses, contacts, orders, and order items for that customer — all presented neatly in a relational structure.

Although this technique may work well for many enterprise applications, the storage, bandwidth, and processing limitations can make this approach unusable in a mobile context. Instead, take an outside-in perspective when designing your mobile services. By designing services based on the consumption of the data within the context of the application you are building, you can more finely tune your services to provide only the information needed, at the time it is needed.

Starting with Your User Experience Design

In Chapter 2, “Designing Your User Experience,” you learned about some of the techniques for designing your user experience. You went through prototyping exercises that helped to establish a basic user experience for the customer management application example. This prototype is used as the basis for the mobile data services design, starting with the model for the application. The customer class represents a company with which a fictitious company does business and must contain basic customer information, such as the customer name, website, and phone number.

    
public class Customer
{
  public Customer()
  {
    ID = "0";
    Name = string.Empty;
    Website = string.Empty;
    PrimaryAddress = new Address();
    Addresses = new List<Address>();
    Contacts = new List<Contact>();
    Orders = new List<Order>();
  }

  public string ID { get; set; }
  public string Name { get; set; }
  public string Website { get; set; }
  public string PrimaryPhone { get; set; }
  public Address PrimaryAddress { get; set; }
  public List<Address> Addresses { get; set; }
  public List<Contact> Contacts { get; set; }
  public List<Order> Orders { get; set; }
}

You want to use the application to manage customers but also need the ability to process orders for the customers. So, you need to include company contact, address, and order information as well. Because each of these new entities represents a one-to-many relationship with the customer class, add three new lists to define these associations.

public class Customer
{
  .
  .
  .

  public string ID { get; set; }
  public string Name { get; set; }
  public string Website { get; set; }
  public string PrimaryPhone { get; set; }
  public Address PrimaryAddress { get; set; }
  public List<Address> Addresses { get; set; }
  public List<Contact> Contacts { get; set; }
  public List<Order> Orders { get; set; }
}

Until now, you've been following standard object-oriented design principles in defining the simple customer model, and this model can serve you well in most cases. But this class has the potential to become heavy in a data management sense. A customer may have dozens of addresses, hundreds of contacts, and thousands of orders over time. If you were to take a coarse-grained approach to populating each customer with a full complement of these associated items, they would quickly grow to a size that could overwhelm the storage, bandwidth, and processing resources of a mobile device. Fortunately, you can apply a few simple principles to optimize the customer services for mobile usage.

Optimizing for Mobile Usage

The existing customer class has all the elements needed for the customer management example. You can easily use this class to both list the customers and display detailed information to the users. But you need to be careful about how much information you provide and at what time you provide it. This is where the outside-in approach to services design comes into practice.

You want to display a list of customers, which includes the customer name, website, and location information, (that is, city and state). The first two elements are present in the base customer class, but the location information is embedded in the address class:

public class Address
{
public string ID { get; set; }
public string Description { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}

To display city and state information on the customer list, populate the Addresses collection on the company object, and apply some logic to determine which address contains the city and state information you want. Because a company may have dozens — perhaps hundreds — of addresses, populating the entire list when you need only a single city and state for the list is unnecessary. When multiplied across potentially thousands of customers in a single list, including unnecessary information can create a large collection of company objects that can quickly consume the bandwidth of a mobile connection and overwhelm the resources on a mobile device.

To ensure efficient management of device resources, you should design your services to allow for lightly loaded lists. To accomplish this, add a PrimaryAddress property to the base customer class:

public class Customer
{
  .
  .
  .

  public string ID { get; set; }
  public string Name { get; set; }
  public string Website { get; set; }
  public string PrimaryPhone { get; set; }
  public Address PrimaryAddress { get; set; }
  public List<Address> Addresses { get; set; }
  public List<Contact> Contacts { get; set; }
  public List<Order> Orders { get; set; }
}    

The addition of this property enables you to include a single primary address as a part of each customer in the list, while leaving the addresses uninitialized when returning a collection of customer objects from the service. This can ensure only the information necessary to render the mobile application view (in this case, the customer list) is available, while reserving delivery of the details until a particular customer's data is requested.

You should design all your model classes in the same manner. Use the prototype screens you've designed as your guide, and make sure the base object contains all the data elements needed for the view in question — but no more. After you design your model with these rules in mind, building out your services becomes simple.

Creating Resource Endpoints

Now that you've designed your model classes, you're ready to start building resource endpoints. The first thing you need to do is create a data store from which to deliver the customer information. Use a simple XML data store for this example, and use LINQ to Objects to deliver results to both the REST and SOAP services. Start with a customer XML that matches the serialized customer model object, as shown in Listing 7.1.

1.1
Listing 7.1: The Customers.xml file
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCompany xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Company>
    <ID>1</ID>
    <Name>Stein Mart, Inc.</Name>
    <Website>http://www.steinmart.com</Website>
    <PrimaryPhone>904-346-1500</PrimaryPhone>
    <PrimaryAddress>
      <ID>1-a1</ID>
      <Description>World Headquarters</Description>
      <Street1>1200 Riverplace Blvd.</Street1>
      <City>Jacksonville</City>
      <State>FL</State>
      <Zip>32207</Zip>
    </PrimaryAddress>
    <Addresses>
      <Address>
        <ID>1-a1</ID>
        <Description>World Headquarters</Description>
        <Street1>1200 Riverplace Blvd.</Street1>
        <City>Jacksonville</City>
        <State>FL</State>
        <Zip>32207</Zip>
      </Address>
    </Addresses>
    <Contacts />
    <Orders />
  </Company> 
  .
  .
  .
</ArrayOfCompany>

Found in the CustomerManagement.Data/Xml/Customers.xml file of the download

1.1To simplify the serialization and deserialization of the model objects in the XML data store, use the System.Xml.Serialization.XmlSerializer class in both the data store and service endpoints. Because this serializer is supported in Mono, it can be used to hydrate object graphs seamlessly on multiple mobile platforms.

To enable LINQ on the list of customer objects, you need to write a method to deserialize the file to create the collection. The GetCustomerList() method can serve this function, as shown in Listing 7.2.

1.1
Listing 7.2: The GetCustomerList() method
static List<Customer> GetCustomerList()
{
  string dataFilePath = Path.Combine(AppPath, 
                        Path.Combine("Xml", "Customers.xml"));

  var loadedData = XDocument.Load(dataFilePath);
  using (var reader = loadedData.Root.CreateReader())
  {
    List<Customer> list = (List<Customer>)new 
      XmlSerializer(typeof(List<Customer>)).Deserialize(reader);
    list.Sort((a, b) => a.Name.CompareTo(b.Name));
    return list;
  }
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Now use the GetCustomerList() method to expose a new data access method to enable display of the full list of customers, as shown in Listing 7.3.

1.1
Listing 7.3: The GetCustomers() method
public static List<Customer> GetCustomers()
 {
     return GetCustomerList();
 }

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Finally, you can expose the GetCustomers() method through a new RESTful service that can deliver the customer list to your application. The GetCustomers service is shown in Listing 7.4.

1.1The remaining service examples in this chapter demonstrate deployment of REST services using WCF. Because the services methods simply pass through to the data store for processing, you can extend your data methods to use SOAP by adding the method signature to your IService interface and implementing them in your Service.cs code file following WCF/SOAP best practices. SOAP service examples are provided in the CustomerMangement.SOAP project in the download.

1.1
Listing 7.4: The GetCustomers() REST service
 [XmlSerializerFormat]
[WebGet(UriTemplate = "customers.xml")]
public List<Customer> GetCustomers()
{
  return XmlDataStore.GetCustomers();
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

3.11The WebGet attribute in WCF uses the DataContractSerializer by default. The DataContractSerializer creates additional attributes and extended element tags in the XML that are not easily handled across mobile devices. To ensure the simpler XmlSerializer is used instead, you must tag each service method with the XmlSerializerFormat attribute.

Building Indexed Lists

Now you've successfully created a simple service to return customer information in a collection, but you still are returning everything in the customer store by default. As long as the customer list remains small, this may work just fine, but as the business grows, you need to add more customers, with many orders that will be managed in the data store. You need to be judicious about the use of device and network resources, so build some automatic filtering into your services.

Start from the customer list view in the application. But rather than just delivering the information necessary to display on the screen, include additional information that can be used on the device to search the list and filter results for consumption. To do this, refactor the GetCustomers() method on the data store to return only the properties you need to index and search the list. In this case, that includes the customer ID, name, primary phone number, and primary address. Listing 7.5 shows the refactored GetCustomers() method.

1.1
Listing 7.5: A refactored GetCustomers() method
public static List<Customer> GetCustomers(string filter)
{
  return (from item in GetCustomerList()
          where 
          item.Name
              .Contains(string.IsNullOrWhiteSpace(filter) ? 
                        filter : item.Name) ||
          item.PrimaryAddress.City
              .Contains(string.IsNullOrWhiteSpace(filter) ? filter :     
          item.PrimaryAddress.City) ||                   
          item.PrimaryAddress.State
              .Contains(string.IsNullOrWhiteSpace(filter) ? filter : 
          item.PrimaryAddress.State) ||
          item.PrimaryAddress.Zip
              .Contains(string.IsNullOrWhiteSpace(filter) ? filter :   
                        item.PrimaryAddress.Zip)
          select new Customer()
          {
            ID = item.ID,
            Name = item.Name,
            PrimaryAddress = item.PrimaryAddress,
            PrimaryPhone = item.PrimaryPhone
          }).ToList();
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

This new and improved GetCustomers() method uses a simple LINQ to Objects query to set only the default values you need to display and index the list on the device. This results in a decreased payload over the wire when the data is delivered through the service. Listing 7.6 shows this lightweight output.

1.1
Listing 7.6: A lightweight Customer list
<ArrayOfCompany xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Company>
    <ID>1</ID>
    <Name>Stein Mart, Inc.</Name>
    <PrimaryPhone>904-346-1500</PrimaryPhone>
    <PrimaryAddress>
      <ID>1-a1</ID>
      <Description>World Headquarters</Description>
      <Street1>1200 Riverplace Blvd.</Street1>
      <City>Jacksonville</City>
      <State>FL</State>
      <Zip>32207</Zip>
    </PrimaryAddress>
  </Company>
  <Company>
    <ID>2</ID>
    <Name>Bgc Partners, Inc.</Name>
    <PrimaryPhone>212-938-5000</PrimaryPhone>
    <PrimaryAddress>
      <ID>2-a1</ID>
      <Description>World Headquarters</Description>
      <Street1>499 Park Ave.</Street1>
      <City>New York</City>
      <State>NY</State>
      <Zip>10022</Zip>
    </PrimaryAddress>
  </Company>
  .
  .
  .
</ArrayOfCompany>

Retrieving Detail Objects

Now that you have created a lightweight customer list, you need a way to return the full details of a customer at the appropriate point in the application workflow. You need the ability to view, add, and edit customer detail information but leave order processing for a separate workflow path. As such, you can design the data method and endpoints accordingly.

The code in Listing 7.7 shows the GetCustomer() data method. The data is organized so that the first address in the Addresses collection is always the default address. You can take that address and set the PrimaryAddress property, if it is not already set, before returning the customer to the service.

1.1
Listing 7.7: The GetCustomer method
public static Customer GetCustomer(string customer)
{
  Customer retval = GetCustomerList().Where(obj => obj.ID == 
                    customer).FirstOrDefault();
  if (retval.Addresses.Count > 0 && retval.PrimaryAddress == null)
  {
    retval.PrimaryAddress = new Address()
    {
      ID = retval.Addresses[0].ID,
      Description = retval.Addresses[0].Description,
      Street1 = retval.Addresses[0].Street1,
      Street2 = retval.Addresses[0].Street2,
      City = retval.Addresses[0].City,
      State = retval.Addresses[0].State,
      Zip = retval.Addresses[0].Zip
    };
  }
  return retval;
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Now you can follow the same technique to expose the customer detail information in your service, as shown in Listing 7.8.

1.1
Listing 7.8: The GetCustomer REST service
[XmlSerializerFormat]
[WebGet(UriTemplate = "customers/{customer}.xml")]
public Customer GetCustomer(string customer)
{
  return XmlDataStore.GetCompany(customer);
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

You can apply the same technique to all the indexed lists and detail retrieval methods in your application. Simply use your user experience prototype as the guide, and return only the information necessary at the time it is needed. This lightly loaded approach can ensure you manage device resources efficiently, and minimize the potential for poor application performance due to resource and bandwidth limitations.

You can now add data methods. Listing 7.9 shows the data retrieval method.

1.1
Listing 7.9: XML data store retrieval methods
public class XmlDataStore
{
   .
   .
   .  
    public static List<Customer> GetCustomers()
    {
      return GetCustomerList();
    }

  public static List<Product> GetProducts()
  {
    return GetProductList();
  }

  public static Product GetProduct(string productId)
  {
    return GetProductList().Where(obj => obj.ID == productId)
                           .FirstOrDefault();
  }

  public static List<Order> GetCustomerOrders(string customer)
  {
    return (from item in GetOrderList()
            where item.Customer.ID == customer
            select new Order()
            {
                ID = item.ID,
                PurchaseOrder = item.PurchaseOrder,
                Customer = item.Customer
            }).ToList();
  }

  public static List<Order> GetCustomerOrder(string customer, string orderId)
  {
    return (from item in GetOrderList()
            where item.Customer.ID == customer && item.ID == orderId
            select new Order()
            {
                ID = item.ID,
                PurchaseOrder = item.PurchaseOrder,
                Customer = item.Customer,
                Items = item.Items
            }).ToList();
  }

  public static Customer GetCustomer(string customer)
  {
    Customer retval = GetCustomerList()
                      .Where(obj => obj.ID == customer)
                      .FirstOrDefault();
    if (retval.Addresses.Count > 0 && retval.PrimaryAddress == null)
    {
      retval.PrimaryAddress = new Address()
      {
        ID = retval.Addresses[0].ID,
        Description = retval.Addresses[0].Description,
        Street1 = retval.Addresses[0].Street1,
        Street2 = retval.Addresses[0].Street2,
        City = retval.Addresses[0].City,
        State = retval.Addresses[0].State,
        Zip = retval.Addresses[0].Zip
      };
    }
    return retval;
  }

  public static List<Contact> GetContacts(string customer)
  {
    return GetCustomerList()
      .Where(obj => obj.ID ==   customer).FirstOrDefault().Contacts;
  }

  public static Contact GetContact(string customer, string contact)
  {
    Contact retval = GetContacts(customer)
      .Where(obj => obj.ID == contact).FirstOrDefault();
    return retval;
  }
    .
    .
    .
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Now that your data retrieval method is complete, you can add service endpoints to support retrieval of company contacts, products, and orders. Listing 7.10 shows the completed services.

1.1
Listing 7.10: Data retrieval REST services
public class Service
{
  #region XML Services

  // GET Methods
  [XmlSerializerFormat]
  [WebGet(UriTemplate = "customers.xml")]
  public List<Customer> GetCustomers()
  {
    return XmlDataStore.GetCustomers();
  }

  [XmlSerializerFormat]
  [WebGet(UriTemplate = "products.xml")]
  public List<Product> GetProducts()
  {
    return XmlDataStore.GetProducts();
  }

  [XmlSerializerFormat]
  [WebGet(UriTemplate = "orders/{customer}.xml")]
  public List<Order> GetCompanyOrders(string customer)
  {
    return XmlDataStore.GetCustomerOrders(customer);
  }

  [XmlSerializerFormat]
  [WebGet(UriTemplate = "customers/{customer}.xml")]
  public Customer GetCustomer(string customer)
  {
    return XmlDataStore.GetCustomer(customer);
  }

  [XmlSerializerFormat]
  [WebGet(UriTemplate = "customers/{customer}/contacts.xml", 
   ResponseFormat =   WebMessageFormat.Xml)]
  public List<Contact> GetContacts(string customer)
  {
    return XmlDataStore.GetContacts(customer);
  }

  [XmlSerializerFormat]
  [WebGet(UriTemplate = "customers/{customer}/{contact}.xml")]
  public Contact GetContact(string customer, string contact)
  {
    return XmlDataStore.GetContact(customer, contact);
  }
      
    .
    .
    .
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

Enabling Transactions

Now that you fleshed out the retrieval services, you need to provide for a way to add, update, and delete the various model objects that enable those transactions. The first thing you need is a way to modify the data in the data store and persist it to durable storage. Following the simple XML serialization pattern, create a save method for each data file that supports transactions. Listing 7.11 shows the method for saving customers after an update to the collection.

1.1
Listing 7.11: The SaveCustomers() method
static void SaveCustomers(List<Customer> customers)
{
  string dataFilePath = Path.Combine(AppPath, 
                        Path.Combine("Xml", "Customers.xml"));
  using (StreamWriter writer = new StreamWriter(dataFilePath))
  {
    var serializer = new XmlSerializer(typeof(List<Customer>));
    serializer.Serialize(writer, customers); 
  }
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Again, use the System.Xml.Serialization.XmlSerializer class to manage the persistence, keeping the formatting consistent from the data store out to the device. This save method can be used by any data transaction methods that require a change to the persisted store, such as the CreateCustomer() method shown in Listing 7.12.

1.1
Listing 7.12: The CreateCustomer() method
public static Customer CreateCustomer(Customer instance)
{
  List<Customer> companies = GetCustomerList();

  // Set ID's
  string ID = (companies.Max(a => Convert.ToInt32(a.ID)) + 1).ToString();
  instance.ID = ID;
  instance.PrimaryAddress.ID = string.Format("{0}-a1", ID);

  companies.Add(instance);
  SaveCustomers(companies);
  return instance;
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

You also use the same save method when processing a customer update, as shown in Listing 7.13.

1.1
Listing 7.13: The UpdateCustomer() method
public static Customer UpdateCustomer(Customer instance)
{
  List<Customer> companies = GetCustomerList();
  if (companies.Where(obj => obj.ID == instance.ID).Count() != 0)
    companies.Remove(companies.First(obj => obj.ID == instance.ID));
  companies.Add(instance);
  SaveCustomers(companies);
  return instance;
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

The same method is used for a customer deletion, as shown in Listing 7.14.

1.1
Listing 7.14: The DeleteCustomer() method
public static void DeleteCustomer(string customer)
{
  List<Customer> companies = GetCustomerList();
  companies.Remove(companies.First(obj => obj.ID == customer));
  SaveCustomers(companies);
} 

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

To expose your new data transaction methods through your RESTful service layer, simply add the methods to your service accordingly, as shown in Listing 7.15.

1.1
Listing 7.15: Company transaction REST services
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;
using System.Xml.Serialization;
using System.Web;

using CustomerManagement.Data;
using CustomerManagement.Shared.Model;

namespace CustomerManagement.REST
{
  [ServiceContract]
  [AspNetCompatibilityRequirements(
     RequirementsMode = 
       AspNetCompatibilityRequirementsMode.Allowed)]
  [ServiceBehavior(
     InstanceContextMode = 
      InstanceContextMode.PerCall)]
  public class Service
  {
      #region XML Services
     .
     .
     .
    [XmlSerializerFormat]
    [WebInvoke(UriTemplate = "customers/customer.xml", 
     Method = "POST", 
     RequestFormat = WebMessageFormat.Xml, 
     ResponseFormat = WebMessageFormat.Xml)]
    public Customer CreateCompany(Customer instance)
    {
      return XmlDataStore.CreateCustomer(instance);
    }
     .
     .
     .
    [XmlSerializerFormat]
    [WebInvoke(UriTemplate = "customers/customer.xml", 
     Method = "PUT", 
     RequestFormat = WebMessageFormat.Xml, 
     ResponseFormat = WebMessageFormat.Xml)]
    public Customer UpdateCompany(Customer instance)
    {
    return XmlDataStore.UpdateCustomer(instance);
    }
     .
     .
     .
    [WebInvoke(UriTemplate = "customers/{customer}", Method = "DELETE")]
    public void DeleteCompany(string customer)
    {
      XmlDataStore.DeleteCustomer(customer);
    }
     .
     .
     .
  }
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

You are now ready to add transaction data methods and endpoints for the remaining entities in your application. Because products are not editable in this application, we will add transaction support for contacts and orders only. Listing 7.16 shows the completed transaction methods.

1.1
Listing 7.16: Data store transaction methods
namespace CustomerManagement.Data
{
  public class XmlDataStore
  {
     .
     .
     .
      // Create Methods
      public static Customer CreateCustomer(Customer instance)
      {
        List<Customer> companies = GetCustomerList();

        // Set ID's
        string ID = (companies.Max(a => Convert.ToInt32(a.ID)) + 1).ToString();
        instance.ID = ID;
        instance.PrimaryAddress.ID = string.Format("{0}-a1", ID);

        companies.Add(instance);
        SaveCustomers(companies);
        return instance;
      }

      public static Contact CreateContact(string customer, Contact instance)
      {
        List<Contact> contacts = GetContacts(customer);

        // Set ID
        string ID = (contacts.Count + 1).ToString();
        instance.ID = string.Format("{0}-c{1}", customer, ID);

        contacts.Add(instance);
        SaveContacts(customer, contacts);
        return instance;
      }

      public static Order CreateOrder(Order instance)
      {
        List<Order> orders = GetOrderList();

        // Set ID
        string ID = (int.Parse(orders.Max(a => a.ID)) + 1).ToString();
        instance.ID = ID;

        orders.Add(instance);
        SaveOrders(orders);
        return instance;
      }

      // Update Methods
      public static Customer UpdateCustomer(Customer instance)
      {
        List<Customer> companies = GetCustomerList();
        if (companies.Where(obj => obj.ID == instance.ID).Count() != 0)
            companies.Remove(companies.First(obj => obj.ID == instance.ID));
        companies.Add(instance);
        SaveCustomers(companies);
        return instance;
      }

      public static Contact UpdateContact(string customer, Contact instance)
      {
        List<Contact> contacts = GetContacts(customer);
        contacts.Remove(contacts.First(obj => obj.ID == instance.ID));
        contacts.Add(instance);
        SaveContacts(customer, contacts);
        return instance;
      }

      public static Order UpdateOrder(Order instance)
      {
        List<Order> orders = GetOrderList();
        orders.Remove(orders.First(obj => obj.ID == instance.ID));
        orders.Add(instance);
        SaveOrders(orders);
        return instance;
      }

      // Delete Methods
      public static void DeleteCustomer(string customer)
      {
        List<Customer> companies = GetCustomerList();
        companies.Remove(companies.First(obj => obj.ID == customer));
        SaveCustomers(companies);
      }

      public static void DeleteContact(string customer, string contact)
      {
        List<Contact> contacts = GetContacts(customer);
        contacts.Remove(contacts.First(obj => obj.ID == contact));
        SaveContacts(customer, contacts);
      }

      public static void DeleteOrder(string order)
      {
        List<Order> orders = GetOrderList();
        orders.Remove(orders.First(obj => obj.ID == order));
        SaveOrders(orders);
      }
     .
     .
     .
  }
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Listing 7.17 shows the completed REST services for these transactions.

1.1
Listing 7.17: Data transaction REST services
namespace CustomerManagement.REST
{
  [ServiceContract]
  [AspNetCompatibilityRequirements(
    RequirementsMode = 
      AspNetCompatibilityRequirementsMode.Allowed)]
  [ServiceBehavior(
    InstanceContextMode = 
      InstanceContextMode.PerCall)]
  public class Service
  {
      #region XML Services
     .
     .
     .
    // POST Methods
    [XmlSerializerFormat]
    [WebInvoke(UriTemplate = "customers/customer.xml", 
     Method = "POST", 
     RequestFormat = WebMessageFormat.Xml, 
     ResponseFormat = WebMessageFormat.Xml)]
    public Customer CreateCustomer(Customer instance)
    {
      return XmlDataStore.CreateCustomer(instance);
    }
        
    [XmlSerializerFormat]
    [WebInvoke(UriTemplate = "customers/{customer}/contact.xml", 
     Method = "POST", 
     RequestFormat = WebMessageFormat.Xml, 
     ResponseFormat = WebMessageFormat.Xml)]
    public Contact CreateContact(string customer, Contact instance)
    {
      return XmlDataStore.CreateContact(customer, instance);
    }

    [XmlSerializerFormat]
    [WebInvoke(UriTemplate = "orders.xml", 
     Method = "POST", 
     RequestFormat = WebMessageFormat.Xml, 
     ResponseFormat = WebMessageFormat.Xml)]
    public Order CreateOrder(Order instance)
    {
      return XmlDataStore.CreateOrder(instance);
    }

    // PUT Methods
    [XmlSerializerFormat]
    [WebInvoke(UriTemplate = "customers/customer.xml", 
     Method = "PUT", 
     RequestFormat = WebMessageFormat.Xml, 
     ResponseFormat = WebMessageFormat.Xml)]
    public Customer UpdateCompany(Customer instance)
    {
      return XmlDataStore.UpdateCustomer(instance);
    }

    [XmlSerializerFormat]
    [WebInvoke(UriTemplate = "customers/{customer}/contact.xml", 
     Method = "PUT", 
     RequestFormat = WebMessageFormat.Xml, 
     ResponseFormat = WebMessageFormat.Xml)]
    public Contact UpdateContact(string customer, Contact instance)
    {
      return XmlDataStore.UpdateContact(customer, instance);
    }

    [XmlSerializerFormat]
    [WebInvoke(UriTemplate = "orders.xml", 
     Method = "PUT", 
     RequestFormat = WebMessageFormat.Xml, 
     ResponseFormat = WebMessageFormat.Xml)]
    public Order UpdateOrder(Order instance)
    {
      return XmlDataStore.UpdateOrder(instance);
    } 

        #endregion
     .
     .
     .
    // DELETE Methods
    [WebInvoke(UriTemplate = "customers/{customer}", 
     Method = "DELETE")]
    public void DeleteCompany(string customer)
    {
      XmlDataStore.DeleteCustomer(customer);
    }

    [WebInvoke(UriTemplate = "customers/{customer}/{contact}", 
     Method = "DELETE")]
    public void DeleteContact(string customer, string contact)
    {
      XmlDataStore.DeleteContact(customer, contact);
    }

    [WebInvoke(UriTemplate = "orders/{order}", 
     Method = "DELETE")]
    public void DeleteOrder(string order)
    {
      XmlDataStore.DeleteOrder(order);
    }
    }
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

Creating JSON Endpoints

XML has been the de facto standard format for web services for many years; but, with the maturity of web technologies such as AJAX and JavaScript Object Notation (JSON), formatting has become increasingly popular. Luckily, WCF makes extending your RESTful services to include JSON output a breeze.

Organizations that have adopted REST as a services architecture often provide developers a choice when requesting data from the services layer. XML often integrates more seamlessly into applications that already use XML data, via SOAP services, or other methods. Web applications with significant logic in JavaScript are often more suited to JSON delivery of information.

Where mobile applications are concerned, XML's verbosity can result in increased bandwidth performance issues, but JSON formatting results in a much more compact data stream — especially where large list retrieval is necessary.

To extend your existing services to deliver JSON-formatted output, simply expose parallel endpoints using a distinguishing route in the endpoint definition. Listing 7.18 uses the file extension technique, (that is, .xml for XML and .json for JSON formatting).

1.1
Listing 7.18: REST services using JSON formatting
namespace CustomerManagement.REST
{
  [ServiceContract]
  [AspNetCompatibilityRequirements(
     RequirementsMode = 
       AspNetCompatibilityRequirementsMode.Allowed)]
  [ServiceBehavior(InstanceContextMode = 
     InstanceContextMode.PerCall)]
  public class Service
  {
   .
   .
   .
#region JSON Services

    // GET Methods
    [WebGet(UriTemplate = "customers.json?filter={filter}", 
     ResponseFormat = WebMessageFormat.Json)]
    public List<Customer> GetCustomersJson(string filter)
    {
      return XmlDataStore.GetCustomers(filter);
    }

    [WebGet(UriTemplate = "products.json", 
     ResponseFormat = WebMessageFormat.Json)]
    public List<Product> GetProductsJson()
    {
      return XmlDataStore.GetProducts();
    }

    [WebGet(UriTemplate = "orders/{customer}.json", 
     ResponseFormat = WebMessageFormat.Json)]
    public List<Order> GetCompanyOrdersJson(string customer)
    {
      return XmlDataStore.GetCustomerOrders(customer);
    }

    [WebGet(UriTemplate = "orders/{customer}/{order}.json", 
     ResponseFormat = WebMessageFormat.Json)]
    public List<Order> GetCompanyOrderJson(string customer, string order)
    {
      return XmlDataStore.GetCompanyOrder(customer, order);
    }

    [WebGet(UriTemplate = "customers/{customer}.json", 
     ResponseFormat = WebMessageFormat.Json)]
    public Customer GetCompanyJson(string customer)
    {
      return XmlDataStore.GetCustomer(customer);
    }

    [WebGet(UriTemplate = "customers/{customer}/contacts.json", 
     ResponseFormat = WebMessageFormat.Json)]
    public List<Contact> GetContactsJson(string customer)
    {
      return XmlDataStore.GetContacts(customer);
    }

    [WebGet(UriTemplate = "customers/{customer}/{contact}.json", 
     ResponseFormat = WebMessageFormat.Json)]
    public Contact GetContactJson(string customer, string contact)
    {
      return XmlDataStore.GetContact(customer, contact);
    }

    // POST Methods
    [WebInvoke(UriTemplate = "customers/customer.json", 
     Method = "POST", 
     RequestFormat = WebMessageFormat.Json, 
     ResponseFormat = WebMessageFormat.Json)]
    public Customer CreateCompanyJson(Customer instance)
    {
      return XmlDataStore.CreateCustomer(instance);
    }

    [WebInvoke(UriTemplate = "customers/{customer}/contact.json", 
     Method = "POST", 
     RequestFormat = WebMessageFormat.Json, 
     ResponseFormat = WebMessageFormat.Json)]
    public Contact CreateContactJson(string customer, Contact instance)
    {
      return XmlDataStore.CreateContact(customer, instance);
    }

    [WebInvoke(UriTemplate = "orders.json", 
     Method = "POST", 
     RequestFormat = WebMessageFormat.Json, 
     ResponseFormat = WebMessageFormat.Json)]
    public Order CreateOrderJson(Order instance)
    {
      return XmlDataStore.CreateOrder(instance);
    }

    // PUT Methods
    [WebInvoke(UriTemplate = "customers/customer.json", 
     Method = "PUT", 
     RequestFormat = WebMessageFormat.Json, 
     ResponseFormat = WebMessageFormat.Json)]
    public Customer UpdateCompanyJson(Customer instance)
    {
      return XmlDataStore.UpdateCustomer(instance);
    }

    [WebInvoke(UriTemplate = "customers/{customer}/contact.json", 
     Method = "PUT", 
     RequestFormat = WebMessageFormat.Json, 
     ResponseFormat = WebMessageFormat.Json)]
    public Contact UpdateContactJson(string customer, Contact instance)
    {
      return XmlDataStore.UpdateContact(customer, instance);
    }

    [WebInvoke(UriTemplate = "orders.json", 
     Method = "PUT", 
     RequestFormat = WebMessageFormat.Json, 
     ResponseFormat = WebMessageFormat.Json)]
    public Order UpdateOrderJson(Order instance)
    {
      return XmlDataStore.UpdateOrder(instance);
    }

    #endregion
   .
   .
   .
  }
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

Using Advanced Techniques

To this point, you've built a solid foundation of services to support the mobile application, but some additional techniques can help manage the bandwidth and resource constraints inherent to mobile devices and networks. In this section you explore some additional enhancements to your services to help support mobile users. The section ends with a discussion of ways in which enterprises can adopt a RESTful approach to deliver information to their users, customers, and beyond.

Specifying Data Elements in the Request

You've created services for a customer management application, and the output of these services is custom-made for the application. But what if you need to develop another application that requires a different view of the customers for whom you have built services?

One successful technique to tailor the data delivered in varied use cases is to specify the elements you require as a part of the request. Most RESTful routing engines, including the routing service in WCF, support inclusion of query parameters in the endpoint. By creating a parameter that specifies the data elements to show, the consumers of your services can customize the output to match their application needs.

For example, if you want to make a request for an XML customer list, the only option is to retrieve the default presentation at the customers.xml endpoint.

GET http://localhost/MXDemo/customers.xml

But if you extend the endpoint to accept a parameter specifying the elements to include in the response, you can process accordingly in the data retrieval logic of your data store.

GET http://localhost/MXDemo/customers.xml?show=id,name,website

Listing 7.19 shows an implementation of the data retrieval method in the data store.

1.1
Listing 7.19: Data retrieval method supporting custom output
namespace CustomerManagement.Data
{
  public class XmlDataStore
  {
   .
   .
   .
      public static List<Customer> GetCustomers(string[] show)
      {
        return (from item in GetCustomerList()
                select new Customer()
                {
                  ID = show.Contains("id") ? item.ID : null,
                  Name = show.Contains("name") ? 
                                   item.Name : null,
                  PrimaryAddress = show.Contains("primaryaddress") ?
                                   item.PrimaryAddress : null,
                  PrimaryPhone = show.Contains("primaryphone") ?
                                   item.PrimaryPhone : null,
                  Website = show.Contains("website") ? 
                                   item.Website : null,
                  Addresses = show.Contains("addresses") ? 
                                   item.Addresses : null,
                  Contacts = show.Contains("contacts") ? 
                                   item.Contacts : null,
                  Orders = show.Contains("orders") ? 
                                   item.Orders : null,
                }).ToList();
      }

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Listing 7.20 shows the modified customer list endpoint in the REST service.

1.1
Listing 7.20: REST service supporting custom output
namespace CustomerManagement.REST
{
  [ServiceContract]
  [AspNetCompatibilityRequirements(
     RequirementsMode = 
       AspNetCompatibilityRequirementsMode.Allowed)]
  [ServiceBehavior(
     InstanceContextMode = InstanceContextMode.PerCall)]
  public class Service
  {
    #region XML Services
    .
    .
    .
    [XmlSerializerFormat]
    [WebGet(UriTemplate = "customers.xml?show={show}")]
    public List<Customer> GetCustomers(string show)
    {
      return show == null ?
          XmlDataStore.GetCustomers() :
          XmlDataStore.GetCustomers(show.Split(char.Parse(",")));
    }
    .
    .
    .
  }
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

Listing 7.21 shows the results of this customized request.

1.1
Listing 7.21: A customized Company list
<ArrayOfCompany xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Company>
    <ID>1</ID>
    <Name>Stein Mart, Inc.</Name>
    <Website>http://www.steinmart.com</Website>
  </Company>
  <Company>
    <ID>2</ID>
    <Name>Bgc Partners, Inc.</Name>
    <Website>http://www.bgcpartners.com</Website>
  </Company>
  .
  .
  .
</ArrayOfCompany>

Building Pagination into Your Services

Enterprise applications often deal with large collections of data that require paging of information to make it easier for the user to consume. Paging in mobile applications can also provide the added benefit of limiting the data set returned to better manage device resources. Both these are compelling reasons to build pagination into your data services.

To extend the customer list endpoint to provide paged information, you need to specify the page number you want to receive, and optionally the number of items on each page.

GET http://localhost/MXDemo/customers.xml?page=1&items=5

Listing 7.22 shows the paged implementation for retrieval from the data store.

1.1
Listing 7.22: Data retrieval method with pagination
namespace CustomerManagement.Data
{
  public class XmlDataStore
  {
    .
    .
    .

    public static List<Customer> GetCustomers(int page, int items)
    {
      return (from item in GetCustomerList()
              select new Customer()
              {
                ID = item.ID,
                Name = item.Name,
                PrimaryAddress = item.PrimaryAddress,
                PrimaryPhone = item.PrimaryPhone
              }).Skip((page - 1) * items).Take(items).ToList();
    }
    .
    .
    .
   }
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Listing 7.23 shows the paged customer list endpoint in the REST service.

1.1
Listing 7.23: REST service with pagination
namespace CustomerManagement.REST
{
  [ServiceContract]
  [AspNetCompatibilityRequirements(RequirementsMode =
   AspNetCompatibilityRequirementsMode.Allowed)]
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
  public class Service
  {
    #region XML Services
    .
    .
    .

    [XmlSerializerFormat]
    [WebGet(UriTemplate = 
     "customers.xml?show={show}&page={page}&items={items}")]
    public List<Company> GetCustomers(string show, int page, int items)
    {
      return show == null ?
          XmlDataStore.GetCustomers(page == 0 ? 1 : 
                                    page, items == 0 ? 5 : 
                                    items) :
          XmlDataStore.GetCustomers(show.Split(char.Parse(",")), 
                                    page == 0 ? 1 : page, 
                                    items == 0 ? 10 : items);
    }
    .
    .
    .
   }
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

Filtering Results on the Server

Navigating and consuming large lists can be cumbersome, especially in a mobile application where the form-factor and interaction paradigms can be limited compared to a desktop or web application. Because of this, mobile applications often provide a means for filtering a large list of information using a data-filtering scheme. However, complex searches that involve selection of specific fields and explicit AND/OR logic can be difficult to use on a mobile device. As a result, most devices use a simple, text-based search field that applies the term to all relevant fields in the data set.

To support this method of filtering in your services, simply add a single filter parameter to your endpoint. Whatever term you provide will be applied to all the fields you specify as searchable. For example, to return only customers who have a primary address in the state of New York, use a filter on the endpoint.

GET http://localhost/MXDemo/customers.xml?filter=NY

If you want to narrow the results to a specific ZIP code, simply provide the proper filter term.

GET http://localhost/MXDemo/customers.xml?filter=10022

If you want to return customers with the term communication in their name, modify your filter accordingly.

GET http://localhost/MXDemo/customers.xml?filter=communication

The code in Listing 7.24 shows the simple filtering code in the data store.

1.1
Listing 7.24: Data retrieval method with simple filtering
namespace CustomerManagement.Data
{
  public class XmlDataStore
  {
    .
    .
    .
    public static List<Customer> GetCustomers(string filter)
    {
      bool test = string.IsNullOrWhiteSpace(filter);
      return (from item in GetCustomerList()
              where 
                item.Name
                    .Contains(!string.IsNullOrWhiteSpace(filter) ? 
                              filter : item.Name) ||                              
                item.PrimaryAddress.City
                    .Contains(!string.IsNullOrWhiteSpace(filter) ? 
                              filter : item.PrimaryAddress.City) ||               
                item.PrimaryAddress.State
                    .Contains(!string.IsNullOrWhiteSpace(filter) ? 
                              filter : item.PrimaryAddress.State) ||              
                item.PrimaryAddress.Zip
                    .Contains(!string.IsNullOrWhiteSpace(filter) ? 
                              filter : item.PrimaryAddress.Zip)
              select new Customer()
              {
                ID = item.ID,
                Name = item.Name,
                Website = item.Website
              }).ToList();
    }
    .
    .
    .
  }
}

Found in the CustomerManagement.Data/XmlDataStore.cs file of the download

Listing 7.25 shows the filtered customer endpoint in the REST service.

1.1
Listing 7.25: REST service with simple filtering
namespace CustomerManagement.REST
{
  [ServiceContract]
  [AspNetCompatibilityRequirements(RequirementsMode = 
   AspNetCompatibilityRequirementsMode.Allowed)]
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
  public class Service
  {
    #region XML Services
    .
    .
    .

    [XmlSerializerFormat]
    [WebGet(UriTemplate =
           "customers.xml?
            filter={filter}&show={show}
            &page={page}&items={items}")]
    public List<Customer> GetCustomers(string filter, 
                                      string show, 
                                      int page, 
                                      int items)
    {            
      return show == null ?
             page == 0 && items == 0 ?
          XmlDataStore.GetCustomers(filter) :
          XmlDataStore.GetCustomers(filter, page, items) :
          XmlDataStore.GetCustomers(filter, 
                       show.Split(char.Parse(",")), page, items);
    }        
    .
    .
    .
   }
}

Found in the CustomerManagement.DataServices/CustomerManagement.REST/Service.cs file of the download

Summary

In this chapter you learned how to optimize and build your web services for mobile applications. The chapter discussed both SOAP and RESTful techniques, and you learned to make your services mirror your application design to best provide data to your application. Finally, you applied techniques to further specify the information needed and to limit the volume of data returned to your mobile application in accordance with mobile application best practices. In the next chapter you learn how to consume these services to populate your mobile application with the information critical to your users.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.211.166