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

View File

@@ -8,7 +8,8 @@ namespace Diary.Component.Entries.Service
public EntryMappings()
{
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 int EnrtyID { get; set; }
public DateTime Date { get; set; }
public DateOnly Date { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime? ValidTo { get; set; }

View File

@@ -1,5 +1,6 @@
using AutoMapper;
using Diary.Component.Entries.Repository;
using Diary.Data;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -18,11 +19,13 @@ namespace Diary.Component.Entries.Service
{
private readonly IEntryRepository _entryRepository;
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));
_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 _unitOfWork.CompleteAsync();
return _mapper.Map<EntryResource>(entry);
}

View File

@@ -3,17 +3,19 @@ using Microsoft.EntityFrameworkCore;
namespace Diary.Data
{
public interface IDiaryDBContext
{
DbSet<Entry> Entries { get; set; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
public class DiaryDBContext : DbContext, IDiaryDBContext
{
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)
@@ -22,5 +24,10 @@ namespace Diary.Data
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;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Diary.Data
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>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ImplicitUsings>true</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="11.0.1" />
<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.SqlServer" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

View File

@@ -1,5 +1,6 @@
using Diary.Component.Entries.Repository;
using Diary.Component.Entries.Service;
using Diary.Data;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -12,7 +13,7 @@ namespace Diary.Installers
{
public static IServiceCollection AddDependencies(this IServiceCollection services)
{
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IEntryService, EntryService>();
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.Installers;
using Diary.Shared;
using Serilog;
using Serilog.Events;
using System.Reflection;
/*
* Settings - Done by sefaulr
* Logging - Done
* Database - Donw
* Dependencies
* automapper - Done
* Automapper - Done
* Cors
* Views/Filters/Validation
* Swagger - Done
* Auth
*
* Exception Middleware - Done
* Validation Filter
* */
//+Setup Logger
@@ -28,45 +34,44 @@ Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.CreateLogger();
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();
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())
{
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 =>
{
@@ -74,16 +79,20 @@ try
opt.RoutePrefix = String.Empty;
}
);
}
}
app.UseSerilogRequestLogging();
app.UseSerilogRequestLogging();
app.UseHttpsRedirection();
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseAuthorization();
app.MapControllers();
app.MapControllers();
app.UseMiddleware<ExceptionHandlingMiddleware>();
try
{
app.Run();
}
catch (Exception ex)

View File

@@ -6,7 +6,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"launchUrl": "",
"applicationUrl": "https://localhost:5001",
"environmentVariables": {
"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());
}
}
}