From fd0466e28618091f15bd2fe3cdf87abbbe87e534 Mon Sep 17 00:00:00 2001 From: pavelbannov Date: Thu, 30 May 2019 12:28:21 +0300 Subject: [PATCH] Api: exception handling --- .../Middleware/CommonApiResponse.cs | 75 ++++++++ .../ASC.Api.Core/Middleware/ResponseParser.cs | 141 +++++++++++++++ .../Middleware/ResponseWrapper.cs | 160 +++--------------- web/ASC.Web.Api/Startup.cs | 12 +- 4 files changed, 241 insertions(+), 147 deletions(-) create mode 100644 common/ASC.Api.Core/Middleware/CommonApiResponse.cs create mode 100644 common/ASC.Api.Core/Middleware/ResponseParser.cs diff --git a/common/ASC.Api.Core/Middleware/CommonApiResponse.cs b/common/ASC.Api.Core/Middleware/CommonApiResponse.cs new file mode 100644 index 0000000000..6ab200fec1 --- /dev/null +++ b/common/ASC.Api.Core/Middleware/CommonApiResponse.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Net; +using System.Runtime.Serialization; + +namespace ASC.Api.Core.Middleware +{ + [DataContract] + public class CommonApiResponse + { + [DataMember(EmitDefaultValue = false)] + public int? Count { get; set; } + + [DataMember] + public int Status { get; set; } + + [DataMember] + public HttpStatusCode StatusCode { get; set; } + + [DataMember(EmitDefaultValue = false)] + public object Response { get; set; } + + [DataMember(EmitDefaultValue = false)] + public CommonApiError Error { get; set; } + + protected CommonApiResponse(HttpStatusCode statusCode, object response = null, Exception error = null) + { + Status = Convert.ToInt32((int)statusCode >= 400); + StatusCode = statusCode; + Response = response; + Error = CommonApiError.FromException(error); + } + + public static CommonApiResponse Create(HttpStatusCode statusCode, object response = null) + { + return new CommonApiResponse(statusCode, response); + } + + public static CommonApiResponse CreateError(HttpStatusCode statusCode, Exception error = null) + { + return new CommonApiResponse(statusCode, error: error); + } + } + + [DataContract] + public class CommonApiError + { + [DataMember] + public string Message { get; set; } + + [DataMember] + public Type Type { get; set; } + + [DataMember] + public string Stack { get; set; } + + [DataMember] + public int Hresult { get; set; } + + [DataMember] + public IDictionary Data { get; set; } + + public static CommonApiError FromException(Exception exception) + { + return new CommonApiError() + { + Message = exception.Message, + Type = exception.GetType(), + Stack = exception.StackTrace, + Hresult = exception.HResult, + Data = exception.Data + }; + } + } +} diff --git a/common/ASC.Api.Core/Middleware/ResponseParser.cs b/common/ASC.Api.Core/Middleware/ResponseParser.cs new file mode 100644 index 0000000000..6333fef5ea --- /dev/null +++ b/common/ASC.Api.Core/Middleware/ResponseParser.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Xml.Linq; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json.Linq; + +namespace ASC.Api.Core.Middleware +{ + abstract class ResponseParser + { + public abstract object Deserialize(string response); + + public abstract string Serialize(CommonApiResponse response); + + public string WrapAndWrite(HttpStatusCode statusCode, string response) + { + switch (statusCode) + { + case HttpStatusCode.Unauthorized: + var result1 = CommonApiResponse.CreateError(statusCode, new UnauthorizedAccessException()); + return Serialize(result1); + default: + var result = CommonApiResponse.Create(statusCode, Deserialize(response)); + return Serialize(result); + } + } + + public string WrapAndWrite(HttpStatusCode statusCode, Exception error) + { + var result = CommonApiResponse.CreateError(statusCode, error); + return Serialize(result); + } + } + + class JsonResponseParser : ResponseParser + { + public override object Deserialize(string response) + { + return JsonConvert.DeserializeObject(response); + } + + public override string Serialize(CommonApiResponse response) + { + if (response.Status == 0) + { + response.Count = response.Response is JObject ? 1 : ((response.Response as JArray)?.Count ?? 0); + } + var settings = new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + + }; + return JsonConvert.SerializeObject(response, settings); + } + } + class XmlResponseParser : ResponseParser + { + public override object Deserialize(string response) + { + return XDocument.Parse(response); + } + + public override string Serialize(CommonApiResponse response) + { + var count = 0; + + var result = new XElement("result"); + var responseElements = new List(); + + if (response.Response != null) + { + var root = ((XDocument)response.Response).Root; + if (root.Name.LocalName.StartsWith("ArrayOf")) + { + var elements = root.Elements(); + + foreach (var e in elements) + { + responseElements.Add(GetResponse(e)); + } + + count = elements.Count(); + } + else + { + count = 1; + responseElements.Add(GetResponse(root)); + } + result.Add(new XElement(nameof(response.Count).ToCamelCase(), count)); + } + else if (response.Error != null) + { + responseElements.Add(new XElement(nameof(response.Error).ToCamelCase(), response.Error)); + } + + result.Add(new XElement(nameof(response.Status).ToCamelCase(), response.Status)); + result.Add(new XElement(nameof(response.StatusCode).ToCamelCase(), (int)response.StatusCode)); + result.Add(responseElements); + + var doc = new XDocument(result); + return doc.ToString(); + + XElement GetResponse(XElement xElement) + { + return ToLowerCamelCase(new XElement(nameof(response.Response).ToCamelCase(), xElement.Elements().Select(ToLowerCamelCase))); + } + + XElement ToLowerCamelCase(XElement xElement) + { + var lowerXElement = new XElement(xElement.Name.LocalName.ToCamelCase()); + + var elements = xElement.Elements(); + if (elements.Any()) + { + lowerXElement.Add(elements.Select(ToLowerCamelCase)); + } + else + { + lowerXElement.Add(xElement.Nodes()); + } + + return lowerXElement; + } + } + } + + public static class StringExtension + { + public static string ToCamelCase(this string str) + { + if (!string.IsNullOrEmpty(str) && str.Length > 1) + { + return Char.ToLowerInvariant(str[0]) + str.Substring(1); + } + return str; + } + } +} diff --git a/common/ASC.Api.Core/Middleware/ResponseWrapper.cs b/common/ASC.Api.Core/Middleware/ResponseWrapper.cs index c82cf3fcd5..dcd2a99175 100644 --- a/common/ASC.Api.Core/Middleware/ResponseWrapper.cs +++ b/common/ASC.Api.Core/Middleware/ResponseWrapper.cs @@ -2,18 +2,11 @@ using System.IO; using System.Net; using System.Threading.Tasks; -using System.Xml.Linq; -using System.Runtime.Serialization; -using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; -namespace ASC.Web.Api.Middleware +namespace ASC.Api.Core.Middleware { public class ResponseWrapper { @@ -26,13 +19,22 @@ namespace ASC.Web.Api.Middleware public async Task Invoke(HttpContext context) { + Exception error = null; var currentBody = context.Response.Body; using var memoryStream = new MemoryStream(); context.Response.Body = memoryStream; - await next(context); + try + { + await next(context); + } + catch(Exception exception) + { + context.Response.StatusCode = 500; + error = exception; + } context.Response.Body = currentBody; memoryStream.Seek(0, SeekOrigin.Begin); @@ -51,7 +53,14 @@ namespace ASC.Web.Api.Middleware } var readToEnd = new StreamReader(memoryStream).ReadToEnd(); - await context.Response.WriteAsync(responseParser.WrapAndWrite((HttpStatusCode)context.Response.StatusCode, readToEnd)); + if(error != null) + { + await context.Response.WriteAsync(responseParser.WrapAndWrite((HttpStatusCode)context.Response.StatusCode, error)); + } + else + { + await context.Response.WriteAsync(responseParser.WrapAndWrite((HttpStatusCode)context.Response.StatusCode, readToEnd)); + } } } @@ -63,135 +72,4 @@ namespace ASC.Web.Api.Middleware return builder.UseMiddleware(); } } - - [DataContract] - public class CommonApiResponse - { - [DataMember] - public int Count { get; set; } - - [DataMember] - public int Status { get; set; } - - [DataMember] - public HttpStatusCode StatusCode { get; set; } - - [DataMember] - public object Response { get; set; } - - protected CommonApiResponse(HttpStatusCode statusCode, object response = null, int status = 0) - { - Status = status; - StatusCode = statusCode; - Response = response; - } - - public static CommonApiResponse Create(HttpStatusCode statusCode, object response = null, int status = 0) - { - return new CommonApiResponse(statusCode, response, status); - } - } - - abstract class ResponseParser - { - public abstract object Deserialize(string response); - - public abstract string Serialize(CommonApiResponse response); - - public string WrapAndWrite(HttpStatusCode statusCode, string response) - { - var result = CommonApiResponse.Create(statusCode, Deserialize(response)); - return Serialize(result); - } - } - - class JsonResponseParser : ResponseParser - { - public override object Deserialize(string response) - { - return JsonConvert.DeserializeObject(response); - } - - public override string Serialize(CommonApiResponse response) - { - response.Count = response.Response is JObject ? 1 : ((response.Response as JArray)?.Count ?? 0); - var settings = new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() }; - return JsonConvert.SerializeObject(response, settings); - } - } - class XmlResponseParser : ResponseParser - { - public override object Deserialize(string response) - { - return XDocument.Parse(response); - } - - public override string Serialize(CommonApiResponse response) - { - var root = ((XDocument)response.Response).Root; - var count = 0; - - var result = new XElement("result"); - var responseElements = new List(); - - if (root.Name.LocalName.StartsWith("ArrayOf")) - { - var elements = root.Elements(); - - foreach(var e in elements) - { - responseElements.Add(GetResponse(e)); - } - - count = elements.Count(); - } - else - { - count = 1; - responseElements.Add(GetResponse(root)); - } - - result.Add(new XElement(nameof(response.Count).ToCamelCase(), count)); - result.Add(new XElement(nameof(response.Status).ToCamelCase(), response.Status)); - result.Add(new XElement(nameof(response.StatusCode).ToCamelCase(), (int)response.StatusCode)); - result.Add(responseElements); - - var doc = new XDocument(result); - return doc.ToString(); - - XElement GetResponse(XElement xElement) - { - return ToLowerCamelCase(new XElement(nameof(response.Response).ToCamelCase(), xElement.Elements().Select(ToLowerCamelCase))); - } - - XElement ToLowerCamelCase(XElement xElement) - { - var lowerXElement = new XElement(xElement.Name.LocalName.ToCamelCase()); - - var elements = xElement.Elements(); - if (elements.Any()) - { - lowerXElement.Add(elements.Select(ToLowerCamelCase)); - } - else - { - lowerXElement.Add(xElement.Nodes()); - } - - return lowerXElement; - } - } - } - - public static class StringExtension - { - public static string ToCamelCase(this string str) - { - if (!string.IsNullOrEmpty(str) && str.Length > 1) - { - return Char.ToLowerInvariant(str[0]) + str.Substring(1); - } - return str; - } - } } \ No newline at end of file diff --git a/web/ASC.Web.Api/Startup.cs b/web/ASC.Web.Api/Startup.cs index 0d548efcab..fc75fe1c1d 100644 --- a/web/ASC.Web.Api/Startup.cs +++ b/web/ASC.Web.Api/Startup.cs @@ -1,3 +1,6 @@ +using System.Linq; +using System.Reflection; +using System.IO; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -12,11 +15,7 @@ using Microsoft.Extensions.Hosting; using ASC.Api.Core; using ASC.Common.Logging; using ASC.Web.Api.Handlers; -using ASC.Web.Api.Middleware; - -using System.Linq; -using System.Reflection; -using System.IO; +using ASC.Api.Core.Middleware; namespace ASC.Web.Api { @@ -50,7 +49,8 @@ namespace ASC.Web.Api .Select(Assembly.LoadFrom) .Where(r => r.GetCustomAttribute() != null); - foreach (var a in assemblies) { + foreach (var a in assemblies) + { builder.AddApplicationPart(a); }