Geeks With Blogs
Pounding Code technological rantings and code samples

If you've found this blog posting, you've probably been banging your head against a wall trying to get those errors generated from your web service to appear in your MVC web site. You may have already discovered that while you can see the messages display in Fiddler, getting them to appear in your project takes a little bit of work.

First, l strongly recommend you check out the work already done on this subject by more dedicated bloggers than l:

http://kenneththorman.blogspot.com/2011/02/wcf-rest-exception-handling.html
http://www.olegsych.com/2008/07/simplifying-wcf-using-exceptions-as-faults/

http://www.robbagby.com/rest/effective-error-handling-with-wcf-rest/

My solution was built on Keneth Thorman's work, so read his posting, download and play with his zip project.

Where my solution differs from Keneth's example is that I'd like to use FluentValidation in my WCF RESTFul web service, use the ChannelFactory and pass coherent messages back to my MVC view. One would expect that to be easily done, right out of the box, but the ChannelFactory returns a ProtocolException which is not going to give you much love.

Let's assume that on your WCF service you've to a FluentValidation on your web site (fig #1), a method for rolling up all your validation errors (fig.#2), and a method that will invoke your validation (fig #3). For your web site, let's assume you're using the ChannelFactory and have an interface and a class (fig #4) which you've got communicating to your service, and everything seems to be working wonderfully, until you've discovered that you're not getting back the sort of error messages that are very useful to a user. The collection of validation errors you saw in Fiddler seem to be getting swallowed by the Protocol exception.

They're not exactly being swallowed, but they're not exactly at your fingertips either. To get the error messages to appear in your project, we need to do the following

  1. Serialize our error messages (fig #5)
  2. Trap the error where the ChannelFactory proxy invokes our service call (fig #6)
  3. Draw the information we need out of the error (courtesy of Keneth Thorman) (fig #7)
  4. Repackage that information into a custom error type (fig #8)
  5. Catch the custom error in our controller and inject our errors into the ModelState (fig #9)

Figure #1

  public class ShoppingCartValidator : AbstractValidator<ShoppingCart>
    {
       public ShoppingCartValidator()
        {
            RuleFor(r => r.CustomerId)
                .NotEmpty();

           RuleFor(r => r.Expires)
                .NotEmpty()
                .MustBeAValidSqlServerDateTime();                 

            RuleFor(r => r.TotalPrice)
                .NotEmpty()
                .LessThan(214748);

           RuleFor(r => r.ShoppingCartItems).SetCollectionValidator(new ShoppingCartItemValidator());
           
          }
    }
}

Figure #2

   public static void ValidateThrow<TValidator>(this TValidator validator, IValidatableEntity entity) where TValidator : IValidator
        {
            var validationResult = validator.Validate<TValidator>(entity);
            if (!validationResult.IsValid)
            {
                throw new WebFaultException<List<ValidationError>>(validationResult.ValidationErrors, System.Net.HttpStatusCode.BadRequest);
            }
        }

 FIgure #3

        public ShoppingCart Save(string id, ShoppingCart model)
        {
            if (model.Id.ToString() == id)
            {
                _cartValidator.ValidateThrow(OnBeforeSave(model));
                _repository.Save(model);
                return model;
            }

            throw new WebFaultException<string>("The input is invalid", System.Net.HttpStatusCode.BadRequest);
        }

Figure #4

 [ServiceContract]
    public interface IShoppingCartService
    {

        [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare,
           ResponseFormat = WebMessageFormat.Xml,
           UriTemplate = ShoppingCart.UriTemplate + UriTemplateHelper.IdTemplate)]
        void Save(string id, ShoppingCart entity);

 }

      [ServiceContract]
    public class ShoppingCartService : ClientBase<IShoppingCartService>, IShoppingCartService
    {

     public void Save(string id, ShoppingCart entity)
        {
            Channel.Save(id, entity);           
        }

}

Figure #5 Validation Error object on wcf service site.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace Model
{
    // Object that provides information about the invalidity of an entity.
    [DataContract]
    public class ValidationError
    {
        // Gets or sets member name associated with the validation result.
        [DataMember]
        public string Source { get; set; }

        // Gets or sets the error message for the validation result.
        [DataMember]
        public string Message { get; set; }
    }
}

Figure #6 We now trap our exception here as compared to #4

        public void Save(string id, ShoppingCart entity)
        {
            try
            {
                Channel.Save(id, entity);
            }
            catch (Exception exceptionThrownByRestWcfCall)
            {
                ExceptionHelper<ShoppingCart>.ProcessError(exceptionThrownByRestWcfCall);
            }
            finally
            {
                ((IDisposable)Channel).Dispose();
            }

        }

Figure #7 This is built very closely to KT's example, but as you can see we're building up a new custom error message, which we throw once we've finished building it up. Again, my work is built on his, so check out his project first...

namespace Helpers
{
    public static class ExceptionHelper<TServiceEntity> where TServiceEntity : class
    {
        public static void ProcessError(Exception exceptionThrownByRestWcfCall)
        {
            var errors = new List<ValidationError>();
            WcfRestExceptionHelper<TServiceEntity, List<ValidationError>>.HandleRestServiceError(
                   exceptionThrownByRestWcfCall,
                   serviceResult =>
                   {
                       HandleServiceResult(serviceResult);
                   },
                   serviceFault =>
                   {
                       foreach (var item in serviceFault)
                       {
                           errors.Add(new ValidationError { Message = item.Message, Source = item.Source });
                       }
                   },
                   exception =>
                   {
                       errors.Add(new ValidationError { Message = exception.ToString(), Source = exception.Source });                     
                   }
            );
            var wcfErr = new WcfFaultException();
            wcfErr.ValidationErrors.AddRange(errors);
            throw wcfErr;

        }
        private static void HandleServiceResult<T>(T serviceResult) where T : class// happy path
        {
            // a hook to do something, along the happy path...
        }
    }
}

 

    public static class WcfRestExceptionHelper<TServiceResult, TServiceFault> where TServiceFault : class
    {
        private static IDictionary<Type, DataContractSerializer> cachedSerializers = new Dictionary<Type, DataContractSerializer>();

        public static void HandleRestServiceError(Exception exception, Action<TServiceResult> serviceResultHandler, Action<TServiceFault> serviceFaultHandler = null, Action<Exception> exceptionHandler = null)
        {
            var serviceResultOrServiceFaultHandled = false;

            if (exception == null) throw new ArgumentNullException("exception");
            if (serviceResultHandler == null) throw new ArgumentNullException("serviceResultHandler");

            // REST uses the HTTP procol status codes to communicate errors that happens on the service side.
            // This means if we have a teller service and you need to supply username and password to login
            // and you do not supply the password, a possible scenario is that you get a 400 - Bad request.
            // However it is still possible that the expected type is returned so it would have been possible
            // to process the response - instead it will manifest as a ProtocolException on the client side.
            var protocolException = exception as ProtocolException;
            if (protocolException != null)
            {
                var webException = protocolException.InnerException as WebException;
                if (webException != null)
                {
                    var responseStream = webException.Response.GetResponseStream();
                    if (responseStream != null)
                    {
                        try
                        {
                            // Debugging code to be able to see the reponse in clear text
                            //SeeResponseAsClearText(responseStream);

                            // Try to deserialize the returned XML to the expected result type (TServiceResult)
                            var response = (TServiceResult) GetSerializer(typeof(TServiceResult)).ReadObject(responseStream);
                            serviceResultHandler(response);
                            serviceResultOrServiceFaultHandled = true;
                        }
                        catch (SerializationException serializationException)
                        {
                            // This happens if we try to deserialize the responseStream to type TServiceResult
                            // when an error occured on the service side. An service side error serialized object
                            // is not deserializable into a TServiceResult

                            // Reset responseStream to beginning and deserialize to a TServiceError instead
                            responseStream.Seek(0, SeekOrigin.Begin);

                            var serviceFault = (TServiceFault) GetSerializer(typeof(TServiceFault)).ReadObject(responseStream);

                            if (serviceFaultHandler != null && serviceFault != null)
                            {
                                serviceFaultHandler(serviceFault);
                                serviceResultOrServiceFaultHandled = true;
                            }
                            else if (serviceFaultHandler == null && serviceFault != null)
                            {
                                throw new WcfServiceException<TServiceFault>() { ServiceFault = serviceFault };
                            }
                        }
                    }
                }
            }

            // If we have not handled the serviceResult or the serviceFault then we have to pass it on to the exceptionHandler delegate
            if (!serviceResultOrServiceFaultHandled && exceptionHandler != null)
            {
                exceptionHandler(exception);
            }
            else if (!serviceResultOrServiceFaultHandled && exceptionHandler == null)
            {
                // Unable to handle and no exceptionHandler passed in throw exception to be handled at a higher level
                throw exception;
            }
        }

        /// <summary>
        /// Based on the knowledge of how the XmlSerializer work, I found it safest to explicitly implement my own caching mechanism.
        /// Just in case the DataContractSerializer has the same implementation. Please see below
        ///
        /// From MSDN:
        /// To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and
        /// deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when
        /// using the following constructors:
        ///
        /// XmlSerializer.XmlSerializer(Type)
        /// XmlSerializer.XmlSerializer(Type, String)
        ///
        /// If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded,
        /// which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned
        /// two constructors. Otherwise, you must cache the assemblies.
        ///
        /// Investigating exactly how the DataContractSerializer is working internally will be the subject of a future blog posting.
        /// </summary>
        /// <param name="classSpecificSerializer"></param>
        /// <returns></returns>
        private static DataContractSerializer GetSerializer(Type classSpecificSerializer)
        {
            if (!cachedSerializers.ContainsKey(classSpecificSerializer))
            {
                cachedSerializers.Add(classSpecificSerializer, new DataContractSerializer(classSpecificSerializer));
            }
            return cachedSerializers[classSpecificSerializer];
        }

        /// <summary>
        /// Debugging helper method in case there are problems with the deserialization
        /// </summary>
        /// <param name="responseStream"></param>
        private static void SeeResponseAsClearText(Stream responseStream)
        {
            var responseStreamLength = responseStream.Length;
            var buffer = new byte[responseStreamLength];
            var x = responseStream.Read(buffer, 0, Convert.ToInt32(responseStreamLength));
            var enc = new UTF8Encoding();
            var response = enc.GetString(buffer);
            Debug.WriteLine(response);
            responseStream.Seek(0, SeekOrigin.Begin);
        }
    }

Figure #8 We create an overloaded type of FaultException (although it could have just been exception)
namespace Helpers.Common
{
    public class WcfFaultException : FaultException
    {
        private List<ValidationError> validationErrors = new List<ValidationError>();
        public WcfFaultException()
        {
        }

        public List<ValidationError> ValidationErrors
        {
            get
            {
                return this.validationErrors;
            }
        }
    }
}

 

Figure #9 the model.

   [HttpPost]
        public virtual ActionResult Add(ShoppingCartForm model)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    _modelSvc.Save(_modelMapper.Map(model));
                    return RedirectToAction("Index");   
                }                   
                catch (WcfFaultException f)  // trap our error
                {
                    foreach(var err in f.ValidationErrors) // convert too validation error.
                    {
                        ModelState.AddModelError(err.Source, err.Message);
                    }                   
                }          
            }

            return View(model);
        }

Posted on Saturday, August 13, 2011 3:20 PM MVC , WCF , Fiddler | Back to top


Comments on this post: Turn WCF REST Protocol Exception into MVC ModelError

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © James Fleming | Powered by: GeeksWithBlogs.net