Gal Ratner
Gal Ratner is a Techie who lives and works in Los Angeles CA and Austin TX. Follow galratner on Twitter Google
Planning for SOA: Returning data in multiple formats

A major part of any SOA implementation is the ability to return multiple data formats and while SOAP still plays a major role in SOA, the need for RSS syndication, JSON and plain XML may require you to add new functionality and steer away from traditional WCF RPC calls. With OData enabled services you can expend the flexibility of your system, but, what about a pain old XML read feed? There is no need to use the overhead of WCF Data Services or RIA OData publish points. Here is a simple class I use along with a generic HTTP Handler. You might recognize the pattern from an earlier blog post. This class converts POCO objects to JSON, XML, RSS20 and ATOM10. Since IHttpHandler defines the contract that ASP.NET implements to synchronously process HTTP Web requests, it should show performance gains over WCF Data Services.
Here is the class DataFormatManager

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Json;
using System.ServiceModel.Syndication;
using System.Xml;
using System.Xml.Serialization;

using InvertedSoftware.SOAExample.POCO.DataAttributes;

namespace InvertedSoftware.SOAExample.BLL
{
    public enum OutputDataFormat
    {
        JSON,
        XML,
        RSS20,
        ATOM10
    }
    public static class DataFormatManager
    {
        public static void WriteFormattedObject<T>(OutputDataFormat outputDataFormat, Stream outputStream, T dataObject)
        {
            switch (outputDataFormat)
            {
                case OutputDataFormat.ATOM10:
                    WriteFeedFormattedObject<T>(outputStream, dataObject, OutputDataFormat.ATOM10);
                    break;
                case OutputDataFormat.JSON:
                    WriteJSONFormattedObject<T>(outputStream, dataObject);
                    break;
                case OutputDataFormat.RSS20:
                    WriteFeedFormattedObject<T>(outputStream, dataObject, OutputDataFormat.RSS20);
                    break;
                case OutputDataFormat.XML:
                    WriteXMLFormattedObject<T>(outputStream, dataObject);
                    break;
                default:
                    break;
            }
        }

        private static void WriteJSONFormattedObject<T>(Stream outputStream, T dataObject)
        {
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
            ser.WriteObject(outputStream, dataObject);
        }

        private static void WriteXMLFormattedObject<T>(Stream outputStream, T dataObject)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            serializer.Serialize(outputStream, dataObject);
        }

        private static void WriteFeedFormattedObject<T>(Stream outputStream, T dataObject, OutputDataFormat outputDataFormat)
        {
            SyndicationFeed feed = new SyndicationFeed("Feed"typeof(T).Name, new Uri("http://Contoso/testfeed"), "ID"DateTime.Now);
            List<SyndicationItem> items = new List<SyndicationItem>();
            Type[] generic = typeof(T).GetGenericArguments();
            PropertyInfo[] propertyInfo = generic[0].GetProperties(BindingFlags.Public | BindingFlags.Instance);
            dynamic dataObjectList = dataObject;
            LoadDataFeed(items, propertyInfo, dataObjectList);
            feed.Items = items;

            XmlWriter feedWriter = XmlWriter.Create(outputStream);
            if (outputDataFormat == OutputDataFormat.RSS20)
            {
                Rss20FeedFormatter rssFormatter = new Rss20FeedFormatter(feed);
                rssFormatter.WriteTo(feedWriter);
            }
            else
            {
                Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter(feed);
                atomFormatter.WriteTo(feedWriter);
            }
            feedWriter.Close();
        }

        /// <summary>
        /// Load List Items into Syndication Items
        /// </summary>
        /// <param name="items"></param>
        /// <param name="propertyInfo"></param>
        /// <param name="dataObjectList"></param>
        private static void LoadDataFeed(List<SyndicationItem> items, PropertyInfo[] propertyInfo, dynamic dataObjectList)
        {
            foreach (var dataItem in dataObjectList)
            {
                SyndicationItem item = new SyndicationItem();
                item.PublishDate = DateTime.Now;
                foreach (var property in propertyInfo)
                {
                    FeedMember feedMember = Attribute.GetCustomAttribute(property, typeof(FeedMember)) as FeedMember;
                    if (feedMember == null)
                        continue;
                    string fieldValue = Convert.ToString(property.GetValue(dataItem, null));
                    switch (feedMember.UsedIn)
                    {
                        case FeedMemberType.Id:
                            item.Id = fieldValue;
                            break;
                        case FeedMemberType.Title:
                            item.Title = new TextSyndicationContent(fieldValue);
                            break;
                        case FeedMemberType.Content:
                            item.Content = new TextSyndicationContent(fieldValue);
                            break;
                        case FeedMemberType.Uri:
                            item.BaseUri = new Uri(fieldValue);
                            break;
                        default:
                            break;
                    }
                }
                items.Add(item);
            }
        }
    }
}


In order display object fields as SyndicationItem I used a custom attribute.

using System;

namespace InvertedSoftware.SOAExample.POCO.DataAttributes
{
    public enum FeedMemberType
    {
        Content,
        Id,
        Title,
        Uri
    }

    public class FeedMember : Attribute
    {
        public FeedMemberType UsedIn { getset; }
    }
}

Our POCO looks like this

using System.Runtime.Serialization;

using InvertedSoftware.SOAExample.POCO.DataAttributes;

namespace InvertedSoftware.SOAExample.POCO
{
    [DataContract()]
    public class Customer
    {
        [DataMember]
        [FeedMember(UsedIn = FeedMemberType.Title)]
        public string FirstName { getset; }
        [DataMember]
        [FeedMember(UsedIn = FeedMemberType.Content)]
        public string LastName { getset; }
    }
}

And invoking it from the handler will look like

<%@ WebHandler Language="C#" Class="Handler" %>

using System;
using System.Web;
using System.Collections.Generic;

using InvertedSoftware.SOAExample.POCO;
using InvertedSoftware.SOAExample.DAL;
using InvertedSoftware.SOAExample.BLL;

public class Handler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        DataFormatManager.WriteFormattedObject<List<Customer>>(OutputDataFormat.ATOM10, context.Response.OutputStream, CustomerRepository.GetCustomers());
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

This approach cannot be considered a departure from traditional RPC and SOAP, but, a supplemental feature that will help your SOA be consumed from both server side and client side systems using diverse technologies.

Shout it


Posted 31 Jan 2011 5:03 AM by Gal Ratner
Filed under: , ,

Powered by Community Server (Non-Commercial Edition), by Telligent Systems