diff --git a/NetSwissTools.Web.sln b/NetSwissTools.Web.sln index b236a82..0575020 100644 --- a/NetSwissTools.Web.sln +++ b/NetSwissTools.Web.sln @@ -1,12 +1,40 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.34031.279 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.34031.81 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{E938E22F-DEA2-42A9-A2DC-F81A555BB1FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{8C65E219-42F9-41FC-ADFB-4938C1DB2E51}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSwissTools.Web", "src\NetSwissTools.Web\NetSwissTools.Web.csproj", "{58742414-637E-4AAA-8FC4-A8C7C93FAE49}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{A82929A9-88AB-4C42-A160-4AC238D4528F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clients.API", "samples\Clients.API\Clients.API.csproj", "{117B883A-1C2C-46AE-880B-403B2609EC0B}" +EndProject Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {58742414-637E-4AAA-8FC4-A8C7C93FAE49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58742414-637E-4AAA-8FC4-A8C7C93FAE49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58742414-637E-4AAA-8FC4-A8C7C93FAE49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58742414-637E-4AAA-8FC4-A8C7C93FAE49}.Release|Any CPU.Build.0 = Release|Any CPU + {117B883A-1C2C-46AE-880B-403B2609EC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {117B883A-1C2C-46AE-880B-403B2609EC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {117B883A-1C2C-46AE-880B-403B2609EC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {117B883A-1C2C-46AE-880B-403B2609EC0B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {58742414-637E-4AAA-8FC4-A8C7C93FAE49} = {8C65E219-42F9-41FC-ADFB-4938C1DB2E51} + {117B883A-1C2C-46AE-880B-403B2609EC0B} = {A82929A9-88AB-4C42-A160-4AC238D4528F} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9BB015AE-CDF3-415F-94FF-B957B290C771} EndGlobalSection diff --git a/samples/Clients.API/Client.cs b/samples/Clients.API/Client.cs new file mode 100644 index 0000000..6702f8b --- /dev/null +++ b/samples/Clients.API/Client.cs @@ -0,0 +1,10 @@ +namespace Clients.API +{ + public class Client + { + public Guid Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + public string Email { get; set; } + } +} \ No newline at end of file diff --git a/samples/Clients.API/Clients.API.csproj b/samples/Clients.API/Clients.API.csproj new file mode 100644 index 0000000..34ef7cb --- /dev/null +++ b/samples/Clients.API/Clients.API.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/samples/Clients.API/Controllers/ClientsController.cs b/samples/Clients.API/Controllers/ClientsController.cs new file mode 100644 index 0000000..5445654 --- /dev/null +++ b/samples/Clients.API/Controllers/ClientsController.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Web.Mvc; +using NetSwissTools.Web.Mvc.Results; +using System; +using System.Net; +using System.Runtime.CompilerServices; + +namespace Clients.API.Controllers +{ + [ApiController] + [Route("[controller]")] + [ApiVersion("1")] + public class ClientsController : SwissControllerApi + { + public ClientsController(ILogger logger) + { + } + + [HttpPost] + public async Task Post(Client client) + { + lock (MemoryStore.Clients) + { + client.Id = Guid.NewGuid(); + + MemoryStore.Clients.Add(client); + } + await Task.Delay(1000); + return base.Created(client.Id.ToString(), client); + } + + [HttpPost("Pending")] + public async Task PostCreate(Client client) + { + lock (MemoryStore.ClientsPending) + { + client.Id = Guid.NewGuid(); + + MemoryStore.ClientsPending.Add(client); + } + await Task.Delay(1000); + return Created(client.Id.ToString(), client); + } + + [HttpPost("Simulate/{id}")] + public async Task PostSimulate(Guid id) + { + await Task.Delay(1000); + lock (MemoryStore.ClientsPending) + { + var client = MemoryStore.ClientsPending.FirstOrDefault(x => x.Id == id); + + if (client == null) + return MovedPermanently(id.ToString()); + + MemoryStore.Clients.Add(client); + MemoryStore.ClientsPending.Remove(client); + + return Created(client.Id.ToString(), client); + } + } + + [HttpGet] + public ActionResult Get() + { + lock (MemoryStore.Clients) + { + return RequestOK(MemoryStore.Clients); + } + } + + [HttpGet("{id}")] + public ActionResult GetById(Guid id) + { + lock (MemoryStore.Clients) + { + return RequestOK(MemoryStore.Clients.Find(x => x.Id == id)); + } + } + } +} \ No newline at end of file diff --git a/samples/Clients.API/MemoryStore.cs b/samples/Clients.API/MemoryStore.cs new file mode 100644 index 0000000..74f89cb --- /dev/null +++ b/samples/Clients.API/MemoryStore.cs @@ -0,0 +1,8 @@ +namespace Clients.API +{ + public static class MemoryStore + { + public static List Clients = new(); + public static List ClientsPending = new(); + } +} diff --git a/samples/Clients.API/Program.cs b/samples/Clients.API/Program.cs new file mode 100644 index 0000000..48863a6 --- /dev/null +++ b/samples/Clients.API/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/samples/Clients.API/Properties/launchSettings.json b/samples/Clients.API/Properties/launchSettings.json new file mode 100644 index 0000000..a09a15c --- /dev/null +++ b/samples/Clients.API/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:38011", + "sslPort": 44392 + } + }, + "profiles": { + "Clients.API": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7038;http://localhost:5031", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/Clients.API/appsettings.Development.json b/samples/Clients.API/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/Clients.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/Clients.API/appsettings.json b/samples/Clients.API/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/Clients.API/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/NetSwissTools.Web/Enums/EExceptionErrorCodes.cs b/src/NetSwissTools.Web/Enums/EExceptionErrorCodes.cs new file mode 100644 index 0000000..6b99e1b --- /dev/null +++ b/src/NetSwissTools.Web/Enums/EExceptionErrorCodes.cs @@ -0,0 +1,18 @@ +namespace NetSwissTools.Web.Enums +{ + public enum EExceptionErrorCodes + { + UnhandledException = 900, + InvalidRequest = 997, + UnauthorizedRequest = 998, + InvalidEstablishmentKey = 999, + ValidationError = -10000, + RegisterNotFound = -15000, + RegisterChild = -16000, + InsertSQLError = -20001, + UpdateSQLError = -20002, + DeleteSQLError = -20003, + SaveSQLError = -20004, + SQLCommandError = -20005 + } +} \ No newline at end of file diff --git a/src/NetSwissTools.Web/Mvc/Helpers/ResultStatusHelper.cs b/src/NetSwissTools.Web/Mvc/Helpers/ResultStatusHelper.cs new file mode 100644 index 0000000..64ca504 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Helpers/ResultStatusHelper.cs @@ -0,0 +1,32 @@ +using System.Net; + +namespace NetSwissTools.Web.Mvc.Helpers +{ + public static class ResultStatusHelper + { + public static bool IsClientError(HttpStatusCode code) => + (int)code >= 400 && (int)code <= 499; + + public static bool IsInformational(HttpStatusCode code) => + (int)code >= 100 && (int)code <= 199; + + public static bool IsRedirect(HttpStatusCode code) => + (int)code >= 300 && (int)code <= 399; + + public static bool IsServerError(HttpStatusCode code) => + (int)code >= 500 && (int)code <= 599; + + public static bool IsSuccess(HttpStatusCode code) => + (int)code >= 200 && (int)code <= 299; + + public static bool IsSuccessReponse(HttpStatusCode code) + { + if (IsInformational(code) || IsSuccess(code) || IsRedirect(code)) + return true; + else if (IsClientError(code) || IsServerError(code)) + return false; + + return false; + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Interfaces/IErrorService.cs b/src/NetSwissTools.Web/Mvc/Interfaces/IErrorService.cs new file mode 100644 index 0000000..a5224f9 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Interfaces/IErrorService.cs @@ -0,0 +1,9 @@ +using NetSwissTools.Exceptions; + +namespace NetSwissTools.Web.Mvc.Interfaces +{ + public interface IErrorService + { + List Errors { get; set; } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Interfaces/IErrorsController.cs b/src/NetSwissTools.Web/Mvc/Interfaces/IErrorsController.cs new file mode 100644 index 0000000..b0a2c53 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Interfaces/IErrorsController.cs @@ -0,0 +1,20 @@ +using NetSwissTools.Exceptions; + +namespace NetSwissTools.Web.Mvc.Interfaces +{ + public interface IErrorsController + { + void AddError(string message); + void AddError(string field, string exception); + void AddError(string field, string exception, string value); + void AddError(int code, string field, string exception, string value); + void AddError(ModelException exception); + void AddError(ModelException[] exceptions); + void AddError(Exception exception); + void ClearErrors(); + ModelException[] GetErrors(); + bool HasAnyErrors(); + bool IsOperationValid(); + bool ValidateModelState(TEntity pModel) where TEntity : class; + } +} diff --git a/src/NetSwissTools.Web/Mvc/Interfaces/IResultStatusController.cs b/src/NetSwissTools.Web/Mvc/Interfaces/IResultStatusController.cs new file mode 100644 index 0000000..bc78ae8 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Interfaces/IResultStatusController.cs @@ -0,0 +1,15 @@ +using System.Net; + +namespace NetSwissTools.Web.Mvc.Interfaces +{ + public interface IResultStatusController + { + bool IsInformational(HttpStatusCode code); + bool IsSuccess(HttpStatusCode code); + bool IsRedirect(HttpStatusCode code); + bool IsClientError(HttpStatusCode code); + bool IsServerError(HttpStatusCode code); + bool IsSuccessReponse(HttpStatusCode code); + + } +} diff --git a/src/NetSwissTools.Web/Mvc/Interfaces/IUrlInfoController.cs b/src/NetSwissTools.Web/Mvc/Interfaces/IUrlInfoController.cs new file mode 100644 index 0000000..4c9ba25 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Interfaces/IUrlInfoController.cs @@ -0,0 +1,13 @@ +namespace NetSwissTools.Web.Mvc.Interfaces +{ + public interface IUrlInfoController + { + IQueryCollection QueryString { get; } + int GetPageNumber(); + int GetPageSize(); + string GetSort(); + string GetFilter(); + string GetQueryColumn(); + DateTime? GetDateTime(string pDate); + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissBadRequestResult.cs b/src/NetSwissTools.Web/Mvc/Results/SwissBadRequestResult.cs new file mode 100644 index 0000000..7bd3bf8 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissBadRequestResult.cs @@ -0,0 +1,98 @@ +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Exceptions; +using NetSwissTools.Web.Mvc.Helpers; +using System.Diagnostics; +using System.Net; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissBadRequestResult : BadRequestResult + { + private readonly ModelException[] ErrorList; + private readonly int ResponseCode = StatusCodes.Status400BadRequest; + private readonly object Data; + + public SwissBadRequestResult(ModelException[] errors) + { + ErrorList = errors; + Data = null; + } + + public SwissBadRequestResult(HttpStatusCode statusCode, ModelException[] errors) + { + ResponseCode = (int)statusCode; + ErrorList = errors; + Data = null; + } + + public SwissBadRequestResult(ModelException errors) + { + ErrorList = new[] { errors }; + Data = null; + } + + public SwissBadRequestResult(HttpStatusCode statusCode, ModelException errors) + { + ResponseCode = (int)statusCode; + ErrorList = new[] { errors }; + Data = null; + } + + public SwissBadRequestResult(object data, ModelException[] errors) + { + ErrorList = errors; + Data = data; + } + + public SwissBadRequestResult(HttpStatusCode statusCode, object data, ModelException[] errors) + { + ResponseCode = (int)statusCode; + ErrorList = errors; + Data = data; + } + + public SwissBadRequestResult(object data, ModelException errors) + { + ErrorList = new[] { errors }; + Data = data; + } + + public SwissBadRequestResult(HttpStatusCode statusCode, object data, ModelException errors) + { + ResponseCode = (int)statusCode; + ErrorList = new[] { errors }; + Data = data; + } + + public override void ExecuteResult(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + Data, + ErrorList != null ? ErrorList.ToList() : new List()) + ) + { + StatusCode = ResponseCode + }; + + objResult.ExecuteResult(context); + } + + public async override Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + Data, + ErrorList != null ? ErrorList.ToList() : new List()) + ) + { + StatusCode = ResponseCode + }; + await objResult.ExecuteResultAsync(context).ConfigureAwait(false); + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissCreatedResult.cs b/src/NetSwissTools.Web/Mvc/Results/SwissCreatedResult.cs new file mode 100644 index 0000000..d2d9f66 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissCreatedResult.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Exceptions; +using NetSwissTools.Utils; +using System.Net; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissCreatedResult : ObjectResult + { + public virtual T Entity { get; } + public virtual string Id { get; } + public virtual string ResourceUrl { get; } + + public SwissCreatedResult(string id, T entity) + : base(entity) + { + Entity = entity; + Id = id; + ResourceUrl = ""; + } + + public SwissCreatedResult(string resourceUrl, string id, T entity) + : base(entity) + { + Entity = entity; + Id = id; + ResourceUrl = resourceUrl; + } + + public async override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + HttpRequest request = context.HttpContext.Request; + HttpResponse response = context.HttpContext.Response; + IActionResult result = SwissResultHelpers.GenerateActionResult(Entity); + Uri location = SwissResultHelpers.GenerateLocationHeader(request, ResourceUrl, Id); + + response.Headers["Location"] = location.AbsoluteUri; + await result.ExecuteResultAsync(context).ConfigureAwait(false); + SwissResultHelpers.AddEntityId(response, Id); + } + + + + + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissMovedPermanently.cs b/src/NetSwissTools.Web/Mvc/Results/SwissMovedPermanently.cs new file mode 100644 index 0000000..e33530c --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissMovedPermanently.cs @@ -0,0 +1,86 @@ +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Exceptions; +using NetSwissTools.Web.Enums; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissMovedPermanently : ObjectResult + { + private readonly int ResponseCode = StatusCodes.Status301MovedPermanently; + private readonly ModelException[] ErrorList; + public virtual string Id { get; } + public virtual string ResourceUrl { get; } + + public SwissMovedPermanently(string id) : + base (null) + { + Id = id; + ResourceUrl = ""; + ErrorList = new List() + { + new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.RegisterNotFound, + Field = "", + Value = "", + Messages = new [] { "Moved Permanently" } + } + }.ToArray(); + } + + public SwissMovedPermanently(string id, ModelException errors) : + base(null) + { + Id = id; + ResourceUrl = ""; + ErrorList = new[] { errors }; + } + + public SwissMovedPermanently(string resourceUrl, string id) : + base(null) + { + Id = id; + ResourceUrl = resourceUrl; + ErrorList = new List() + { + new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.RegisterNotFound, + Field = "", + Value = "", + Messages = new [] { "Moved Permanently" } + } + }.ToArray(); + } + + public SwissMovedPermanently(string resourceUrl, string id, ModelException errors) : + base(null) + { + Id = id; + ResourceUrl = resourceUrl; + ErrorList = new[] { errors }; + } + + public async override Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + ErrorList.ToList())) + { + StatusCode = ResponseCode + }; + + HttpRequest request = context.HttpContext.Request; + HttpResponse response = context.HttpContext.Response; + Uri location = SwissResultHelpers.GenerateLocationHeader(request, ResourceUrl, Id); + + var ss = location.AbsoluteUri; + + response.Headers["Location"] = location.AbsoluteUri; + await objResult.ExecuteResultAsync(context).ConfigureAwait(false); + // SwissResultHelpers.AddEntityId(response, Id); + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissNotFoundResult.cs b/src/NetSwissTools.Web/Mvc/Results/SwissNotFoundResult.cs new file mode 100644 index 0000000..c70e19f --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissNotFoundResult.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Exceptions; +using NetSwissTools.Web.Enums; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissNotFoundResult : NotFoundResult + { + private readonly int ResponseCode = StatusCodes.Status404NotFound; + private readonly ModelException[] ErrorList; + + public SwissNotFoundResult() + { + ErrorList = new List() + { + new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.RegisterNotFound, + Field = "", + Value = "", + Messages = new [] { "Not Found" } + } + }.ToArray(); + } + + public SwissNotFoundResult(ModelException errors) + { + ErrorList = new[] { errors }; + } + + public override void ExecuteResult(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + ErrorList.ToList())) + { + StatusCode = ResponseCode + }; + + objResult.ExecuteResult(context); + } + + public async override Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + ErrorList.ToList())) + { + StatusCode = ResponseCode + }; + await objResult.ExecuteResultAsync(context).ConfigureAwait(false); + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissOkResult.cs b/src/NetSwissTools.Web/Mvc/Results/SwissOkResult.cs new file mode 100644 index 0000000..9d454ed --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissOkResult.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Exceptions; +using System.Net; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissOkResult: OkResult + { + private readonly int ResponseCode = StatusCodes.Status200OK; + private readonly object Data; + + public SwissOkResult(object data) + { + Data = data; + } + + public SwissOkResult(HttpStatusCode statusCode, object data) + { + ResponseCode = (int)statusCode; + Data = data; + } + + public override void ExecuteResult(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + Data, + new List()) + ) + { + StatusCode = ResponseCode + }; + + objResult.ExecuteResult(context); + } + + public async override Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + Data, + new List()) + ) + { + StatusCode = ResponseCode + }; + await objResult.ExecuteResultAsync(context).ConfigureAwait(false); + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissResult.cs b/src/NetSwissTools.Web/Mvc/Results/SwissResult.cs new file mode 100644 index 0000000..95dc9f9 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissResult.cs @@ -0,0 +1,35 @@ +using NetSwissTools.Exceptions; +using NetSwissTools.Web.Mvc.Helpers; +using System.Net; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissResult + { + public SwissResult( + int status, + List error) + { + this.status = status; + this.success = ResultStatusHelper.IsSuccessReponse((HttpStatusCode)status); + this.data = null; + this.error = error; + } + + public SwissResult( + int status, object data, + List error) + { + this.status = status; + this.success = ResultStatusHelper.IsSuccessReponse((HttpStatusCode)status); + this.data = data; + this.error = error; + } + + public int status { get; private set; } + public bool success { get; private set; } + public object data { get; private set; } + public List error { get; private set; } + + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissResultHelpers.cs b/src/NetSwissTools.Web/Mvc/Results/SwissResultHelpers.cs new file mode 100644 index 0000000..2a795f4 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissResultHelpers.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Exceptions; +using NetSwissTools.Utils; +using System.Net; + +namespace NetSwissTools.Web.Mvc.Results +{ + public static class SwissResultHelpers + { + static string IdHeaderName = "Id"; + + internal static void AddEntityId(HttpResponse response, string entityId) + { + if (response.StatusCode == (int)HttpStatusCode.NoContent || + response.StatusCode == (int)HttpStatusCode.MovedPermanently) + { + response.Headers.Add(IdHeaderName, entityId); + } + } + + internal static Uri GenerateLocationHeader(HttpRequest request, string resourceUrl, string id) + { + if (!resourceUrl.IsEmpty()) + return new Uri(resourceUrl); + + var controller = request.RouteValues.FirstOrDefault(x => x.Key.Equals("controller", StringComparison.OrdinalIgnoreCase)).Value ?? + (request.Path.Value ?? "/")[1..]; + + var url = $"{request.Scheme}://{request.Host}/{controller}"; + + if (!id.IsEmpty()) + url += $"/{id}"; + + return new Uri(url); + } + + internal static IActionResult GenerateActionResult(T entity) + { + if (entity == null) + return new StatusCodeResult((int)HttpStatusCode.NoContent); + + return new ObjectResult(new SwissResult( + (int)HttpStatusCode.Created, + entity, + new List())) + { + StatusCode = (int)HttpStatusCode.Created + }; + } + + public static string GetBaseUrl(this HttpContext context) + { + var request = context.Request; + + var controller = request.RouteValues.FirstOrDefault(x => x.Key.Equals("controller", StringComparison.OrdinalIgnoreCase)).Value ?? + (request.Path.Value ?? "/")[1..]; + + return $"{request.Scheme}://{request.Host}/{controller}"; + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissUnauthorizedResult.cs b/src/NetSwissTools.Web/Mvc/Results/SwissUnauthorizedResult.cs new file mode 100644 index 0000000..f96a04c --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissUnauthorizedResult.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Exceptions; +using NetSwissTools.Web.Enums; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissUnauthorizedResult : UnauthorizedResult + { + private readonly int ResponseCode = StatusCodes.Status404NotFound; + private readonly ModelException[] ErrorList; + + public SwissUnauthorizedResult() + { + ErrorList = new List() + { + new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.RegisterNotFound, + Field = "", + Value = "", + Messages = new [] { "Not Authorized" } + } + }.ToArray(); + } + + public SwissUnauthorizedResult(ModelException errors) + { + ErrorList = new[] { errors }; + } + + public override void ExecuteResult(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + ErrorList.ToList())) + { + StatusCode = ResponseCode + }; + + objResult.ExecuteResult(context); + } + + public async override Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + ErrorList.ToList())) + { + StatusCode = ResponseCode + }; + await objResult.ExecuteResultAsync(context).ConfigureAwait(false); + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissUnprocessableEntityResult.cs b/src/NetSwissTools.Web/Mvc/Results/SwissUnprocessableEntityResult.cs new file mode 100644 index 0000000..d074c68 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissUnprocessableEntityResult.cs @@ -0,0 +1,96 @@ +using Microsoft.AspNetCore.Mvc; +using NetSwissTools.Exceptions; +using System.Net; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissUnprocessableEntityResult: UnprocessableEntityResult + { + private readonly ModelException[] ErrorList; + private readonly int ResponseCode = StatusCodes.Status422UnprocessableEntity; + private readonly object Data; + + public SwissUnprocessableEntityResult(ModelException[] errors) + { + ErrorList = errors; + Data = null; + } + + public SwissUnprocessableEntityResult(HttpStatusCode statusCode, ModelException[] errors) + { + ResponseCode = (int)statusCode; + ErrorList = errors; + Data = null; + } + + public SwissUnprocessableEntityResult(ModelException errors) + { + ErrorList = new[] { errors }; + Data = null; + } + + public SwissUnprocessableEntityResult(HttpStatusCode statusCode, ModelException errors) + { + ResponseCode = (int)statusCode; + ErrorList = new[] { errors }; + Data = null; + } + + public SwissUnprocessableEntityResult(object data, ModelException[] errors) + { + ErrorList = errors; + Data = data; + } + + public SwissUnprocessableEntityResult(HttpStatusCode statusCode, object data, ModelException[] errors) + { + ResponseCode = (int)statusCode; + ErrorList = errors; + Data = data; + } + + public SwissUnprocessableEntityResult(object data, ModelException errors) + { + ErrorList = new[] { errors }; + Data = data; + } + + public SwissUnprocessableEntityResult(HttpStatusCode statusCode, object data, ModelException errors) + { + ResponseCode = (int)statusCode; + ErrorList = new[] { errors }; + Data = data; + } + + public override void ExecuteResult(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + Data, + ErrorList != null ? ErrorList.ToList() : new List()) + ) + { + StatusCode = ResponseCode + }; + + objResult.ExecuteResult(context); + } + + public async override Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.StatusCode = ResponseCode; + + ObjectResult objResult = new(new SwissResult( + ResponseCode, + Data, + ErrorList != null ? ErrorList.ToList() : new List()) + ) + { + StatusCode = ResponseCode + }; + await objResult.ExecuteResultAsync(context).ConfigureAwait(false); + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/Results/SwissUpdatedResult.cs b/src/NetSwissTools.Web/Mvc/Results/SwissUpdatedResult.cs new file mode 100644 index 0000000..ba96318 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/Results/SwissUpdatedResult.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace NetSwissTools.Web.Mvc.Results +{ + public class SwissUpdatedResult : ObjectResult + { + public virtual T Entity { get; } + public virtual string Id { get; } + public virtual string ResourceUrl { get; } + + public SwissUpdatedResult(string id, T entity) + : base(entity) + { + Entity = entity; + Id = id; + ResourceUrl = ""; + } + + public SwissUpdatedResult(string resourceUrl, string id, T entity) + : base(entity) + { + Entity = entity; + Id = id; + ResourceUrl = resourceUrl; + } + + public async override Task ExecuteResultAsync(ActionContext context) + { + IActionResult result = GenerateActionResult(); + await result.ExecuteResultAsync(context).ConfigureAwait(false); + } + + internal IActionResult GenerateActionResult() + { + if (Entity == null) + return new StatusCodeResult((int)HttpStatusCode.NoContent); + + return new ObjectResult(Entity) + { + StatusCode = (int)HttpStatusCode.OK + }; + } + } +} diff --git a/src/NetSwissTools.Web/Mvc/SwissControllerApi.cs b/src/NetSwissTools.Web/Mvc/SwissControllerApi.cs new file mode 100644 index 0000000..4db5922 --- /dev/null +++ b/src/NetSwissTools.Web/Mvc/SwissControllerApi.cs @@ -0,0 +1,347 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using NetSwissTools.Exceptions; +using NetSwissTools.Services.Interfaces; +using NetSwissTools.Utils; +using NetSwissTools.Web.Enums; +using NetSwissTools.Web.Mvc.Helpers; +using NetSwissTools.Web.Mvc.Interfaces; +using NetSwissTools.Web.Mvc.Results; +using System.Net; +using System.Text.RegularExpressions; + +namespace NetSwissTools.Web.Mvc +{ + [ApiController] + public abstract class SwissControllerApi : ControllerBase//, IErrorsController, IUrlInfoController, IResultStatusController + { + private readonly List Errors = new(); + protected IErrorBaseService[] Services { get; set; } + protected IQueryCollection QueryString { get => HttpContext.Request.Query; } + + protected void AddError(Exception exception) => + Errors.Add(new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.UnhandledException, + Field = "", + Messages = new[] { exception.Message }, + Value = "" + }); + + protected void AddError(string exception) => + Errors.Add(new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.UnhandledException, + Field = "", + Messages = new[] { exception }, + Value = "" + }); + + protected void AddError(string field, string exception) => + Errors.Add(new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.UnhandledException, + Field = field, + Messages = new[] { exception }, + Value = "" + }); + + protected void AddError(string field, string exception, string value) => + Errors.Add(new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.UnhandledException, + Field = field, + Messages = new[] { exception }, + Value = value + }); + + protected void AddError(int code, string field, string exception, string value) => + Errors.Add(new ModelException + { + ErrorCode = code, + Field = field, + Messages = new[] { exception }, + Value = value + }); + + protected void AddError(ModelException exception) => + Errors.Add(exception); + + protected void AddError(ModelException[] exceptions) => + Errors.AddRange(exceptions); + + protected void ClearErrors() => + Errors.Clear(); + + protected ModelException[] GetErrors() + { + if (IsOperationValid()) + return Errors.ToArray(); + + if (Services != null) + { + return Services + .SelectMany(r => r.Errors) + .Select(r => r) + .ToArray(); + } + + return null; + } + + protected bool HasAnyErrors() + { + if (!IsOperationValid()) + return true; + + if (Services != null) + return Services.Where(r => r.Errors.Any()).Any(); + + return false; + } + + protected bool IsOperationValid() => + !Errors.Any(); + + protected bool IsClientError(HttpStatusCode code) => + ResultStatusHelper.IsClientError(code); + + protected bool IsInformational(HttpStatusCode code) => + ResultStatusHelper.IsInformational(code); + + protected bool IsRedirect(HttpStatusCode code) => + ResultStatusHelper.IsRedirect(code); + + protected bool IsServerError(HttpStatusCode code) => + ResultStatusHelper.IsServerError(code); + + protected bool IsSuccess(HttpStatusCode code) => + ResultStatusHelper.IsSuccess(code); + + protected bool IsSuccessReponse(HttpStatusCode code) => + ResultStatusHelper.IsSuccessReponse(code); + + protected int GetPageNumber() + { + int page; + try + { + var value = QueryString["page"]; + if (!int.TryParse(value, out page)) + page = 1; + } + catch (Exception) + { + page = 1; + } + + return page; + } + + protected int GetPageSize() + { + int limit; + try + { + var value = QueryString["limit"]; + if (!int.TryParse(value, out limit)) + limit = 10; + } + catch (Exception) + { + limit = 10; + } + + return limit; + } + + protected string GetSort() + { + string sort; + + try + { + sort = QueryString["sort"].ToString().ToUpper(); + } + catch (Exception) + { + sort = ""; + } + return sort; + } + + protected string GetFilter() + { + string filter; + + try + { + filter = QueryString["filter"].ToString().ToUpper(); + } + catch (Exception) + { + filter = ""; + } + return filter; + } + + protected string GetQueryColumn() + { + string column; + + try + { + column = QueryString["column"]; + } + catch (Exception) + { + column = ""; + } + return column; + } + + protected DateTime? GetDateTime(string pDate) + { + try + { + string[] date = null; + string[] time = new string[] + { + "00", + "00" + }; + string seconds = "00"; + + if (pDate.Length >= 8) + { + date = new[] + { + pDate.SubStr(0, 2), + pDate.SubStr(2, 2), + pDate.SubStr(4, 4) + }; + } + + if (date == null) + return null; + + if (pDate.Length >= 12) + { + time = new[] + { + pDate.SubStr(8, 2), + pDate.SubStr(10, 2) + }; + } + + if (pDate.Length >= 14) + seconds = pDate.SubStr(12, 2); + + + return new DateTime( + Convert.ToInt32(date[2]), + Convert.ToInt32(date[1]), + Convert.ToInt32(date[0]), + Convert.ToInt32(time[0]), + Convert.ToInt32(time[1]), + Convert.ToInt32(seconds)); + } + catch (Exception) + { + + } + return null; + } + + protected bool ValidateModelState(TEntity pModel) where TEntity : class + { + ModelState.Clear(); + var result = TryValidateModel(pModel); + + foreach (var item in ModelState.ToList()) + { + if (item.Value.Errors.Any()) + { + var modelError = new ModelException + { + ErrorCode = (int)EExceptionErrorCodes.ValidationError, + Field = item.Key, + Messages = item.Value.Errors.Select(x => x.ErrorMessage).ToArray(), + Value = "" + }; + + Errors.Add(modelError); + } + } + + return result; + } + + protected virtual SwissCreatedResult Created(string id, T entity) => + new(id, entity); + + protected virtual SwissCreatedResult Created(string resourceUrl, string id, T entity) => + new(resourceUrl, id, entity); + + protected virtual SwissUpdatedResult Updated(string id, T entity) => + new(id, entity); + + protected virtual SwissUpdatedResult Updated(string resourceUrl, string id, T entity) => + new(resourceUrl, id, entity); + + protected virtual SwissOkResult RequestOK(object result) => + new(result); + + protected virtual SwissOkResult RequestOK(object result, HttpStatusCode httpStatusCode) => + new(httpStatusCode, result); + + protected virtual SwissBadRequestResult BadRequest(object data, ModelException[] errors) => + new(data, errors); + + protected virtual SwissBadRequestResult BadRequest(object data, ModelException error) => + new(data, error); + + protected virtual SwissBadRequestResult BadRequest(ModelException error) => + new(error); + + protected virtual SwissBadRequestResult BadRequest(ModelException[] errors) => + new(errors); + + protected virtual SwissBadRequestResult UnprocessableEntity(object data, ModelException[] errors) => + new(data, errors); + + protected virtual SwissBadRequestResult UnprocessableEntity(object data, ModelException error) => + new(data, error); + + protected virtual SwissBadRequestResult UnprocessableEntity(ModelException error) => + new(error); + + protected virtual SwissBadRequestResult UnprocessableEntity(ModelException[] errors) => + new(errors); + + protected virtual SwissNotFoundResult NotFound(ModelException error) => + new(error); + + protected new virtual SwissNotFoundResult NotFound() => + new(); + + protected virtual SwissNotFoundResult Unauthorized(ModelException error) => + new(error); + + protected new virtual SwissNotFoundResult Unauthorized() => + new(); + + protected virtual SwissMovedPermanently MovedPermanently(string id) => + new(id); + + protected virtual SwissMovedPermanently MovedPermanently(string id, ModelException error) => + new(id, error); + + protected virtual SwissMovedPermanently MovedPermanently(string resourceUrl, string id) => + new(resourceUrl, id); + + protected virtual SwissMovedPermanently MovedPermanently(string resourceUrl, string id, ModelException error) => + new(resourceUrl, id, error); + + } +} diff --git a/src/NetSwissTools.Web/NetSwissTools.Web.csproj b/src/NetSwissTools.Web/NetSwissTools.Web.csproj new file mode 100644 index 0000000..6b8b196 --- /dev/null +++ b/src/NetSwissTools.Web/NetSwissTools.Web.csproj @@ -0,0 +1,51 @@ + + + + net6.0 + disable + enable + pt-BR + NetSwissTools.Web + Jonathan Vale + KennyMack + A collection of classes and tools to improve the web development with .NET + NetSwissToolsIcon.png + https://github.com/KennyMack/NetSwissTools.Web + git + False + Swiss;Tools;SwissArmy;Common;Implementations;Validation;Exception;Task;Empty;Net Tools;Web;Controller;Action + README.md + MIT + true + False + Library + + + + 1701;1702;1701;1702;1307 + + + + 1701;1702;1701;1702;1307 + + + + + True + \ + + + True + \ + + + + + + + + + + + + diff --git a/src/NetSwissTools.Web/Properties/launchSettings.json b/src/NetSwissTools.Web/Properties/launchSettings.json new file mode 100644 index 0000000..79747f7 --- /dev/null +++ b/src/NetSwissTools.Web/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:57932/", + "sslPort": 44324 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "NetSwissTools.Web": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file