├── .editorconfig ├── .github └── workflows │ ├── BuildStatus.yml │ └── deploy.yml ├── .gitignore ├── App.sln ├── README.md ├── Tests └── App.WebApi.IntegrationTest │ ├── App.WebApi.IntegrationTest.csproj │ ├── AppControllerTests.cs │ ├── Infrastructure │ ├── ClientSerializer.cs │ ├── ControllerTestBase.cs │ └── TestServerFixture.cs │ ├── ProjectControllerTests.cs │ ├── Properties │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json └── src ├── App.Application.Commands ├── App.Application.Commands.csproj └── ProjectBC │ ├── AddProject.cs │ ├── DeleteProject.cs │ ├── Handlers │ ├── AddProjectHandler.cs │ ├── DeleteProjectHandler.cs │ └── UpdateProjectHandler.cs │ ├── UpdateProject.cs │ └── Validators │ ├── CreateProjectValidator.cs │ ├── DeleteProjectValidator.cs │ └── UpdateProjectValidator.cs ├── App.Application.Events ├── App.Application.Events.csproj ├── Handlers │ └── DummyProjectCreatedHandler.cs └── ProjectCreated.cs ├── App.Application.Queries ├── App.Application.Queries.csproj ├── App.Application.QueryHandlers.csproj ├── MapDomainToQueryResult.cs └── ProjectBC │ ├── GetProject.cs │ ├── GetProjects.cs │ └── Handlers │ ├── GetProjectHandler.cs │ └── GetProjectsHandler.cs ├── App.Core ├── App.Core.csproj ├── Domain │ └── ProjectBC │ │ ├── IProjectRepository.cs │ │ └── Project.cs ├── ErrorMessages.cs └── IWorkContext.cs ├── App.Infrastructure.Persistence.SqlServer ├── App.Infrastructure.Persistence.SqlServer.csproj ├── Context │ ├── AppDbContext.cs │ └── DbContextFactory.cs ├── Migrations │ ├── 20200218115151_initial.Designer.cs │ ├── 20200218115151_initial.cs │ └── AppDbContextModelSnapshot.cs └── ProjectBC │ ├── Configurations │ └── ConfigProject.cs │ └── ProjectRepository.cs ├── App.WebApi ├── App.WebApi.csproj ├── App.WebApi.xml ├── Controllers │ └── ProjectsController.cs ├── Extensions │ ├── ServicesExtensions.cs │ └── SwaggerExtensions.cs ├── Infrastructure │ ├── DependencyRegistrations.cs │ ├── Scopes.cs │ ├── UserClaimsExtender.cs │ └── WorkContext.cs ├── Localization │ ├── LocalizerService.cs │ └── Resources.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Resources │ └── Localization.Resources.en.resx ├── Startup.cs ├── WebApiStartup.cs ├── appsettings.Development.json └── appsettings.json ├── MyApp.IdentityServer ├── Config.cs ├── MyApp.IdentityServer.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json └── tempkey.rsa └── SeedWork ├── BinaryOrigin.SeedWork.Core ├── AppEngine.cs ├── BinaryOrigin.SeedWork.Core.csproj ├── Domain │ ├── BaseEntity.cs │ ├── IRepository.cs │ ├── IResult.cs │ ├── Maybe.cs │ ├── Result.cs │ ├── ValidationError.cs │ ├── ValidationResponse.cs │ └── ValueObject.cs ├── EngineContext.cs ├── Exceptions │ ├── CommandValidationException.cs │ └── GeneralException.cs ├── Extensions │ ├── CommonExtensions.cs │ ├── GuardExtensions.cs │ ├── LinqExtensions.cs │ └── StringExtensions.cs ├── Interfaces │ ├── IAppFileProvider.cs │ ├── IAppStartup.cs │ ├── IDataProvider.cs │ ├── IDependencyRegistration.cs │ ├── IEngine.cs │ ├── IMapperProfile.cs │ ├── IPaginationService.cs │ ├── ITypeAdapter.cs │ ├── ITypeFinder.cs │ └── IUnitOfWork.cs └── Internal │ ├── AppDomainTypeFinder.cs │ ├── AppTypeFinder.cs │ ├── ReflectionHelper.cs │ └── TypeConverterHelper.cs ├── BinaryOrigin.SeedWork.Infrastructure ├── AppFileProvider.cs └── BinaryOrigin.SeedWork.Infrastructure.csproj ├── BinaryOrigin.SeedWork.Messages ├── BinaryOrigin.SeedWork.Messages.csproj ├── Decorators │ ├── DecoratorOrderAttribute.cs │ ├── ICommandHandlerDecorator.cs │ └── IQueryHandlerDecorator.cs ├── Extensions │ └── RegistrationExtensions.cs ├── IBus.cs ├── ICommandHandler.cs ├── IEventHandler.cs ├── ILocalizerService.cs ├── IQueryHandler.cs ├── ISequenceEventHandler.cs ├── Internal │ ├── Decorators │ │ ├── LocalizationCommandHandlerDecorator.cs │ │ ├── LocalizationQueryHandlerDecorator.cs │ │ └── ValidateCommandHandlerDecorator.cs │ ├── InMemoryBus.cs │ ├── NullLocalizerService.cs │ └── Resolvers │ │ ├── HandlerResolver.cs │ │ └── IHandlerResolver.cs ├── MessageTypes │ ├── ICommand.cs │ ├── IEvent.cs │ └── IQuery.cs └── Validation │ └── ICommandValidationProvider.cs ├── BinaryOrigin.SeedWork.Persistence.MySql ├── BinaryOrigin.SeedWork.Persistence.Ef.MySql.csproj ├── BinaryOrigin.SeedWork.Persistence.MySql.csproj ├── MySqlDataProvider.cs ├── MySqlDataProviderExtensions.cs └── MySqlDbExeptionParserProvider.cs ├── BinaryOrigin.SeedWork.Persistence.SqlServer ├── BinaryOrigin.SeedWork.Persistence.SqlServer.csproj ├── SqlDataProviderExtensions.cs ├── SqlServerDataProvider.cs └── SqlServerDbExceptionParserProvider.cs ├── BinaryOrigin.SeedWork.Persistence ├── BinaryOrigin.SeedWork.Persistence.Ef.csproj ├── DbErrorMessagesConfiguration.cs ├── DbExceptionParser.cs ├── EfEntityTypeConfiguration.cs ├── EfObjectContext.cs ├── EfRepository.cs ├── Extensions │ ├── DataProviderExtensions.cs │ ├── DbContextExtensions.cs │ └── EngineExtensions.cs ├── IDbContext.cs ├── IDbExceptionParserProvider.cs ├── IMappingConfiguration.cs └── PaginationService.cs └── BinaryOrigin.SeedWork.WebApi ├── Authorization ├── HasScopeHandler.cs ├── HasScopeRequirement.cs ├── ScopeAuthorizationPolicyProvider.cs └── TestAuthenticationHandler.cs ├── BinaryOrigin.SeedWork.WebApi.csproj ├── Controllers └── AppController.cs ├── ErrorHandlingMiddleware.cs ├── Extensions ├── EngineExtensions.cs └── ServicesExtensions.cs ├── IWebApiEngine.cs ├── IWebApiStartup.cs ├── Mapping ├── AutoMapperConfiguration.cs └── AutoMapperTypeAdapter.cs ├── ModelBinders ├── QueryModelBinder.cs ├── QueryModelBinderProvider.cs ├── RouteDataComplexTypeModelBinder.cs └── RouteDataComplexTypeModelBinderProvider.cs ├── Validations └── FluentValidationProvider.cs └── WebApiEngine.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # ASP0001: Authorization middleware is incorrectly configured. 4 | dotnet_diagnostic.ASP0001.severity = error 5 | 6 | # EF1001: Internal EF Core API usage. 7 | dotnet_diagnostic.EF1001.severity = warning 8 | -------------------------------------------------------------------------------- /.github/workflows/BuildStatus.yml: -------------------------------------------------------------------------------- 1 | name: Build Status 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Setup .NET Core 13 | uses: actions/setup-dotnet@v1 14 | with: 15 | dotnet-version: 3.1.100 16 | - name: Build with dotnet 17 | run: dotnet build --configuration Release 18 | - name: run tests 19 | run: dotnet test 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core deploy 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/heads 6 | branches: 7 | - master # Push events on master branch 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.100 20 | - name: Build with dotnet 21 | run: dotnet build --configuration Release 22 | - name: run tests 23 | run: dotnet test 24 | - name: nuget pack 25 | run: dotnet pack --configuration Release 26 | - name: Install Nuget Client 27 | uses: warrenbuckley/Setup-Nuget@v1 28 | - name: setApiKey 29 | run: nuget setApiKey ${{ secrets.nuget_key }} -s https://api.nuget.org/v3/index.json 30 | - name: Push generated package to GitHub registry 31 | run: nuget push **.nupkg -Source "https://api.nuget.org/v3/index.json" -SkipDuplicate -NoSymbols 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.obj 6 | *.pdb 7 | *.user 8 | *.aps 9 | *.pch 10 | *.vspscc 11 | *_i.c 12 | *_p.c 13 | *.ncb 14 | *.suo 15 | *.tlb 16 | *.tlh 17 | *.bak 18 | *.cache 19 | *.ilk 20 | *.log 21 | [Bb]in 22 | [Dd]ebug*/ 23 | *.lib 24 | *.sbr 25 | obj/ 26 | [Rr]elease*/ 27 | _ReSharper*/ 28 | [Tt]est[Rr]esult* 29 | packages/* 30 | .vs/ 31 | /SPA/.sonarqube 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monolithic architecture based on CQRS and DDD principles 2 | This repository presents an approach on how to build an application using Monolithic architecture, ASP.NET Core, EntityFrameworkCore, Identity Server, CQRS, DDD etc. 3 | 4 | ![Build Status](https://github.com/getson/MonolithicArchitecture/workflows/Build%20Status/badge.svg?branch=master) 5 | 6 | ## Packages 7 | | Package | Latest Stable | 8 | | --- | --- | 9 | | [BinaryOrigin.SeedWork.Core](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Core) | [![Nuget Package](https://img.shields.io/badge/nuget-1.1.2-blue.svg)](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Core) | 10 | | [BinaryOrigin.SeedWork.Infrastructure](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Infrastructure) | [![Nuget Package](https://img.shields.io/badge/nuget-1.1.1-blue.svg)](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Infrastructure) | 11 | | [BinaryOrigin.SeedWork.Messages](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Messages) | [![Nuget Package](https://img.shields.io/badge/nuget-1.1.1-blue.svg)](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Messages) | 12 | | [BinaryOrigin.SeedWork.Persistence.Ef](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Persistence.Ef) | [![Nuget Package](https://img.shields.io/badge/nuget-1.1.3-blue.svg)](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Persistence.Ef) | 13 | | [BinaryOrigin.SeedWork.WebApi](https://www.nuget.org/packages/BinaryOrigin.SeedWork.WebApi) | [![Nuget Package](https://img.shields.io/badge/nuget-1.1.3-blue.svg)](https://www.nuget.org/packages/BinaryOrigin.SeedWork.WebApi) | 14 | 15 | ## Providers 16 | | Package | Latest Stable | 17 | | --- | --- | 18 | | [BinaryOrigin.SeedWork.Persistence.MySql](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Persistence.MySql) | [![Nuget Package](https://img.shields.io/badge/nuget-1.1.1-blue.svg)](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Persistence.MySql) | 19 | | [BinaryOrigin.SeedWork.Persistence.SqlServer](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Persistence.SqlServer) | [![Nuget Package](https://img.shields.io/badge/nuget-1.1.3-blue.svg)](https://www.nuget.org/packages/BinaryOrigin.SeedWork.Persistence.SqlServer) | 20 | 21 | # Getting started: 22 | 23 | > 1 - Ensure that identity server is started 24 | > 2 - Use this request body for generating a token: 25 | >> client_id:clientId 26 | >> client_secret:secret 27 | >> scope:myApi profile openid 28 | >> username:getson 29 | >> password:password 30 | >> grant_type:password 31 | 32 | > 3 - Copy access token and paste it to swagger 33 | > 4 - Enjoy debugging. 34 | 35 | ## Your feedback is welcomed :) 36 | -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/App.WebApi.IntegrationTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 1591 9 | 10 | 11 | 12 | netcoreapp3.1 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Always 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/AppControllerTests.cs: -------------------------------------------------------------------------------- 1 | using App.WebApi.IntegrationTest.Infrastructure; 2 | 3 | namespace App.WebApi.IntegrationTest 4 | { 5 | public class AppControllerTests : ControllerTestBase 6 | { 7 | private readonly TestServerFixture _fixture; 8 | 9 | public AppControllerTests(TestServerFixture fixture) 10 | : base(fixture) 11 | { 12 | _fixture = fixture; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/Infrastructure/ClientSerializer.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | using System; 4 | 5 | namespace App.WebApi.IntegrationTest.Infrastructure 6 | { 7 | public static class ClientSerializer 8 | { 9 | static ClientSerializer() 10 | { 11 | JsonConvert.DefaultSettings = () => new JsonSerializerSettings 12 | { 13 | NullValueHandling = NullValueHandling.Ignore, 14 | ContractResolver = new CamelCasePropertyNamesContractResolver() 15 | }; 16 | } 17 | 18 | public static T Deserialize(string jsonString) 19 | { 20 | try 21 | { 22 | return JsonConvert.DeserializeObject(jsonString); 23 | } 24 | catch (Exception) 25 | { 26 | // ignored 27 | } 28 | return default(T); 29 | } 30 | 31 | public static string Serialize(T @object) 32 | { 33 | return JsonConvert.SerializeObject(@object); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/Infrastructure/ControllerTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Threading.Tasks; 7 | using System.Web; 8 | using Xunit; 9 | using HttpMethod = System.Net.Http.HttpMethod; 10 | 11 | namespace App.WebApi.IntegrationTest.Infrastructure 12 | { 13 | public static class Extensions 14 | { 15 | public static async Task GetObjectAsync(this HttpResponseMessage responseMessage) 16 | { 17 | var response = await responseMessage.Content.ReadAsStringAsync(); 18 | 19 | return ClientSerializer.Deserialize(response); 20 | } 21 | 22 | public static async Task> GetCollectionAsync(this HttpResponseMessage responseMessage) 23 | { 24 | var response = await responseMessage.Content.ReadAsStringAsync(); 25 | return ClientSerializer.Deserialize>(response); 26 | } 27 | 28 | public static T GetObject(this HttpResponseMessage responseMessage) 29 | { 30 | var response = responseMessage.Content.ReadAsStringAsync().Result; 31 | 32 | return ClientSerializer.Deserialize(response); 33 | } 34 | 35 | public static List GetCollection(this HttpResponseMessage responseMessage) 36 | { 37 | var response = responseMessage.Content.ReadAsStringAsync().Result; 38 | return ClientSerializer.Deserialize>(response); 39 | } 40 | 41 | public static HttpContent ToJsonContent(this T @object) 42 | { 43 | var jsonString = ClientSerializer.Serialize(@object); 44 | return new StringContent(jsonString, System.Text.Encoding.UTF8, "application/json"); 45 | } 46 | } 47 | 48 | public class ControllerTestBase : IClassFixture 49 | { 50 | private readonly TestServerFixture _fixture; 51 | 52 | public ControllerTestBase(TestServerFixture fixture) 53 | { 54 | _fixture = fixture; 55 | } 56 | 57 | protected async Task GetAsync(string url) 58 | { 59 | return await _fixture.Client.GetAsync(url); 60 | } 61 | 62 | protected async Task PutAsync(string url, T @object) 63 | { 64 | var requestMessage = new HttpRequestMessage(HttpMethod.Put, url) 65 | { 66 | Content = @object.ToJsonContent() 67 | }; 68 | return await _fixture.Client.SendAsync(requestMessage); 69 | } 70 | 71 | protected async Task PostAsync(string url, T @object) 72 | { 73 | var requestMessage = new HttpRequestMessage(HttpMethod.Post, url) 74 | { 75 | Content = @object.ToJsonContent() 76 | }; 77 | return await _fixture.Client.SendAsync(requestMessage); 78 | } 79 | 80 | protected async Task GetWithHeadersAsync(string url, Dictionary headers, T @object) 81 | { 82 | var requestMessage = new HttpRequestMessage(HttpMethod.Get, url) 83 | { 84 | Content = @object.ToJsonContent() 85 | }; 86 | 87 | foreach (var header in headers) 88 | { 89 | if (header.Value.Equals("Authorization")) 90 | { 91 | var authorization = new AuthenticationHeaderValue("Bearer", header.Value); 92 | requestMessage.Headers.Authorization = authorization; 93 | } 94 | else 95 | { 96 | requestMessage.Headers.Add(header.Key, header.Value); 97 | } 98 | } 99 | 100 | return await _fixture.Client.SendAsync(requestMessage); 101 | } 102 | 103 | protected async Task DeleteAsync(string url, T @object) 104 | { 105 | var requestMessage = new HttpRequestMessage(HttpMethod.Delete, url + "?" + GetQueryString(@object)); 106 | //{ 107 | // Content = @object.ToJsonContent() 108 | //}; 109 | 110 | return await _fixture.Client.SendAsync(requestMessage); 111 | } 112 | 113 | public string GetQueryString(object obj) 114 | { 115 | var properties = from p in obj.GetType().GetProperties() 116 | where p.GetValue(obj, null) != null 117 | select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString()); 118 | 119 | return String.Join("&", properties.ToArray()); 120 | } 121 | 122 | protected async Task DeleteAsync(string url) 123 | { 124 | return await _fixture.Client.DeleteAsync(url); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/Infrastructure/TestServerFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.TestHost; 3 | using System; 4 | using System.Net.Http; 5 | 6 | namespace App.WebApi.IntegrationTest.Infrastructure 7 | { 8 | public sealed class TestServerFixture : IDisposable 9 | { 10 | public HttpClient Client { get; } 11 | private static readonly TestServer TestServer; 12 | 13 | static TestServerFixture() 14 | { 15 | var builder = new WebHostBuilder() 16 | .UseStartup(); 17 | 18 | TestServer = new TestServer(builder); 19 | } 20 | 21 | public TestServerFixture() 22 | { 23 | Client = TestServer.CreateClient(); 24 | } 25 | 26 | public void Dispose() 27 | { 28 | Client.Dispose(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/ProjectControllerTests.cs: -------------------------------------------------------------------------------- 1 | using App.Application.Commands.ProjectBC; 2 | using App.Application.Queries.ProjectBC; 3 | using App.WebApi.IntegrationTest.Infrastructure; 4 | using BinaryOrigin.SeedWork.Core; 5 | using FluentAssertions; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Net; 10 | using System.Threading.Tasks; 11 | using Xunit; 12 | 13 | namespace App.WebApi.IntegrationTest 14 | { 15 | public class ProjectControllerTests : ControllerTestBase 16 | { 17 | private const string _apiEndpoint = "api/projects"; 18 | private readonly TestServerFixture _fixture; 19 | 20 | public ProjectControllerTests(TestServerFixture fixture) 21 | : base(fixture) 22 | { 23 | _fixture = fixture; 24 | Init(); 25 | } 26 | 27 | [Fact] 28 | public async Task Should_return_projects_with_success() 29 | { 30 | var projects = await GetTop20(); 31 | projects.Should().NotBeNull(); 32 | } 33 | 34 | [Fact] 35 | public async Task Should_return_project_by_id_with_success() 36 | { 37 | var projects = await GetTop20(); 38 | var firstProject = projects.FirstOrDefault(); 39 | 40 | var response = await GetAsync($"{_apiEndpoint}/{firstProject.Id}"); 41 | 42 | response.StatusCode.Should().Be(HttpStatusCode.OK); 43 | var getProject = response.GetObject(); 44 | 45 | response.GetObjectAsync().Should().NotBeNull(); 46 | getProject.Id.Should().Be(firstProject.Id); 47 | getProject.Name.Should().Be(firstProject.Name); 48 | getProject.Description.Should().Be(firstProject.Description); 49 | } 50 | 51 | [Fact] 52 | public async Task Project_Should_Be_Created() 53 | { 54 | var addProject = new AddProject 55 | { 56 | Name = "title 1", 57 | Description = "Description 1" 58 | }; 59 | 60 | var response = await PostAsync(_apiEndpoint, addProject); 61 | 62 | response.StatusCode.Should().Be(HttpStatusCode.Created); 63 | var createdProject = response.GetObject(); 64 | 65 | createdProject.Should().NotBe(Guid.Empty); 66 | } 67 | 68 | [Fact] 69 | public async Task Project_Should_Be_Updated() 70 | { 71 | var project = (await GetTop20()).First(); 72 | var updateProjectCommand = new UpdateProject 73 | { 74 | Id = project.Id, 75 | Name = "Name updated", 76 | Description = "Description updated" 77 | }; 78 | var response = await PutAsync(_apiEndpoint, updateProjectCommand); 79 | response.EnsureSuccessStatusCode(); 80 | 81 | var updatedProject = (await GetTop20()).FirstOrDefault(x => x.Id == project.Id); 82 | 83 | updatedProject.Description.Should().Be(updateProjectCommand.Description); 84 | updatedProject.Name.Should().Be(updateProjectCommand.Name); 85 | } 86 | 87 | [Fact] 88 | public async Task Project_Should_not_Be_Updated() 89 | { 90 | var updateProjectCommand = new UpdateProject 91 | { 92 | Id = Guid.Empty, 93 | Name = "Name updated", 94 | Description = "Description updated" 95 | }; 96 | 97 | var putResponse = await PutAsync(_apiEndpoint, updateProjectCommand); 98 | putResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest); 99 | } 100 | 101 | [Fact] 102 | public async Task Project_should_be_deleted() 103 | { 104 | var project = new AddProject 105 | { 106 | Name = "Name 3", 107 | Description = "Description 3" 108 | }; 109 | 110 | var response = await PostAsync(_apiEndpoint, project); 111 | response.StatusCode.Should().Be(HttpStatusCode.Created); 112 | var projectId = response.GetObject(); 113 | 114 | var deleteCommand = new DeleteProject(projectId); 115 | var deleteResponse = await DeleteAsync($"{_apiEndpoint}/{deleteCommand.Id}"); 116 | deleteResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); 117 | } 118 | 119 | [Fact] 120 | public async Task Project_should_not_be_deleted() 121 | { 122 | var deleteResponse = await DeleteAsync($"{_apiEndpoint}/{Guid.Empty}"); 123 | deleteResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest); 124 | } 125 | 126 | private async Task> GetTop20() 127 | { 128 | var result = await GetAsync($"{_apiEndpoint}?pageSize=20"); 129 | result.EnsureSuccessStatusCode(); 130 | return result.GetObject>().Data; 131 | } 132 | 133 | private void Init() 134 | { 135 | var project = new AddProject 136 | { 137 | Name = "Name 3", 138 | Description = "Description 3" 139 | }; 140 | 141 | var response = PostAsync(_apiEndpoint, project).Result; 142 | response.StatusCode.Should().Be(HttpStatusCode.Created); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:56836", 7 | "sslPort": 44344 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Testing" 16 | } 17 | }, 18 | "App.WebApi.IntegrationTest": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Tests/App.WebApi.IntegrationTest/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "Db": { 9 | "Type": "InMemory" 10 | }, 11 | "Auth": { 12 | "Authority": "http://localhost:5000", 13 | "Audience": "myApi", 14 | "RequireHttps": "false" 15 | }, 16 | "IsTesting": "true" 17 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/App.Application.Commands.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/AddProject.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Messages; 2 | using System; 3 | 4 | namespace App.Application.Commands.ProjectBC 5 | { 6 | public sealed class AddProject : ICommand 7 | { 8 | public string Name { get; set; } 9 | public string Description { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/DeleteProject.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Messages; 2 | using System; 3 | 4 | namespace App.Application.Commands.ProjectBC 5 | { 6 | public sealed class DeleteProject : ICommand 7 | { 8 | public DeleteProject() 9 | { 10 | } 11 | 12 | public DeleteProject(Guid id) 13 | { 14 | Id = id; 15 | } 16 | 17 | public Guid Id { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/Handlers/AddProjectHandler.cs: -------------------------------------------------------------------------------- 1 | using App.Core.Domain.ProjectBC; 2 | using BinaryOrigin.SeedWork.Core.Domain; 3 | using BinaryOrigin.SeedWork.Messages; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace App.Application.Commands.ProjectBC.Handlers 8 | { 9 | public sealed class AddProjectHandler : ICommandHandler 10 | { 11 | private readonly IProjectRepository _projectRepository; 12 | 13 | public AddProjectHandler(IProjectRepository projectRepository) 14 | { 15 | _projectRepository = projectRepository; 16 | } 17 | 18 | public async Task> HandleAsync(AddProject command) 19 | { 20 | var projectResult = Project.Create(Guid.NewGuid(), 21 | command.Name, 22 | command.Description); 23 | if (projectResult.IsFailure) 24 | { 25 | return Result.Fail(projectResult.Error); 26 | } 27 | await _projectRepository.CreateAsync(projectResult.Value); 28 | 29 | return Result.Ok(projectResult.Value.Id); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/Handlers/DeleteProjectHandler.cs: -------------------------------------------------------------------------------- 1 | using App.Core.Domain.ProjectBC; 2 | using BinaryOrigin.SeedWork.Core.Domain; 3 | using BinaryOrigin.SeedWork.Messages; 4 | using System.Threading.Tasks; 5 | 6 | namespace App.Application.Commands.ProjectBC.Handlers 7 | { 8 | public sealed class DeleteProjectHandler : ICommandHandler 9 | { 10 | private readonly IProjectRepository _projectRepository; 11 | 12 | public DeleteProjectHandler(IProjectRepository projectRepository) 13 | { 14 | _projectRepository = projectRepository; 15 | } 16 | 17 | public IProjectRepository ProjectRepository { get; } 18 | 19 | public async Task> HandleAsync(DeleteProject command) 20 | { 21 | var projectOrNothing = await _projectRepository.GetByIdAsync(command.Id); 22 | if (projectOrNothing.HasNoValue) 23 | { 24 | return Result.Ok(true); 25 | } 26 | await _projectRepository.DeleteAsync(projectOrNothing.Value); 27 | return Result.Ok(true); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/Handlers/UpdateProjectHandler.cs: -------------------------------------------------------------------------------- 1 | using App.Core; 2 | using App.Core.Domain.ProjectBC; 3 | using BinaryOrigin.SeedWork.Core.Domain; 4 | using BinaryOrigin.SeedWork.Messages; 5 | using System.Threading.Tasks; 6 | 7 | namespace App.Application.Commands.ProjectBC.Handlers 8 | { 9 | public sealed class UpdateProjectHandler : ICommandHandler 10 | { 11 | private readonly IProjectRepository _projectRepository; 12 | 13 | public UpdateProjectHandler(IProjectRepository projectRepository) 14 | { 15 | _projectRepository = projectRepository; 16 | } 17 | 18 | public async Task> HandleAsync(UpdateProject command) 19 | { 20 | var projectOrNothing = await _projectRepository.GetByIdAsync(command.Id); 21 | if (projectOrNothing.HasNoValue) 22 | { 23 | return Result.Fail(ErrorMessages.ProjectNotFound); 24 | } 25 | var result = projectOrNothing.Value.UpdateDetails(command.Name, command.Description); 26 | if (result.IsFailure) 27 | { 28 | return Result.Fail(result.Error); 29 | } 30 | await _projectRepository.UpdateAsync(projectOrNothing.Value); 31 | return Result.Ok(true); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/UpdateProject.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Messages; 2 | using System; 3 | 4 | namespace App.Application.Commands.ProjectBC 5 | { 6 | public sealed class UpdateProject : ICommand 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public string Name { get; set; } 11 | 12 | public string Description { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/Validators/CreateProjectValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace App.Application.Commands.ProjectBC.Validators 4 | { 5 | public class CreateProjectValidator : AbstractValidator 6 | { 7 | public CreateProjectValidator() 8 | { 9 | RuleFor(x => x.Name).NotEmpty(); 10 | RuleFor(x => x.Description).NotEmpty(); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/Validators/DeleteProjectValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using System; 3 | 4 | namespace App.Application.Commands.ProjectBC.Validators 5 | { 6 | public class DeleteProjectValidator : AbstractValidator 7 | { 8 | public DeleteProjectValidator() 9 | { 10 | RuleFor(x => x.Id).NotEqual(Guid.Empty); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/App.Application.Commands/ProjectBC/Validators/UpdateProjectValidator.cs: -------------------------------------------------------------------------------- 1 | using App.Core; 2 | using FluentValidation; 3 | using System; 4 | 5 | namespace App.Application.Commands.ProjectBC.Validators 6 | { 7 | public class UpdateProjectValidator : AbstractValidator 8 | { 9 | public UpdateProjectValidator() 10 | { 11 | RuleFor(x => x.Id) 12 | .NotEqual(Guid.Empty) 13 | .WithMessage(ErrorMessages.InvalidId); 14 | RuleFor(x => x.Name).NotEmpty(); 15 | RuleFor(x => x.Description).NotEmpty(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/App.Application.Events/App.Application.Events.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/App.Application.Events/Handlers/DummyProjectCreatedHandler.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Messages; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace App.Application.Events.Handlers 6 | { 7 | internal class DummyProjectCreatedHandler : IEventHandler 8 | { 9 | public Task HandleAsync(ProjectCreated message) 10 | { 11 | Console.WriteLine($"DummyHandler: {message.Id} - {message.Name}"); 12 | return Task.CompletedTask; 13 | } 14 | } 15 | 16 | internal class Dummy2ProjectCreatedHandler : IEventHandler 17 | { 18 | public Task HandleAsync(ProjectCreated message) 19 | { 20 | Console.WriteLine($"DummyHandler2: {message.Id} - {message.Name}"); 21 | return Task.CompletedTask; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/App.Application.Events/ProjectCreated.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Messages; 2 | using System; 3 | 4 | namespace App.Application.Events 5 | { 6 | public class ProjectCreated : IEvent 7 | { 8 | public Guid Id { get; set; } 9 | public string Name { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/App.Application.Queries/App.Application.Queries.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/App.Application.Queries/App.Application.QueryHandlers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/App.Application.Queries/MapDomainToQueryResult.cs: -------------------------------------------------------------------------------- 1 | using App.Application.Queries.ProjectBC; 2 | using App.Core.Domain.ProjectBC; 3 | using AutoMapper; 4 | using BinaryOrigin.SeedWork.Core; 5 | 6 | namespace App.Application.Queries 7 | { 8 | public class MapDomainToQueryResult : Profile, IMapperProfile 9 | { 10 | public int Order => 1; 11 | 12 | public MapDomainToQueryResult() 13 | { 14 | CreateMap(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/App.Application.Queries/ProjectBC/GetProject.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Messages; 2 | using System; 3 | 4 | namespace App.Application.Queries.ProjectBC 5 | { 6 | public sealed class GetProject : IQuery 7 | { 8 | public Guid Id { get; set; } 9 | } 10 | 11 | public sealed class GetProjectResult 12 | { 13 | public Guid Id { get; set; } 14 | 15 | public string Name { get; set; } 16 | 17 | public string Description { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/App.Application.Queries/ProjectBC/GetProjects.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Messages; 3 | 4 | namespace App.Application.Queries.ProjectBC 5 | { 6 | public sealed class GetProjects : IQuery> 7 | { 8 | public int PageIndex { get; set; } 9 | public int PageSize { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/App.Application.Queries/ProjectBC/Handlers/GetProjectHandler.cs: -------------------------------------------------------------------------------- 1 | using App.Core; 2 | using App.Core.Domain.ProjectBC; 3 | using BinaryOrigin.SeedWork.Core; 4 | using BinaryOrigin.SeedWork.Core.Domain; 5 | using BinaryOrigin.SeedWork.Messages; 6 | using BinaryOrigin.SeedWork.Persistence.Ef; 7 | using Microsoft.EntityFrameworkCore; 8 | using System.Threading.Tasks; 9 | 10 | namespace App.Application.Queries.ProjectBC.Handlers 11 | { 12 | public class GetProjectHandler : IQueryHandler 13 | { 14 | private readonly IDbContext _dbContext; 15 | private readonly ITypeAdapter _typeAdapter; 16 | 17 | public GetProjectHandler(IDbContext dbContext, ITypeAdapter typeAdapter) 18 | { 19 | _dbContext = dbContext; 20 | _typeAdapter = typeAdapter; 21 | } 22 | 23 | public async Task> HandleAsync(GetProject queryModel) 24 | { 25 | var result = await _dbContext.Set() 26 | .SingleOrDefaultAsync(p => p.Id == queryModel.Id); 27 | 28 | if (result == null) 29 | { 30 | return Result.Fail(ErrorMessages.ProjectNotFound); 31 | } 32 | 33 | return Result.Ok(_typeAdapter.Adapt(result)); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/App.Application.Queries/ProjectBC/Handlers/GetProjectsHandler.cs: -------------------------------------------------------------------------------- 1 | using App.Core.Domain.ProjectBC; 2 | using BinaryOrigin.SeedWork.Core; 3 | using BinaryOrigin.SeedWork.Core.Domain; 4 | using BinaryOrigin.SeedWork.Messages; 5 | using BinaryOrigin.SeedWork.Persistence.Ef; 6 | using Microsoft.EntityFrameworkCore; 7 | using System.Threading.Tasks; 8 | 9 | namespace App.Application.Queries.ProjectBC.Handlers 10 | { 11 | public class GetProjectsHandler : IQueryHandler> 12 | { 13 | private readonly IDbContext _dbContext; 14 | private readonly IPaginationService _paginationService; 15 | 16 | public GetProjectsHandler(IDbContext dbContext, 17 | IPaginationService paginationService) 18 | { 19 | _dbContext = dbContext; 20 | _paginationService = paginationService; 21 | } 22 | 23 | public async Task>> HandleAsync(GetProjects queryModel) 24 | { 25 | var baseQuery = _dbContext.Set().AsNoTracking(); 26 | 27 | var result = await _paginationService.PaginateAsync 28 | ( 29 | baseQuery, 30 | queryModel.PageIndex, 31 | queryModel.PageSize 32 | ); 33 | 34 | return Result.Ok(result); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/App.Core/App.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | App.Core 6 | App.Core 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/App.Core/Domain/ProjectBC/IProjectRepository.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | 3 | namespace App.Core.Domain.ProjectBC 4 | { 5 | public interface IProjectRepository : IRepository 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /src/App.Core/Domain/ProjectBC/Project.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Core.Domain; 3 | using System; 4 | 5 | namespace App.Core.Domain.ProjectBC 6 | { 7 | public sealed class Project : BaseEntity 8 | { 9 | public string Name { get; private set; } 10 | public string Description { get; private set; } 11 | 12 | public static Result Create(Guid id, string name, string description) 13 | { 14 | if (id == Guid.Empty) 15 | { 16 | return Result.Fail(ErrorMessages.InvalidId); 17 | } 18 | if (name.IsNullOrEmpty()) 19 | { 20 | return Result.Fail(ErrorMessages.NameShouldNotBeEmpty); 21 | } 22 | var project = new Project 23 | { 24 | Id = id, 25 | Name = name, 26 | Description = description 27 | }; 28 | return Result.Ok(project); 29 | } 30 | 31 | public Result UpdateDetails(string name, string description) 32 | { 33 | if (name.IsNullOrEmpty()) 34 | { 35 | return Result.Fail(ErrorMessages.NameShouldNotBeEmpty); 36 | } 37 | this.Name = name; 38 | Description = description; 39 | 40 | return Result.Ok(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/App.Core/ErrorMessages.cs: -------------------------------------------------------------------------------- 1 | namespace App.Core 2 | { 3 | public static class ErrorMessages 4 | { 5 | public const string ProjectNotFound = "PROJECT_NOT_FOUND"; 6 | public const string NameShouldNotBeEmpty = "NAME_SHOULD_NOT_BE_EMPTY"; 7 | 8 | public const string InvalidId = "INVALID_ID"; 9 | public const string GenericUniqueError = @"{0} should be unique"; 10 | public const string GenericCombinationUniqueError = @"Combination of {0} should be unique"; 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/App.Core/IWorkContext.cs: -------------------------------------------------------------------------------- 1 | namespace App.Core 2 | { 3 | public interface IWorkContext 4 | { 5 | string UserId { get; } 6 | string UserName { get; } 7 | string FullName { get; } 8 | string Email { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/App.Infrastructure.Persistence.SqlServer/App.Infrastructure.Persistence.SqlServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/App.Infrastructure.Persistence.SqlServer/Context/AppDbContext.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Persistence.Ef; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace App.Infrastructure.Persistence.SqlServer.Context 5 | { 6 | public class AppDbContext : EfObjectContext 7 | { 8 | public AppDbContext(DbContextOptions dbContextOptions) 9 | : base(dbContextOptions) 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/App.Infrastructure.Persistence.SqlServer/Context/DbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Design; 3 | using Microsoft.Extensions.Configuration; 4 | using System; 5 | 6 | namespace App.Infrastructure.Persistence.SqlServer.Context 7 | { 8 | public class AppObjectContextFactory : IDesignTimeDbContextFactory 9 | { 10 | /// 11 | /// create design time DbContext 12 | /// 13 | /// 14 | /// 15 | public virtual AppDbContext CreateDbContext(string[] args) 16 | { 17 | var optionsBuilder = new DbContextOptionsBuilder(); 18 | var configuration = new ConfigurationBuilder() 19 | .SetBasePath(AppContext.BaseDirectory) 20 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 21 | .Build(); 22 | optionsBuilder.UseSqlServer(configuration["Db:ConnectionString"], x => 23 | { 24 | x.MigrationsAssembly(this.GetType().Assembly.GetName().Name); 25 | x.MigrationsHistoryTable("MigrationHistory"); 26 | }); 27 | return new AppDbContext(optionsBuilder.Options); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/App.Infrastructure.Persistence.SqlServer/Migrations/20200218115151_initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using App.Infrastructure.Persistence.SqlServer.Context; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | namespace App.Infrastructure.Persistence.SqlServer.Migrations 11 | { 12 | [DbContext(typeof(AppDbContext))] 13 | [Migration("20200218115151_initial")] 14 | partial class initial 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "3.1.1") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("App.Core.Domain.ProjectBC.Project", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasColumnType("uniqueidentifier"); 29 | 30 | b.Property("Description") 31 | .HasColumnType("nvarchar(500)") 32 | .HasMaxLength(500); 33 | 34 | b.Property("Name") 35 | .IsRequired() 36 | .HasColumnType("nvarchar(300)") 37 | .HasMaxLength(300); 38 | 39 | b.HasKey("Id"); 40 | 41 | b.HasIndex("Name") 42 | .IsUnique(); 43 | 44 | b.ToTable("Projects"); 45 | }); 46 | #pragma warning restore 612, 618 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/App.Infrastructure.Persistence.SqlServer/Migrations/20200218115151_initial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace App.Infrastructure.Persistence.SqlServer.Migrations 5 | { 6 | public partial class initial : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "Projects", 12 | columns: table => new 13 | { 14 | Id = table.Column(nullable: false), 15 | Name = table.Column(maxLength: 300, nullable: false), 16 | Description = table.Column(maxLength: 500, nullable: true) 17 | }, 18 | constraints: table => 19 | { 20 | table.PrimaryKey("PK_Projects", x => x.Id); 21 | }); 22 | 23 | migrationBuilder.CreateIndex( 24 | name: "IX_Projects_Name", 25 | table: "Projects", 26 | column: "Name", 27 | unique: true); 28 | } 29 | 30 | protected override void Down(MigrationBuilder migrationBuilder) 31 | { 32 | migrationBuilder.DropTable( 33 | name: "Projects"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/App.Infrastructure.Persistence.SqlServer/Migrations/AppDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using App.Infrastructure.Persistence.SqlServer.Context; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | namespace App.Infrastructure.Persistence.SqlServer.Migrations 10 | { 11 | [DbContext(typeof(AppDbContext))] 12 | partial class AppDbContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "3.1.1") 19 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 21 | 22 | modelBuilder.Entity("App.Core.Domain.ProjectBC.Project", b => 23 | { 24 | b.Property("Id") 25 | .ValueGeneratedOnAdd() 26 | .HasColumnType("uniqueidentifier"); 27 | 28 | b.Property("Description") 29 | .HasColumnType("nvarchar(500)") 30 | .HasMaxLength(500); 31 | 32 | b.Property("Name") 33 | .IsRequired() 34 | .HasColumnType("nvarchar(300)") 35 | .HasMaxLength(300); 36 | 37 | b.HasKey("Id"); 38 | 39 | b.HasIndex("Name") 40 | .IsUnique(); 41 | 42 | b.ToTable("Projects"); 43 | }); 44 | #pragma warning restore 612, 618 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/App.Infrastructure.Persistence.SqlServer/ProjectBC/Configurations/ConfigProject.cs: -------------------------------------------------------------------------------- 1 | using App.Core.Domain.ProjectBC; 2 | using BinaryOrigin.SeedWork.Persistence.Ef; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace App.Infrastructure.Persistence.SqlServer.ProjectBC.Configurations 6 | { 7 | internal class ConfigProject : EfEntityTypeConfiguration 8 | { 9 | public override void PostConfigure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.Property(x => x.Name).HasMaxLength(300).IsRequired(); 13 | builder.Property(x => x.Description).HasMaxLength(500); 14 | 15 | builder.HasIndex(x => x.Name).IsUnique(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/App.Infrastructure.Persistence.SqlServer/ProjectBC/ProjectRepository.cs: -------------------------------------------------------------------------------- 1 | using App.Core.Domain.ProjectBC; 2 | using BinaryOrigin.SeedWork.Persistence.Ef; 3 | 4 | namespace App.Infrastructure.Persistence.SqlServer.ProjectBC 5 | { 6 | public sealed class ProjectRepository : EfRepository, IProjectRepository 7 | { 8 | public ProjectRepository(IDbContext dbContext) 9 | : base(dbContext) 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/App.WebApi/App.WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | App.WebApi 6 | App.WebApi 7 | 1591 8 | false 9 | 10 | 11 | App.WebApi.xml 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/App.WebApi/App.WebApi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App.WebApi 5 | 6 | 7 | 8 | [FromRoute] and [FromQuery] is added in order to generate swagger documentaion correctly until it will be fixed 9 | in swagger configuration 10 | 11 | This controller contains methods for the projects 12 | 13 | 14 | 15 | 16 | the controller cunstructor 17 | 18 | 19 | 20 | 21 | 22 | Get All projects 23 | 24 | 25 | 26 | 27 | 28 | get project by Id 29 | 30 | 31 | 32 | 33 | 34 | 35 | Add a project 36 | 37 | 38 | 39 | 40 | 41 | 42 | Update a project 43 | 44 | 45 | 46 | 47 | 48 | 49 | Delete a project 50 | 51 | 52 | 53 | 54 | 55 | 56 | Add entity framework services 57 | 58 | 59 | 60 | 61 | 62 | 63 | Configure Swagger 64 | 65 | 66 | 67 | 68 | 69 | Add services to the application and configure service provider 70 | 71 | Collection of service descriptors 72 | 73 | 74 | 75 | Configure the application HTTP request pipeline 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/App.WebApi/Controllers/ProjectsController.cs: -------------------------------------------------------------------------------- 1 | using App.Application.Commands.ProjectBC; 2 | using App.Application.Events; 3 | using App.Application.Queries.ProjectBC; 4 | using BinaryOrigin.SeedWork.Messages; 5 | using BinaryOrigin.SeedWork.WebApi.Controllers; 6 | using Microsoft.AspNetCore.Authorization; 7 | using Microsoft.AspNetCore.Mvc; 8 | using System.Threading.Tasks; 9 | 10 | namespace App.WebApi.Controllers 11 | { 12 | 13 | /// [FromRoute] and [FromQuery] is added in order to generate swagger documentaion correctly until it will be fixed 14 | /// in swagger configuration 15 | 16 | 17 | /// 18 | /// This controller contains methods for the projects 19 | /// 20 | [Route("api/projects")] 21 | public class ProjectsController : AppController 22 | { 23 | private readonly IBus _bus; 24 | 25 | /// 26 | /// the controller cunstructor 27 | /// 28 | /// 29 | public ProjectsController(IBus bus) 30 | { 31 | _bus = bus; 32 | } 33 | 34 | /// 35 | /// Get All projects 36 | /// 37 | /// 38 | [HttpGet] 39 | public async Task GetAll([FromQuery]GetProjects queryModel) 40 | { 41 | var result = await _bus.QueryAsync(queryModel); 42 | return Ok(result); 43 | } 44 | 45 | /// 46 | /// get project by Id 47 | /// 48 | /// 49 | /// 50 | [HttpGet("{id}")] 51 | public async Task GetById([FromRoute]GetProject queryModel) 52 | { 53 | var result = await _bus.QueryAsync(queryModel); 54 | 55 | return Ok(result); 56 | } 57 | 58 | /// 59 | /// Add a project 60 | /// 61 | /// 62 | /// 63 | [HttpPost] 64 | [Authorize(Scopes.CreateProject)] 65 | public async Task Post(AddProject command) 66 | { 67 | var result = await _bus.ExecuteAsync(command); 68 | await _bus.PublishAsync(new ProjectCreated 69 | { 70 | Id = result 71 | }); 72 | return Created(result); 73 | } 74 | 75 | /// 76 | /// Update a project 77 | /// 78 | /// 79 | /// 80 | [HttpPut] 81 | [Authorize(Scopes.UpdateProject)] 82 | public async Task Put(UpdateProject command) 83 | { 84 | _ = await _bus.ExecuteAsync(command); 85 | return Updated(); 86 | } 87 | /// 88 | /// Delete a project 89 | /// 90 | /// 91 | /// 92 | [HttpDelete("{id}")] 93 | [Authorize(Scopes.DeleteProject)] 94 | public async Task Delete([FromRoute]DeleteProject command) 95 | { 96 | _ = await _bus.ExecuteAsync(command); 97 | return Deleted(); 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/App.WebApi/Extensions/ServicesExtensions.cs: -------------------------------------------------------------------------------- 1 | using App.Core; 2 | using App.Infrastructure.Persistence.SqlServer.Context; 3 | using BinaryOrigin.SeedWork.Core; 4 | using BinaryOrigin.SeedWork.Persistence.Ef; 5 | using BinaryOrigin.SeedWork.WebApi; 6 | using BinaryOrigin.SeedWork.WebApi.Authorization; 7 | using Microsoft.AspNetCore.Authentication; 8 | using Microsoft.AspNetCore.Authentication.JwtBearer; 9 | using Microsoft.AspNetCore.Authorization; 10 | using Microsoft.AspNetCore.Builder; 11 | using Microsoft.AspNetCore.Http; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.DependencyInjection; 14 | 15 | namespace App.WebApi.Extensions 16 | { 17 | public static class ServicesExtensions 18 | { 19 | /// 20 | /// Add entity framework services 21 | /// 22 | /// 23 | /// 24 | public static void AddDbServices(this IEngine engine, IConfiguration configuration) 25 | { 26 | var connectionString = configuration["Db:ConnectionString"]; 27 | var dbType = configuration["Db:Type"]; 28 | 29 | if (dbType == "InMemory") 30 | { 31 | engine.AddInMemoryDbContext(); 32 | } 33 | else 34 | { 35 | // 1 36 | engine.AddDbContext(connectionString); 37 | //var optionsBuilder = new DbContextOptionsBuilder(); 38 | //optionsBuilder.UseSqlServer(connectionString); 39 | // 2 - engine.AddDbContext(() => new AppDbContext(optionsBuilder.Options)); 40 | 41 | // 3 - engine.AddDbContext(optionsBuilder.Options); 42 | } 43 | engine.AddSqlServerDbExceptionParser(new DbErrorMessagesConfiguration 44 | { 45 | UniqueErrorTemplate = ErrorMessages.GenericUniqueError, 46 | CombinationUniqueErrorTemplate = ErrorMessages.GenericCombinationUniqueError 47 | }); 48 | engine.AddRepositories(); 49 | } 50 | 51 | public static void AddAuth(this IServiceCollection services, IConfiguration configuration) 52 | 53 | { 54 | var authConfig = configuration.GetSection("Auth"); 55 | 56 | if (configuration.GetValue("IsTesting")) 57 | { 58 | services.AddAuthentication(options => 59 | { 60 | options.DefaultAuthenticateScheme = "Test Scheme"; 61 | options.DefaultChallengeScheme = "Test Scheme"; 62 | }).AddTestAuth(options => 63 | { 64 | options.Scopes = ReflectionHelper.GetConstants(); 65 | options.Authority = authConfig["Authority"]; 66 | }); 67 | } 68 | else 69 | { 70 | services.AddAuthentication(options => 71 | { 72 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 73 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 74 | }).AddJwtBearer(options => 75 | { 76 | options.Authority = authConfig["Authority"]; 77 | options.RequireHttpsMetadata = authConfig.GetValue("RequireHttps"); 78 | options.Audience = authConfig["Audience"]; 79 | }); 80 | } 81 | services.AddAuthorization(); 82 | services.AddSingleton(); 83 | services.AddTransient(x=>new UserClaimsExtender(authConfig["Authority"])); 84 | } 85 | public static void UseAppExceptionHandler(this IApplicationBuilder app) 86 | { 87 | app.UseMiddleware(); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/App.WebApi/Extensions/SwaggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.OpenApi.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Reflection; 8 | 9 | namespace App.WebApi.Extensions 10 | { 11 | public static class SwaggerExtensions 12 | { 13 | public static void UseAppSwagger(this IApplicationBuilder app) 14 | { 15 | app.UseSwagger(); 16 | app.UseSwaggerUI(options => 17 | { 18 | options.RoutePrefix = string.Empty; 19 | options.SwaggerEndpoint($"/swagger/v1/swagger.json", "App"); 20 | }); 21 | } 22 | 23 | /// 24 | /// Configure Swagger 25 | /// 26 | /// 27 | public static void AddAppSwagger(this IServiceCollection services) 28 | { 29 | services.AddSwaggerGen(c => 30 | { 31 | c.SwaggerDoc("v1", new OpenApiInfo 32 | { 33 | Title = "My App", 34 | Version = "v1" 35 | }); 36 | c.DescribeAllParametersInCamelCase(); 37 | c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme 38 | { 39 | Name = "Bearer", 40 | Description = "Please enter your token access token", 41 | Type = SecuritySchemeType.Http, 42 | Scheme = "bearer", 43 | In = ParameterLocation.Header 44 | }); 45 | 46 | c.AddSecurityRequirement(new OpenApiSecurityRequirement 47 | { 48 | { 49 | new OpenApiSecurityScheme 50 | { 51 | Reference = new OpenApiReference 52 | { 53 | Type = ReferenceType.SecurityScheme, 54 | Id = "Bearer" 55 | }, 56 | }, new List() 57 | } 58 | }); 59 | // Set the comments path for the Swagger JSON and UI. 60 | var basePath = AppContext.BaseDirectory; 61 | var xmlPath = Path.Combine(basePath, $"{Assembly.GetEntryAssembly().GetName().Name}.xml"); 62 | c.IncludeXmlComments(xmlPath); 63 | }); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/App.WebApi/Infrastructure/DependencyRegistrations.cs: -------------------------------------------------------------------------------- 1 | using App.WebApi.Localization; 2 | using Autofac; 3 | using BinaryOrigin.SeedWork.Core; 4 | using BinaryOrigin.SeedWork.Messages; 5 | using BinaryOrigin.SeedWork.WebApi.Authorization; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.Authorization; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.Configuration; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Threading.Tasks; 14 | 15 | namespace App.WebApi.Infrastructure 16 | { 17 | public class DependencyRegistrations : IDependencyRegistration 18 | { 19 | public int Order => 2; 20 | 21 | public void Register(ContainerBuilder builder, ITypeFinder typeFinder, IConfiguration config) 22 | { 23 | builder.RegisterType() 24 | .As() 25 | .SingleInstance(); 26 | builder.RegisterType() 27 | .As() 28 | .SingleInstance(); 29 | builder.RegisterInstance(new HasScopeHandler(config["Auth:Authority"])) 30 | .As() 31 | .SingleInstance(); 32 | builder.RegisterType() 33 | .As() 34 | .InstancePerLifetimeScope(); 35 | 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/App.WebApi/Infrastructure/Scopes.cs: -------------------------------------------------------------------------------- 1 | namespace App.WebApi 2 | { 3 | public class Scopes 4 | { 5 | public const string CreateProject = "create:project"; 6 | public const string UpdateProject = "update:project"; 7 | public const string DeleteProject = "delete:project"; 8 | } 9 | } -------------------------------------------------------------------------------- /src/App.WebApi/Infrastructure/UserClaimsExtender.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using Microsoft.AspNetCore.Authentication; 3 | using System.Linq; 4 | using System.Security.Claims; 5 | using System.Threading.Tasks; 6 | 7 | namespace App.WebApi 8 | { 9 | public class UserClaimsExtender : IClaimsTransformation 10 | { 11 | private readonly string _authority; 12 | 13 | public UserClaimsExtender(string authority) 14 | { 15 | _authority = authority; 16 | } 17 | 18 | public Task TransformAsync(ClaimsPrincipal principal) 19 | { 20 | // Here you can extend claims 21 | 22 | var identity = principal.Identities.FirstOrDefault(); 23 | if (identity == null) 24 | { 25 | return null; 26 | } 27 | // actually all the scopes will be provided to every user 28 | // but normally this is not a real world example 29 | var claims = ReflectionHelper.GetConstants() 30 | .Select(x => new Claim("scope", x, ClaimValueTypes.String, _authority)) 31 | .ToList(); 32 | 33 | claims.AddRange(identity.Claims); 34 | 35 | var claimsIdentity = new ClaimsIdentity(claims, identity.AuthenticationType); 36 | var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); 37 | 38 | return Task.FromResult(claimsPrincipal); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/App.WebApi/Infrastructure/WorkContext.cs: -------------------------------------------------------------------------------- 1 | using App.Core; 2 | using BinaryOrigin.SeedWork.Core; 3 | using Microsoft.AspNetCore.Http; 4 | using System.Linq; 5 | 6 | namespace App.WebApi 7 | { 8 | public class WorkContext : IWorkContext 9 | { 10 | private readonly IHttpContextAccessor _httpContextAccessor; 11 | 12 | private string _userId; 13 | private string _fullName; 14 | private string _email; 15 | private string _username; 16 | 17 | public WorkContext(IHttpContextAccessor httpContextAccessor) 18 | { 19 | _httpContextAccessor = httpContextAccessor; 20 | } 21 | 22 | public string UserId 23 | { 24 | get 25 | { 26 | if (string.IsNullOrEmpty(_userId)) 27 | { 28 | _userId = GetClaimValue("user_id"); 29 | } 30 | return _userId; 31 | } 32 | } 33 | 34 | private string GetClaimValue(string claimType) 35 | { 36 | var claim = _httpContextAccessor.HttpContext 37 | .User.Claims 38 | .FirstOrDefault(x => x.Type == claimType); 39 | return claim.Value.ToString(); 40 | } 41 | 42 | public string FullName 43 | { 44 | get 45 | { 46 | if (_fullName.IsNullOrEmpty()) 47 | { 48 | _fullName = GetClaimValue("full_name"); 49 | } 50 | return _fullName; 51 | } 52 | } 53 | 54 | public string Email 55 | { 56 | get 57 | { 58 | if (_email.IsNullOrEmpty()) 59 | { 60 | _email = GetClaimValue("email"); 61 | } 62 | 63 | return _email; 64 | } 65 | } 66 | 67 | public string UserName 68 | { 69 | get 70 | { 71 | if (_username.IsNullOrEmpty()) 72 | { 73 | _username = GetClaimValue("username"); 74 | } 75 | 76 | return _username; 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/App.WebApi/Localization/LocalizerService.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Messages; 2 | using Microsoft.Extensions.Localization; 3 | 4 | namespace App.WebApi.Localization 5 | { 6 | public class LocalizerService : ILocalizerService 7 | { 8 | private readonly IStringLocalizer _localizer; 9 | 10 | public LocalizerService(IStringLocalizer localizer) 11 | { 12 | _localizer = localizer; 13 | } 14 | 15 | public string Localize(string message) 16 | { 17 | return _localizer.GetString(message).Value; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/App.WebApi/Localization/Resources.cs: -------------------------------------------------------------------------------- 1 | namespace App.WebApi.Localization 2 | { 3 | public class Resources 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/App.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace App.WebApi 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateWebHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/App.WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5001", 7 | "sslPort": 0 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "App.WebApi": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "http://localhost:5001" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/App.WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.WebApi; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using System; 9 | 10 | [assembly: ApiConventionType(typeof(DefaultApiConventions))] 11 | 12 | namespace App.WebApi 13 | { 14 | public class Startup 15 | { 16 | public IConfigurationRoot Configuration { get; } 17 | 18 | public Startup(IWebHostEnvironment environment) 19 | { 20 | Configuration = new ConfigurationBuilder() 21 | .SetBasePath(environment.ContentRootPath) 22 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 23 | .AddEnvironmentVariables() 24 | .Build(); 25 | } 26 | 27 | /// 28 | /// Add services to the application and configure service provider 29 | /// 30 | /// Collection of service descriptors 31 | public IServiceProvider ConfigureServices(IServiceCollection services) 32 | { 33 | 34 | var engine = (AppWebApiEngine)EngineContext.Create(); 35 | 36 | engine.Initialize(Configuration); 37 | 38 | return engine.ConfigureServices(services); 39 | 40 | } 41 | 42 | /// 43 | /// Configure the application HTTP request pipeline 44 | /// 45 | /// 46 | public void Configure(IApplicationBuilder application) 47 | { 48 | ((AppWebApiEngine)EngineContext.Current).ConfigureRequestPipeline(application); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/App.WebApi/WebApiStartup.cs: -------------------------------------------------------------------------------- 1 | using App.WebApi.Extensions; 2 | using BinaryOrigin.SeedWork.Core; 3 | using BinaryOrigin.SeedWork.Messages; 4 | using BinaryOrigin.SeedWork.WebApi; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | namespace App.WebApi 11 | { 12 | /// 13 | public class WebApiStartup : IWebAppStartup 14 | { 15 | /// 16 | public int Order => 1; 17 | 18 | /// 19 | public void ConfigureServices(IServiceCollection services, IEngine engine, IConfiguration configuration) 20 | { 21 | //add framework services 22 | services.AddCors(); 23 | services.AddControllers() 24 | .AddNewtonsoftJson() 25 | .SetCompatibilityVersion(CompatibilityVersion.Version_3_0); 26 | services.AddAppSwagger(); 27 | services.AddDefaultEfSecondLevelCache(); 28 | services.AddLocalization(options => options.ResourcesPath = "Resources"); 29 | services.AddAuth(configuration); 30 | 31 | // add custom services 32 | engine.AddAutoMapper(); 33 | engine.AddDbServices(configuration); 34 | engine.AddInMemoryBus(); 35 | engine.AddFluentValidation(); 36 | engine.AddHandlers(); 37 | engine.AddDefaultDecorators(); 38 | engine.AddDefaultPagination(c => 39 | { 40 | c.MaxPageSizeAllowed = 100; 41 | }); 42 | } 43 | 44 | /// 45 | public void Configure(IApplicationBuilder application, IConfiguration configuration) 46 | { 47 | application.UseAppExceptionHandler(); 48 | 49 | application.UseAuthentication(); 50 | 51 | application.UseRouting(); 52 | 53 | application.UseAuthorization(); 54 | 55 | application.UseEndpoints(cfg => 56 | { 57 | cfg.MapControllers(); 58 | }); 59 | 60 | application.UseAppSwagger(); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/App.WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/App.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "Db": { 9 | "Type": "sqlserver", 10 | "ConnectionString": "Data Source=localhost;Initial Catalog=App;Persist Security Info=True;User ID=local;Password=local" 11 | }, 12 | "Auth": { 13 | "Authority": "http://localhost:5000", 14 | "Audience": "myApi", 15 | "RequireHttps": "false" 16 | } 17 | } -------------------------------------------------------------------------------- /src/MyApp.IdentityServer/Config.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using IdentityServer4; 5 | using IdentityServer4.Models; 6 | using IdentityServer4.Test; 7 | using System.Collections.Generic; 8 | using System.Security.Claims; 9 | 10 | namespace MyApp.IdentityServer 11 | { 12 | public static class Config 13 | { 14 | // scopes define the resources in your system 15 | public static IEnumerable GetIdentityResources() 16 | { 17 | return new List 18 | { 19 | new IdentityResources.OpenId(), 20 | new IdentityResources.Profile() 21 | }; 22 | } 23 | 24 | public static IEnumerable GetApiResources() 25 | { 26 | return new List 27 | { 28 | new ApiResource("myApi", "My API") 29 | }; 30 | } 31 | 32 | // clients want to access resources (aka scopes) 33 | public static IEnumerable GetClients() 34 | { 35 | // client credentials client 36 | return new List 37 | { 38 | // OpenID Connect hybrid flow and client credentials client (MVC) 39 | new Client 40 | { 41 | ClientId = "clientId", 42 | ClientName = "Sample client", 43 | AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 44 | ClientSecrets = 45 | { 46 | new Secret("secret".Sha256()) 47 | }, 48 | RedirectUris = { "http://localhost:5001" }, 49 | AllowedScopes = 50 | { 51 | IdentityServerConstants.StandardScopes.OpenId, 52 | IdentityServerConstants.StandardScopes.Profile, 53 | "myApi" 54 | }, 55 | AllowOfflineAccess = true 56 | } 57 | }; 58 | } 59 | 60 | public static List GetUsers() 61 | { 62 | return new List 63 | { 64 | new TestUser 65 | { 66 | SubjectId = "1", 67 | Username = "getson", 68 | Password = "password", 69 | 70 | Claims = new List 71 | { 72 | new Claim(ClaimTypes.Email, "cela.getson@gmail.com"), 73 | new Claim(ClaimTypes.Name, "getson") 74 | } 75 | } 76 | }; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/MyApp.IdentityServer/MyApp.IdentityServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 1591 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/MyApp.IdentityServer/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | using System; 7 | 8 | namespace MyApp.IdentityServer 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | Console.Title = "IdentityServer"; 15 | 16 | BuildWebHost(args).Run(); 17 | } 18 | 19 | public static IWebHost BuildWebHost(string[] args) => 20 | WebHost.CreateDefaultBuilder(args) 21 | .UseStartup() 22 | .Build(); 23 | } 24 | } -------------------------------------------------------------------------------- /src/MyApp.IdentityServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5000", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": ".well-known/openid-configuration", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "MyApp.IdentityServer": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "applicationUrl": "http://localhost:5000", 23 | "launchUrl": ".well-known/openid-configuration", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/MyApp.IdentityServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace MyApp.IdentityServer 6 | { 7 | public class Startup 8 | { 9 | public void ConfigureServices(IServiceCollection services) 10 | { 11 | services.AddMvc(); 12 | services.AddIdentityServer() 13 | .AddDeveloperSigningCredential() 14 | .AddInMemoryIdentityResources(Config.GetIdentityResources()) 15 | .AddInMemoryApiResources(Config.GetApiResources()) 16 | .AddInMemoryClients(Config.GetClients()) 17 | .AddTestUsers(Config.GetUsers()); 18 | } 19 | 20 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 21 | { 22 | if (env.EnvironmentName == "dev") 23 | { 24 | app.UseDeveloperExceptionPage(); 25 | } 26 | app.UseIdentityServer(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/MyApp.IdentityServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/MyApp.IdentityServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } -------------------------------------------------------------------------------- /src/MyApp.IdentityServer/tempkey.rsa: -------------------------------------------------------------------------------- 1 | {"KeyId":"6b00d297bb2cc450e23fcf1b5e1680de","Parameters":{"D":"Qe4zktzVfkbH4nElCkgqkJQSkRIJ0kHWae4H1MwfR4K5Rem+Pq+VbE9UBqbSVgigWk56ISS8cynrlkxul+10/DnvKpj+RVInxgUTlLvF5+D9s4ublE+Nxae7Oet6mzYzlBQXgPgY/1SWsGuGcnohV0o9YJQaEZlUxStExfGtFs5ofPksfPaSLbahz5HDkFevCMfxhGPdZoJAGqtzTyPP3JDbMbS7Bm1+flfZAqY4Skt0YvV84X4B0qAAKqlnpQmsYW+yEKPpi0Dr43yLNbkwNQOlPFERk9f7gI2k0njNpkomD+cZrjCeUUIINKSE+2NxFnorADsSsTrbD1VzF92DWQ==","DP":"tkG+shaufqJmskqXIq/RhCc6rWaX95oVn+w7gqgD0ma/UzZQeuN2347+cwJFYJVid2uNZlGDwOYDtZEjW5PK4il7AzCKlTKfmk4I9JEgoESpnKVZQ9lficyHGfV4nHqAm8tTjkI2m03G7e/ZMYzWQvlM1GQmQAkTwMZ41fONfz8=","DQ":"C+Nzu4euIOaN1y6V6sqjXMxD+WWlZIjG7oQX6uZpC4H8b+7F4iGZ4G6xk/5aXEOEA1DvUlhuMnc+nMbNQvhTWLqw0XcGx/ixI4c+1dhLx9TrQyR/6o3idNkL/lvlKhD28jWjSTJk9RvhAndpFdm2PHlcZTdvLEJbhRk08J33uac=","Exponent":"AQAB","InverseQ":"C4sYa0v06V+DdJ+S9M+gn0OybvhRP/WGWklplfa4P0vf8X/ULQpflWH+XtbtjPDndP3oIld0lQM7UyPkVEykU+HAOyaaAlfFB2pYPATW3HKGQ+st6iRG990GSFMXjPphI/lH+5xkldsyJn+DB4fvalYxZz/kq2f13fAXftsRyGA=","Modulus":"xVrStwTYTfha19v6/SNAb3xUhziyVy1CM4vveehVugHtQJo0h1ROPw5K2z+AvyPgsqaaSt3mOmXOMRATgfQJj5jc/467NC2FUn2BObsjMOLPY1VGvWlgJspjOF33/KRWpWyqxcoAseNObEzgwdMxAk9stHWE60wBwn8xSYdp3xMJGCtCzOgaXwUIoYw8d+nmVYDiFxl+wOpgLBxIn/KwlhF2KWO9aiivwatNhDfzSIX3/692Aa1oS4+EIt8g3P0vO+0psTCdMD2CSdKqXfdLctTgROOjty1YnBsNdJ1yOuAKPYrjuEOCZ08vAEeoRUp5tgwf+8xXjg/NkumP5lDFWQ==","P":"9AK5dJflIEmpjm18hWTrQOjyToE2bT0YQr2t0Hr0BVkGKmaS+Mxi+6dFymYuJsmfI6wJMXOn1uywmFCVmDo66EZQgoKzMJMgbIPI+d2wVnbm+4311Ehq/uhHCFgqZE/VBhJE7O+8IEz+Px3HWTlyjlcYP6PtzTHqpBwaPAdcTbM=","Q":"zw09f6dmj90VI77D4uurVM8ja82TKbbmCDvCtCr94q5KjmcfSaRuWSjATUdAmmvqbDvGQsoWpa9jh9FVXPhVhLsN0iL8DdeqS8/+tqv02gSzVVzMTNx9FyorF1O2qP9SDpkrzPSDZtn6lL50sMxe2P2tlf8S9bIFiqDT1gTJEsM="}} -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/BinaryOrigin.SeedWork.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.1.2 6 | https://github.com/getson/MonolithicArchitecture 7 | https://github.com/getson/MonolithicArchitecture 8 | CQRS,DDD 9 | Getson Cela 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Domain/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BinaryOrigin.SeedWork.Core.Domain 4 | { 5 | /// 6 | /// Base class for entities 7 | /// 8 | public abstract class BaseEntity : IEquatable 9 | { 10 | /// 11 | /// get or set the identifier 12 | /// 13 | public Guid Id { get; set; } 14 | 15 | public bool IsTransient() 16 | { 17 | return Id == default; 18 | } 19 | 20 | /// 21 | /// check for object equality 22 | /// 23 | /// Object 24 | /// Result 25 | public override bool Equals(object obj) 26 | { 27 | return Equals(obj as BaseEntity); 28 | } 29 | 30 | /// 31 | /// Equals 32 | /// 33 | /// other entity 34 | /// Result 35 | public virtual bool Equals(BaseEntity other) 36 | { 37 | if (other == null) 38 | return false; 39 | 40 | if (ReferenceEquals(this, other)) 41 | return true; 42 | 43 | if (IsTransient() || other.IsTransient() || !Equals(Id, other.Id)) 44 | return false; 45 | 46 | var otherType = other.GetType(); 47 | var thisType = GetType(); 48 | 49 | return thisType.IsAssignableFrom(otherType) || otherType.IsAssignableFrom(thisType); 50 | } 51 | 52 | /// 53 | /// Get hash code 54 | /// 55 | /// 56 | public override int GetHashCode() 57 | { 58 | return Id.GetHashCode(); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Domain/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace BinaryOrigin.SeedWork.Core.Domain 6 | { 7 | public interface IRepository where TEntity : BaseEntity 8 | { 9 | Task> GetAllAsync(); 10 | 11 | Task CreateAsync(TEntity entity); 12 | 13 | Task CreateAsync(IEnumerable entities); 14 | 15 | Task UpdateAsync(TEntity entity); 16 | 17 | Task DeleteAsync(TEntity entity); 18 | 19 | Task> GetByIdAsync(Guid id); 20 | 21 | Task ExistsAsync(Guid id); 22 | 23 | IUnitOfWork UnitOfWork { get; } 24 | } 25 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Domain/IResult.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Core.Domain 2 | { 3 | public interface IResult 4 | { 5 | bool IsFailure { get; } 6 | bool IsSuccess { get; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Domain/Maybe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BinaryOrigin.SeedWork.Core.Domain 4 | { 5 | public struct Maybe : IEquatable> 6 | { 7 | private readonly MaybeValueWrapper _value; 8 | 9 | public T Value 10 | { 11 | get 12 | { 13 | if (HasNoValue) 14 | throw new InvalidOperationException(); 15 | 16 | return _value._value; 17 | } 18 | } 19 | 20 | public static Maybe None => new Maybe(); 21 | 22 | public bool HasValue => _value != null; 23 | public bool HasNoValue => !HasValue; 24 | 25 | private Maybe(T value) 26 | { 27 | _value = value == null ? null : new MaybeValueWrapper(value); 28 | } 29 | 30 | public static implicit operator Maybe(T value) 31 | { 32 | return new Maybe(value); 33 | } 34 | 35 | public static Maybe From(T obj) 36 | { 37 | return new Maybe(obj); 38 | } 39 | 40 | public static bool operator ==(Maybe maybe, T value) 41 | { 42 | if (maybe.HasNoValue) 43 | return false; 44 | 45 | return maybe.Value.Equals(value); 46 | } 47 | 48 | public static bool operator !=(Maybe maybe, T value) 49 | { 50 | return !(maybe == value); 51 | } 52 | 53 | public static bool operator ==(Maybe first, Maybe second) 54 | { 55 | return first.Equals(second); 56 | } 57 | 58 | public static bool operator !=(Maybe first, Maybe second) 59 | { 60 | return !(first == second); 61 | } 62 | 63 | public override bool Equals(object obj) 64 | { 65 | if (obj is T) 66 | { 67 | obj = new Maybe((T)obj); 68 | } 69 | 70 | if (!(obj is Maybe)) 71 | return false; 72 | 73 | var other = (Maybe)obj; 74 | return Equals(other); 75 | } 76 | 77 | public bool Equals(Maybe other) 78 | { 79 | if (HasNoValue && other.HasNoValue) 80 | return true; 81 | 82 | if (HasNoValue || other.HasNoValue) 83 | return false; 84 | 85 | return _value._value.Equals(other._value._value); 86 | } 87 | 88 | public override int GetHashCode() 89 | { 90 | if (HasNoValue) 91 | return 0; 92 | 93 | return _value._value.GetHashCode(); 94 | } 95 | 96 | public override string ToString() 97 | { 98 | if (HasNoValue) 99 | return "No value"; 100 | 101 | return Value.ToString(); 102 | } 103 | 104 | private class MaybeValueWrapper 105 | { 106 | public MaybeValueWrapper(T value) 107 | { 108 | _value = value; 109 | } 110 | 111 | internal readonly T _value; 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Domain/ValidationError.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Core.Domain 2 | { 3 | public class ValidationError 4 | { 5 | public string PropertyName { get; set; } 6 | public string ErrorMessage { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Domain/ValidationResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BinaryOrigin.SeedWork.Core.Domain 4 | { 5 | public class ValidationResponse 6 | { 7 | public IList Errors { get; set; } = new List(); 8 | public bool IsValid => Errors.Count == 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Domain/ValueObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace BinaryOrigin.SeedWork.Core.Domain 5 | { 6 | public abstract class ValueObject 7 | { 8 | protected static bool EqualOperator(ValueObject left, ValueObject right) 9 | { 10 | if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null)) 11 | { 12 | return false; 13 | } 14 | return ReferenceEquals(left, null) || left.Equals(right); 15 | } 16 | 17 | protected static bool NotEqualOperator(ValueObject left, ValueObject right) 18 | { 19 | return !(EqualOperator(left, right)); 20 | } 21 | 22 | protected abstract IEnumerable GetAtomicValues(); 23 | 24 | public override bool Equals(object obj) 25 | { 26 | if (obj == null || obj.GetType() != GetType()) 27 | { 28 | return false; 29 | } 30 | 31 | var other = (ValueObject)obj; 32 | var thisValues = GetAtomicValues().GetEnumerator(); 33 | var otherValues = other.GetAtomicValues().GetEnumerator(); 34 | while (thisValues.MoveNext() && otherValues.MoveNext()) 35 | { 36 | if (ReferenceEquals(thisValues.Current, null) ^ 37 | ReferenceEquals(otherValues.Current, null)) 38 | { 39 | return false; 40 | } 41 | 42 | if (thisValues.Current != null && 43 | !thisValues.Current.Equals(otherValues.Current)) 44 | { 45 | return false; 46 | } 47 | } 48 | return !thisValues.MoveNext() && !otherValues.MoveNext(); 49 | } 50 | 51 | public override int GetHashCode() 52 | { 53 | return GetAtomicValues() 54 | .Select(x => x != null ? x.GetHashCode() : 0) 55 | .Aggregate((x, y) => x ^ y); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/EngineContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace BinaryOrigin.SeedWork.Core 6 | { 7 | /// 8 | /// Provides access to the singleton instance of the App engine. 9 | /// 10 | public class EngineContext 11 | { 12 | [SuppressMessage("NDepend", "ND1901:AvoidNonReadOnlyStaticFields", Justification = "It's not needed")] 13 | private static EngineContext _context; 14 | 15 | private readonly IEngine _installedEngine; 16 | 17 | private EngineContext(IEngine installedEngine) 18 | { 19 | _installedEngine = installedEngine; 20 | } 21 | 22 | private EngineContext() 23 | { 24 | } 25 | 26 | /// 27 | /// Gets the singleton App engine used to access App services. 28 | /// 29 | public static IEngine Current => Create(); 30 | 31 | /// 32 | /// Create a static instance of the App engine. 33 | /// 34 | [MethodImpl(MethodImplOptions.Synchronized)] 35 | public static IEngine Create() 36 | where T : IEngine 37 | { 38 | if (_context == null) 39 | { 40 | var engine = Activator.CreateInstance(); 41 | _context = new EngineContext(engine); 42 | } 43 | return (T)_context._installedEngine; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Exceptions/CommandValidationException.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Runtime.Serialization; 5 | 6 | namespace BinaryOrigin.SeedWork.Core 7 | { 8 | /// 9 | /// Exception wich can be used to throw error when command is not valid 10 | /// 11 | [Serializable] 12 | public class CommandValidationException : Exception 13 | { 14 | private readonly IEnumerable _validationErrors; 15 | 16 | public CommandValidationException(IEnumerable validationErrors) 17 | { 18 | _validationErrors = validationErrors; 19 | foreach (var validationError in _validationErrors) 20 | { 21 | Data[validationError.PropertyName] = validationError.ErrorMessage; 22 | } 23 | } 24 | 25 | protected CommandValidationException(SerializationInfo serializationInfo, StreamingContext streamingContext) 26 | : base(serializationInfo, streamingContext) 27 | { 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Exceptions/GeneralException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace BinaryOrigin.SeedWork.Core.Exceptions 5 | { 6 | /// 7 | /// 8 | /// The custom exception for validation errors 9 | /// 10 | [Serializable] 11 | public class GeneralException : Exception 12 | { 13 | /// 14 | /// 15 | /// Create new instance of Application validation error exception 16 | /// 17 | public GeneralException(string message) : base(message) 18 | { 19 | } 20 | 21 | public GeneralException(string messageFormat, params object[] args) 22 | : this(string.Format(messageFormat, args)) 23 | { 24 | } 25 | 26 | protected GeneralException(SerializationInfo serializationInfo, StreamingContext streamingContext) 27 | :base(serializationInfo,streamingContext) 28 | { 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Extensions/CommonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace BinaryOrigin.SeedWork.Core 7 | { 8 | /// 9 | /// Common extensions 10 | /// 11 | public static class CommonExtensions 12 | { 13 | /// 14 | /// Is null or default 15 | /// 16 | /// Type 17 | /// Value to evaluate 18 | /// Result 19 | public static bool IsNullOrDefault(this T? value) where T : struct 20 | { 21 | return default(T).Equals(value.GetValueOrDefault()); 22 | } 23 | 24 | public static bool IsNullOrDefault(this T value) where T : struct 25 | { 26 | return default(T).Equals(value); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Extensions/GuardExtensions.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Exceptions; 2 | using System; 3 | using System.Diagnostics; 4 | 5 | namespace BinaryOrigin.SeedWork.Core 6 | { 7 | [DebuggerStepThrough] 8 | public static class GuardExtensions 9 | { 10 | public static void ThrowIfNullOrEmpty(this string value, string argument) 11 | { 12 | if (string.IsNullOrWhiteSpace(value)) throw new GeneralException(argument); 13 | } 14 | 15 | public static void ThrowIfNullOrEmpty(this string value, string argument, string message) 16 | { 17 | if (string.IsNullOrWhiteSpace(value)) throw new GeneralException(argument, message); 18 | } 19 | 20 | public static void ThrowIfNull(this object @object, string argument) 21 | { 22 | if (@object == null) throw new GeneralException(argument); 23 | } 24 | 25 | public static void ThrowIfNull(this object @object, string argument, string message) 26 | { 27 | if (@object == null) throw new GeneralException(argument, message); 28 | } 29 | 30 | public static void ThrowIfNotAllowed(this T @value, T notAllowedValue, string argument, string message) 31 | where T : struct 32 | { 33 | if (@value.Equals(notAllowedValue)) 34 | { 35 | throw new ArgumentException(argument, message); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Extensions/LinqExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace System.Linq 4 | { 5 | public static class LinqExtensions 6 | { 7 | public static IEnumerable DistinctBy 8 | (this IEnumerable source, Func keySelector) 9 | { 10 | var seenKeys = new HashSet(); 11 | foreach (var element in source) 12 | { 13 | if (seenKeys.Add(keySelector(element))) 14 | { 15 | yield return element; 16 | } 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace BinaryOrigin.SeedWork.Core 5 | { 6 | public static class StringExtensions 7 | { 8 | public static bool EqualsIgnoreCase(this string str1, params string[] strings) 9 | { 10 | return strings != null && 11 | strings.All(str => string.Compare(str1, str, StringComparison.InvariantCultureIgnoreCase) == 0); 12 | } 13 | 14 | public static bool EqualsAnyIgnoreCase(this string str1, params string[] strings) 15 | { 16 | return strings != null && 17 | strings.Any(str => string.Compare(str1, str, StringComparison.InvariantCultureIgnoreCase) == 0); 18 | } 19 | 20 | public static bool IsNullOrEmpty(this string @string) 21 | { 22 | return string.IsNullOrWhiteSpace(@string); 23 | } 24 | 25 | /// 26 | /// Ensure that a string doesn't exceed maximum allowed length 27 | /// 28 | /// Input string 29 | /// Maximum length 30 | /// A string to add to the end if the original string was shorten 31 | /// Input string if its length is OK; otherwise, truncated input string 32 | public static string EnsureMaximumLength(this string str, int maxLength, string postfix = null) 33 | { 34 | if (string.IsNullOrEmpty(str)) 35 | return str; 36 | 37 | if (str.Length <= maxLength) 38 | return str; 39 | 40 | var pLen = postfix?.Length ?? 0; 41 | 42 | var result = str.Substring(0, maxLength - pLen); 43 | if (!string.IsNullOrEmpty(postfix)) 44 | { 45 | result += postfix; 46 | } 47 | 48 | return result; 49 | } 50 | 51 | /// 52 | /// Ensures that a string only contains numeric values 53 | /// 54 | /// Input string 55 | /// Input string with only numeric values, empty string if input is null/empty 56 | public static string EnsureNumericOnly(this string str) 57 | { 58 | return string.IsNullOrEmpty(str) ? string.Empty : new string(str.Where(char.IsDigit).ToArray()); 59 | } 60 | 61 | /// 62 | /// Ensure that a string is not null 63 | /// 64 | /// Input string 65 | /// Result 66 | public static string EnsureNotNull(this string str) 67 | { 68 | return str ?? string.Empty; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/IAppStartup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace BinaryOrigin.SeedWork.Core 5 | { 6 | /// 7 | /// Represents object for the configuring services and middleware on application startup 8 | /// 9 | public interface IAppStartup 10 | { 11 | /// 12 | /// Gets order of this startup configuration implementation 13 | /// 14 | int Order { get; } 15 | 16 | /// 17 | /// Add and configure any of the middleware 18 | /// 19 | /// Collection of service descriptors 20 | /// 21 | /// Configuration root of the application 22 | void ConfigureServices(IServiceCollection services, IEngine engine, IConfiguration configuration); 23 | } 24 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/IDataProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Common; 2 | 3 | namespace BinaryOrigin.SeedWork.Core 4 | { 5 | /// 6 | /// Represents a data provider 7 | /// 8 | public interface IDataProvider 9 | { 10 | /// 11 | /// Initialize database 12 | /// 13 | void InitializeDatabase(); 14 | 15 | /// 16 | /// Get a support database parameter object (used by stored procedures) 17 | /// 18 | /// Parameter 19 | DbParameter GetParameter(); 20 | 21 | void UpdateDatabase(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/IDependencyRegistration.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace BinaryOrigin.SeedWork.Core 6 | { 7 | /// 8 | /// Dependency registrar interface 9 | /// 10 | public interface IDependencyRegistration 11 | { 12 | /// 13 | /// Gets order of this dependency registrar implementation 14 | /// 15 | int Order { get; } 16 | 17 | /// 18 | /// Register services and interfaces 19 | /// 20 | /// 21 | /// Type finder 22 | /// Config 23 | void Register(ContainerBuilder builder, ITypeFinder typeFinder, IConfiguration config); 24 | } 25 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/IEngine.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | 8 | namespace BinaryOrigin.SeedWork.Core 9 | { 10 | /// 11 | /// Classes implementing this interface can serve as a portal for the various services composing the App engine. 12 | /// Edit functionality, modules and implementations access most App functionality through this interface. 13 | /// 14 | public interface IEngine 15 | { 16 | /// 17 | /// Initialize engine 18 | /// 19 | /// Collection of service descriptors 20 | /// File provider 21 | /// 22 | void Initialize(IAppFileProvider appFileProvider, IConfiguration configuration); 23 | 24 | /// 25 | /// Add and configure services 26 | /// 27 | /// Collection of service descriptors 28 | /// Configuration root of the application 29 | /// Service provider 30 | IServiceProvider ConfigureServices(IServiceCollection services); 31 | 32 | void Register(Action registerAction); 33 | 34 | 35 | /// 36 | /// Resolve dependency 37 | /// 38 | /// Type of resolved service 39 | /// Resolved service 40 | T Resolve() where T : class; 41 | 42 | /// 43 | /// Resolve generic type 44 | /// 45 | /// 46 | /// 47 | /// 48 | object Resolve(object param, Type type); 49 | 50 | /// 51 | /// Resolve dependency 52 | /// 53 | /// Type of resolved service 54 | /// Resolved service 55 | object Resolve(Type type); 56 | 57 | /// 58 | /// Resolve all the concrete implementation of type t 59 | /// 60 | /// Type of resolved services 61 | /// Collection of resolved services 62 | IEnumerable ResolveAll(); 63 | 64 | IEnumerable FindClassesOfType(Assembly assembly, bool onlyConcreteClasses = true); 65 | 66 | IEnumerable FindClassesOfType(bool onlyConcreteClasses = true); 67 | 68 | IEnumerable FindClassesOfType(Type type, bool onlyConcreteClasses = true); 69 | } 70 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/IMapperProfile.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Core 2 | { 3 | /// 4 | /// Mapper profile registrar interface 5 | /// 6 | public interface IMapperProfile 7 | { 8 | /// 9 | /// Gets order of this configuration implementation 10 | /// 11 | int Order { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/IPaginationService.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace BinaryOrigin.SeedWork.Core 7 | { 8 | public interface IPaginationService 9 | { 10 | /// 11 | /// Get a IQueryable source and execute apply pagination against it. 12 | /// It queries DB two times, first time to get the totatl count of elements 13 | /// and the second time to get items based on pageSize and PageIndex. 14 | /// 15 | /// 16 | /// IQueryable<> where paginated will be performed 17 | /// Current page index (0 based) 18 | /// Number of rows per page 19 | /// 20 | Task> PaginateAsync(IQueryable source, 21 | int pageIndex, 22 | int pageSize); 23 | /// 24 | /// Get a IQueryable source and execute apply pagination against it. 25 | /// It queries DB two times, first time to get the totatl count of elements 26 | /// and the second time to get items based on pageSize and PageIndex. 27 | /// After queries are executed a mapping oepration will be executed 28 | /// 29 | /// 30 | /// IQueryable<> where paginated will be performed 31 | /// Current page index (0 based) 32 | /// Number of rows per page 33 | /// 34 | Task> PaginateAsync(IQueryable source, 35 | int pageIndex, 36 | int pageSize) where TItemResult : class; 37 | } 38 | public class PaginatedItemsResult where TItemResult : class 39 | { 40 | public PaginatedItemsResult(IEnumerable data, int totalPages, long count) 41 | { 42 | TotalPages = totalPages; 43 | Count = count; 44 | Data = data; 45 | } 46 | 47 | public int TotalPages { get; private set; } 48 | public long Count { get; private set; } 49 | public IEnumerable Data { get; private set; } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/ITypeAdapter.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Core 2 | { 3 | /// 4 | /// Base contract for map dto to aggregate or aggregate to dto. 5 | /// 6 | /// This is a contract for work with "auto" mappers ( automapper,emitmapper,valueinjecter...) 7 | /// or adhoc mappers 8 | /// 9 | /// 10 | public interface ITypeAdapter 11 | { 12 | /// 13 | /// Adapt a source object to an instance of type 14 | /// 15 | /// Type of source item 16 | /// Type of target item 17 | /// Instance to adapt 18 | /// mapped to 19 | TTarget Adapt(TSource source) 20 | where TTarget : class 21 | where TSource : class; 22 | 23 | /// 24 | /// Adapt a source object to an instnace of type 25 | /// 26 | /// Type of target item 27 | /// Instance to adapt 28 | /// mapped to 29 | TTarget Adapt(object source) 30 | where TTarget : class; 31 | } 32 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/ITypeFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace BinaryOrigin.SeedWork.Core 6 | { 7 | /// 8 | /// Classes implementing this interface provide information about types 9 | /// to various services in the App engine. 10 | /// 11 | public interface ITypeFinder 12 | { 13 | /// 14 | /// Find classes of type 15 | /// 16 | /// Type 17 | /// A value indicating whether to find only concrete classes 18 | /// Result 19 | IEnumerable FindClassesOfType(bool onlyConcreteClasses = true); 20 | 21 | /// 22 | /// Find classes of type 23 | /// 24 | /// Assign type from 25 | /// A value indicating whether to find only concrete classes 26 | /// Result 27 | /// 28 | IEnumerable FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses = true); 29 | 30 | /// 31 | /// Find classes of type 32 | /// 33 | /// Type 34 | /// Assemblies 35 | /// A value indicating whether to find only concrete classes 36 | /// Result 37 | IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses = true); 38 | 39 | /// 40 | /// Find classes of type 41 | /// 42 | /// Assign type from 43 | /// Assemblies 44 | /// A value indicating whether to find only concrete classes 45 | /// Result 46 | IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses = true); 47 | 48 | /// 49 | /// Gets the assemblies related to the current implementation. 50 | /// 51 | /// A list of assemblies 52 | IList GetAssemblies(); 53 | } 54 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Interfaces/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace BinaryOrigin.SeedWork.Core 8 | { 9 | public interface IUnitOfWork 10 | { 11 | /// 12 | /// Saves all changes made in this context to the database 13 | /// 14 | /// The number of state entries written to the database 15 | Task SaveChangesAsync(CancellationToken cancellationToken = default); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Internal/AppTypeFinder.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | namespace BinaryOrigin.SeedWork.Core 7 | { 8 | /// 9 | /// Provides information about types in the current web application. 10 | /// Optionally this class can look at all assemblies in the bin folder. 11 | /// 12 | public class AppTypeFinder : AppDomainTypeFinder 13 | { 14 | private bool _binFolderAssembliesLoaded; 15 | 16 | public AppTypeFinder(IAppFileProvider fileProvider) : base(fileProvider) 17 | { 18 | } 19 | 20 | /// 21 | /// Gets or sets whether assemblies in the bin folder of the web application should be specifically checked for being loaded on application load. This is need in situations where plugins need to be loaded in the AppDomain after the application been reloaded. 22 | /// 23 | public bool EnsureBinFolderAssembliesLoaded { get; set; } = true; 24 | 25 | /// 26 | /// Gets a physical disk path of \Bin directory 27 | /// 28 | /// The physical path. E.g. "c:\inetpub\wwwroot\bin" 29 | public virtual string GetBinDirectory() 30 | { 31 | return AppContext.BaseDirectory; 32 | } 33 | 34 | /// 35 | /// Get assemblies 36 | /// 37 | /// Result 38 | public override IList GetAssemblies() 39 | { 40 | if (!EnsureBinFolderAssembliesLoaded || _binFolderAssembliesLoaded) 41 | return base.GetAssemblies(); 42 | 43 | _binFolderAssembliesLoaded = true; 44 | var binPath = GetBinDirectory(); 45 | LoadMatchingAssemblies(binPath); 46 | 47 | return base.GetAssemblies(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Core/Internal/TypeConverterHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | using System.Linq; 5 | 6 | namespace BinaryOrigin.SeedWork.Core.Helpers 7 | { 8 | public static class TypeConverterHelper 9 | { 10 | /// 11 | /// Converts a value to a destination type. 12 | /// 13 | /// The value to convert. 14 | /// The type to convert the value to. 15 | /// The converted value. 16 | public static object To(object value, Type destinationType) 17 | { 18 | return To(value, destinationType, CultureInfo.InvariantCulture); 19 | } 20 | 21 | /// 22 | /// Converts a value to a destination type. 23 | /// 24 | /// The value to convert. 25 | /// The type to convert the value to. 26 | /// Culture 27 | /// The converted value. 28 | public static object To(object value, Type destinationType, CultureInfo culture) 29 | { 30 | if (value == null) 31 | return null; 32 | 33 | var sourceType = value.GetType(); 34 | 35 | var destinationConverter = TypeDescriptor.GetConverter(destinationType); 36 | if (destinationConverter.CanConvertFrom(value.GetType())) 37 | return destinationConverter.ConvertFrom(null, culture, value); 38 | 39 | var sourceConverter = TypeDescriptor.GetConverter(sourceType); 40 | if (sourceConverter.CanConvertTo(destinationType)) 41 | return sourceConverter.ConvertTo(null, culture, value, destinationType); 42 | 43 | if (destinationType.IsEnum && value is int) 44 | return Enum.ToObject(destinationType, (int)value); 45 | 46 | if (!destinationType.IsInstanceOfType(value)) 47 | return Convert.ChangeType(value, destinationType, culture); 48 | 49 | return value; 50 | } 51 | 52 | /// 53 | /// Converts a value to a destination type. 54 | /// 55 | /// The value to convert. 56 | /// The type to convert the value to. 57 | /// The converted value. 58 | public static T To(object value) 59 | { 60 | return (T)To(value, typeof(T)); 61 | } 62 | 63 | /// 64 | /// Convert enum string to Enum Type 65 | /// 66 | /// 67 | /// 68 | /// 69 | public static T GetEnumValue(string enumName) 70 | { 71 | var values = Enum.GetNames(typeof(T)); 72 | 73 | var selected = values.FirstOrDefault(value => value.EqualsIgnoreCase(enumName)); 74 | 75 | if (selected == null) 76 | throw new ArgumentException($"Cannot parse value '{enumName}' in {typeof(T).FullName} "); 77 | 78 | return (T)Enum.Parse(typeof(T), selected); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Infrastructure/BinaryOrigin.SeedWork.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.1.1 6 | https://github.com/getson/MonolithicArchitecture 7 | https://github.com/getson/MonolithicArchitecture 8 | CQRS,DDD 9 | Getson Cela 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/BinaryOrigin.SeedWork.Messages.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.1.1 6 | https://github.com/getson/MonolithicArchitecture 7 | https://github.com/getson/MonolithicArchitecture 8 | CQRS,DDD 9 | Getson Cela 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Decorators/DecoratorOrderAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BinaryOrigin.SeedWork.Messages 4 | { 5 | public class DecoratorOrderAttribute : Attribute 6 | { 7 | public DecoratorOrderAttribute(int order) 8 | { 9 | Order = order; 10 | } 11 | 12 | public int Order { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Decorators/ICommandHandlerDecorator.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Messages 2 | { 3 | public interface ICommandHandlerDecorator 4 | : ICommandHandler 5 | where TCommand : ICommand 6 | { 7 | ICommandHandler Handler { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Decorators/IQueryHandlerDecorator.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Messages 2 | { 3 | public interface IQueryHandlerDecorator 4 | : IQueryHandler 5 | where TQuery : IQuery 6 | { 7 | IQueryHandler Handler { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/IBus.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace BinaryOrigin.SeedWork.Messages 4 | { 5 | public interface IBus 6 | { 7 | Task ExecuteAsync(ICommand command); 8 | 9 | Task QueryAsync(IQuery queryModel); 10 | 11 | Task PublishAsync(TEvent @event) where TEvent : IEvent; 12 | } 13 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/ICommandHandler.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using System.Threading.Tasks; 3 | 4 | namespace BinaryOrigin.SeedWork.Messages 5 | { 6 | public interface ICommandHandler 7 | where TCommand : ICommand 8 | { 9 | Task> HandleAsync(TCommand command); 10 | } 11 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/IEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace BinaryOrigin.SeedWork.Messages 4 | { 5 | /// 6 | /// This type should be used when we want to create handlers for events 7 | /// 8 | /// 9 | public interface IEventHandler where TMessage : IEvent 10 | { 11 | Task HandleAsync(TMessage message); 12 | } 13 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/ILocalizerService.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Messages 2 | { 3 | public interface ILocalizerService 4 | { 5 | string Localize(string message); 6 | } 7 | public class NullLocalizerService : ILocalizerService 8 | { 9 | public string Localize(string message) 10 | { 11 | return message; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/IQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using System.Threading.Tasks; 3 | 4 | namespace BinaryOrigin.SeedWork.Messages 5 | { 6 | public interface IQueryHandler 7 | where TQuery : IQuery 8 | { 9 | Task> HandleAsync(TQuery queryModel); 10 | } 11 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/ISequenceEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace BinaryOrigin.SeedWork.Messages 4 | { 5 | /// 6 | /// This type is neccessary to be used when we have more than one EventHandler for an event, 7 | /// and we want to process that in a specific order 8 | /// 9 | /// 10 | public interface ISequenceEventHandler where TMessage : IEvent 11 | { 12 | int Order { get; } 13 | Task HandleAsync(TMessage message); 14 | } 15 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Internal/Decorators/LocalizationCommandHandlerDecorator.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using System.Threading.Tasks; 3 | 4 | namespace BinaryOrigin.SeedWork.Messages.Decorators 5 | { 6 | [DecoratorOrder(2)] 7 | public class LocalizationCommandHandlerDecorator 8 | : ICommandHandlerDecorator 9 | where TCommand : ICommand 10 | { 11 | private readonly ILocalizerService _localizerService; 12 | 13 | public LocalizationCommandHandlerDecorator(ICommandHandler handler, 14 | ILocalizerService localizerService) 15 | { 16 | Handler = handler; 17 | _localizerService = localizerService; 18 | } 19 | 20 | public ICommandHandler Handler { get; } 21 | 22 | public async Task> HandleAsync(TCommand command) 23 | { 24 | var result = await Handler.HandleAsync(command).ConfigureAwait(false); 25 | if (result.IsFailure) 26 | { 27 | return Result.Fail( 28 | _localizerService.Localize(result.Error) 29 | ); 30 | } 31 | return result; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Internal/Decorators/LocalizationQueryHandlerDecorator.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using System.Threading.Tasks; 3 | 4 | namespace BinaryOrigin.SeedWork.Messages.Decorators 5 | { 6 | [DecoratorOrder(1)] 7 | public sealed class LocalizationQueryHandlerDecorator 8 | : IQueryHandlerDecorator 9 | where TQuery : IQuery 10 | { 11 | private readonly ILocalizerService _localizerService; 12 | 13 | public LocalizationQueryHandlerDecorator(IQueryHandler handler, 14 | ILocalizerService localizerService) 15 | { 16 | Handler = handler; 17 | _localizerService = localizerService; 18 | } 19 | 20 | public IQueryHandler Handler { get; } 21 | 22 | public async Task> HandleAsync(TQuery queryModel) 23 | { 24 | var result = await Handler.HandleAsync(queryModel).ConfigureAwait(false); 25 | if (result.IsFailure) 26 | { 27 | return Result.Fail( 28 | _localizerService.Localize(result.Error) 29 | ); 30 | } 31 | return result; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Internal/Decorators/ValidateCommandHandlerDecorator.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Core.Domain; 3 | using BinaryOrigin.SeedWork.Messages.Validation; 4 | using System.Threading.Tasks; 5 | 6 | namespace BinaryOrigin.SeedWork.Messages.Decorators 7 | { 8 | [DecoratorOrder(1)] 9 | public class ValidateCommandHandlerDecorator 10 | : ICommandHandlerDecorator 11 | where TCommand : ICommand 12 | { 13 | private readonly ICommandValidationProvider _validationProvider; 14 | 15 | public ValidateCommandHandlerDecorator(ICommandHandler handler, 16 | ICommandValidationProvider validationProvider) 17 | { 18 | Handler = handler; 19 | _validationProvider = validationProvider; 20 | } 21 | 22 | public ICommandHandler Handler { get; } 23 | 24 | public async Task> HandleAsync(TCommand command) 25 | { 26 | var validationResult = await _validationProvider.ValidateAsync(command); 27 | if (!validationResult.IsValid) 28 | { 29 | throw new CommandValidationException(validationResult.Errors); 30 | } 31 | return await Handler.HandleAsync(command).ConfigureAwait(false); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Internal/InMemoryBus.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Exceptions; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace BinaryOrigin.SeedWork.Messages 6 | { 7 | public sealed class InMemoryBus : IBus 8 | { 9 | private readonly IHandlerResolver _handlerResolver; 10 | 11 | public InMemoryBus(IHandlerResolver handlerResolver) 12 | { 13 | _handlerResolver = handlerResolver; 14 | } 15 | 16 | public async Task ExecuteAsync(ICommand command) 17 | { 18 | var handlerInstance = (dynamic)_handlerResolver.ResolveCommandHandler(command, typeof(ICommandHandler<,>)); 19 | 20 | var result = await handlerInstance.HandleAsync((dynamic)command); 21 | if (result.IsSuccess) 22 | { 23 | return result.Value; 24 | } 25 | 26 | throw new GeneralException(result.Error); 27 | } 28 | 29 | public async Task QueryAsync(IQuery query) 30 | { 31 | var handlerInstance = (dynamic)_handlerResolver.ResolveQueryHandler(query, typeof(IQueryHandler<,>)); 32 | 33 | var result = await handlerInstance.HandleAsync((dynamic)query); 34 | if (result.IsSuccess) 35 | { 36 | return result.Value; 37 | } 38 | throw new GeneralException(result.Error); 39 | } 40 | 41 | public async Task PublishAsync(TEvent @event) where TEvent : IEvent 42 | { 43 | var handlers = _handlerResolver.ResolveEventHandlers>(); 44 | var orderedHandlers = _handlerResolver.ResolveEventHandlers>() 45 | .OrderBy(x=>x.Order); 46 | 47 | foreach(var handler in orderedHandlers) 48 | { 49 | await handler.HandleAsync(@event); 50 | } 51 | foreach (var handler in handlers) 52 | { 53 | await handler.HandleAsync(@event); 54 | } 55 | } 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Internal/NullLocalizerService.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Messages.Decorators 2 | { 3 | public class NullLocalizerService : ILocalizerService 4 | { 5 | public string Localize(string message) 6 | { 7 | return message; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Internal/Resolvers/HandlerResolver.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace BinaryOrigin.SeedWork.Messages 7 | { 8 | public class HandlerResolver : IHandlerResolver 9 | { 10 | private readonly IEngine _engine; 11 | 12 | public HandlerResolver(IEngine engine) 13 | { 14 | _engine = engine; 15 | } 16 | 17 | public object ResolveHandler(Type handlerType) 18 | { 19 | return _engine.Resolve(handlerType); 20 | } 21 | 22 | public object ResolveCommandHandler(object param, Type type) 23 | { 24 | var commandType = param.GetType(); 25 | var commandInterface = commandType.GetInterfaces().Last(); 26 | var resultType = commandInterface.GetGenericArguments()[0]; 27 | var handlerType = type.MakeGenericType(commandType, resultType); 28 | 29 | return ResolveHandler(handlerType); 30 | } 31 | 32 | public object ResolveQueryHandler(object query, Type type) 33 | { 34 | var queryType = query.GetType(); 35 | var queryInterface = queryType.GetInterfaces()[0]; 36 | var resultType = queryInterface.GetGenericArguments()[0]; 37 | var handlerType = type.MakeGenericType(queryType, resultType); 38 | 39 | return ResolveHandler(handlerType); 40 | } 41 | 42 | public IEnumerable ResolveEventHandlers() 43 | { 44 | return _engine.ResolveAll(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Internal/Resolvers/IHandlerResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BinaryOrigin.SeedWork.Messages 5 | { 6 | public interface IHandlerResolver 7 | { 8 | object ResolveHandler(Type handlerType); 9 | 10 | object ResolveCommandHandler(object param, Type type); 11 | 12 | object ResolveQueryHandler(object query, Type type); 13 | 14 | IEnumerable ResolveEventHandlers(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/MessageTypes/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Messages 2 | { 3 | /// 4 | /// Kind of messages that perform an action or side effects in the system, 5 | /// it has only one handler 6 | /// 7 | /// 8 | public interface ICommand 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/MessageTypes/IEvent.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Messages 2 | { 3 | /// 4 | /// It is kind of message that notify that something has happened in the system. 5 | /// One event can have more than one handler 6 | /// 7 | public interface IEvent 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/MessageTypes/IQuery.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Messages 2 | { 3 | /// 4 | /// Kind of message that will tell the system to do a query. It has only one handler 5 | /// 6 | /// 7 | public interface IQuery 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Messages/Validation/ICommandValidationProvider.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace BinaryOrigin.SeedWork.Messages.Validation 6 | { 7 | public interface ICommandValidationProvider 8 | { 9 | Task ValidateAsync(ICommand command); 10 | } 11 | public class DefaultCommandValidationProvider : ICommandValidationProvider 12 | { 13 | public Task ValidateAsync(ICommand command) 14 | { 15 | throw new NotImplementedException("Validation is not supported! Please provider an implementation for ICommandValidationProvider"); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.MySql/BinaryOrigin.SeedWork.Persistence.Ef.MySql.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.MySql/BinaryOrigin.SeedWork.Persistence.MySql.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.1.2 6 | https://github.com/getson/MonolithicArchitecture 7 | https://github.com/getson/MonolithicArchitecture 8 | CQRS,DDD 9 | Getson Cela 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.MySql/MySqlDataProvider.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Persistence.Ef.Extensions; 3 | using MySql.Data.MySqlClient; 4 | using System.Data.Common; 5 | 6 | namespace BinaryOrigin.SeedWork.Persistence.Ef.MySql 7 | { 8 | /// 9 | /// Represents My Sql data provider 10 | /// 11 | public class MySqlDataProvider : IDataProvider 12 | { 13 | /// 14 | /// Initialize database 15 | /// 16 | public virtual void InitializeDatabase() 17 | { 18 | var context = EngineContext.Current.Resolve(); 19 | _ = EngineContext.Current.Resolve(); 20 | 21 | context.CreateDatabase(); 22 | } 23 | 24 | /// 25 | /// Get a support database parameter object (used by Stored procedures) 26 | /// 27 | /// Parameter 28 | public virtual DbParameter GetParameter() 29 | { 30 | return new MySqlParameter(); 31 | } 32 | 33 | /// 34 | /// Gets a value indicating whether this data provider supports backup 35 | /// 36 | public virtual bool BackupSupported => true; 37 | 38 | /// 39 | /// Gets a maximum length of the data for HASHBYTES functions, returns 0 if HASHBYTES function is not supported 40 | /// 41 | public virtual int SupportedLengthOfBinaryHash => 8000; //for My Sql 2008 and above HASHBYTES function has a limit of 8000 characters. 42 | 43 | protected virtual string MySqlScriptUpgradePath => "~/App_Data/Upgrade"; 44 | 45 | public void UpdateDatabase() 46 | { 47 | var context = EngineContext.Current.Resolve(); 48 | 49 | //update schema 50 | context.UpdateDatabase(); 51 | 52 | ExecuteUpgradeScripts(context); 53 | } 54 | 55 | private void ExecuteUpgradeScripts(IDbContext context) 56 | { 57 | var fileProvider = EngineContext.Current.Resolve(); 58 | var files = fileProvider.GetFiles(fileProvider.MapPath(MySqlScriptUpgradePath), "*.sql", false); 59 | foreach (var sqlFile in files) 60 | { 61 | context.ExecuteSqlScriptFromFile(sqlFile); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.MySql/MySqlDataProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using BinaryOrigin.SeedWork.Persistence.Ef; 3 | using BinaryOrigin.SeedWork.Persistence.Ef.MySql; 4 | using Microsoft.EntityFrameworkCore; 5 | using System; 6 | 7 | namespace BinaryOrigin.SeedWork.Core 8 | { 9 | public static class MySqlDataProviderExtensions 10 | { 11 | public static void AddDbContext(this IEngine engine, Func @delegate) 12 | where TContext : IDbContext 13 | { 14 | engine.Register(builder => 15 | { 16 | builder.RegisterType() 17 | .As() 18 | .SingleInstance(); 19 | builder.Register(instance => @delegate.Invoke()) 20 | .As() 21 | .InstancePerLifetimeScope(); 22 | }); 23 | } 24 | public static void AddDbContext(this IEngine engine, string connectionString) 25 | where TContext : EfObjectContext 26 | { 27 | var optionsBuilder = new DbContextOptionsBuilder(); 28 | optionsBuilder.UseMySQL(connectionString); 29 | 30 | engine.Register(builder => 31 | { 32 | builder.RegisterType() 33 | .As() 34 | .SingleInstance(); 35 | builder.Register(instance => Activator.CreateInstance(typeof(TContext), new object[] { optionsBuilder.Options })) 36 | .As() 37 | .InstancePerLifetimeScope(); 38 | }); 39 | } 40 | 41 | public static void AddDbContext(this IEngine engine, DbContextOptions options) 42 | where TContext : EfObjectContext 43 | { 44 | engine.Register(builder => 45 | { 46 | builder.RegisterType() 47 | .As() 48 | .SingleInstance(); 49 | builder.Register(instance => Activator.CreateInstance(typeof(TContext), new object[] { options })) 50 | .As() 51 | .InstancePerLifetimeScope(); 52 | }); 53 | } 54 | 55 | public static void AddMySqlDbExceptionParser(this IEngine engine, DbErrorMessagesConfiguration errorMessagesConfig) 56 | { 57 | engine.Register(builder => 58 | { 59 | builder.Register(x => new MySqlDbExeptionParserProvider(errorMessagesConfig)) 60 | .As() 61 | .SingleInstance(); 62 | }); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.MySql/MySqlDbExeptionParserProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using MySql.Data.MySqlClient; 3 | using System; 4 | 5 | namespace BinaryOrigin.SeedWork.Persistence.Ef.MySql 6 | { 7 | public class MySqlDbExeptionParserProvider : DbExceptionParser, IDbExceptionParserProvider 8 | { 9 | public const int MySqlViolationOfUniqueIndex = 1062; 10 | private readonly DbErrorMessagesConfiguration _errorMessages; 11 | 12 | public MySqlDbExeptionParserProvider(DbErrorMessagesConfiguration errorMessages) 13 | { 14 | _errorMessages = errorMessages ?? throw new ArgumentNullException(nameof(errorMessages)); 15 | } 16 | 17 | public override string Parse(Exception e) 18 | { 19 | var dbUpdateEx = e as DbUpdateException; 20 | 21 | if (dbUpdateEx?.InnerException is MySqlException mySqlEx) 22 | { 23 | //This is a DbUpdateException on a SQL database 24 | 25 | if (mySqlEx.Number == MySqlViolationOfUniqueIndex) 26 | { 27 | //We have an error we can process 28 | var valError = ParseUniquenessError(mySqlEx.Message, _errorMessages.UniqueErrorTemplate, _errorMessages.CombinationUniqueErrorTemplate); 29 | if (valError != null) 30 | { 31 | return valError; 32 | } 33 | } 34 | } 35 | //TODO: add other types of exception we can handle 36 | //otherwise exception wasn't handled, so return null 37 | return null; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.SqlServer/BinaryOrigin.SeedWork.Persistence.SqlServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.1.3 6 | https://github.com/getson/MonolithicArchitecture 7 | https://github.com/getson/MonolithicArchitecture 8 | CQRS,DDD 9 | Getson Cela 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.SqlServer/SqlDataProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using BinaryOrigin.SeedWork.Persistence.Ef; 3 | using BinaryOrigin.SeedWork.Persistence.SqlServer; 4 | using Microsoft.EntityFrameworkCore; 5 | using System; 6 | 7 | namespace BinaryOrigin.SeedWork.Core 8 | { 9 | public static class SqlDataProviderExtensions 10 | { 11 | public static void AddDbContext(this IEngine engine, Func @delegate) 12 | where TContext : IDbContext 13 | { 14 | engine.Register(builder => 15 | { 16 | builder.RegisterType() 17 | .As() 18 | .SingleInstance(); 19 | builder.Register(instance => @delegate.Invoke()) 20 | .As() 21 | .InstancePerLifetimeScope(); 22 | }); 23 | } 24 | public static void AddDbContext(this IEngine engine, string connectionString) 25 | where TContext : EfObjectContext 26 | { 27 | var optionsBuilder = new DbContextOptionsBuilder(); 28 | optionsBuilder.UseSqlServer(connectionString); 29 | 30 | engine.Register(builder => 31 | { 32 | builder.RegisterType() 33 | .As() 34 | .SingleInstance(); 35 | builder.Register(instance => Activator.CreateInstance(typeof(TContext), new object[] { optionsBuilder.Options })) 36 | .As() 37 | .InstancePerLifetimeScope(); 38 | }); 39 | } 40 | 41 | public static void AddDbContext(this IEngine engine, DbContextOptions options) 42 | where TContext : EfObjectContext 43 | { 44 | engine.Register(builder => 45 | { 46 | builder.RegisterType() 47 | .As() 48 | .SingleInstance(); 49 | builder.Register(instance => Activator.CreateInstance(typeof(TContext), new object[] { options })) 50 | .As() 51 | .InstancePerLifetimeScope(); 52 | }); 53 | } 54 | /// 55 | /// Add default sql server exception parser which will be executed in case of a DbUpdateException, 56 | /// The goal of this parser is to build a human error message based on constraint names and error message; 57 | /// 58 | /// 59 | /// 60 | public static void AddSqlServerDbExceptionParser(this IEngine engine, DbErrorMessagesConfiguration errorMessagesConfig) 61 | { 62 | engine.Register(builder => 63 | { 64 | builder.Register(x => new SqlServerDbExceptionParserProvider(errorMessagesConfig)) 65 | .As() 66 | .SingleInstance(); 67 | }); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.SqlServer/SqlServerDataProvider.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Persistence.Ef; 3 | using BinaryOrigin.SeedWork.Persistence.Ef.Extensions; 4 | using Microsoft.Data.SqlClient; 5 | using System.Data.Common; 6 | 7 | namespace BinaryOrigin.SeedWork.Persistence.SqlServer 8 | { 9 | /// 10 | /// Represents SQL Server data provider 11 | /// 12 | public class SqlServerDataProvider : IDataProvider 13 | { 14 | /// 15 | /// Initialize database 16 | /// 17 | public virtual void InitializeDatabase() 18 | { 19 | var context = EngineContext.Current.Resolve(); 20 | var fileProvider = EngineContext.Current.Resolve(); 21 | 22 | context.CreateDatabase(); 23 | 24 | //create indexes 25 | context.ExecuteSqlScriptFromFile(fileProvider.MapPath(SqlServerIndexesFilePath)); 26 | 27 | //create Stored procedures 28 | context.ExecuteSqlScriptFromFile(fileProvider.MapPath(SqlServerStoredProceduresFilePath)); 29 | } 30 | 31 | /// 32 | /// Get a support database parameter object (used by Stored procedures) 33 | /// 34 | /// Parameter 35 | public virtual DbParameter GetParameter() 36 | { 37 | return new SqlParameter(); 38 | } 39 | 40 | /// 41 | /// Gets a path to the file that contains script to create SQL Server indexes 42 | /// 43 | protected virtual string SqlServerIndexesFilePath => "~/App_Data/Install/SqlServer.Indexes.sql"; 44 | 45 | /// 46 | /// Gets a path to the file that contains script to create SQL Server Stored procedures 47 | /// 48 | protected virtual string SqlServerStoredProceduresFilePath => "~/App_Data/Install/SqlServer.StoredProcedures.sql"; 49 | 50 | protected virtual string SqlServerScriptUpgradePath => "~/App_Data/Upgrade"; 51 | 52 | public void UpdateDatabase() 53 | { 54 | var context = EngineContext.Current.Resolve(); 55 | 56 | //update schema 57 | context.UpdateDatabase(); 58 | 59 | ExecuteUpgradeScripts(context); 60 | } 61 | 62 | private void ExecuteUpgradeScripts(IDbContext context) 63 | { 64 | var fileProvider = EngineContext.Current.Resolve(); 65 | var files = fileProvider.GetFiles(fileProvider.MapPath(SqlServerScriptUpgradePath), "*.sql", false); 66 | foreach (var sqlFile in files) 67 | { 68 | context.ExecuteSqlScriptFromFile(sqlFile); 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence.SqlServer/SqlServerDbExceptionParserProvider.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Persistence.Ef; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Data.SqlClient; 4 | using System; 5 | 6 | namespace BinaryOrigin.SeedWork.Persistence.SqlServer 7 | { 8 | public class SqlServerDbExceptionParserProvider : DbExceptionParser, IDbExceptionParserProvider 9 | { 10 | public const int SqlServerViolationOfUniqueIndex = 2601; 11 | public const int SqlServerViolationOfUniqueConstraint = 2627; 12 | private readonly DbErrorMessagesConfiguration _errorMessages; 13 | 14 | public SqlServerDbExceptionParserProvider(DbErrorMessagesConfiguration errorMessages) 15 | { 16 | _errorMessages = errorMessages ?? throw new ArgumentNullException(nameof(errorMessages)); 17 | } 18 | 19 | public override string Parse(Exception e) 20 | { 21 | var dbUpdateEx = e as DbUpdateException; 22 | 23 | if (dbUpdateEx?.InnerException is SqlException sqlEx) 24 | { 25 | //This is a DbUpdateException on a SQL database 26 | 27 | if (sqlEx.Number == SqlServerViolationOfUniqueConstraint 28 | || sqlEx.Number == SqlServerViolationOfUniqueIndex) 29 | { 30 | //We have an error we can process 31 | var valError = ParseUniquenessError(sqlEx.Message, _errorMessages.UniqueErrorTemplate, _errorMessages.CombinationUniqueErrorTemplate); 32 | if (valError != null) 33 | { 34 | return valError; 35 | } 36 | } 37 | } 38 | //TODO: add other types of exception we can handle 39 | //otherwise exception wasn't handled, so return null 40 | return null; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/BinaryOrigin.SeedWork.Persistence.Ef.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.1.3 6 | https://github.com/getson/MonolithicArchitecture 7 | https://github.com/getson/MonolithicArchitecture 8 | CQRS,DDD 9 | Getson Cela 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/DbErrorMessagesConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace BinaryOrigin.SeedWork.Persistence.Ef 2 | { 3 | public class DbErrorMessagesConfiguration 4 | { 5 | public string UniqueErrorTemplate { get; set; } 6 | public string CombinationUniqueErrorTemplate { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/DbExceptionParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace BinaryOrigin.SeedWork.Persistence.Ef 7 | { 8 | public abstract class DbExceptionParser : IDbExceptionParserProvider 9 | { 10 | protected virtual Regex UniqueConstraintRegex => new Regex("'([a-zA-Z0-9]*)_([a-zA-Z0-9]*)_([a-zA-Z0-9]*)_?([a-zA-Z0-9]*)?_?([a-zA-Z0-9]*)?_?([a-zA-Z0-9]*)?'", RegexOptions.Compiled); 11 | 12 | public virtual string ParseUniquenessError(string dbErrorMessage, string uniqueErrorTemplate, string combinationUniqueErrorTemplate) 13 | { 14 | var matches = UniqueConstraintRegex.Matches(dbErrorMessage); 15 | if (matches.Count == 0) 16 | { 17 | return null; 18 | } 19 | 20 | var matchedStrings = matches[0].Groups.Cast() 21 | .Select(g => g.Value).Where(v => !string.IsNullOrWhiteSpace(v)) 22 | .Skip(3) // skip name of the constraint + IV + table 23 | .ToArray(); 24 | 25 | if (matchedStrings.Length == 1) 26 | { 27 | return string.Format(uniqueErrorTemplate, matchedStrings[0]); 28 | } 29 | else 30 | { 31 | var combinationFields = new StringBuilder(); 32 | for (var i = 0; i < matchedStrings.Length; i++) 33 | { 34 | var uniqueField = matchedStrings[i]; 35 | var isIdSuffix = uniqueField.EndsWith("Id", StringComparison.CurrentCultureIgnoreCase); 36 | if (isIdSuffix) 37 | { 38 | combinationFields.Append(uniqueField.Remove(uniqueField.Length - 2)); 39 | } 40 | else 41 | { 42 | combinationFields.Append(uniqueField); 43 | } 44 | if (i != matchedStrings.Length - 1) 45 | { 46 | combinationFields.Append(", "); 47 | } 48 | } 49 | return string.Format(combinationUniqueErrorTemplate, combinationFields); 50 | } 51 | } 52 | 53 | public abstract string Parse(Exception e); 54 | } 55 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/EfEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using Inflector; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 5 | using System.ComponentModel.DataAnnotations.Schema; 6 | using System.Globalization; 7 | 8 | namespace BinaryOrigin.SeedWork.Persistence.Ef 9 | { 10 | /// 11 | /// Represents base entity mapping configuration 12 | /// 13 | /// Entity type 14 | public abstract class EfEntityTypeConfiguration : IMappingConfiguration, IEntityTypeConfiguration 15 | where TEntity : BaseEntity 16 | { 17 | static EfEntityTypeConfiguration() 18 | { 19 | Inflector.Inflector.SetDefaultCultureFunc = () => new CultureInfo("en-us"); 20 | } 21 | 22 | /// 23 | /// Developers should override this method in custom classes in order to apply their custom configuration code 24 | /// 25 | /// The builder to be used to configure the entity 26 | public abstract void PostConfigure(EntityTypeBuilder builder); 27 | 28 | /// 29 | /// Configures the entity 30 | /// 31 | /// The builder to be used to configure the entity 32 | public virtual void Configure(EntityTypeBuilder builder) 33 | { 34 | string tblName = typeof(TEntity).Name; 35 | var customTableAttribute = typeof(TEntity).GetCustomAttributes(false); 36 | if (customTableAttribute.Length > 0) 37 | tblName = ((TableAttribute)customTableAttribute[0]).Name; 38 | 39 | builder.ToTable(tblName.Pluralize()); 40 | //add custom configuration 41 | PostConfigure(builder); 42 | } 43 | 44 | /// 45 | /// Apply this mapping configuration 46 | /// 47 | /// The builder being used to construct the model for the database context 48 | public virtual void ApplyConfiguration(ModelBuilder modelBuilder) 49 | { 50 | modelBuilder.ApplyConfiguration(this); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/EfRepository.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Core.Domain; 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace BinaryOrigin.SeedWork.Persistence.Ef 10 | { 11 | public class EfRepository : IRepository where TEntity : BaseEntity 12 | { 13 | private DbSet _entities; 14 | protected readonly IDbContext _dbContext; 15 | 16 | public EfRepository(IDbContext dbContext) 17 | { 18 | _dbContext = dbContext; 19 | } 20 | 21 | /// 22 | /// Gets a table 23 | /// 24 | public virtual IQueryable Table => Entities; 25 | 26 | /// 27 | /// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations 28 | /// 29 | public virtual IQueryable TableNoTracking => Entities.AsNoTracking(); 30 | 31 | /// 32 | /// Gets an entity set 33 | /// 34 | protected virtual DbSet Entities => _entities ?? (_entities = _dbContext.Set()); 35 | 36 | public async Task> GetAllAsync() 37 | { 38 | return await Entities.ToListAsync(); 39 | } 40 | 41 | public async Task CreateAsync(TEntity entity) 42 | { 43 | Entities.Add(entity); 44 | await _dbContext.SaveChangesAsync(); 45 | } 46 | 47 | public async Task CreateAsync(IEnumerable entities) 48 | { 49 | Entities.AddRange(entities); 50 | await _dbContext.SaveChangesAsync(); 51 | } 52 | 53 | public async virtual Task> GetByIdAsync(Guid id) 54 | { 55 | return Maybe.From( 56 | await Entities.SingleOrDefaultAsync(t => t.Id == id)); 57 | } 58 | 59 | /// 60 | /// Update entity 61 | /// 62 | /// Entity 63 | public async virtual Task UpdateAsync(TEntity entity) 64 | { 65 | Entities.Update(entity); 66 | await _dbContext.SaveChangesAsync(); 67 | } 68 | 69 | public async Task UpdateAsync(IEnumerable entities) 70 | { 71 | Entities.UpdateRange(entities); 72 | await _dbContext.SaveChangesAsync(); 73 | } 74 | 75 | public async virtual Task DeleteAsync(TEntity entity) 76 | { 77 | Entities.Remove(entity); 78 | await _dbContext.SaveChangesAsync(); 79 | } 80 | 81 | public async Task ExistsAsync(Guid id) 82 | { 83 | return (await Entities.SingleOrDefaultAsync(x => x.Id == id)) != null; 84 | } 85 | public IUnitOfWork UnitOfWork => _dbContext; 86 | } 87 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/Extensions/DbContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core.Domain; 2 | using Microsoft.EntityFrameworkCore; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.ComponentModel.DataAnnotations; 7 | using System.IO; 8 | using System.Text; 9 | using System.Text.RegularExpressions; 10 | 11 | namespace BinaryOrigin.SeedWork.Persistence.Ef.Extensions 12 | { 13 | /// 14 | /// Represents database context extensions 15 | /// 16 | public static class DbContextExtensions 17 | { 18 | private static readonly ConcurrentDictionary _tableNames = new ConcurrentDictionary(); 19 | 20 | /// 21 | /// Get SQL commands from the script 22 | /// 23 | /// SQL script 24 | /// List of commands 25 | private static IList GetCommandsFromScript(string sql) 26 | { 27 | var commands = new List(); 28 | 29 | //origin from the Microsoft.EntityFrameworkCore.Migrations.SqlServerMigrationsSqlGenerator.Generate method 30 | sql = Regex.Replace(sql, @"\\\r?\n", string.Empty); 31 | var batches = Regex.Split(sql, @"^\s*(GO[ \t]+[0-9]+|GO)(?:\s+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline); 32 | 33 | for (var i = 0; i < batches.Length; i++) 34 | { 35 | if (string.IsNullOrWhiteSpace(batches[i]) || batches[i].StartsWith("GO", StringComparison.OrdinalIgnoreCase)) 36 | continue; 37 | 38 | var count = 1; 39 | if (i != batches.Length - 1 && batches[i + 1].StartsWith("GO", StringComparison.OrdinalIgnoreCase)) 40 | { 41 | var match = Regex.Match(batches[i + 1], "([0-9]+)"); 42 | if (match.Success) 43 | count = int.Parse(match.Value); 44 | } 45 | 46 | var builder = new StringBuilder(); 47 | for (var j = 0; j < count; j++) 48 | { 49 | builder.Append(batches[i]); 50 | if (i == batches.Length - 1) 51 | builder.AppendLine(); 52 | } 53 | 54 | commands.Add(builder.ToString()); 55 | } 56 | 57 | return commands; 58 | } 59 | 60 | /// 61 | /// Get table name of entity 62 | /// 63 | /// Entity type 64 | /// Database context 65 | /// Table name 66 | public static string GetTableName(this IDbContext context) where TEntity : BaseEntity 67 | { 68 | if (context == null) 69 | throw new ValidationException(nameof(context)); 70 | 71 | //try to get the EF database context 72 | if (!(context is DbContext dbContext)) 73 | throw new InvalidOperationException("Context does not support operation"); 74 | 75 | var entityTypeFullName = typeof(TEntity).FullName; 76 | if (!_tableNames.ContainsKey(entityTypeFullName)) 77 | { 78 | //get entity type 79 | var entityType = dbContext.Model.FindEntityType(typeof(TEntity)); 80 | 81 | //get the name of the table to which the entity type is mapped 82 | _tableNames.TryAdd(entityTypeFullName, entityType.GetTableName()); 83 | } 84 | 85 | _tableNames.TryGetValue(entityTypeFullName, out var tableName); 86 | 87 | return tableName; 88 | } 89 | 90 | /// 91 | /// Get database name 92 | /// 93 | /// Database context 94 | /// Database name 95 | public static string DbName(this IDbContext context) 96 | { 97 | if (context == null) 98 | throw new ValidationException(nameof(context)); 99 | 100 | //try to get the EF database context 101 | if (!(context is DbContext dbContext)) 102 | throw new InvalidOperationException("Context does not support operation"); 103 | 104 | return dbContext.Database.GetDbConnection().Database; 105 | } 106 | 107 | /// 108 | /// Execute commands from the SQL script against the context database 109 | /// 110 | /// Database context 111 | /// SQL script 112 | public static void ExecuteSqlScript(this IDbContext context, string sql) 113 | { 114 | if (context == null) 115 | throw new ValidationException(nameof(context)); 116 | 117 | var sqlCommands = GetCommandsFromScript(sql); 118 | foreach (var command in sqlCommands) 119 | context.ExecuteSqlCommandAsync(command); 120 | } 121 | 122 | /// 123 | /// Execute commands from a file with SQL script against the context database 124 | /// 125 | /// Database context 126 | /// Path to the file 127 | public static void ExecuteSqlScriptFromFile(this IDbContext context, string filePath) 128 | { 129 | if (context == null) 130 | throw new ValidationException(nameof(context)); 131 | 132 | if (!File.Exists(filePath)) 133 | return; 134 | 135 | context.ExecuteSqlScript(File.ReadAllText(filePath)); 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/Extensions/EngineExtensions.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using BinaryOrigin.SeedWork.Core.Domain; 3 | using BinaryOrigin.SeedWork.Persistence.Ef; 4 | using System; 5 | using System.Linq; 6 | 7 | namespace BinaryOrigin.SeedWork.Core 8 | { 9 | public static class EngineExtensions 10 | { 11 | 12 | public static void AddDefaultPagination(this IEngine engine, Action option) 13 | { 14 | var paginationOptions = new PaginationOptions(); 15 | option.Invoke(paginationOptions); 16 | 17 | engine.Register(builder => 18 | { 19 | builder.Register(c => new PaginationService(c.Resolve(), paginationOptions)) 20 | .As() 21 | .SingleInstance(); 22 | }); 23 | } 24 | public static void AddRepositories(this IEngine engine) 25 | { 26 | engine.Register(builder => 27 | { 28 | var assembliesWithRepositories = engine.FindClassesOfType(typeof(IRepository<>)) 29 | .Where(x => !x.AssemblyQualifiedName.Contains("SeedWork")) 30 | .Select(x => x.Assembly) 31 | .DistinctBy(x => x.FullName) 32 | .ToList(); 33 | 34 | foreach (var asm in assembliesWithRepositories) 35 | { 36 | builder.RegisterAssemblyTypes(asm) 37 | .AsClosedTypesOf(typeof(IRepository<>)) 38 | .InstancePerLifetimeScope(); 39 | } 40 | }); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/IDbContext.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Core.Domain; 3 | using Microsoft.EntityFrameworkCore; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace BinaryOrigin.SeedWork.Persistence.Ef 8 | { 9 | /// 10 | /// Represents DB context 11 | /// 12 | public interface IDbContext : IUnitOfWork 13 | { 14 | /// 15 | /// Creates a DbSet that can be used to query and save instances of entity 16 | /// 17 | /// A set for the given entity type 18 | DbSet Set() where TEntity : BaseEntity; 19 | 20 | /// 21 | /// Generate a script to create all tables for the current model 22 | /// 23 | /// A SQL script 24 | string GenerateCreateScript(); 25 | 26 | /// 27 | /// Create the database 28 | /// 29 | bool CreateDatabase(); 30 | 31 | /// 32 | /// Update database based on migrations 33 | /// 34 | void UpdateDatabase(); 35 | 36 | bool IsDatabaseUpdated(); 37 | 38 | /// 39 | /// Executes the given SQL against the database in async mode 40 | /// 41 | /// The SQL to execute 42 | /// true - the transaction creation is not ensured; false - the transaction creation is ensured. 43 | /// The timeout to use for command. Note that the command timeout is distinct from the connection timeout, which is commonly set on the database connection string 44 | /// Parameters to use with the SQL 45 | /// The number of rows affected 46 | Task ExecuteSqlCommandAsync(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters); 47 | /// 48 | /// Executes the given SQL against the database 49 | /// 50 | /// The SQL to execute 51 | /// true - the transaction creation is not ensured; false - the transaction creation is ensured. 52 | /// The timeout to use for command. Note that the command timeout is distinct from the connection timeout, which is commonly set on the database connection string 53 | /// Parameters to use with the SQL 54 | /// The number of rows affected 55 | int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters); 56 | /// 57 | /// Detach an entity from the context 58 | /// 59 | /// Entity type 60 | /// Entity 61 | void Detach(TEntity entity) where TEntity : BaseEntity; 62 | } 63 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/IDbExceptionParserProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BinaryOrigin.SeedWork.Persistence.Ef 4 | { 5 | public interface IDbExceptionParserProvider 6 | { 7 | /// 8 | /// Parse exception raised by DB provider 9 | /// 10 | /// exception object 11 | /// Error message extracted from the exception 12 | string Parse(Exception e); 13 | } 14 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/IMappingConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace BinaryOrigin.SeedWork.Persistence.Ef 4 | { 5 | /// 6 | /// Represents database context model mapping configuration 7 | /// 8 | public interface IMappingConfiguration 9 | { 10 | /// 11 | /// Apply this mapping configuration 12 | /// 13 | /// The builder being used to construct the model for the database context 14 | void ApplyConfiguration(ModelBuilder modelBuilder); 15 | } 16 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.Persistence/PaginationService.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Core.Domain; 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace BinaryOrigin.SeedWork.Persistence.Ef 10 | { 11 | public class PaginationService : IPaginationService 12 | { 13 | private readonly ITypeAdapter _typeAdapter; 14 | private readonly PaginationOptions _options; 15 | 16 | public PaginationService(ITypeAdapter typeAdapter, PaginationOptions options) 17 | { 18 | _typeAdapter = typeAdapter; 19 | _options = options; 20 | } 21 | 22 | public async Task> PaginateAsync 23 | ( 24 | IQueryable source, 25 | int pageIndex, 26 | int pageSize 27 | ) 28 | { 29 | if (pageIndex < 0) 30 | { 31 | pageIndex = 0; 32 | } 33 | 34 | pageSize = pageSize == 0 ? _options.DefaultPageSize : Math.Min(pageSize, _options.MaxPageSizeAllowed); 35 | 36 | var count = await source.LongCountAsync(); 37 | 38 | var data = await source.Skip(pageIndex * pageSize) 39 | .Take(pageSize) 40 | .ToListAsync(); 41 | 42 | return new PaginatedItemsResult 43 | ( 44 | data, 45 | (int)Math.Floor((decimal)count / pageSize), 46 | count 47 | ); 48 | } 49 | 50 | public async Task> PaginateAsync 51 | ( 52 | IQueryable source, 53 | int pageIndex, 54 | int pageSize 55 | ) where TItemResult : class 56 | { 57 | var paginatedResult = await PaginateAsync(source, pageIndex, pageSize); 58 | 59 | return new PaginatedItemsResult 60 | ( 61 | _typeAdapter.Adapt>(paginatedResult.Data), 62 | paginatedResult.TotalPages, 63 | paginatedResult.Count 64 | ); 65 | } 66 | } 67 | 68 | public class PaginationOptions 69 | { 70 | /// 71 | /// Default is 20 72 | /// 73 | public int MaxPageSizeAllowed { get; set; } = 20; 74 | 75 | public int DefaultPageSize { get; set; } = 20; 76 | } 77 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Authorization/HasScopeHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace BinaryOrigin.SeedWork.WebApi.Authorization 6 | { 7 | /// 8 | /// This handler will be called for evaluating if the user has the specific 9 | /// scope(permission) in its claims 10 | /// 11 | public class HasScopeHandler : AuthorizationHandler 12 | { 13 | private readonly string _issuer; 14 | /// 15 | /// 16 | /// 17 | /// Auth0 tenant 18 | public HasScopeHandler(string issuer) 19 | { 20 | _issuer = issuer; 21 | } 22 | /// 23 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement) 24 | { 25 | // If user does not have the scope claim, get out of here 26 | if (!context.User.HasClaim(c => c.Type == "scope" && c.Issuer == _issuer)) 27 | return Task.CompletedTask; 28 | 29 | // Split the scopes string into an array 30 | var scope = context.User.FindFirst(c => c.Type == "scope" 31 | && c.Issuer == _issuer 32 | && c.Value == requirement.Scope); 33 | 34 | // Succeed if the scope exists 35 | if (scope != null) 36 | context.Succeed(requirement); 37 | 38 | return Task.CompletedTask; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Authorization/HasScopeRequirement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using System; 3 | 4 | namespace BinaryOrigin.SeedWork.WebApi.Authorization 5 | { 6 | public class HasScopeRequirement : IAuthorizationRequirement 7 | { 8 | public string Scope { get; } 9 | 10 | /// 11 | /// 12 | /// 13 | /// 14 | public HasScopeRequirement(string scope) 15 | { 16 | Scope = scope ?? throw new ArgumentNullException(nameof(scope)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Authorization/ScopeAuthorizationPolicyProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.Extensions.Options; 3 | using System.Threading.Tasks; 4 | 5 | namespace BinaryOrigin.SeedWork.WebApi.Authorization 6 | { 7 | /// 8 | /// Authorization policy provider to automatically turn all permissions of a user into a ASP.NET Core authorization policy 9 | /// 10 | /// 11 | public class ScopeAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The options. 17 | public ScopeAuthorizationPolicyProvider(IOptions options) 18 | : base(options) 19 | { 20 | } 21 | 22 | /// 23 | /// Gets a from the given 24 | /// 25 | /// The policy name to retrieve. 26 | /// 27 | /// The named . 28 | /// 29 | public async override Task GetPolicyAsync(string policyName) 30 | { 31 | // check static policies first 32 | var policy = await base.GetPolicyAsync(policyName); 33 | 34 | if (policy == null) 35 | { 36 | policy = new AuthorizationPolicyBuilder() 37 | .AddRequirements(new HasScopeRequirement(policyName)) 38 | .Build(); 39 | } 40 | 41 | return policy; 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Authorization/TestAuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.Extensions.Options; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Security.Claims; 8 | using System.Text.Encodings.Web; 9 | using System.Threading.Tasks; 10 | 11 | namespace BinaryOrigin.SeedWork.WebApi.Authorization 12 | { 13 | public class TestAuthenticationHandler : AuthenticationHandler 14 | { 15 | public TestAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, 16 | UrlEncoder encoder, ISystemClock clock) 17 | : base(options, logger, encoder, clock) 18 | { 19 | } 20 | 21 | protected override Task HandleAuthenticateAsync() 22 | { 23 | var authenticationTicket = new AuthenticationTicket( 24 | new ClaimsPrincipal(Options.Identity), 25 | new AuthenticationProperties(), 26 | "Test Scheme"); 27 | 28 | return Task.FromResult(AuthenticateResult.Success(authenticationTicket)); 29 | } 30 | } 31 | 32 | public static class TestAuthenticationExtensions 33 | { 34 | public static AuthenticationBuilder AddTestAuth(this AuthenticationBuilder builder, Action configureOptions) 35 | { 36 | return builder.AddScheme("Test Scheme", "Test Auth", configureOptions); 37 | } 38 | } 39 | 40 | public class TestAuthenticationOptions : AuthenticationSchemeOptions 41 | { 42 | public IEnumerable Scopes { get; set; } 43 | public string Authority { get; set; } 44 | public virtual ClaimsIdentity Identity => new ClaimsIdentity(GetScopes()); 45 | 46 | private IEnumerable GetScopes() 47 | { 48 | var scopes = Scopes.Select(s => 49 | new Claim("scope",s, ClaimValueTypes.String, Authority) 50 | ).ToList(); 51 | 52 | scopes.Add(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Guid.NewGuid().ToString())); 53 | 54 | return scopes; 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/BinaryOrigin.SeedWork.WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.1.3 6 | https://github.com/getson/MonolithicArchitecture 7 | https://github.com/getson/MonolithicArchitecture 8 | CQRS,DDD 9 | Getson Cela 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Controllers/AppController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | 4 | namespace BinaryOrigin.SeedWork.WebApi.Controllers 5 | { 6 | [ApiController] 7 | public class AppController : ControllerBase 8 | { 9 | /// 10 | /// return status code 204 (NoContent) 11 | /// 12 | /// 13 | [ProducesResponseType(204)] 14 | protected IActionResult Deleted() 15 | { 16 | return NoContent(); 17 | } 18 | 19 | /// 20 | /// return status code 200 and no content 21 | /// 22 | /// 23 | [ProducesResponseType(204)] 24 | protected IActionResult Updated() 25 | { 26 | return Ok(); 27 | } 28 | 29 | /// 30 | /// return status code 200 and no content 31 | /// 32 | /// 33 | [ProducesResponseType(201)] 34 | protected IActionResult Created(Guid resourceId) 35 | { 36 | if(Uri.TryCreate($"{Request.Host}{Request.Path}/{resourceId}", UriKind.RelativeOrAbsolute, out var uri)) 37 | { 38 | return base.Created(uri, resourceId); 39 | } 40 | return base.Created("", resourceId); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/ErrorHandlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Core.Exceptions; 3 | using Microsoft.AspNetCore.Http; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Net; 7 | using System.Threading.Tasks; 8 | 9 | namespace BinaryOrigin.SeedWork.WebApi 10 | { 11 | /// 12 | /// Middleware for error handling of api exceptions 13 | /// For more info https://stackoverflow.com/questions/38630076/asp-net-core-web-api-exception-handling 14 | /// 15 | public class ErrorHandlingMiddleware 16 | { 17 | private readonly RequestDelegate _next; 18 | 19 | public ErrorHandlingMiddleware(RequestDelegate next) 20 | { 21 | _next = next; 22 | } 23 | 24 | public async Task Invoke(HttpContext context) 25 | { 26 | try 27 | { 28 | await _next(context); 29 | } 30 | #pragma warning disable CA1031 // Do not catch general exception types 31 | catch (Exception ex) 32 | { 33 | await HandleExceptionAsync(context, ex); 34 | } 35 | #pragma warning restore CA1031 // Do not catch general exception types 36 | } 37 | 38 | private static Task HandleExceptionAsync(HttpContext context, Exception exception) 39 | { 40 | var code = HttpStatusCode.InternalServerError; // 500 if unexpected 41 | object errorMessage = null; 42 | 43 | switch (exception) 44 | { 45 | case GeneralException _: 46 | code = HttpStatusCode.BadRequest; 47 | break; 48 | case CommandValidationException _: 49 | code = HttpStatusCode.BadRequest; 50 | errorMessage = exception.Data; 51 | break; 52 | case ArgumentException _: 53 | code = HttpStatusCode.BadRequest; 54 | break; 55 | } 56 | 57 | var result = JsonConvert.SerializeObject(new 58 | { 59 | errors = errorMessage ?? exception.Message 60 | }); 61 | context.Response.ContentType = "application/json"; 62 | context.Response.StatusCode = (int)code; 63 | return context.Response.WriteAsync(result); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Extensions/EngineExtensions.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using AutoMapper; 3 | using BinaryOrigin.SeedWork.Messages.Validation; 4 | using BinaryOrigin.SeedWork.Persistence.Ef; 5 | using BinaryOrigin.SeedWork.Persistence.SqlServer; 6 | using BinaryOrigin.SeedWork.WebApi.Mapping; 7 | using BinaryOrigin.SeedWork.WebApi.Validations; 8 | using FluentValidation; 9 | using Microsoft.EntityFrameworkCore; 10 | using System; 11 | using System.Linq; 12 | 13 | namespace BinaryOrigin.SeedWork.Core 14 | { 15 | /// 16 | /// Register application dependencies 17 | /// 18 | 19 | public static class EngineExtensions 20 | { 21 | public static void AddInMemoryDbContext(this IEngine engine) 22 | { 23 | var optionsBuilder = new DbContextOptionsBuilder(); 24 | optionsBuilder.UseInMemoryDatabase("InMemoryDb"); 25 | 26 | engine.Register(builder => 27 | { 28 | builder.RegisterType() 29 | .As() 30 | .SingleInstance(); 31 | builder.Register(instance => new EfObjectContext(optionsBuilder.Options)) 32 | .As() 33 | .InstancePerLifetimeScope(); 34 | }); 35 | } 36 | 37 | public static void AddFluentValidation(this IEngine engine) 38 | { 39 | engine.Register(builder => 40 | { 41 | var asmsWithCommandValidator = engine.FindClassesOfType(typeof(IValidator<>)) 42 | .Where(x => !x.AssemblyQualifiedName.Contains("SeedWork")) 43 | .Select(x => x.Assembly) 44 | .Distinct() 45 | .ToList(); 46 | 47 | foreach (var asm in asmsWithCommandValidator) 48 | { 49 | builder.RegisterAssemblyTypes(asm) 50 | .AsClosedTypesOf(typeof(IValidator<>)) 51 | .InstancePerLifetimeScope(); 52 | } 53 | builder.RegisterType() 54 | .As() 55 | .InstancePerLifetimeScope(); 56 | }); 57 | } 58 | public static void AddAutoMapper(this IEngine engine) 59 | { 60 | //find mapper configurations provided by other assemblies 61 | var mapperConfigurations = engine.FindClassesOfType(); 62 | 63 | //create and sort instances of mapper configurations 64 | var instances = mapperConfigurations 65 | .Select(mapperConfiguration => (IMapperProfile)Activator.CreateInstance(mapperConfiguration)) 66 | .OrderBy(mapperConfiguration => mapperConfiguration.Order); 67 | 68 | //create AutoMapper configuration 69 | var config = new MapperConfiguration(cfg => 70 | { 71 | foreach (var instance in instances) 72 | { 73 | cfg.AddProfile(instance.GetType()); 74 | } 75 | }); 76 | 77 | engine.Register(builder => 78 | { 79 | builder.RegisterInstance(config.CreateMapper()).As().SingleInstance(); 80 | builder.RegisterType().As().SingleInstance(); 81 | }); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Extensions/ServicesExtensions.cs: -------------------------------------------------------------------------------- 1 | using CacheManager.Core; 2 | using EFSecondLevelCache.Core; 3 | using System; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection 6 | { 7 | public static class ServicesExtensions 8 | { 9 | /// 10 | /// Add Second level cache for entity framework while using InMemory cache 11 | /// 12 | /// 13 | public static void AddDefaultEfSecondLevelCache(this IServiceCollection services) 14 | { 15 | services.AddEFSecondLevelCache(); 16 | 17 | // Add an in-memory cache service provider 18 | services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>)); 19 | services.AddSingleton(typeof(ICacheManagerConfiguration), 20 | new ConfigurationBuilder() 21 | .WithJsonSerializer() 22 | .WithMicrosoftMemoryCacheHandle(instanceName: "MemoryCache1") 23 | .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10)) 24 | .Build()); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/IWebApiEngine.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace BinaryOrigin.SeedWork.WebApi 6 | { 7 | public interface IAppWebEngine : IEngine 8 | { 9 | public void Initialize(IConfiguration configuration); 10 | /// 11 | /// Configure HTTP request pipeline 12 | /// 13 | /// Builder for configuring an application's request pipeline 14 | void ConfigureRequestPipeline(IApplicationBuilder application); 15 | } 16 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/IWebApiStartup.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace BinaryOrigin.SeedWork.WebApi 7 | { 8 | /// 9 | /// 10 | /// Represents object for the configuring services and middleware on application startup 11 | /// 12 | public interface IWebAppStartup : IAppStartup 13 | { 14 | /// 15 | /// Configure the using of added middleware 16 | /// 17 | /// Builder for configuring an application's request pipeline 18 | /// 19 | void Configure(IApplicationBuilder application, IConfiguration configuration); 20 | } 21 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Mapping/AutoMapperConfiguration.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace BinaryOrigin.SeedWork.WebApi.Mapping 4 | { 5 | /// 6 | /// AutoMapper configuration 7 | /// 8 | public static class AutoMapperConfiguration 9 | { 10 | /// 11 | /// Mapper 12 | /// 13 | public static IMapper Mapper { get; private set; } 14 | 15 | /// 16 | /// Mapper configuration 17 | /// 18 | public static MapperConfiguration MapperConfiguration { get; private set; } 19 | 20 | /// 21 | /// Initialize mapper 22 | /// 23 | /// Mapper configuration 24 | public static void Init(MapperConfiguration config) 25 | { 26 | MapperConfiguration = config; 27 | Mapper = config.CreateMapper(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Mapping/AutoMapperTypeAdapter.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using BinaryOrigin.SeedWork.Core; 3 | 4 | namespace BinaryOrigin.SeedWork.WebApi.Mapping 5 | { 6 | /// 7 | /// 8 | /// AutoMapper type adapter implementation 9 | /// 10 | public class AutoMapperTypeAdapter : ITypeAdapter 11 | { 12 | private readonly IMapper _mapper; 13 | 14 | public AutoMapperTypeAdapter(IMapper mapper) 15 | { 16 | _mapper = mapper; 17 | } 18 | 19 | public TTarget Adapt(TSource source) 20 | where TSource : class 21 | where TTarget : class 22 | { 23 | if (source == null) 24 | { 25 | return default; 26 | } 27 | return _mapper.Map(source); 28 | } 29 | 30 | public TTarget Adapt(object source) 31 | where TTarget : class 32 | { 33 | if(source == null) 34 | { 35 | return default; 36 | } 37 | return _mapper.Map(source); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/ModelBinders/QueryModelBinder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace BinaryOrigin.SeedWork.WebApi.ModelBinders 9 | { 10 | public class QueryModelBinder : IModelBinder 11 | { 12 | public QueryModelBinder() 13 | { 14 | } 15 | 16 | public Task BindModelAsync(ModelBindingContext bindingContext) 17 | { 18 | if (bindingContext == null) 19 | { 20 | throw new ArgumentNullException(nameof(bindingContext)); 21 | } 22 | 23 | if (bindingContext.HttpContext.Request.QueryString.HasValue) 24 | { 25 | var queryParameters = bindingContext.HttpContext 26 | .Request 27 | .Query 28 | .ToDictionary(pair => pair.Key, pair => pair.Value.ToString()); 29 | 30 | var jsonString = JsonConvert.SerializeObject(queryParameters); 31 | var parameterType = ((ControllerContext)bindingContext.ActionContext) 32 | .ActionDescriptor.Parameters.FirstOrDefault()? 33 | .ParameterType; 34 | 35 | var obj = JsonConvert.DeserializeObject(jsonString, parameterType); 36 | bindingContext.Model = obj; 37 | } 38 | 39 | bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); 40 | 41 | return Task.CompletedTask; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/ModelBinders/QueryModelBinderProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using System.Linq; 3 | 4 | namespace BinaryOrigin.SeedWork.WebApi.ModelBinders 5 | { 6 | public class QueryModelBinderProvider : IModelBinderProvider 7 | { 8 | public IModelBinder GetBinder(ModelBinderProviderContext context) 9 | { 10 | //TODO GETSON remove static string 11 | var @interface = context.Metadata 12 | .ModelType 13 | .GetInterfaces() 14 | .FirstOrDefault(i => i.Name.StartsWith("IQuery")); 15 | if (@interface != null) 16 | { 17 | return new QueryModelBinder(); 18 | } 19 | return null; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/ModelBinders/RouteDataComplexTypeModelBinder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using System.Threading.Tasks; 3 | 4 | namespace BinaryOrigin.SeedWork.WebApi.ModelBinders 5 | { 6 | public class RouteDataComplexTypeModelBinder : IModelBinder 7 | { 8 | private readonly IModelBinder _complexTypeModelBinder; 9 | 10 | public RouteDataComplexTypeModelBinder(IModelBinder complexTypeModelBinder) 11 | { 12 | _complexTypeModelBinder = complexTypeModelBinder; 13 | } 14 | 15 | public async Task BindModelAsync(ModelBindingContext bindingContext) 16 | { 17 | if (bindingContext.ActionContext.RouteData.Values.TryGetValue(bindingContext.ModelName, out var value)) 18 | bindingContext.Result = ModelBindingResult.Success(value); 19 | else if (_complexTypeModelBinder != null) 20 | await _complexTypeModelBinder.BindModelAsync(bindingContext); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/ModelBinders/RouteDataComplexTypeModelBinderProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | 4 | namespace BinaryOrigin.SeedWork.WebApi.ModelBinders 5 | { 6 | public class RouteDataComplexTypeModelBinderProvider : IModelBinderProvider 7 | { 8 | private readonly IModelBinderProvider _complexTypeModelBinderProvider; 9 | private readonly IHttpContextAccessor _httpContextAccessor; 10 | 11 | public RouteDataComplexTypeModelBinderProvider(IModelBinderProvider complexTypeModelBinderProvider, IHttpContextAccessor httpContextAccessor) 12 | { 13 | _httpContextAccessor = httpContextAccessor; 14 | _complexTypeModelBinderProvider = complexTypeModelBinderProvider; 15 | } 16 | 17 | public IModelBinder GetBinder(ModelBinderProviderContext context) 18 | { 19 | var httpMethod = _httpContextAccessor.HttpContext.Request.Method; 20 | if ((httpMethod == HttpMethods.Get || httpMethod == HttpMethods.Delete) && !_httpContextAccessor.HttpContext.Request.QueryString.HasValue) 21 | { 22 | if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) 23 | return new RouteDataComplexTypeModelBinder(_complexTypeModelBinderProvider.GetBinder(context)); 24 | } 25 | return null; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/Validations/FluentValidationProvider.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Messages; 2 | using BinaryOrigin.SeedWork.Core; 3 | using System.Threading.Tasks; 4 | using System.Threading; 5 | using BinaryOrigin.SeedWork.Core.Domain; 6 | using BinaryOrigin.SeedWork.Messages.Validation; 7 | using FluentValidation.Results; 8 | using System.Linq; 9 | using FluentValidation; 10 | 11 | namespace BinaryOrigin.SeedWork.WebApi.Validations 12 | { 13 | public class FluentValidationProvider : ICommandValidationProvider 14 | { 15 | private static ValidationResponse BuildValidationResponse(ValidationResult validationResult) 16 | { 17 | return new ValidationResponse 18 | { 19 | Errors = validationResult.Errors.Select(failure => new ValidationError 20 | { 21 | PropertyName = failure.PropertyName, 22 | ErrorMessage = failure.ErrorMessage 23 | }).ToList() 24 | }; 25 | } 26 | 27 | public async Task ValidateAsync(ICommand command) 28 | { 29 | var validator = EngineContext.Current.Resolve(command, typeof(IValidator<>)); 30 | if (validator == null) 31 | { 32 | // No validator found! 33 | return new ValidationResponse(); 34 | } 35 | var validateMethod = validator.GetType().GetMethod("ValidateAsync", new[] { command.GetType(), typeof(CancellationToken) }); 36 | var validationResult = await (Task)validateMethod.Invoke(validator, new object[] { command, default(CancellationToken) }); 37 | 38 | return BuildValidationResponse(validationResult); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/SeedWork/BinaryOrigin.SeedWork.WebApi/WebApiEngine.cs: -------------------------------------------------------------------------------- 1 | using BinaryOrigin.SeedWork.Core; 2 | using BinaryOrigin.SeedWork.Infrastructure; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using System; 8 | using System.Linq; 9 | 10 | namespace BinaryOrigin.SeedWork.WebApi 11 | { 12 | public class AppWebApiEngine : AppEngine, IAppWebEngine 13 | { 14 | /// 15 | /// Configure HTTP request pipeline 16 | /// 17 | /// Builder for configuring an application's request pipeline 18 | public void ConfigureRequestPipeline(IApplicationBuilder application) 19 | { 20 | var typeFinder = Resolve(); 21 | //find startup configurations provided by other assemblies 22 | var startupConfigurations = typeFinder.FindClassesOfType(); 23 | 24 | //create and sort instances of startup configurations 25 | var instances = startupConfigurations 26 | .Select(startup => (IWebAppStartup)Activator.CreateInstance(startup)) 27 | .OrderBy(startup => startup.Order); 28 | 29 | //configure request pipeline 30 | foreach (var instance in instances) 31 | { 32 | instance.Configure(application, Configuration); 33 | } 34 | } 35 | /// 36 | /// Initialize WebApiEngine 37 | /// 38 | /// 39 | public void Initialize(IConfiguration configuration) 40 | { 41 | var fileProvider = new AppFileProvider(AppContext.BaseDirectory); 42 | Initialize(fileProvider, configuration); 43 | } 44 | 45 | protected override IServiceProvider GetServiceProvider() 46 | { 47 | var accessor = ServiceProvider.GetService(); 48 | var context = accessor.HttpContext; 49 | return context?.RequestServices ?? ServiceProvider; 50 | } 51 | } 52 | } --------------------------------------------------------------------------------