diff --git a/SessionCompanion/SessionCompanion.Extensions/EitherType/Either.cs b/SessionCompanion/SessionCompanion.Extensions/EitherType/Either.cs new file mode 100644 index 0000000..c845eef --- /dev/null +++ b/SessionCompanion/SessionCompanion.Extensions/EitherType/Either.cs @@ -0,0 +1,119 @@ +using System; + +namespace SessionCompanion.Extensions.EitherType +{ + public class Either + { + /// + /// Kontruktor dla lewej zmiennej + /// + /// Wartość lewej zmiennej + public Either(TL left) + { + this.Left = left; + this.IsLeft = true; + } + + /// + /// Konstruktor dla prawej zmiennej + /// + /// Wartość prawej zmiennej + public Either(TR right) + { + this.Right = right; + this.IsLeft = false; + } + + /// + /// Lewa zmienna + /// + public TL Left { get; } + + /// + /// Prawa zmienna + /// + public TR Right { get; } + + /// + /// Zmienna informujaca czy wykorzystana zostÄ…Å‚a zmienna lewa czy prawa + /// + public bool IsLeft { get; } + + /// + /// Tworzy obiekt typu Either wykorzystujÄ…c zmiennÄ… lewÄ… + /// + /// Wartość lewej zmiennej + public static implicit operator Either(TL left) => new Either(left); + + /// + /// Tworzy obiekt typu Either wykorzystujÄ…c zmiennÄ… prawÄ… + /// + /// Wartosć prawej zmiennej + public static implicit operator Either(TR right) => new Either(right); + + /// + /// Bazowa metoda dopasowania wzorów. + /// JeÅ›li podana jest wartość lewa, to Match zwróci wynik lewej funkcji, w przeciwnym razie wynik prawej funkcji. + /// + /// Typ zwracanej wartoÅ›ci + /// Lewa funkcja + /// Prawa funkcja + /// Wynik prawej/lewej funkcji + public T Match(Func leftFunc, Func rightFunc) + { + if (leftFunc == null) + { + throw new ArgumentNullException(nameof(leftFunc)); + } + + if (rightFunc == null) + { + throw new ArgumentNullException(nameof(rightFunc)); + } + + return this.IsLeft ? leftFunc(this.Left) : rightFunc(this.Right); + } + + /// + /// Bazowa metoda dopasowania wzorów. + /// JeÅ›li podana jest wartość lewa, to Match wykona lewÄ… funkcjÄ™, w przeciwnym razie wykona funkcjÄ™ prawÄ…. + /// + /// Lewa funkcja + /// Prawa funkcja + public void Match(Action leftFunc, Action rightFunc) + { + if (leftFunc == null) + { + throw new ArgumentNullException(nameof(leftFunc)); + } + + if (rightFunc == null) + { + throw new ArgumentNullException(nameof(rightFunc)); + } + + if (this.IsLeft) + { + leftFunc(this.Left); + } + else + { + rightFunc(this.Right); + } + } + + /// + /// Funkcja ustala czy uzyta zostaÅ‚a lewa zmienna, jesli tak to zwraca jej wartość inaczej zwraca defaultowy obiekt + /// o typie takim jak lewa zmienna + /// + /// Wartosć lewej zmiennej lub defaultowÄ… wartość o typie lewej zmiennej + public TL LeftOrDefault() => this.Match(l => l, r => default(TL)); + + /// + /// Funkcja ustala czy uzyta zostaÅ‚a prawa zmienna, jesli tak to zwraca jej wartość inaczej zwraca defaultowy obiekt + /// o typie takim jak lewa zmienna + /// + /// Wartosć lewej zmiennej lub defaultowÄ… wartość o typie prawej zmiennej + public TR RightOrDefault() => this.Match(l => default(TR), r => r); + } +} diff --git a/SessionCompanion/SessionCompanion.Extensions/EitherType/EitherDocumentation.md b/SessionCompanion/SessionCompanion.Extensions/EitherType/EitherDocumentation.md new file mode 100644 index 0000000..dfcca7c --- /dev/null +++ b/SessionCompanion/SessionCompanion.Extensions/EitherType/EitherDocumentation.md @@ -0,0 +1,184 @@ +# Either + +### Jak u¿ywaæ + +Either umo¿liwa nam zwrócenie z metody b¹dŸ nawet kontrolera, dwóch mo¿liwych wyników. +Dla u³atwienia zrozumienai poni¿ej podajê przyk³ady stosowania. + +Jako typ zmiennej zwracanej z api: +W tym przypadku wskazujemy, ¿e nasze api zwróci albo CharacterViewModel albo ErrorResponse +```cs +public async Task> Get(int id) +``` +Aby zwróciæ ten typ mamy dwie mo¿liwoœci. +Mo¿emy wywo³aæ metodê z serisu, która ma nam zwróciæ CharacterViewModel. +Nastêpnie sprawdziæ czy otrzymana wartoœæ CharacerViewModel zawiera jakiekolwiek dane i odpowiednio, jeœli tak to zwracamy otrzymany viewmodel a jeœli nie tworzymy ErrorResponse i zwracamy wynik: +```cs +public async Task> Get(int id) + { + Either result = await _service.Get(id); + + if (result.Left != null) + { + return result; + } + else + { + result = new ErrorResponse { StatusCode = 400, Message = "Coœ posz³o nie tak" }; + + return result; + } + } +``` +W przypadku jesli serwis nie znajdzie wartoœci otrzymamy: +```json +{ +"left":null, +"right":{ + "statusCode":400, + "message":"Coœ posz³o nie tak"}, +"isLeft":false} +``` + +Drugim sposobem jest zmiana dzia³ania serwisu: + +```cs +public async Task> GetCharacter(int id) + { + var repoResult = await this.Repository.Get(id); + if (repoResult != null) + { + return Mapper.Map(repoResult); + } + else + { + return new ErrorResponse() + { + StatusCode = 404, + Message = "Coœ posz³o nie tak" + }; + } + } +``` + +Wtedy kontroler wygl¹da tak : +```sh +public async Task> Get(int id) + { + return await this._service.GetCharacter(id); + } +``` + +### Przypisanie nowej wartoœci +Jeœli z jakiegoœpowodu bêdziemy chcieli zmieniæ wartoœæ obiektu Either, musimy dokonaæ tego w taki oto sposób: +```cs +UserViewModel user = new UserViewModel() { Id = 1, Nickname = "Testowy", Password = "Secret" }; +Either test = "asd"; + +// zmieniamy wartoœæ Either +test = user; + +// Warto zauwa¿yæ, ¿e mo¿emy zmieniæ wartoœæ obiektu znajdujacego siê wewn¹trz +// którejœ ze zmiennej o ile jest on publiczny +test.Left.Nickname = "test"; + +// Jednak jeœli dokonamy tego po zmianie wartoœci obietu na inny pomimo tego, ¿e +// dla kodu jest to ok, to nie zadzia³a to poprawnie, poniewa¿ wartoœæ starego obiektu +// jest zmieniana na null(dla boola na false) +test = "asd"; +test.Left.Nickname = "b³¹d"; + +``` + +### LeftOrDefault() oraz RightOrDefault() + +Zwracaj¹ one wartoœæ wybranej zmiennej b¹dŸ jej wartoœæ defaultow¹. +Dla wiekszoœci obiektów jest to null, wyjatkiem jest tutaj bool, który zwróci nam false. +```cs +var test = (Either)true; + +var a = test.LeftOrDefault(); // zwróci nam true + var b = test.RightOrDefault(); // zwróci nam null + +test = "asd"; + +var c = test.LeftOrDefault(); // zwróci nam false +var d = test.RightOrDefault(); // zwróci nam "asd" +``` + +### Match + +Funkcja ta pozwala nam na odpalenie odpowiedniej metody w zale¿noœci od tego jako typ zmiennej jest aktualnie przechowywany w obiekcie Either. Niestety ograniczeniem jest to, ze obie funkcje musza docelowo zwróciæ ten sam typ. +```cs +// ³adujemy userviewmodel do obiektu Either i wywo³ywamy matcha pdaj¹c dwie funkcje +test = user; +// zwwrócny zostanie string "Test1" +test.Match(this.Test1, this.Test2); + +// ³adujemy stringa do obiektu Either i wywo³ywamy matcha pdaj¹c dwie funkcje +test = "asd"; +// zwwrócny zostanie string "Test2" +test.Match(this.Test1, this.Test2); + +public string Test1(UserViewModel viewModel) +{ + return "Test1"; +} + + public string Test2(string text) +{ + return "Test2"; +} +``` + +Warto zauwa¿yæ, ¿e mo¿emy wykorzystaæ tutaj lambde by rozszerzyæ nasze mo¿liwoœci: +```cs +test = "asd"; + +test.Match( + Test1, + second => + { + int z = Test2(second); + // tutaj mo¿emy robiæ ju¿ co nam siê tylko podoba + // pamiêtaæ nale¿y ze finalnie musimy coœ zwróciæ + // chyba, ¿e nasze obie funkcje s¹ typu void + return z.ToString(); +}); + +public string Test1(UserViewModel viewModel) +{ + return "Test1"; +} + + public int Test2(string text) +{ + return 12; +} +``` + +Mo¿emy te¿ podaæ jedna funkcjê typu void a drug¹ typu innego np. string albo int: +```cs +y = ""; +test = "asd"; + +test.Match( + Test1, + second => + { + var x = this.Test2(second); + //jednak nie ma mo¿liwoœci zwrócenia wartoœci + // mo¿emy za to zwróciæ wyniki do zewnêtrznej zmiennej + y = x; +}); + +public void Test1(UserViewModel viewModel) +{ + //...coœ robi +} + + public string Test2(string text) +{ + return "Test2"; +} +``` \ No newline at end of file diff --git a/SessionCompanion/SessionCompanion.Extensions/SessionCompanion.Extensions.csproj b/SessionCompanion/SessionCompanion.Extensions/SessionCompanion.Extensions.csproj new file mode 100644 index 0000000..6015009 --- /dev/null +++ b/SessionCompanion/SessionCompanion.Extensions/SessionCompanion.Extensions.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/SessionCompanion/SessionCompanion.Services/Profiles/UserProfile.cs b/SessionCompanion/SessionCompanion.Services/Profiles/UserProfile.cs index 5b2c1a1..4c7fdc2 100644 --- a/SessionCompanion/SessionCompanion.Services/Profiles/UserProfile.cs +++ b/SessionCompanion/SessionCompanion.Services/Profiles/UserProfile.cs @@ -11,8 +11,7 @@ namespace SessionCompanion.Services.Profiles { public UserProfile() { - CreateMap(); - CreateMap(); + CreateMap().ReverseMap(); } } } diff --git a/SessionCompanion/SessionCompanion.ViewModels/ApiResponses/ErrorResponse.cs b/SessionCompanion/SessionCompanion.ViewModels/ApiResponses/ErrorResponse.cs new file mode 100644 index 0000000..262dd62 --- /dev/null +++ b/SessionCompanion/SessionCompanion.ViewModels/ApiResponses/ErrorResponse.cs @@ -0,0 +1,22 @@ +using System.Text.Json; + +namespace SessionCompanion.ViewModels.ApiResponses +{ + public class ErrorResponse + { + /// + /// Kod bÅ‚Ä™du + /// + public int StatusCode { get; set; } + + /// + /// Wiadomość bÅ‚Ä™du do pokazania + /// + public string Message { get; set; } + + public override string ToString() + { + return JsonSerializer.Serialize(this); + } + } +} diff --git a/SessionCompanion/SessionCompanion.ViewModels/ApiResponses/SuccessResponse.cs b/SessionCompanion/SessionCompanion.ViewModels/ApiResponses/SuccessResponse.cs new file mode 100644 index 0000000..4cefb98 --- /dev/null +++ b/SessionCompanion/SessionCompanion.ViewModels/ApiResponses/SuccessResponse.cs @@ -0,0 +1,28 @@ +namespace SessionCompanion.ViewModels.ApiResponses +{ + /// + /// Wiadomość mówiÄ…ca o poprawnym wykonaniu zadania + /// + public class SuccessResponse + { + /// + /// Konstruktor obiektu + /// + /// Wiadomość dotyczÄ…ca operacji + public SuccessResponse(string message) + { + this.SuccessCode = 200; + this.SuccessMessage = message; + } + + /// + /// Kod odpowiedzi, domyÅ›lnie nadawany jest 200 + /// + public int SuccessCode { get; set; } + + /// + /// Wiadomość dotyczÄ…ca wykoanania operacji + /// + public string SuccessMessage { get; set; } + } +} diff --git a/SessionCompanion/SessionCompanion.sln b/SessionCompanion/SessionCompanion.sln index c634e22..edd8402 100644 --- a/SessionCompanion/SessionCompanion.sln +++ b/SessionCompanion/SessionCompanion.sln @@ -9,7 +9,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SessionCompanion.Database", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SessionCompanion.ViewModels", "SessionCompanion.ViewModels\SessionCompanion.ViewModels.csproj", "{7762AA75-7B60-4F28-B80A-B03E39140F89}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SessionCompanion.Services", "SessionCompanion.Services\SessionCompanion.Services.csproj", "{C0A172ED-0F4C-4E78-8B64-28E2A756F62F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SessionCompanion.Services", "SessionCompanion.Services\SessionCompanion.Services.csproj", "{C0A172ED-0F4C-4E78-8B64-28E2A756F62F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SessionCompanion.Extensions", "SessionCompanion.Extensions\SessionCompanion.Extensions.csproj", "{1EE35EB3-C703-407C-B390-5605A0A46884}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,6 +35,10 @@ Global {C0A172ED-0F4C-4E78-8B64-28E2A756F62F}.Debug|Any CPU.Build.0 = Debug|Any CPU {C0A172ED-0F4C-4E78-8B64-28E2A756F62F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C0A172ED-0F4C-4E78-8B64-28E2A756F62F}.Release|Any CPU.Build.0 = Release|Any CPU + {1EE35EB3-C703-407C-B390-5605A0A46884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EE35EB3-C703-407C-B390-5605A0A46884}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EE35EB3-C703-407C-B390-5605A0A46884}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EE35EB3-C703-407C-B390-5605A0A46884}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SessionCompanion/SessionCompanion/Configurations/ExceptionMiddleware.cs b/SessionCompanion/SessionCompanion/Configurations/ExceptionMiddleware.cs new file mode 100644 index 0000000..b7b37bf --- /dev/null +++ b/SessionCompanion/SessionCompanion/Configurations/ExceptionMiddleware.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using SessionCompanion.ViewModels.ApiResponses; + +namespace SessionCompanion.Configurations +{ + using System.Net; + + using Microsoft.AspNetCore.Http; + + using SessionCompanion.Extensions.ApiErrors; + + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + public ExceptionMiddleware(RequestDelegate next) + { + _next = next; + } + public async Task InvokeAsync(HttpContext httpContext) + { + try + { + await _next(httpContext); + } + catch (Exception ex) + { + await HandleExceptionAsync(httpContext, ex); + } + } + private Task HandleExceptionAsync(HttpContext context, Exception exception) + { + context.Response.ContentType = "application/json"; + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + return context.Response.WriteAsync(new ErrorResponse() + { + StatusCode = context.Response.StatusCode, + Message = exception.Message + }.ToString()); + } + } +} diff --git a/SessionCompanion/SessionCompanion/Configurations/ExceptionMiddlewareExtensions.cs b/SessionCompanion/SessionCompanion/Configurations/ExceptionMiddlewareExtensions.cs new file mode 100644 index 0000000..6c3aee6 --- /dev/null +++ b/SessionCompanion/SessionCompanion/Configurations/ExceptionMiddlewareExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Http; +using System.Net; + +namespace SessionCompanion.Extensions.ApiErrors +{ + using Microsoft.AspNetCore.Diagnostics; + + using SessionCompanion.Configurations; + + public static class ExceptionMiddlewareExtensions + { + public static void ConfigureCustomExceptionMiddleware(this IApplicationBuilder app) + { + app.UseMiddleware(); + } + } +} diff --git a/SessionCompanion/SessionCompanion/Controllers/CharacterController.cs b/SessionCompanion/SessionCompanion/Controllers/CharacterController.cs index afb6af1..49eb93b 100644 --- a/SessionCompanion/SessionCompanion/Controllers/CharacterController.cs +++ b/SessionCompanion/SessionCompanion/Controllers/CharacterController.cs @@ -1,13 +1,10 @@ using System.Threading.Tasks; - using Microsoft.AspNetCore.Mvc; - using SessionCompanion.Services.Interfaces; +using SessionCompanion.ViewModels.CharacterViewModels; namespace SessionCompanion.Controllers { - using SessionCompanion.ViewModels.CharacterViewModels; - [Route("api/character")] [ApiController] public class CharacterController : Controller diff --git a/SessionCompanion/SessionCompanion/SessionCompanion.csproj b/SessionCompanion/SessionCompanion/SessionCompanion.csproj index 576f08d..c455a8f 100644 --- a/SessionCompanion/SessionCompanion/SessionCompanion.csproj +++ b/SessionCompanion/SessionCompanion/SessionCompanion.csproj @@ -42,6 +42,7 @@ + diff --git a/SessionCompanion/SessionCompanion/Startup.cs b/SessionCompanion/SessionCompanion/Startup.cs index 7cb2723..6e96eed 100644 --- a/SessionCompanion/SessionCompanion/Startup.cs +++ b/SessionCompanion/SessionCompanion/Startup.cs @@ -21,6 +21,8 @@ namespace SessionCompanion using Microsoft.OpenApi.Models; + using SessionCompanion.Extensions.ApiErrors; + public class Startup { @@ -40,12 +42,14 @@ namespace SessionCompanion Configuration.GetConnectionString("DefaultConnection"))); services.AddRepositories(); services.AddServices(); - services.AddAutoMapper(typeof(Startup)); + + services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + services.AddSignalR(); services.AddSwaggerGen(s => { - s.SwaggerDoc("v1", new OpenApiInfo { Title = "Dostêpne API", Version = "v1" }); + s.SwaggerDoc("v1", new OpenApiInfo { Title = "Dost�pne API", Version = "v1" }); var basePath = AppContext.BaseDirectory; var xmlPath = Path.Combine(basePath, "SessionCompanion.xml"); s.IncludeXmlComments(xmlPath); @@ -81,6 +85,8 @@ namespace SessionCompanion app.UseHsts(); } + app.ConfigureCustomExceptionMiddleware(); + app.UseHttpsRedirection(); app.UseStaticFiles(); if (!env.IsDevelopment())