Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Combining Web Services with MVC

, 29 Sep 2009 CC (ASA 2.5)
Rate this:
Please Sign up or sign in to vote.
WebServices make for a convenient way to share functionality across many different applications. MVC makes it easy to call methods by using standard HTTP calls. With a little bit of Reflection, you can map an MVC Controller to a Web Service.

Introduction

I always liked the concept behind Web Services. Having a single place to store a bunch of complex but commonly used functions is a great way to decrease the 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 Web Services 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 to 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 Web Service with other applications.

A Simple Solution

The idea here is to create a controller that acts as a wrapper to a Web Service. By doing this, we can override a few methods on our Controller that use Reflection to invoke the matching method on the Web Service. 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 {

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

        #region Constructors

        /// <span class="code-SummaryComment"><summary></span>
        /// Creates a new Controller Wrapper for a WebService
        /// <span class="code-SummaryComment"></summary></span>
        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 Web Service 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 Web Service 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 objects 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. Smile | :)

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.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License

Share

About the Author

webdev_hb

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 3 PinprofessionalManoj kumar V21-Jan-14 1:33 
QuestionError in the code Pinmemberlzliu21-Aug-12 6:44 
AnswerRe: Error in the code PinmemberJeff West12-Sep-13 11:55 
GeneralCongratulations PinmemberCarlos Gutierrez28-Dec-11 7:55 
GeneralPlease give me a sample application Pinmemberjibugeo29-Sep-09 4:38 
GeneralRe: Please give me a sample application Pinmemberwebdev_hb29-Sep-09 4:48 
GeneralRe: Please give me a sample application PinmemberMember 1005599013-Dec-14 2:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150327.1 | Last Updated 29 Sep 2009
Article Copyright 2009 by webdev_hb
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid