Skip to content

Commit d72864e

Browse files
committed
Added global Exception handler in Claims processor. Used DateTimeOffset
1 parent c93bf17 commit d72864e

File tree

11 files changed

+120
-35
lines changed

11 files changed

+120
-35
lines changed

ClaimsSolution.API/Dtos/RequestDtos/SubmitClaimRequestDto.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class SubmitClaimRequestDto
2020
[Required(ErrorMessage = "IncidentDate is required")]
2121
[DataType(DataType.Date)]
2222
[CustomValidation(typeof(SubmitClaimRequestDto), nameof(ValidateIncidentDate))]
23-
public DateTime IncidentDate { get; init; }
23+
public DateTimeOffset IncidentDate { get; init; }
2424

2525
[Required(ErrorMessage = "VehicleMake is required")]
2626
[StringLength(50, MinimumLength = 1,
@@ -32,14 +32,14 @@ public class SubmitClaimRequestDto
3232
ErrorMessage = "VehicleModel must be between 1 and 50 characters")]
3333
public string VehicleModel { get; init; } = string.Empty;
3434

35-
public static ValidationResult? ValidateIncidentDate(DateTime incidentDate, ValidationContext context)
35+
public static ValidationResult? ValidateIncidentDate(DateTimeOffset incidentDate, ValidationContext context)
3636
{
3737
if (incidentDate == default)
3838
{
3939
return new ValidationResult("IncidentDate cannot be empty");
4040
}
4141

42-
if (incidentDate.Date > DateTime.UtcNow.Date)
42+
if (incidentDate.Date > DateTimeOffset.UtcNow.Date)
4343
{
4444
return new ValidationResult("IncidentDate cannot be in the future");
4545
}

ClaimsSolution.API/Dtos/ResponseDtos/ClaimResponseDto.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ public class ClaimResponseDto
55
public Guid ClaimId { get; init; }
66
public Guid UserId { get; init; }
77
public string PhoneNumber { get; init; } = string.Empty;
8-
public DateTime IncidentDate { get; init; }
8+
public DateTimeOffset IncidentDate { get; init; }
99
public string VehicleMake { get; init; } = string.Empty;
1010
public string VehicleModel { get; init; } = string.Empty;
1111
public string Status { get; init; } = string.Empty;
12-
public DateTime CreatedAtUtc { get; init; }
12+
public DateTimeOffset CreatedAtUtc { get; init; }
1313
}
1414
}

ClaimsSolution.API/Extensions/ApplicationBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
3333

