Route not found exception with the generic response model - c#

I am trying to capture the exception when the route is not found and wrap the exception with the generic response model.
I tried to implement, as given in the answer to the question, but this solution also doesn't seem to work in my use case.
Because the status code 404 is also added to the response when the resource is not found, like when Id is not found.
app.UseStatusCodePages(new StatusCodePagesOptions()
{
HandleAsync = (ctx) =>
{
if (ctx.HttpContext.Response.StatusCode == 404)
{
throw new RouteNotFoundException("Route not found");
}
return Task.FromResult(0);
}
})
RouteNotFoundException
public class RouteNotFoundException : Exception
{
public RouteNotFoundException()
: base()
{
}
public RouteNotFoundException(string message)
: base(message)
{
}
}
ApiExceptionFilterAttribute
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;
public ApiExceptionFilterAttribute()
{
// Register known exception types and handlers.
_exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
{
{ typeof(RouteNotFoundException), HandleNotFoundException }
};
}
public override void OnException(ExceptionContext context)
{
HandleException(context);
base.OnException(context);
}
private void HandleException(ExceptionContext context)
{
Type type = context.Exception.GetType();
if (_exceptionHandlers.ContainsKey(type))
{
_exceptionHandlers[type].Invoke(context);
return;
}
HandleUnknownException(context);
}
private void HandleNotFoundException(ExceptionContext context)
{
var exception = context.Exception as RouteNotFoundException;
var details = new ProblemDetails()
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Title = "The specified resource was not found.",
Detail = exception.Message
};
context.Result = new NotFoundObjectResult(details);
context.ExceptionHandled = true;
}
private void HandleUnknownException(ExceptionContext context)
{
var details = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "An error occurred while processing your request.",
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1"
};
context.Result = new ObjectResult(details)
{
StatusCode = StatusCodes.Status500InternalServerError
};
context.ExceptionHandled = true;
}
}
ResponseWrapperMiddleware
public class ResponseWrapperMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ResponseWrapperMiddleware> _logger;
public ResponseWrapperMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = loggerFactory?.CreateLogger<ResponseWrapperMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));
}
public async Task Invoke(HttpContext httpContext)
{
try
{
var currentBody = httpContext.Response.Body;
using (var memoryStream = new MemoryStream())
{
//set the current response to the memorystream.
httpContext.Response.Body = memoryStream;
await _next(httpContext);
//reset the body
httpContext.Response.Body = currentBody;
memoryStream.Seek(0, SeekOrigin.Begin);
var readToEnd = new StreamReader(memoryStream).ReadToEnd();
var objResult = JsonConvert.DeserializeObject(readToEnd);
var result = CommonApiResponse.Create((HttpStatusCode)httpContext.Response.StatusCode, objResult, null);
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(result));
}
}
catch (Exception ex)
{
if (httpContext.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the http status code middleware will not be executed.");
throw;
}
return;
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class ResponseWrapperMiddlewareExtensions
{
public static IApplicationBuilder UseResponseWrapperMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ResponseWrapperMiddleware>();
}
}
Generic Response Model
public class CommonApiResponse
{
public static CommonApiResponse Create(HttpStatusCode statusCode, object result = null, string errorMessage = null)
{
return new CommonApiResponse(statusCode, result, errorMessage);
}
public string Version => "1.2.3";
public int StatusCode { get; set; }
public string RequestId { get; }
public string ErrorMessage { get; set; }
public object Result { get; set; }
protected CommonApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
{
RequestId = Guid.NewGuid().ToString();
StatusCode = (int)statusCode;
Result = result;
ErrorMessage = errorMessage;
}
}
How to handle the error if the route is not found and capture the error in the generic model? What is the workaround for this case?

Related

ASP.NET Core - How to check if a field exists from a given third party API before sending Email

