ILearnable .Net

June 24, 2010

WCF service, Asp.Net ajax, jquery, component reuse, wohoo!

Filed under: Uncategorized — andreakn @ 20:06

recently switched projects, and I was immediately struck by how much infrastructure I had just taken for granted in the previous one., even though I had done a large part in building it.

One of the conveniences I had been getting so used to was to have a WCF ajax web service perform some task and return JSON-serialized data to the client, which in and of itself is pretty standard stuff, but we would have some of that data be actual html-snippets that would be inserted directly into the page. Still somewhat straight forward, you might say? What if I added that we would use the *same user controls” when a service returned html snippets as we would use when rendering the page normally.

If this sounds intriguing, do read on:

Lets use an example: say that we are creating a web site where users can mark pages as a favourite page so that they will have that page available in a global shortcut menu in the right column. We will need an ajax service to work as a custom profile service (we’ll just call it ProfileService) that will store whether a page is a given users favourite page or not. When  a page is set to be a favourite ,an ajax call will be made to the ProfileService which will update the profile store and perform a rendering of the “favourites overview” web control whose html will be returned to the client in the form of a specialized service result which will expose the html and other meta data as a JSON string which can easily be turned into a javascript object.

1)

First and foremost we need to have a WCF service defined in web.config (if you add an “WCF ajax service” from the right click menu in solution explorer you get the stuff you need. If you have trouble with doing that in your solution because of some weird config-o-rama in your project (it happens) just make a new web application project and add one there and copy the config changes from that web.config into your web.config

2)

Then we need to reference that service in a scriptmanager on the page. it is as simple as:

<asp:ScriptManager runat="server">
   <Scripts>
      <asp:ScriptReference Path="~/path/to/SomeService.svc" />
   </Scripts>
</asp:ScriptManager>

3)

The data being returned to the client should be in the format of a JSON string to be easily accessible from javascript. There are quite a few libraries out there that will serialize objects to JSON, but the .net library itself does a decent enough job if the classes to be serialized are all marked with the DataContract attribute. Here’s an implementation of a JsonServiceResult which encapsulates all the serialization logic:

    /// General JSON service result container
    [DataContract(Namespace = "")]
    public class JsonServiceResult
    {
        [DataMember]
        public bool Success;

        /// Can contain messages to end user
        [DataMember]
        public string Message;

        /// If needed, the Result should be a json serialized string
        [DataMember]
        public string Result;

        public JsonServiceResult()
        {
            // needs an empty constructor for contract serialization.
        }

        //as setting up a serializer for a given type is somewhat expensive, this can be cached in a static variable,
        //no need to worry about thread safety as the worst case scenario is that a serializer is created a couple of times
        private static readonly Dictionary<Type, DataContractJsonSerializer> _serializers = new Dictionary<Type, DataContractJsonSerializer>();

        public JsonServiceResult(bool success, string message, object result)
        {
            Success = success;
            Message = message;

            if (result == null){Result = "null"; //json deserialization doesn't like nulls
            } else if (result is string){ Result = result.ToString(); //no need to serialize it if only a string
            } else {
                DataContractJsonSerializer jsonSerializer = GetJsonSerializer(result);

                using (var memoryStream = new MemoryStream())
                    using (var xmlWriter = GetJsonWriter(memoryStream))
                        Result = WriteJsonObject(result, memoryStream, jsonSerializer, xmlWriter);
            }
        }

        private static string WriteJsonObject(object result, MemoryStream memoryStream, XmlObjectSerializer jsonSerializer, XmlDictionaryWriter xmlWriter)
        {
            jsonSerializer.WriteObject(xmlWriter, result);
            xmlWriter.Flush();
            var json = Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
            return json;
        }

        private static XmlDictionaryWriter GetJsonWriter(Stream memoryStream)
        {
            var xmlWriter = JsonReaderWriterFactory.CreateJsonWriter(memoryStream);
            return xmlWriter;
        }

        private static DataContractJsonSerializer GetJsonSerializer(object result)
        {
            Type serializerType = result.GetType();
            if (!_serializers.ContainsKey(serializerType))
            {
                _serializers[serializerType] = new DataContractJsonSerializer(serializerType);
            }
            return _serializers[serializerType];
        }

    }

A few points worth noting:

-the namespace is set to blank: this has no big effect for DataContract types, except if you want to instantiate them from javascript. Like ServiceContracts (which should always have a blank namespace IMHO) they are given their .net namespace as the namespace if none are provided, making it bloody cumbersome to use them in javascript.