3434
public static WebApplication ConfigureMiddleware(this WebApplication app)
3535
{
36-
app.UseMiddleware<GlobalExceptionHandlerMiddleware>();
36+
app.UseMiddleware<ExceptionHandlingMiddleware>();
3737

3838
app.UseSwagger();
3939
app.UseSwaggerUI();

ClaimsSolution.API/Middleware/GlobalExceptionHandlerMiddleware.cs renamed to ClaimsSolution.API/Middleware/ExceptionHandlingMiddleware.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
using ClaimsSolution.Common;
2+
13
namespace ClaimsSolution.API.Middleware
24
{
3-
public class GlobalExceptionHandlerMiddleware
5+
public class ExceptionHandlingMiddleware
46
{
57
private readonly RequestDelegate _next;
6-
private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger;
8+
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
79

8-
public GlobalExceptionHandlerMiddleware(RequestDelegate next, ILogger<GlobalExceptionHandlerMiddleware> logger)
10+
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
911
{
1012
_next = next;
1113
_logger = logger;
@@ -19,7 +21,7 @@ public async Task InvokeAsync(HttpContext context)
1921
}
2022
catch (Exception ex)
2123
{
22-
_logger.LogError($"An unhandled exception occurred: {ex.Message}\n{ex.StackTrace}");
24+
_logger.LogError(ex, "");
2325
await HandleExceptionAsync(context, ex);
2426
}
2527
}
@@ -29,8 +31,8 @@ private static Task HandleExceptionAsync(HttpContext context, Exception exceptio
2931
context.Response.ContentType = "application/json";
3032
var response = new ErrorResponse
3133
{
32-
Message = $"An internal server error occurred. {exception.Message}",
3334
StatusCode = StatusCodes.Status500InternalServerError,
35+
Message = $"{Constants.UncaughtException}. {exception.Message}",
3436
StackTrace = exception.StackTrace ?? string.Empty
3537
};
3638

ClaimsSolution.API/Publishers/ClaimPublisher.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Azure.Storage.Queues;
22
using ClaimsSolution.Common.Contracts;
3+
using ClaimsSolution.Common;
34
using System.Text;
45
using System.Text.Json;
56

@@ -9,7 +10,7 @@ public class ClaimPublisher : IClaimPublisher
910
{
1011
private readonly QueueServiceClient _queueServiceClient;
1112
private readonly ILogger<ClaimPublisher> _logger;
12-
private const string QueueName = "claims-queue";
13+
private const string ClaimsQueueName = Constants.Queues.ClaimsQueue;
1314

1415
public ClaimPublisher(QueueServiceClient queueServiceClient, ILogger<ClaimPublisher> logger)
1516
{
@@ -19,7 +20,7 @@ public ClaimPublisher(QueueServiceClient queueServiceClient, ILogger<ClaimPublis
1920

2021
public async Task PublishAsync(ClaimSubmittedMessage message, CancellationToken ct)
2122
{
22-
var queueClient = _queueServiceClient.GetQueueClient(QueueName);
23+
var queueClient = _queueServiceClient.GetQueueClient(ClaimsQueueName);
2324
await queueClient.CreateIfNotExistsAsync(cancellationToken: ct);
2425

2526
var messageJson = JsonSerializer.Serialize(message);

ClaimsSolution.API/Services/ClaimService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ public async Task<SubmitClaimResponseDto> SubmitAsync(SubmitClaimRequestDto requ
3333
UserId = request.UserId,
3434
PhoneNumber = request.PhoneNumber.Trim(),
3535
Ssn = request.Ssn.Trim(),
36-
IncidentDate = request.IncidentDate.Date,
36+
IncidentDate = request.IncidentDate,
3737
VehicleMake = request.VehicleMake.Trim(),
3838
VehicleModel = request.VehicleModel.Trim(),
39-
SubmittedAtUtc = DateTime.UtcNow
39+
SubmittedAtUtc = DateTimeOffset.UtcNow
4040
};
4141

4242
_logger.LogInformation($"Publishing claim with ID {claimId} and Correlation ID {correlationId}");

ClaimsSolution.ClaimsProcessor/Functions/ProcessClaimFunction.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Microsoft.Azure.Functions.Worker;
66
using Microsoft.Extensions.Logging;
77
using System.Text.Json;
8+
using ClaimsSolution.Common;
89

910
namespace ClaimsSolution.ClaimsProcessor.Functions
1011
{
@@ -13,6 +14,7 @@ public class ProcessClaimFunction
1314
private readonly ILogger<ProcessClaimFunction> _logger;
1415
private readonly IClaimRepository _claimRepository;
1516

17+
1618
public ProcessClaimFunction(ILogger<ProcessClaimFunction> logger, IClaimRepository claimRepository)
1719
{
1820
_logger = logger;
@@ -21,7 +23,7 @@ public ProcessClaimFunction(ILogger<ProcessClaimFunction> logger, IClaimReposito
2123

2224
[Function(nameof(IngestClaims))]
2325
public async Task IngestClaims(
24-
[QueueTrigger("claims-queue", Connection = "AzureWebJobsStorage")] QueueMessage message,
26+
[QueueTrigger(Constants.Queues.ClaimsQueue, Connection = "AzureWebJobsStorage")] QueueMessage message,
2527
FunctionContext context)
2628
{
2729
var messageText = message.MessageText;
@@ -52,7 +54,7 @@ private async Task ProcessClaimAsync(ClaimSubmittedMessage claimMessage, Cancell
5254
VehicleModel = claimMessage.VehicleModel,
5355
Status = "Received",
5456
CreatedAtUtc = claimMessage.SubmittedAtUtc,
55-
UpdatedAtUtc = DateTime.UtcNow
57+
UpdatedAtUtc = DateTimeOffset.UtcNow
5658
};
5759

5860
await _claimRepository.AddClaimAsync(claimEntity, ct);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using ClaimsSolution.Common;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.Azure.Functions.Worker;
4+
using Microsoft.Azure.Functions.Worker.Http;
5+
using Microsoft.Azure.Functions.Worker.Middleware;
6+
using Microsoft.Extensions.Logging;
7+
using System.Net;
8+
9+
namespace ClaimsSolution.ClaimsProcessor.Middlewares
10+
{
11+
internal sealed class ExceptionHandlingMiddleware : IFunctionsWorkerMiddleware
12+
{
13+
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
14+
15+
public ExceptionHandlingMiddleware(ILogger<ExceptionHandlingMiddleware> logger)
16+
{
17+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
18+
}
19+
20+
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
21+
{
22+
try
23+
{
24+
await next(context);
25+
}
26+
catch (Exception ex)
27+
{
28+
_logger.LogError(ex, Constants.UncaughtException);
29+
30+
var httpReqData = await context.GetHttpRequestDataAsync();
31+
32+
if (httpReqData != null)
33+
{
34+
var newHttpResponse = httpReqData.CreateResponse(HttpStatusCode.InternalServerError);
35+
36+
var errorResponse = new
37+
{
38+
statusCode = StatusCodes.Status500InternalServerError,
39+
message = $"{Constants.UncaughtException}. {ex.Message}",
40+
StackTrace = ex.StackTrace ?? string.Empty
41+
};
42+
43+
await newHttpResponse.WriteAsJsonAsync(errorResponse);
44+
45+
var invocationResult = context.GetInvocationResult();
46+
47+
var httpOutputBindingFromMultipleOutputBindings = GetHttpOutputBindingFromMultipleOutputBinding(context);
48+
if (httpOutputBindingFromMultipleOutputBindings is not null)
49+
{
50+
httpOutputBindingFromMultipleOutputBindings.Value = newHttpResponse;
51+
}
52+
else
53+
{
54+
invocationResult.Value = newHttpResponse;
55+
}
56+
}
57+
}
58+
}
59+
60+
private static OutputBindingData<HttpResponseData>? GetHttpOutputBindingFromMultipleOutputBinding(FunctionContext context)
61+
{
62+
var httpOutputBinding = context.GetOutputBindings<HttpResponseData>()
63+
.FirstOrDefault(b => b.BindingType == "http" && b.Name != "$return");
64+
65+
return httpOutputBinding;
66+
}
67+
}
68+
}

ClaimsSolution.Common/Constants.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace ClaimsSolution.Common
2+
{
3+
public static class Constants
4+
{
5+
public const string X_REQUEST_ID = "X-Request-ID";
6+
public const string UncaughtException = "An unexpected error occurred";
7+
8+
public static class Queues
9+
{
10+
public const string ClaimsQueue = "claims-queue";
11+
}
12+
}
13+
}
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
namespace ClaimsSolution.Common.Contracts
2-
{
3-
public class ClaimSubmittedMessage
1+
 namespace ClaimsSolution.Common.Contracts
42
{
5-
public Guid ClaimId { get; init; }
6-
public Guid CorrelationId { get; init; }
7-
public Guid UserId { get; init; }
8-
public string PhoneNumber { get; init; } = string.Empty;
9-
public string Ssn { get; init; } = string.Empty;
10-
public DateTime IncidentDate { get; init; }
11-
public string VehicleMake { get; init; } = string.Empty;
12-
public string VehicleModel { get; init; } = string.Empty;
13-
public DateTime SubmittedAtUtc { get; init; }
3+
public class ClaimSubmittedMessage
4+
{
5+
public Guid ClaimId { get; init; }
6+
public Guid CorrelationId { get; init; }
7+
public Guid UserId { get; init; }
8+
public string PhoneNumber { get; init; } = string.Empty;
9+
public string Ssn { get; init; } = string.Empty;
10+
public DateTimeOffset IncidentDate { get; init; }
11+
public string VehicleMake { get; init; } = string.Empty;
12+
public string VehicleModel { get; init; } = string.Empty;
13+
public DateTimeOffset SubmittedAtUtc { get; init; }
14+
}
1415
}
15-
}

0 commit comments

Comments
 (0)