In my ASP.NET Core-6 Web API, I am given a third party API to consume and then return the account details. I am using HttpClient.
api:
https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail?accountNumber=
In appsettings.json I have:
"Endpoints": {
"customerUrl": "https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail?accountNumber="
}
DTO:
public class GetCustomerDetailDto
{
public class CustomerDetail
{
public string AccountNumber { get; set; }
public string Fullname { get; set; }
public string EmailAddress { get; set; }
}
}
Then I have this Data Util:
public class DataUtil : IDataUtil
{
private readonly IConfiguration _config;
private readonly ILogger<DataUtil> _logger;
private readonly HttpClient _myClient;
public DataUtil
(
IConfiguration config,
ILogger<DataUtil> logger,
HttpClient myClient
)
{
_config = config;
_logger = logger;
_myClient = myClient;
}
public CustomerDetail GetCustomerDetail(string accountNumber)
{
var responseResults = new CustomerDetail();
try
{
string custAccountNoUrl = _config.GetSection("Endpoints").GetValue<string>("customerUrl") + accountNumber;
_myClient.DefaultRequestHeaders.Accept.Clear();
_myClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = _myClient.GetAsync(custAccountNoUrl).Result;
if (response.IsSuccessStatusCode)
{
var stringResult = response.Content.ReadAsStringAsync().Result;
responseResults = JsonConvert.DeserializeObject<CustomerDetail>(stringResult);
}
}
catch (Exception ex)
{
_logger.LogError($"An Error occured " + ex.ToString());
}
return responseResults;
}
}
Then this is the implementation:
public async Task<Response<string>> CreateCustomerDetailAsync(CreateDto requestDto)
{
var response = new Response<string>();
var accDetail = _dataAccess.GetCustomerDetail(requestDto.DrAccountNumber);
if (accDetail.EmailAddress != null)
{
var accountName = accDetail.Fullname;
var emailAddress = accDetail.EmailAddress.ToLower();
var mailBody = await EmailBodyBuilder.GetCustomerEmailBody(accountName, emailTempPath: "wwwroot/files/Html/CustomerEmail.html");
var mailRequest = new MailRequest()
{
Subject = "Customer Notification",
Body = mailBody,
ToEmail = emailAddress
};
bool emailResult = await _mailService.SendEmailAsync(mailRequest);
if (emailResult)
{
response.StatusCode = (int)HttpStatusCode.OK;
response.Successful = true;
response.Message = "Successful!";
return response;
}
}
}
From the third-party API given, there are moments when the customer does not have email address, then the EmailAddress field will not even appear at all from the response.
So that made me to get error here:
bool emailResult = await _mailService.SendEmailAsync(mailRequest);
I tried
if (accDetail.EmailAddress != null)
but it's not solving the problem.
Making use of
public async Task<Response<string>> CreateCustomerDetailAsync(CreateDto requestDto)
How do I make the application not to send email at all whenever EmailAddress field does not exist or it's null?

Best approach for a middleware in an application running jobs

I have a service that calls endpoints from a external API. I developed a custom middleware for handling possible exceptions from these API calls and logging them. However, although the middleware includes a try and catch block in the Invoke method, my application's flow is being interrupted after an Exception is caught by the catch block. The flow simply ends, although the application keeps running. As this application is executing jobs that sync data, I need that the flow continues (and possible exceptions be logged, as I mentioned).
Anyone has already experienced this or have any clue how to make the flow go on? Is there a better approach for doing this? I'm totally open for suggestions, please.
This is my code:
public class ApiException
{
private readonly RequestDelegate _next;
private readonly ILogger<ApiException> _logger;
private readonly ApiExceptionOptions _options;
public ApiException(ApiExceptionOptions options, RequestDelegate next,
ILogger<ApiException> logger)
{
_next = next;
_logger = logger;
_options = options;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
ApiError error = _options.AddResponseDetails?.Invoke(exception)
?? ApiErrorFactory.New(exception);
LogApiException(exception, error);
return CreateResponse(context, error);
}
private static Task CreateResponse(HttpContext context, ApiError error)
{
var result = JsonSerializer.Serialize(error,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)error.Status;
return context.Response.WriteAsync(result);
}
private void LogApiException(Exception exception, ApiError error)
{
var innerExMessage = GetInnermostExceptionMessage(exception);
var level = _options.DetermineLogLevel?.Invoke(exception) ?? GetLogLevel(exception);
_logger.Log(level, exception, "ERROR: {message} -- {ErrorId}.", innerExMessage, error.Id);
}
private static LogLevel GetLogLevel(Exception exception) =>
exception switch
{
InvalidInputException _ => LogLevel.Warning,
_ => LogLevel.Error
};
private string GetInnermostExceptionMessage(Exception exception)
{
if (exception.InnerException != null)
return GetInnermostExceptionMessage(exception.InnerException);
return exception.Message;
}
}
public class ApiExceptionOptions
{
public Func<Exception, ApiError> AddResponseDetails { get; set; }
public Func<Exception, LogLevel> DetermineLogLevel { get; set; }
}
public static class ApiErrorFactory
{
public static ApiError New(Exception exception) =>
exception switch
{
InvalidInputException e => new ApiError
{
Details = e.Details,
Message = e.Message,
Status = BadRequest
},
_ => new ApiError()
};
}
public class ApiError
{
public static string DefaultErrorMessage = "Erro no processamento da requisição.";
public string Id { get; } = Guid.NewGuid().ToString();
public HttpStatusCode Status { get; set; } = HttpStatusCode.InternalServerError;
public string Title { get; set; } = "API Error";
public string Message { get; set; } = DefaultErrorMessage;
public IDictionary<string, object> Details { get; set; } = new Dictionary<string, object>();
}
public static class ApiExceptionExtensions
{
public static IApplicationBuilder UseApiExceptionHandler(this IApplicationBuilder builder)
{
var options = new ApiExceptionOptions();
return builder.UseMiddleware<ApiException>(options);
}
public static IApplicationBuilder UseApiExceptionHandler(this IApplicationBuilder builder,
Action<ApiExceptionOptions> configureOptions)
{
var options = new ApiExceptionOptions();
configureOptions(options);
return builder.UseMiddleware<ApiException>(options);
}
}

