Created
January 14, 2017 23:58
-
-
Save wardsa013/cea062f8a65e01018e2becaa91b7a801 to your computer and use it in GitHub Desktop.
Error Support for Hypermedia.JsonApi.WebApi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class ErrorKeys | |
{ | |
public const string Status = "Status"; | |
public const string Title = "Title"; | |
public const string Detail = "Detail"; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A couple helpful extension methods to create an HttpResponse | |
public static HttpResponseMessage CreateJsonApiErrorResponse(this HttpRequestMessage request, HttpError error) | |
{ | |
HttpStatusCode code = HttpStatusCode.InternalServerError; | |
TryParseStatusCode(error[ErrorKeys.Status].ToString(), out code); | |
return request.CreateErrorResponse(code, error); | |
} | |
public static HttpResponseMessage CreateJsonApiErrorResponse(this HttpRequestMessage request, Exception ex) | |
{ | |
var error = ex.ToHttpError(); | |
return request.CreateJsonApiErrorResponse(error); | |
} | |
// Parser for HttpStatusCode from a string status code | |
public static bool TryParseStatusCode(string statusCode, out HttpStatusCode result) | |
{ | |
result = HttpStatusCode.InternalServerError; | |
int parsedStatusCode = -1; | |
if (!int.TryParse(statusCode, out parsedStatusCode)) return false; | |
result = (HttpStatusCode)parsedStatusCode; | |
return true; | |
} | |
// An example helper to generate properly formatted HttpErrors | |
public static HttpError ToHttpError(this Exception exception) | |
{ | |
var result = new HttpError(exception, true); | |
if (typeof(AuthorizationException).IsAssignableFrom(exception.GetType())) | |
{ | |
result[ErrorKeys.Status] = "401"; | |
result[ErrorKeys.Title] = "Authentication Failed"; | |
result[ErrorKeys.Detail] = exception.Message; | |
} | |
else if (typeof(Services.TimeoutException).IsAssignableFrom(exception.GetType())) | |
{ | |
result[ErrorKeys.Status] = "401"; | |
result[ErrorKeys.Title] = "Session Timedout"; | |
result[ErrorKeys.Detail] = "Session timedout please login."; | |
} | |
else | |
{ | |
result[ErrorKeys.Status] = "500"; | |
result[ErrorKeys.Title] = nameof(exception); | |
result[ErrorKeys.Detail] = exception.Message; | |
} | |
return result; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class JsonApiMediaTypeFormatterWithErrorSupport : JsonApiMediaTypeFormatter | |
{ | |
// No access to JsonApiMediaTypeFormatter.fieldNamingStrategy | |
// so created it here | |
private readonly IFieldNamingStrategy fieldNamingStrategy; | |
public JsonApiMediaTypeFormatterWithErrorSupport(IContractResolver contractResolver) | |
: this(contractResolver, new DasherizedFieldNamingStrategy()) { } | |
public JsonApiMediaTypeFormatterWithErrorSupport(IContractResolver contractResolver, IFieldNamingStrategy fieldNamingStrategy) | |
: base(contractResolver) | |
{ | |
this.fieldNamingStrategy = fieldNamingStrategy; | |
} | |
protected override JsonValue SerializeValue(Type type, object value) | |
{ | |
var errorSerializer = new JsonApiSerializerWithErrorSupport(fieldNamingStrategy); | |
if (typeof(HttpError).IsAssignableFrom(type)) | |
{ | |
return errorSerializer.SerializeError((HttpError)value); | |
} | |
return base.SerializeValue(type, value); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class JsonApiSerializerWithErrorSupport | |
{ | |
private readonly IFieldNamingStrategy fieldNamingStrategy; | |
public JsonApiSerializerWithErrorSupport(IFieldNamingStrategy fieldNamingStrategy) | |
{ | |
this.fieldNamingStrategy = fieldNamingStrategy; | |
} | |
public JsonValue SerializeError(HttpError value) | |
{ | |
if(value == null) | |
{ | |
throw new ArgumentNullException(nameof(value)); | |
} | |
var serializer = new Serializer(fieldNamingStrategy); | |
var members = new List<JsonMember> | |
{ | |
new JsonMember("errors", new JsonArray(serializer.Serialize(new [] { value }).ToList())) | |
}; | |
return new JsonObject(members); | |
} | |
class Serializer | |
{ | |
private readonly IJsonSerializer jsonSerializer; | |
private HashSet<JsonObject> visited = new HashSet<JsonObject>(JsonApiEntityKeyEqualityComparer.Instance); | |
internal Serializer(IFieldNamingStrategy fieldNamingStrategy) | |
{ | |
jsonSerializer = new JsonSerializer(new JsonConverterFactory(), fieldNamingStrategy); | |
} | |
internal IEnumerable<JsonObject> Serialize(IEnumerable<HttpError> errors) | |
{ | |
foreach(var error in errors) | |
{ | |
var jsonObject = Serialize(error); | |
yield return jsonObject; | |
} | |
} | |
internal JsonObject Serialize(HttpError error) | |
{ | |
var members = new List<JsonMember>(); | |
members.Add(new JsonMember("status", SerializeValue(error[ErrorKeys.Status]))); | |
members.Add(new JsonMember("title", SerializeValue(error[ErrorKeys.Title]))); | |
members.Add(new JsonMember("detail", SerializeValue(error[ErrorKeys.Detail]))); | |
return new JsonObject(members); | |
} | |
internal JsonValue SerializeValue(object value) | |
{ | |
return jsonSerializer.SerializeValue(value); | |
} | |
bool HasVisited(JsonObject jsonObject) | |
{ | |
return visited.Contains(jsonObject); | |
} | |
} | |
/// <summary> | |
/// Direct copy of Hypermedia.JsonApi.JsonApiEntityKeyEqualityComparer because it is internal. | |
/// </summary> | |
class JsonApiEntityKeyEqualityComparer : IEqualityComparer<JsonObject> | |
{ | |
internal static readonly IEqualityComparer<JsonObject> Instance = new JsonApiEntityKeyEqualityComparer(); | |
/// <summary> | |
/// Constructor. | |
/// </summary> | |
JsonApiEntityKeyEqualityComparer() { } | |
/// <summary> | |
/// Determines whether the specified objects are equal. | |
/// </summary> | |
/// <param name="x">The first object of type <paramref name="T"/> to compare.</param> | |
/// <param name="y">The second object of type <paramref name="T"/> to compare.</param> | |
/// <returns>true if the specified objects are equal; otherwise, false.</returns> | |
public bool Equals(JsonObject x, JsonObject y) | |
{ | |
return String.Equals(CreateKey(x), CreateKey(y)); | |
} | |
/// <summary> | |
/// Returns a hash code for the specified object. | |
/// </summary> | |
/// <param name="obj">The <see cref="T:System.Object"/> for which a hash code is to be returned.</param> | |
/// <exception cref="T:System.ArgumentNullException">The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.</exception> | |
/// <returns>A hash code for the specified object.</returns> | |
public int GetHashCode(JsonObject obj) | |
{ | |
if (obj == null) | |
{ | |
throw new ArgumentNullException(nameof(obj)); | |
} | |
return CreateKey(obj).GetHashCode(); | |
} | |
/// <summary> | |
/// Creates a string key representation that is used for comparissons. | |
/// </summary> | |
/// <param name="jsonObject">The JSON object to create the key for.</param> | |
/// <returns>The string key that represents the given JSON object.</returns> | |
static string CreateKey(JsonObject jsonObject) | |
{ | |
if (jsonObject["id"] != null) | |
{ | |
return $"{jsonObject["type"].Stringify()}:{jsonObject["id"].Stringify()}".ToLower(); | |
} | |
return jsonObject["type"].Stringify(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment