Add Exception middleware

This commit is contained in:
2022-09-01 23:02:49 +01:00
parent 2ac5937096
commit dcf3b1020a
14 changed files with 241 additions and 90 deletions

View File

@@ -1,10 +1,5 @@
using System; using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Diary.Component.Entries.Repository namespace Diary.Component.Entries.Repository
{ {
@@ -15,15 +10,14 @@ namespace Diary.Component.Entries.Repository
public int EnrtyID { get; set; } public int EnrtyID { get; set; }
[Required] [Required]
[Column(TypeName="date")] [Column(TypeName = "date")]
public DateTime Date { get; set; } public DateTime Date { get; set; }
[Required] [Required]
public DateTime ValidFrom { get; set; } public DateTime ValidFrom { get; set; } = DateTime.Now;
public DateTime? ValidTo { get; set; } public DateTime? ValidTo { get; set; }
public string Note { get; set; } public string Note { get; set; }
} }
} }

View File

@@ -8,7 +8,8 @@ namespace Diary.Component.Entries.Service
public EntryMappings() public EntryMappings()
{ {
CreateMap<CreateEntryResource, Entry>(); CreateMap<CreateEntryResource, Entry>();
CreateMap<Entry, EntryResource>(); CreateMap<Entry, EntryResource>()
.ForMember(dest => dest.Date, opt => opt.MapFrom(src => src.Date.Date));
} }
} }
} }

View File

