Permalink
Browse files

Add better support for end-to-end Error Handling.

Now you don't have to return a typed Response DTO to get error handling - if there is no Response DTO it uses the Generic ErrorResponse DTO to capture errors.
  • Loading branch information...
mythz committed Sep 22, 2012
1 parent bb0335e commit 1874900c39aa3c8c51bcaa0c4c0066aa540062bc
@@ -3,11 +3,13 @@
using System.IO;
using System.Net;
#if !(MONOTOUCH || SILVERLIGHT)
+using System.Reflection;
using System.Web;
#endif
using ServiceStack.Common;
using ServiceStack.Common.Web;
using ServiceStack.Logging;
+using ServiceStack.Net30.Collections.Concurrent;
using ServiceStack.Service;
using ServiceStack.ServiceHost;
using ServiceStack.Text;
@@ -74,8 +76,7 @@ protected ServiceClientBase()
{
this.HttpMethod = DefaultHttpMethod;
this.CookieContainer = new CookieContainer();
- asyncClient = new AsyncServiceClient
- {
+ asyncClient = new AsyncServiceClient {
ContentType = ContentType,
StreamSerializer = SerializeToStream,
StreamDeserializer = StreamDeserializer,
@@ -350,7 +351,12 @@ public virtual TResponse Send<TResponse>(object request)
{
TResponse response;
- if (!HandleResponseException(ex, requestUri, () => SendRequest(Web.HttpMethod.Post, requestUri, request), c => c.GetResponse(), out response))
+ if (!HandleResponseException(ex,
+ request,
+ requestUri,
+ () => SendRequest(Web.HttpMethod.Post, requestUri, request),
+ c => c.GetResponse(),
+ out response))
{
throw;
}
@@ -359,7 +365,8 @@ public virtual TResponse Send<TResponse>(object request)
}
}
- private bool HandleResponseException<TResponse>(Exception ex, string requestUri, Func<WebRequest> createWebRequest, Func<WebRequest, WebResponse> getResponse, out TResponse response)
+ private bool HandleResponseException<TResponse>(Exception ex, object request, string requestUri,
+ Func<WebRequest> createWebRequest, Func<WebRequest, WebResponse> getResponse, out TResponse response)
{
try
{
@@ -385,20 +392,47 @@ private bool HandleResponseException<TResponse>(Exception ex, string requestUri,
// than the old one.
// The new exception is either this one or the one thrown
// by the following method.
- HandleResponseException<TResponse>(subEx, requestUri);
+ ThrowResponseTypeException<TResponse>(request, subEx, requestUri);
throw;
}
// If this doesn't throw, the calling method
// should rethrow the original exception upon
// return value of false.
- HandleResponseException<TResponse>(ex, requestUri);
+ ThrowResponseTypeException<TResponse>(request, ex, requestUri);
response = default(TResponse);
return false;
}
- private void HandleResponseException<TResponse>(Exception ex, string requestUri)
+ readonly ConcurrentDictionary<Type,Action<Exception,string>> ResponseHandlers
+ = new ConcurrentDictionary<Type, Action<Exception, string>>();
+
+ private void ThrowResponseTypeException<TResponse>(object request, Exception ex, string requestUri)
+ {
+ if (request == null)
+ {
+ ThrowWebServiceException<TResponse>(ex, requestUri);
+ return;
+ }
+
+ var responseType = WebRequestUtils.GetErrorResponseDtoType(request);
+ Action<Exception, string> responseHandler;
+ if (!ResponseHandlers.TryGetValue(responseType, out responseHandler))
+ {
+ var mi = GetType().GetMethod("ThrowWebServiceException",
+ BindingFlags.Instance | BindingFlags.NonPublic)
+ .MakeGenericMethod(new[] { responseType });
+
+ responseHandler = (Action<Exception, string>)Delegate.CreateDelegate(
+ typeof(Action<Exception, string>), this, mi);
+
+ ResponseHandlers[responseType] = responseHandler;
+ }
+ responseHandler(ex, requestUri);
+ }
+
+ internal void ThrowWebServiceException<TResponse>(Exception ex, string requestUri)
{
var webEx = ex as WebException;
if (webEx != null && webEx.Status == WebExceptionStatus.ProtocolError)
@@ -408,8 +442,7 @@ private void HandleResponseException<TResponse>(Exception ex, string requestUri)
log.DebugFormat("Status Code : {0}", errorResponse.StatusCode);
log.DebugFormat("Status Description : {0}", errorResponse.StatusDescription);
- var serviceEx = new WebServiceException(errorResponse.StatusDescription)
- {
+ var serviceEx = new WebServiceException(errorResponse.StatusDescription) {
StatusCode = (int)errorResponse.StatusCode,
StatusDescription = errorResponse.StatusDescription,
};
@@ -433,8 +466,7 @@ private void HandleResponseException<TResponse>(Exception ex, string requestUri)
catch (Exception innerEx)
{
// Oh, well, we tried
- throw new WebServiceException(errorResponse.StatusDescription, innerEx)
- {
+ throw new WebServiceException(errorResponse.StatusDescription, innerEx) {
StatusCode = (int)errorResponse.StatusCode,
StatusDescription = errorResponse.StatusDescription,
ResponseBody = serviceEx.ResponseBody
@@ -459,8 +491,7 @@ private WebRequest SendRequest(string requestUri, object request)
private WebRequest SendRequest(string httpMethod, string requestUri, object request)
{
- return PrepareWebRequest(httpMethod, requestUri, request, client =>
- {
+ return PrepareWebRequest(httpMethod, requestUri, request, client => {
using (var requestStream = client.GetRequestStream())
{
SerializeToStream(null, request, requestStream);
@@ -736,7 +767,13 @@ public virtual TResponse Send<TResponse>(string httpMethod, string relativeOrAbs
{
TResponse response;
- if (!HandleResponseException(ex, requestUri, () => SendRequest(httpMethod, requestUri, request), c => c.GetResponse(), out response))
+ if (!HandleResponseException(
+ ex,
+ request,
+ requestUri,
+ () => SendRequest(httpMethod, requestUri, request),
+ c => c.GetResponse(),
+ out response))
{
throw;
}
@@ -815,7 +852,7 @@ public virtual void Patch(IReturnVoid request)
SendOneWay(Web.HttpMethod.Patch, request.ToUrl(Web.HttpMethod.Patch), request);
}
- public virtual TResponse Patch<TResponse>(string relativeOrAbsoluteUrl, object request)
+ public virtual TResponse Patch<TResponse>(string relativeOrAbsoluteUrl, object request)
{
return Send<TResponse>(Web.HttpMethod.Patch, relativeOrAbsoluteUrl, request);
}
@@ -895,7 +932,8 @@ public virtual TResponse PostFileWithRequest<TResponse>(string relativeOrAbsolut
// restore original position before retry
fileToUpload.Seek(currentStreamPosition, SeekOrigin.Begin);
- if (!HandleResponseException(ex, requestUri, createWebRequest, c => c.GetResponse(), out response))
+ if (!HandleResponseException(
+ ex, request, requestUri, createWebRequest, c => c.GetResponse(), out response))
{
throw;
}
@@ -929,7 +967,12 @@ public virtual TResponse PostFile<TResponse>(string relativeOrAbsoluteUrl, Strea
// restore original position before retry
fileToUpload.Seek(currentStreamPosition, SeekOrigin.Begin);
- if (!HandleResponseException(ex, requestUri, createWebRequest, c => { c.UploadFile(fileToUpload, fileName, mimeType); return c.GetResponse(); }, out response))
+ if (!HandleResponseException(ex,
+ null,
+ requestUri,
+ createWebRequest,
+ c => { c.UploadFile(fileToUpload, fileName, mimeType); return c.GetResponse(); },
+ out response))
{
throw;
}
@@ -2,6 +2,9 @@
using System.Net;
using System.Text;
using ServiceStack.Common.Web;
+using ServiceStack.ServiceHost;
+using ServiceStack.ServiceInterface.ServiceModel;
+using ServiceStack.Text;
namespace ServiceStack.ServiceClient.Web
{
@@ -20,14 +23,14 @@ public AuthenticationException(string message, Exception innerException) : base(
}
}
- internal static class WebRequestUtils
+ public static class WebRequestUtils
{
internal static AuthenticationException CreateCustomException(string uri, AuthenticationException ex)
{
if (uri.StartsWith("https"))
{
return new AuthenticationException(
- string.Format("Invalid remote SSL certificate, overide with: \nServicePointManager.ServerCertificateValidationCallback += ((sender, certificate, chain, sslPolicyErrors) => isValidPolicy);"),
+ String.Format("Invalid remote SSL certificate, overide with: \nServicePointManager.ServerCertificateValidationCallback += ((sender, certificate, chain, sslPolicyErrors) => isValidPolicy);"),
ex);
}
return null;
@@ -39,8 +42,8 @@ internal static bool ShouldAuthenticate(Exception ex, string userName, string pa
return (webEx != null
&& webEx.Response != null
&& ((HttpWebResponse) webEx.Response).StatusCode == HttpStatusCode.Unauthorized
- && !string.IsNullOrEmpty(userName)
- && !string.IsNullOrEmpty(password));
+ && !String.IsNullOrEmpty(userName)
+ && !String.IsNullOrEmpty(password));
}
internal static void AddBasicAuth(this WebRequest client, string userName, string password)
@@ -49,6 +52,43 @@ internal static void AddBasicAuth(this WebRequest client, string userName, strin
= "basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(userName + ":" + password));
}
+ /// <summary>
+ /// Naming convention for the request's Response DTO
+ /// </summary>
+ public const string ResponseDtoSuffix = "Response";
+
+ public static string GetResponseDtoName<TRequest>(TRequest request)
+ {
+ return typeof(TRequest) != typeof(object)
+ ? typeof(TRequest).FullName + ResponseDtoSuffix
+ : request.GetType().FullName + ResponseDtoSuffix;
+ }
+
+ public static Type GetErrorResponseDtoType(object request)
+ {
+ //If a conventionally-named Response type exists use that regardless if it has ResponseStatus or not
+ var responseDtoType = AssemblyUtils.FindType(GetResponseDtoName(request));
+ if (responseDtoType == null)
+ {
+ var genericDef = request.GetType().GetTypeWithGenericTypeDefinitionOf(typeof(IReturn<>));
+ if (genericDef != null)
+ {
+
+ var returnDtoType = genericDef.GetGenericArguments()[0];
+ var hasResponseStatus = returnDtoType is IHasResponseStatus
+ || returnDtoType.GetProperty("ResponseStatus") != null;
+
+ //Only use the specified Return type if it has a ResponseStatus property
+ if (hasResponseStatus)
+ {
+ responseDtoType = returnDtoType;
+ }
+ }
+ }
+
+ return responseDtoType ?? typeof(ErrorResponse);
+ }
+
}
}
@@ -1,4 +1,5 @@
using System.IO;
+using System.Xml;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceModel.Serialization;
using ServiceStack.Text;
@@ -33,12 +34,23 @@ public override string ContentType
public override void SerializeToStream(IRequestContext requestContext, object request, Stream stream)
{
+ if (request == null) return;
DataContractSerializer.Instance.SerializeToStream(request, stream);
}
public override T DeserializeFromStream<T>(Stream stream)
{
- return DataContractDeserializer.Instance.DeserializeFromStream<T>(stream);
+ try
+ {
+ return DataContractDeserializer.Instance.DeserializeFromStream<T>(stream);
+ }
+ catch (XmlException ex)
+ {
+ if (ex.Message == "Unexpected end of file.") //Empty responses
+ return default(T);
+
+ throw;
+ }
}
public override StreamDeserializerDelegate StreamDeserializer
@@ -8,7 +8,7 @@ namespace ServiceStack.ServiceInterface.ServiceModel
/// </summary>
[DataContract]
- public class ErrorResponse
+ public class ErrorResponse : IHasResponseStatus
{
[DataMember]
public ResponseStatus ResponseStatus { get; set; }
@@ -1,6 +1,8 @@
using System;
using ServiceStack.Messaging;
+using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceHost;
+using ServiceStack.Text;
namespace ServiceStack.ServiceInterface
{
@@ -37,7 +39,7 @@ public override object ExecuteAsync(TRequest request)
producer.Publish(request);
}
- return DtoUtils.CreateResponseDto(request);
+ return WebRequestUtils.GetErrorResponseDtoType(request).CreateInstance();
}
/// <summary>
Oops, something went wrong.

0 comments on commit 1874900

Please sign in to comment.