What is the best way to know the User who is log?

Hello i have the next design problem:
Before accessing to my Controller i have a filter to check the authentication and the authorization so in order to do so i have to know the user. Until there everything is perfect, but the problem starts when i want to know the user who is log so i can do more things. Any ideas?
[AdministratorAuth("DogController")]
public class DogController : ControllerBase
{
[HttpGet]
public IAction GetDogsOfUser()
{
return Ok(dogLogic.GetDogsOfUser());
}
}
public class LoginAuth : Attribute, IActionFilter
{
public static Guid Token { get; private set; }
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
string headerToken = context.HttpContext.Request.Headers["Authorization"];
if (headerToken is null)
{
context.Result = new ContentResult()
{
Content = "Token is required",
};
} else
{
try
{
Guid token = Guid.Parse(headerToken);
VerifyToken(token, context);
Token = token;
} catch (FormatException)
{
context.Result = new ContentResult()
{
Content = "Invalid Token format",
};
}
}
}
private void VerifyToken(Guid token, ActionExecutingContext context)
{
using (var sessions = GetSessionLogic(context))
{
if (!sessions.IsValidToken(token))
{
context.Result = new ContentResult()
{
Content = "Invalid Token",
};
}
}
}
private ISessionLogic GetSessionLogic(ActionExecutingContext context)
{
var typeOfSessionsLogic = typeof(ISessionLogic);
return context.HttpContext.RequestServices.GetService(typeOfSessionsLogic) as ISessionLogic;
}
}
public class AdministratorAuth : LoginAuth
{
private readonly string permission;
public AdministratorAuth(string permission)
{
this.permission = permission;
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
string headerToken = context.HttpContext.Request.Headers["Authorization"];
Guid token = Guid.Parse(headerToken);
using (var sessions = GetSessionLogic(context))
{
if (!sessions.HasLevel(token, permission))
{
context.Result = new ContentResult()
{
Content = "The user hasn't the permission to access " + permission,
};
}
}
}
private ISessionLogic GetSessionLogic(ActionExecutingContext context)
{
var typeOfSessionsLogic = typeof(ISessionLogic);
return context.HttpContext.RequestServices.GetService(typeOfSessionsLogic) as ISessionLogic;
}
}
So let's imagine that i have this, if i want to know the dogs of the user who is log, how can i do?
You can simply use Nlog or log4net function,
or
create a model which contains
Logged = DateTime.Now,
LoginHost = Request.Host.ToString(),
LoginIP = Request.HttpContext.Connection.LocalIpAddress.ToString(),
SessionId = Request.HttpContext.Session.Id

Writing a generic ExceptionFitler in asp.net core 2.0