@@ -3,7 +3,7 @@
public class EntryResource public class EntryResource
{ {
public int EnrtyID { get; set; } public int EnrtyID { get; set; }
public DateTime Date { get; set; } public DateOnly Date { get; set; }
public DateTime ValidFrom { get; set; } public DateTime ValidFrom { get; set; }
public DateTime? ValidTo { get; set; } public DateTime? ValidTo { get; set; }

View File

@@ -1,5 +1,6 @@
using AutoMapper; using AutoMapper;
using Diary.Component.Entries.Repository; using Diary.Component.Entries.Repository;
using Diary.Data;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -18,11 +19,13 @@ namespace Diary.Component.Entries.Service
{ {
private readonly IEntryRepository _entryRepository; private readonly IEntryRepository _entryRepository;
private readonly IMapper _mapper; private readonly IMapper _mapper;
private readonly IUnitOfWork _unitOfWork;
public EntryService(IEntryRepository entryRepository, IMapper mapper) public EntryService(IEntryRepository entryRepository, IMapper mapper, IUnitOfWork unitOfWork)
{ {
_entryRepository = entryRepository ?? throw new ArgumentNullException(nameof(entryRepository)); _entryRepository = entryRepository ?? throw new ArgumentNullException(nameof(entryRepository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
} }
@@ -32,6 +35,8 @@ namespace Diary.Component.Entries.Service
await _entryRepository.CreateAsync(entry); await _entryRepository.CreateAsync(entry);
await _unitOfWork.CompleteAsync();
return _mapper.Map<EntryResource>(entry); return _mapper.Map<EntryResource>(entry);
} }

View File

@@ -3,17 +3,19 @@ using Microsoft.EntityFrameworkCore;
namespace Diary.Data namespace Diary.Data
{ {
public interface IDiaryDBContext public interface IDiaryDBContext
{ {
DbSet<Entry> Entries { get; set; } DbSet<Entry> Entries { get; set; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
} }
public class DiaryDBContext : DbContext, IDiaryDBContext public class DiaryDBContext : DbContext, IDiaryDBContext
{ {
public DbSet<Entry> Entries { get; set; } public DbSet<Entry> Entries { get; set; }
public DiaryDBContext(DbContextOptions<DiaryDBContext> options) : base(options){ public DiaryDBContext(DbContextOptions<DiaryDBContext> options) : base(options)
{
} }
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
@@ -22,5 +24,10 @@ namespace Diary.Data
builder.ApplyConfiguration(new EntryConfiguration()); builder.ApplyConfiguration(new EntryConfiguration());
} }
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
} }
} }

View File

@@ -1,17 +1,23 @@
using System; namespace Diary.Data
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Diary.Data
{ {
public class UnitOfWork
{
public UnitOfWork()
{
public interface IUnitOfWork
{
Task CompleteAsync();
}
public class UnitOfWork:IUnitOfWork
{
private readonly IDiaryDBContext _context;
public UnitOfWork(IDiaryDBContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
} }
public async Task CompleteAsync()
{
await _context.SaveChangesAsync();
}
} }
} }

View File

@@ -1,20 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>true</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper" Version="11.0.1" /> <PackageReference Include="AutoMapper" Version="11.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

View File

@@ -1,5 +1,6 @@
using Diary.Component.Entries.Repository; using Diary.Component.Entries.Repository;
using Diary.Component.Entries.Service; using Diary.Component.Entries.Service;
using Diary.Data;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -12,7 +13,7 @@ namespace Diary.Installers
{ {
public static IServiceCollection AddDependencies(this IServiceCollection services) public static IServiceCollection AddDependencies(this IServiceCollection services)
{ {
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IEntryService, EntryService>(); services.AddScoped<IEntryService, EntryService>();
services.AddScoped<IEntryRepository, EntryRepository>(); services.AddScoped<IEntryRepository, EntryRepository>();

View File

@@ -0,0 +1,12 @@
using Diary.Shared;
namespace Diary.Installers
{
public static class InstallExceptionsMiddleware
{
public static void AddExceptionsMiddleware(this IServiceCollection services)
{
services.AddScoped<ExceptionHandlingMiddleware>();
}
}
}

View File

@@ -1,20 +1,26 @@
using Diary.Data; using Diary.Data;
using Diary.Installers; using Diary.Installers;
using Diary.Shared;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using System.Reflection; using System.Reflection;
/* /*
* Settings - Done by sefaulr * Settings - Done by sefaulr
* Logging - Done * Logging - Done
* Database - Donw * Database - Donw
* Dependencies * Dependencies
* automapper - Done * Automapper - Done
* Cors * Cors
* Views/Filters/Validation * Views/Filters/Validation
* Swagger - Done * Swagger - Done
* Auth * Auth
* * Exception Middleware - Done
* Validation Filter
* */ * */
//+Setup Logger //+Setup Logger
@@ -28,62 +34,65 @@ Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.CreateLogger(); .CreateLogger();
Log.Information("Starting up");
var builder = WebApplication.CreateBuilder(args);
//---Y
//Add Serilog
builder.Host.UseSerilog(Log.Logger);
builder.Services.AddSingleton<IHostedService>(new LoggingStartService(Log.Logger, Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().ManifestModule.ScopeName)));
//---Y
//Database
builder.Services.AddDatabase(builder.Configuration);
builder.Services.AddScoped<IDiaryDBContext, DiaryDBContext>();
//---y
//Dependancies
builder.Services.AddDependencies();
//---Y
//Automapper
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
builder.Services.AddControllers();
builder.Services.AddExceptionsMiddleware();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//---R
//+Build
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(opt =>
{
opt.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
opt.RoutePrefix = String.Empty;
}
);
}
app.UseSerilogRequestLogging();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseMiddleware<ExceptionHandlingMiddleware>();
try try
{ {
Log.Information("Starting up");
var builder = WebApplication.CreateBuilder(args);
//---Y
//Add Serilog
builder.Host.UseSerilog(Log.Logger);
builder.Services.AddSingleton<IHostedService>(new LoggingStartService(Log.Logger, Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().ManifestModule.ScopeName)));
//---Y
//Database
builder.Services.AddDatabase(builder.Configuration);
builder.Services.AddScoped<IDiaryDBContext, DiaryDBContext>();
//---y
//Dependancies
builder.Services.AddDependencies();
//---Y
//Automapper
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//---R
//+Build
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(opt =>
{
opt.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
opt.RoutePrefix = String.Empty;
}
);
}
app.UseSerilogRequestLogging();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run(); app.Run();
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -6,7 +6,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "swagger", "launchUrl": "",
"applicationUrl": "https://localhost:5001", "applicationUrl": "https://localhost:5001",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"

22
Shared/AppException.cs Normal file
View File

@@ -0,0 +1,22 @@
using System.Net;
namespace Diary.Shared
{
public class AppException : Exception
{
public string Title { get; }
public HttpStatusCode StatusCode { get; }
public AppException(HttpStatusCode statusCode, string title, string message) : base(message)
{
StatusCode = statusCode;
Title = title;
}
public AppException(HttpStatusCode statusCode, string title, string message, Exception innerException) : base(message, innerException)
{
StatusCode = statusCode;
Title = title;
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Text.Json;
namespace Diary.Shared
{
public class ExceptionDetails
{
public string? Message { get; set; }
public string? Title { get; set; }
public override string ToString()
{
var jsonSerializerSettings = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
return JsonSerializer.Serialize(this, jsonSerializerSettings);
}
}
public class ValidationExceptionDetails : ExceptionDetails
{
public List<ValidationProblemDescriptor> ModelState { get; set; } = new();
}
public class ValidationProblemDescriptor
{
public string? Property { get; set; }
public string[]? Errors { get; set; }
}
}

View File

@@ -0,0 +1,60 @@

namespace Diary.Shared
{
public partial class ExceptionHandlingMiddleware : IMiddleware
{
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(ILogger<ExceptionHandlingMiddleware> logger)
{
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (Exception e)
{
_logger.LogError(e, "Error received from ExceptionHandlingMiddleware");
await HandleExceptionAsync(context, e);
}
}
private static async Task HandleExceptionAsync(HttpContext httpContext, Exception exception)
{
ExceptionDetails exceptionDetails;
httpContext.Response.ContentType = "application/json";
if (exception.InnerException is AppException)
{
exception = exception.InnerException;
}
if (exception is AppException applicationException)
{
httpContext.Response.StatusCode = (int)applicationException.StatusCode;
exceptionDetails = new()
{
Message = applicationException.Message,
Title = applicationException.Title
};
}
else
{
httpContext.Response.StatusCode = 500;
exceptionDetails = new()
{
Message = exception.Message,
Title = "API Error"
};
}
await httpContext.Response.WriteAsync(exceptionDetails.ToString());
}
}
}