Skip to content

Commit 2d0e0df

Browse files
Part 7
Part 7
1 parent d5de1cf commit 2d0e0df

12 files changed

+1349
-68
lines changed

UserManagement.API/ClientApp/src/app/user-management-api.ts

Lines changed: 601 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
namespace UserManagement.API.Common
2+
{
3+
using Microsoft.AspNetCore.Builder;
4+
using Microsoft.AspNetCore.Http;
5+
using Newtonsoft.Json;
6+
using System;
7+
using System.Net;
8+
using System.Threading.Tasks;
9+
using UserManagement.Application.Common.Exceptions;
10+
11+
public class CustomExceptionHandlerMiddleware
12+
{
13+
private readonly RequestDelegate _next;
14+
15+
public CustomExceptionHandlerMiddleware(RequestDelegate next)
16+
{
17+
_next = next;
18+
}
19+
20+
public async Task Invoke(HttpContext context)
21+
{
22+
try
23+
{
24+
await _next(context);
25+
}
26+
catch (Exception ex)
27+
{
28+
await HandleExceptionAsync(context, ex);
29+
}
30+
}
31+
32+
private Task HandleExceptionAsync(HttpContext context, Exception exception)
33+
{
34+
var code = HttpStatusCode.InternalServerError;
35+
36+
var result = string.Empty;
37+
38+
switch (exception)
39+
{
40+
case ValidationException validationException:
41+
code = HttpStatusCode.BadRequest;
42+
result = JsonConvert.SerializeObject(validationException.Errors);
43+
break;
44+
case NotFoundException _:
45+
code = HttpStatusCode.NotFound;
46+
break;
47+
}
48+
49+
context.Response.ContentType = "application/json";
50+
context.Response.StatusCode = (int)code;
51+
52+
if (string.IsNullOrEmpty(result))
53+
{
54+
result = JsonConvert.SerializeObject(new { error = exception.Message });
55+
}
56+
57+
return context.Response.WriteAsync(result);
58+
}
59+
}
60+
61+
public static class CustomExceptionHandlerMiddlewareExtensions
62+
{
63+
public static IApplicationBuilder UseCustomExceptionHandler(this IApplicationBuilder builder)
64+
{
65+
return builder.UseMiddleware<CustomExceptionHandlerMiddleware>();
66+
}
67+
}
68+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace UserManagement.API.Controllers
2+
{
3+
using MediatR;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
[Route("api/[controller]")]
8+
[ApiController]
9+
public class BaseController : ControllerBase
10+
{
11+
private IMediator mediator;
12+
13+
/// <summary>
14+
/// Gets the Mediator.
15+
/// </summary>
16+
protected IMediator Mediator => this.mediator ??= this.HttpContext.RequestServices.GetService<IMediator>();
17+
}
18+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace UserManagement.API.Controllers
2+
{
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Mvc;
5+
using UserManagement.Application.User.Commands;
6+
using UserManagement.Application.User.Queries;
7+
using UserManagement.Application.User.VM;
8+
9+
[Route("api/[controller]")]
10+
[ApiController]
11+
public class UserController : BaseController
12+
{
13+
[HttpGet("[action]")]
14+
public async Task<ActionResult<UserVM>> Get(int userID)
15+
{
16+
return await this.Mediator.Send(new GetSingleUserQuery { UserID = userID });
17+
}
18+
19+
[HttpGet("[action]")]
20+
public async Task<ActionResult<UserVM>> GetAll()
21+
{
22+
return await this.Mediator.Send(new GetAllUserQuery());
23+
}
24+
25+
[HttpPost("[action]")]
26+
public async Task<ActionResult<int>> Post(AddUserCommand command)
27+
{
28+
return await this.Mediator.Send(command);
29+
}
30+
31+
[HttpPut("[action]")]
32+
public async Task<ActionResult<bool>> Put(UpdateUserCommand command)
33+
{
34+
return await this.Mediator.Send(command);
35+
}
36+
37+
[HttpDelete("[action]")]
38+
public async Task<ActionResult<bool>> Delete(int userID)
39+
{
40+
return await this.Mediator.Send(new DeleteUserCommand { UserID = userID });
41+
}
42+
}
43+
}

UserManagement.API/Controllers/WeatherForecastController.cs

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
namespace UserManagement.API.Filters
2+
{
3+
using UserManagement.Application.Common.Exceptions;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.AspNetCore.Mvc.Filters;
7+
using System;
8+
using System.Collections.Generic;
9+
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
10+
{
11+
12+
private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;
13+
14+
public ApiExceptionFilterAttribute()
15+
{
16+
// Register known exception types and handlers.
17+
_exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
18+
{
19+
{ typeof(ValidationException), HandleValidationException },
20+
{ typeof(NotFoundException), HandleNotFoundException },
21+
};
22+
}
23+
24+
public override void OnException(ExceptionContext context)
25+
{
26+
HandleException(context);
27+
28+
base.OnException(context);
29+
}
30+
31+
private void HandleException(ExceptionContext context)
32+
{
33+
Type type = context.Exception.GetType();
34+
if (_exceptionHandlers.ContainsKey(type))
35+
{
36+
_exceptionHandlers[type].Invoke(context);
37+
return;
38+
}
39+
40+
if (!context.ModelState.IsValid)
41+
{
42+
HandleInvalidModelStateException(context);
43+
return;
44+
}
45+
46+
HandleUnknownException(context);
47+
}
48+
49+
private void HandleUnknownException(ExceptionContext context)
50+
{
51+
var details = new ProblemDetails
52+
{
53+
Status = StatusCodes.Status500InternalServerError,
54+
Title = "An error occurred while processing your request.",
55+
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1"
56+
};
57+
58+
context.Result = new ObjectResult(details)
59+
{
60+
StatusCode = StatusCodes.Status500InternalServerError
61+
};
62+
63+
context.ExceptionHandled = true;
64+
}
65+
66+
private void HandleValidationException(ExceptionContext context)
67+
{
68+
var exception = context.Exception as ValidationException;
69+
70+
var details = new ValidationProblemDetails(exception.Errors)
71+
{
72+
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
73+
};
74+
75+
context.Result = new BadRequestObjectResult(details);
76+
77+
context.ExceptionHandled = true;
78+
}
79+
80+
private void HandleInvalidModelStateException(ExceptionContext context)
81+
{
82+
var details = new ValidationProblemDetails(context.ModelState)
83+
{
84+
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
85+
};
86+
87+
context.Result = new BadRequestObjectResult(details);
88+
89+
context.ExceptionHandled = true;
90+
}
91+
92+
private void HandleNotFoundException(ExceptionContext context)
93+
{
94+
var exception = context.Exception as NotFoundException;
95+
96+
var details = new ProblemDetails()
97+
{
98+
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
99+
Title = "The specified resource was not found.",
100+
Detail = exception.Message
101+
};
102+
103+
context.Result = new NotFoundObjectResult(details);
104+
105+
context.ExceptionHandled = true;
106+
}
107+
}
108+
}

UserManagement.API/Startup.cs

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
using Microsoft.AspNetCore.Builder;
2-
using Microsoft.AspNetCore.Hosting;
3-
using Microsoft.AspNetCore.HttpsPolicy;
4-
using Microsoft.AspNetCore.SpaServices.AngularCli;
5-
using Microsoft.Extensions.Configuration;
6-
using Microsoft.Extensions.DependencyInjection;
7-
using Microsoft.Extensions.Hosting;
8-
91
namespace UserManagement.API
102
{
3+
using FluentValidation.AspNetCore;
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.AspNetCore.SpaServices.AngularCli;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Hosting;
11+
using UserManagement.API.Filters;
12+
using UserManagement.Application;
13+
using UserManagement.Persistence;
1114
public class Startup
1215
{
1316
public Startup(IConfiguration configuration)
@@ -20,12 +23,35 @@ public Startup(IConfiguration configuration)
2023
// This method gets called by the runtime. Use this method to add services to the container.
2124
public void ConfigureServices(IServiceCollection services)
2225
{
23-
services.AddControllersWithViews();
26+
services.AddApplication();
27+
services.AddPersistance();
28+
29+
services.AddHttpContextAccessor();
30+
31+
services.AddControllersWithViews(options =>
32+
options.Filters.Add(new ApiExceptionFilterAttribute()))
33+
.AddFluentValidation();
34+
35+
services.AddRazorPages();
36+
37+
// Customise default API behaviour
38+
services.Configure<ApiBehaviorOptions>(options =>
39+
{
40+
options.SuppressModelStateInvalidFilter = true;
41+
});
42+
2443
// In production, the Angular files will be served from this directory
2544
services.AddSpaStaticFiles(configuration =>
2645
{
2746
configuration.RootPath = "ClientApp/dist";
2847
});
48+
49+
services.AddOpenApiDocument(configure =>
50+
{
51+
configure.Title = "UserManagement API";
52+
});
53+
54+
services.AddLogging();
2955
}
3056

3157
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -49,13 +75,20 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
4975
app.UseSpaStaticFiles();
5076
}
5177

78+
app.UseSwaggerUi3(settings =>
79+
{
80+
settings.Path = "/api";
81+
settings.DocumentPath = "/api/specification.json";
82+
});
83+
5284
app.UseRouting();
5385

5486
app.UseEndpoints(endpoints =>
5587
{
5688
endpoints.MapControllerRoute(
5789
name: "default",
5890
pattern: "{controller}/{action=Index}/{id?}");
91+
endpoints.MapRazorPages();
5992
});
6093

6194
app.UseSpa(spa =>
@@ -72,4 +105,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
72105
});
73106
}
74107
}
75-
}
108+
}

0 commit comments

Comments
 (0)