Below is my implementation. I have written a CustomExceptionFilterAttribute which inherits from ExceptionFilterAttribute. Each error is placed inside a if and else to generate proper result. What I would like to do is create a callback function so that I can remove if and else block and error can be handled in more generic way.
public class HostedServicesController : BaseController
{
public IActioResult Index()
{
throw new NotFoundInDatabaseException("Error in Index Controller");
}
}
public class NotFoundInDatabaseException : Exception
{
public NotFoundInDatabaseException(string objectName, object objectId) :
base(message: $"No {objectName} with id '{objectId}' was found")
{
}
}
public class CustomExceptionFilterAttribute :ExceptionFilterAttribute
{
private SystemManager SysMgr { get; }
public CustomExceptionFilterAttribute(SystemManager systemManager)
{
SysMgr = systemManager;
}
public override void OnException(ExceptionContext context)
{
var le = SysMgr.Logger.NewEntry();
try
{
le.Message = context.Exception.Message;
le.AddException(context.Exception);
var exception = context.Exception;
if (exception is NotFoundInDatabaseException)
{
le.Type = LogType.ClientFaultMinor;
context.Result = new NotFoundObjectResult(new Error(ExceptionCode.ResourceNotFound, exception.Message));
}
else if (exception is ConfigurationException)
{
le.Type = LogType.ErrorMinor;
context.Result = new BadRequestObjectResult(new Error(ExceptionCode.NotAuthorised, exception.Message));
}
else
{
le.Type = LogType.ErrorSevere;
context.Result = new InternalServerErrorObjectResult(new Error(ExceptionCode.Unknown, exception.Message));
}
le.AddProperty("context.Result", context.Result);
//base.OnException(context);
}
finally
{
Task.Run(() => SysMgr.Logger.LogAsync(le)).Wait();
}
}
}
You could use a Dictionary with a custom type for this. Think of something like this:
public class ErrorHandlerData
{
public LogType LogType { get; set; }
public string ExceptionCode { get; set; } // not sure if string
}
public class CustomExceptionFilterAttribute :ExceptionFilterAttribute
{
private static Dictionary<Type, ErrorHandlerData> MyDictionary = new Dictionary<Type, ErrorHandlerData>();
static CustomExceptionFilterAttribute()
{
MyDictionary.Add(typeof(NotFoundInDatabaseException), new ErrorHandlerData
{
LogType = LogType.ClientFaultMinor,
ExceptionCode = ExceptionCode.ResourceNotFound
};
//general catch-all
MyDictionary.Add(typeof(Exception), new ErrorHandlerData
{
LogType = LogType.ErrorSevere,
ExceptionCode = ExceptionCode.Unknown
};
}
So you can then use it like this:
public override void OnException(ExceptionContext context)
{
var le = SysMgr.Logger.NewEntry();
try
{
le.Message = context.Exception.Message;
le.AddException(context.Exception);
var exception = context.Exception;
var exeptionType = exception.GetType();
var errorHandlerData = MyDictionary.ContainsKey(exceptionType) ?
MyDictionary[exceptionType] : MyDictionary[typeof(Exception)];
le.Type = errorHandlerData.LogType;
context.Result = new NotFoundObjectResult(new Error(errorHandlerData.ExceptionCode, exception.Message));
le.AddProperty("context.Result", context.Result);
}
finally
{
Task.Run(() => SysMgr.Logger.LogAsync(le)).Wait();
}
}

WebApi2 OData return custom error object

I've searched this intensive but can't get it to work.
I've haven an Web AP2 OData API and need to return a custom error class.
Here is what I have:
public class Error
{
public string Code { get; set; }
public string Message { get; set; }
}
In Global.asax.cs
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
GlobalConfiguration.Configuration.MessageHandlers.Add(new MessageInterceptor());
}
public class MessageInterceptor : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(
task =>
{
var body = task.Result.Content.ReadAsStringAsync().Result;
var resultObj = JsonConvert.DeserializeObject(body.Replace("value", "Results"));
task.Result.Content = new ObjectContent(typeof(object), resultObj, new JsonMediaTypeFormatter());
return task.Result;
}, cancellationToken);
}
}
In WebApiConfig,cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new HandleApiExceptionAttribute());
}
}
public class HandleApiExceptionAttribute : ExceptionFilterAttribute, IExceptionFilter
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is Error)
{
var res = context.Exception.Message;
//Define the Response Message
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(res),
ReasonPhrase = res
};
//Create the Error Response
context.Response = response;
}
}
public class OrdersController : ODataControllerA controller
{
private Context db = new Context();
// GET: odata/Orders
[Queryable]
public IQueryable<Orders> GetOrders(ODataQueryOptions<Orders> opts)
{
<some code producing error>
Error theError = new Error()
{
Code = "1000",
Message = "Geen filter gespecificeerd"
};
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.BadRequest, theError);
//return Request.CreateResponse(HttpStatusCode.BadRequest, theError);
throw new HttpResponseException(response);
}
When I try this this crashes in the MessageInterceptor.
This is there because a third party consuming the API want's it in the specific format.
When the code runs correct it returns results{}
On error it should return Error{code:, message: }
Anyone ideas?
Jeroen
This is what I came up with:
I changed the controller to:
[Queryable, BasicAuthentication]
public IHttpActionResult GetOrders(ODataQueryOptions<Orders> opts)
{
else
{
Error theError = new Error()
{
Code = "1000",
Message = "Geen filter gespecificeerd"
};
return new ErrorResult(theError, Request);
}
return Ok(resultList);
}
Using this class:
public class ErrorResult : IHttpActionResult
{
Error _error;
HttpRequestMessage _request;
public ErrorResult(Error error, HttpRequestMessage request)
{
_error = error;
_request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
List<Error> _errorList = new List<Error>();
_errorList.Add(_error);
error err = new error()
{
errors = _errorList
};
var response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent<error>(err, new JsonMediaTypeFormatter()),
RequestMessage = _request
};
return Task.FromResult(response);
}
}
public class Error
{
public string Code { get; set; }
public string Message { get; set; }
}
public class error
{
public List<Error> errors { get; set; }
}

Categories

Resources