Hugoware

The product of a web developer with a little too much caffeine

Combining WebServices with MVC

with 7 comments

I always liked the concept behind WebServices. Having a single place to store a bunch of complex but commonly used functions is a great way to decrease complexity of other programs that all sit on the same network. If you’re like me and tend to do a lot of intranet applications, a web service can prevent a lot of duplicate code.

My only real problem with WebServices was having to use SOAP. Adding a Web Reference to a project wasn’t that big of a deal but if you ever wanted to just call a function real quick, say from a script file (yes, I do VBScript occasionally… ick) then it isn’t quite as I’d prefer. You end up spending more time making sure your XML is well formed and less time on the logic inside your quick script.

MVC helps get around that problem by allowing you to perform normal HTTP calls and plug all your arguments into the query string or the body of the request – something much easier to do. The problem, however, is that you end up losing the convenience of using a WebService with other applications.

A Simple Solution

The idea here is to create a controller that acts as a wrapper to a WebService. By doing this we can override a few methods on our Controller that use reflection to invoke the matching method on the WebService. Also, because the Controller is still a unique class, we can still attach any number of Actions to it as we normally would. Below is some code that I wrote the other day. It isn’t battle tested so if you use it be sure to verify it does everything that you need it to do.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Mvc;
using System.Reflection;
using System.Xml.Serialization;
using System.Xml.Linq;
using System.IO;

namespace Interface.Controllers {

    /// <summary>
    /// Abstract wrapper that handles calling WebService methods on behalf of a controller
    /// </summary
    public abstract class WebServiceControllerWrapper<T> : Controller where T : WebService {

        #region Constructors

        /// <summary>
        /// Creates a new Controller Wrapper for a WebService
        /// </summary>
        public WebServiceControllerWrapper() {
            this.Service = Activator.CreateInstance<T>();
        }

        #endregion

        #region Properties

        //The service that is being used for this call
        private T Service { get; set; }

        #endregion

        #region Overriding Methods

        //finds the correct method for the WebService method
        protected override void HandleUnknownAction(string actionName) {

            //find if the method exists
            MethodInfo method = this.Service
                .GetType()
                .GetMethods()
                .Where(found =>
                    found.IsPublic &&
                    found.Name.Equals(actionName, StringComparison.OrdinalIgnoreCase) &&
                    found.GetCustomAttributes(typeof(WebMethodAttribute), true).Count() > 0
                    )
                    .FirstOrDefault();

            //if no method was found, just give up
            if (method == null) { return; }

            //check if all the arguments were found
            List<object> arguments = new List<object>();
            ParameterInfo[] parameters = method.GetParameters();
            foreach (ParameterInfo param in parameters) {

                //check if this argument was found
                object arg = this.ValueProvider[param.Name].ConvertTo(param.ParameterType);
                arguments.Add(arg);
            }

            //with the arguments try and call the result
            object result = method.Invoke(this.Service, arguments.ToArray());

            //if there is a return value, serialize it and write it
            this.Response.ContentType = "text/xml";
            if (method.ReturnType != null) {

                //if this is an XElement of some kind, just use it as is
                if (result is XObject) {

                    //write the string
                    using (StreamWriter writer = new StreamWriter(this.Response.OutputStream)) {
                        writer.Write(result.ToString());
                    }

                }
                else {

                    //try and serialize it
                    XmlSerializer serialize = new XmlSerializer(result.GetType());
                    serialize.Serialize(this.Response.OutputStream, result);
                }

            }

        }

        #endregion

    }

}

The idea here is to inherit this class instead of the standard Controller class and provide the name of the WebService we want to wrap around as our Generic argument. For example…


//that's about it - actions are mapped automatically to the correct method on the webservice
public class AccountController : WebServiceControllerWrapper<AccountWebService> { }

By doing this, when our controller receives an Action, is checks the WebService instance for the same method and then tries to call the method with the arguments it finds as part of the request!

And that’s it! A quick and simple way to map incoming actions to a matching method on a Web Service!

The Return Type

I’m not sure the best way to handle the return type at this time. It seems to me that the XmlSerializer should be sufficient for object with the exception of Xml which should probably just be written out as a string. If you have a suggestion on a better way to respond to incoming requests, please let me know. 🙂

Remember: This is just some quick and dirty code – This needs some more polish and exception handling before I’d use it in a real project, but at least it might help you get going in the right direction.

Written by hugoware

September 27, 2009 at 9:51 pm

7 Responses

Subscribe to comments with RSS.

  1. As always I enjoy your posts. Regarding the return type of XmlSerializer, why not return JSON?

    ActiveEngine Sensei

    September 30, 2009 at 11:35 am

    • That’s actually a good idea – I was thinking along the lines to keeping them both returning XML but JSON format would be a great addition.

      I might be a good idea to simply allow an optional query parameter such as returnAs that accepts xml or json or really any format at that point. That or something applied to the header… dunno really 🙂

      webdev_hb

      September 30, 2009 at 7:08 pm

      • I cant’ resist – You’re the jLinq Dude! Json should be your first choice! It’s in the title “LINQ to JSON”!!

        Just teasing – today I was working on yet another project where I use jLinq. It rocks!! But I got laughing when I realized you didn’t think of Json first 🙂

        Anyway, here’s another controversial statement: jLinq is good enough for me that when paired with jQuery and micro-templates you do not need MVC and many other server side solutions. I jettisoned Telerik RADGrid and chose simple web services and client side templating, and for a mid sized project it was great. jLinq helped a lot too.

        Keep up the good work man. You write great code and it’s really helped me out.

        ActiveEngine Sensei

        October 1, 2009 at 6:43 pm

      • HAHA!!! I guess it is a bit of a surprise that I’d default to XML – Funny stuff, man!

        Anyways, I’m glad to hear it is helping as much as it is – I kinda felt jLinq was a solution to a non-existent problem. Glad it is coming in handy for people.

        Thanks for the compliments!

        webdev_hb

        October 1, 2009 at 6:58 pm

  2. Jlinq is one of the reasons I have not gone to MVC yet. Think of it this way: one the reasons to stay server side is data manipulation. Stored proc with the DB and lambdas are great when you have to manipulate data on the fly round tripping to the server is difficult to do well with RIA. When I can do this on the client my architecture and data interfaces are much simpler. I am turning around to a workflow and data entry app in half day with processing done on the client. Like I said with Telerik I don’t think I could as much so quickly.

    ActiveEngine Sensei

    October 1, 2009 at 8:19 pm

  3. Hi Mate, Can you post some code. How you implemented in MVC. So webservice will be your Model as in MVC term’s ?

    Barry

    September 1, 2010 at 7:14 am

    • The line…

      public class AccountController : WebServiceControllerWrapper { }

      … is all that is required.

      Instead of invoking commands on the WebService with the full SOAP message you can use parameters in your query string.

      This probably doesn’t work with complex objects. You’ll probably have either write some sort of deserialization functionality in order to use anything other than basic types.

      hugoware

      September 2, 2010 at 12:45 am


Leave a comment