– the JsonServiceResult reuses the .net JsonDataContractSerializers (they are stored in a static variable as they are instantiated, as they are not free to create but totally reusable.

4)

Next it is the ajax service.

the ProfileService.svc.cs file could look something like this:

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    [ServiceContract(Namespace = "")]
    public class ProfileService
    {
        [OperationContract]
        public JsonServiceResult SetPageAsFavouriteForCurrentUser(int pageId)
        {
            TheProfileStore.AddFavouritePage(pageId);
            Renderer renderer = new Renderer();
            FavouritesControl favouritesControl = (FavouritesControl)renderer.LoadControl("~/Path/To/UserControl/FavouritesControl.ascx");
            List<string> currentFavourites = TheProfileStore.GetFavouritesForCurrentUser();
            favouritesControl.FavouritesList = currentFavourites;
            string htmlsnippet = renderer.RenderControl(favouritesControl);
            FavouritesHolder holder = new FavouritesHolder
                                          {
                                              LastChanged = TheProfileStore.GetFavouritesLastChangedForCurrentUser(),
                                              HtmlSnippet = htmlsnippet
                                          };
            return new JsonServiceResult(true, "Page added to favourites", holder);
        }
    }

    [DataContract(Namespace = "")]
    public class FavouritesHolder
    {
        [DataMember]
        public string HtmlSnippet { get; set; }
        [DataMember]
        public DateTime LastChanged{ get; set; }
    }

Ok, it might be a little far fetched, but in order to show the proper Json serializing and deserializing in action I needed to have to return something more than just the html, as that would be too easy-peasy-japaneasy so i snuck a timestamp in there, I did it for you, dear reader, so there!

A couple of points to notice:

-the service has an empty namespace. As explained earlier that means it will be accessible in Javascript as only ProfileService (and not the full namespace)

-the service is set to be AspNetCompatibilityRequirementsMode.Required, this is not strictly necessary for all services, but as this service needed to figure out who the current user was it was necessary here. Without it there will be no user info to be found in HttpContext.Current

-there’s a few lines which has nothing to do with the demonstration of ajax/WCF/JSON as such, but are there for a sense of completeness: the ones referring to a static TheProfileStore

-there’s an interesting use of a class named Renderer, which is where the rendering magic happens, we will look at that next:

6)

The renderer class is created to be able to render web controls (user controls) if given their virtual path on the server. First a dummy page is newed up into which the control is loaded. then that control may receive any data it needs in its lifecycle. Then the control is rendered (using a new dummy Page object), and the resulting html is returned

Here’s the code:

    public class Renderer
    {
        public Control LoadControl(string path)
        {
            return new Page().LoadControl(path);
        }

        public string RenderControl(Control control)
        {
            string html;
            var page = new Page();
            page.Controls.Add(control);

            using (var writer = new StringWriter())
            {
                HttpContext.Current.Server.Execute(page, writer, false);
                html = writer.ToString();
            }

            return html;
        }
    }

7)

The endresult of all this is that one can perform the following stuff in the javascript

function setPageAsfavourite(pageId){
   ProfileService.SetPageAsFavouriteForCurrentUser(pageId, onSetFavouriteReturn, onServiceError);
}
function onServiceError(error){
   alert(error);
}
function onSetfavouriteReturn(serviceResponse){
        if (serviceResponse.Success) {
            var result = Sys.Serialization.JavaScriptSerializer.deserialize(serviceResponse.Result); //here we deserialize json into a js object
            $('#userFavourites').html(result.Htmlsnippet);
            $('#favouritesLastUpdated').html(result.LastChanged);
        }
}

Conclusion)
Using the two utility classes Renderer and ServiceResult it is fairly straightforward to render a web control in response to a service result. 
This web control will be rendered in its own lifecycle which will be mercifully efficient as it won’t have any page events, no master page, no nothing.
The upshot is that you can use the same web control when rendering the page normally as you do when small stuff should change in response to ajax events. This should mean less maintenance as time goes by

You might ask yourself why I bothered to do all this when all I really needed to do was to put an updatepanel around the control in question and handle a postback event, well doing that would mean that:
1) the entire page lifecycle for the current page would run, which would be harder on the server and slower for the end user
2) all the pages containing this shortcut list would need to be aware that they could get a postback from that control.
3) it would be more work to add effects when the html arrived on the client
4) any script tags in the resulting html would *not* be executed (updatepanels suppresses the running of javascript in the partial update html)

Blog at WordPress.com.