├── icon.png ├── tests └── WebAPI │ ├── Program.cs │ ├── appsettings.json │ ├── wwwroot │ └── swagger-ui │ │ └── custom-swagger.css │ ├── Identity │ ├── ApplicationUser.cs │ ├── IdentityResultExtensions.cs │ └── IdentityService.cs │ ├── Models │ └── WeatherForecast.cs │ ├── Domain │ ├── BaseEntity.cs │ └── Entities │ │ └── Language.cs │ ├── Services │ └── CurrentUserService.cs │ ├── Controllers │ └── WeatherForecastController.cs │ ├── Persistence │ ├── ApplicationDbContextInitialiser.cs │ ├── ApplicationDbContext.cs │ └── Interceptors │ │ └── AuditableEntitySaveChangesInterceptor.cs │ ├── WebAPI.csproj │ ├── Migrations │ ├── 20220717185026_Init.cs │ ├── ApplicationDbContextModelSnapshot.cs │ └── 20220717185026_Init.Designer.cs │ └── Properties │ └── launchSettings.json ├── src ├── CodeZero.Builder │ ├── GlobalUsings.cs │ ├── Microsoft │ │ └── Configuration │ │ │ ├── ConfigurationBuilderOptions.cs │ │ │ └── ConfigurationHelper.cs │ ├── CodeZero.Builder.csproj │ └── Builder │ │ └── CodeZeroHostBuilder.cs ├── CodeZero.Shared │ ├── Exception │ │ ├── Interfaces │ │ │ ├── IBusinessException.cs │ │ │ ├── IHasErrorCode.cs │ │ │ ├── IUserFriendlyException.cs │ │ │ ├── IHasErrorDetails.cs │ │ │ ├── IHasHttpStatusCode.cs │ │ │ └── IHasValidationErrors.cs │ │ ├── UnauthorizedException.cs │ │ ├── UserFriendlyException.cs │ │ ├── BusinessException.cs │ │ ├── NotFoundException.cs │ │ └── CodeZeroException.cs │ ├── Logging │ │ ├── Interfaces │ │ │ ├── IExceptionWithSelfLogging.cs │ │ │ └── IHasLogLevel.cs │ │ ├── HasLogLevelExtensions.cs │ │ ├── EFCoreLoggingUtils.cs │ │ └── ErrorCode.cs │ ├── System │ │ ├── DebugHelper.cs │ │ ├── CharExtensions.cs │ │ ├── Text │ │ │ └── Json │ │ │ │ └── JsonExtensions.cs │ │ ├── StringValueAttribute.cs │ │ ├── ComparableExtensions.cs │ │ ├── NullableExtensions.cs │ │ ├── Collections │ │ │ └── Generic │ │ │ │ ├── CollectionExtensions.cs │ │ │ │ └── DictionaryExtensions.cs │ │ └── ExceptionExtensions.cs │ ├── GlobalUsings.cs │ ├── Helpers │ │ ├── Reflection │ │ │ └── TypeHelper.cs │ │ └── ObjectHelper.cs │ └── CodeZero.Shared.csproj ├── CodeZero.Domain │ ├── Data │ │ ├── IDispatchEvents.cs │ │ ├── IBaseDbContext.cs │ │ ├── IHandleCreation.cs │ │ ├── IHandleDeletion.cs │ │ ├── IHandleModification.cs │ │ ├── IEntityTracker.cs │ │ └── IHandleVersioned.cs │ ├── Events │ │ ├── IEventStore.cs │ │ └── StoredEvent.cs │ ├── Mediator │ │ ├── IHandle.cs │ │ ├── IMediatorHandler.cs │ │ └── MediatorHandler.cs │ ├── Interfaces │ │ ├── IHasDomainEvent.cs │ │ ├── IDomainEventDispatcher.cs │ │ └── IGeneratesDomainEvents.cs │ ├── Common │ │ ├── Interfaces │ │ │ ├── ICurrentUserService.cs │ │ │ ├── IPagedList.cs │ │ │ └── IIdentityService.cs │ │ ├── Responses │ │ │ ├── ValidationIssue.cs │ │ │ └── BaseResponse.cs │ │ ├── Models │ │ │ ├── Result.cs │ │ │ └── PaginatedList.cs │ │ ├── Behaviors │ │ │ ├── RequestLogger.cs │ │ │ ├── UnhandledExceptionBehavior.cs │ │ │ ├── LoggingBehavior.cs │ │ │ ├── ValidationBehavior.cs │ │ │ ├── PerformanceBehavior.cs │ │ │ └── AuthorizationBehavior.cs │ │ ├── MediatorExtensions.cs │ │ ├── Security │ │ │ └── AuthorizeAttribute.cs │ │ └── ValueObject.cs │ ├── Exception │ │ ├── ForbiddenAccessException.cs │ │ ├── DomainException.cs │ │ └── EntityNotFoundException.cs │ ├── Services │ │ └── IDomainService.cs │ ├── Messaging │ │ ├── DomainEvent.cs │ │ ├── Message.cs │ │ ├── Event.cs │ │ ├── Command.cs │ │ ├── ResponseMessage.cs │ │ └── CommandHandler.cs │ ├── Aggregate │ │ ├── IAggregateRoot.cs │ │ ├── AggregateRoot.cs │ │ └── BasicAggregateRoot.cs │ ├── Entities │ │ ├── IActive.cs │ │ ├── IDisplaySequence.cs │ │ ├── IHasConcurrencyStamp.cs │ │ ├── ISoftDelete.cs │ │ ├── IValidatableEntity.cs │ │ ├── IEntity.cs │ │ ├── Auditing │ │ │ ├── IFullAudited.cs │ │ │ ├── IAudited.cs │ │ │ ├── IDeletion.cs │ │ │ ├── ICreation.cs │ │ │ ├── IModification.cs │ │ │ ├── CreationAudited.cs │ │ │ ├── AuditedEntity.cs │ │ │ └── FullAuditedEntity.cs │ │ └── IHasMetaTages.cs │ ├── GlobalUsings.cs │ ├── Extensions │ │ └── DomainExtensions.cs │ ├── DomainEventDispatcher.cs │ └── CodeZero.Domain.csproj ├── CodeZero.ServiceInstaller │ ├── Libraries │ │ ├── Swagger │ │ │ ├── ApiVersioning │ │ │ │ ├── IRequestedApiVersion.cs │ │ │ │ ├── NullRequestedApiVersion.cs │ │ │ │ ├── HttpContextRequestedApiVersion.cs │ │ │ │ └── AddApiVersioningExtensions.cs │ │ │ ├── Filters │ │ │ │ ├── RemoveVersionFromParameter.cs │ │ │ │ ├── ReplaceVersionWithExactValueInPath.cs │ │ │ │ ├── AcceptLanguageHeader.cs │ │ │ │ └── SwaggerDefaultValues.cs │ │ │ └── UseSwaggerVersionedExtensions.cs │ │ ├── SerilogConfig.cs │ │ └── RateLimit │ │ │ ├── RateLimitingClientIDExtensions.cs │ │ │ └── RateLimitingClientIPExtensions.cs │ ├── Configuration │ │ └── Models │ │ │ ├── License.cs │ │ │ ├── KeyManagement.cs │ │ │ ├── Scopes.cs │ │ │ ├── HttpsRedirectionOptions.cs │ │ │ ├── Contact.cs │ │ │ ├── HstsOptions.cs │ │ │ ├── ResponseCompressionConfig.cs │ │ │ ├── CorsSettings.cs │ │ │ ├── HeadersConfig.cs │ │ │ ├── MiniProfilerConfig.cs │ │ │ ├── Language.cs │ │ │ ├── ProxySettings.cs │ │ │ ├── DataProtectionConfig.cs │ │ │ ├── SwaggerInfo.cs │ │ │ ├── CustomCorsPolicy.cs │ │ │ ├── SwaggerConfig.cs │ │ │ └── ForwardedHeadersOptions.cs │ ├── GlobalUsings.cs │ ├── ServiceInstallers │ │ └── AddCodeZeroExtensions.cs │ ├── Helpers │ │ └── HttpContextHelper.cs │ ├── PoweredBy │ │ ├── PoweredByMiddleware.cs │ │ └── PoweredByCodeZeroExtensions.cs │ ├── Extensions │ │ ├── Antiforgery │ │ │ ├── UseAntiforgeryExtensions.cs │ │ │ └── AntiforgeryExtensions.cs │ │ ├── HeadersExtensions.cs │ │ ├── ContentNegotiationExtensions.cs │ │ ├── ResponseCompressionExtensions.cs │ │ ├── AddMvcServicesExtensions.cs │ │ ├── Localization │ │ │ ├── LocalizationExtensions.cs │ │ │ └── UseLocalizationExtensions.cs │ │ ├── ReverseProxy │ │ │ └── UseProxyExtensions.cs │ │ ├── CorsConfigExtensions.cs │ │ └── DataProtectionExtensions.cs │ └── Utils │ │ └── Network.cs ├── CodeZero.Configuration │ ├── Models │ │ ├── ConnectionStrings.cs │ │ ├── SeqOptions.cs │ │ ├── PaginationOptions.cs │ │ ├── ExternalServices.cs │ │ ├── Jwt.cs │ │ ├── CosmosDBSettings.cs │ │ ├── HostedServices.cs │ │ ├── Authentication.cs │ │ ├── ApiKeyConfig.cs │ │ ├── CacheConfig.cs │ │ ├── ServiceMedia.cs │ │ ├── RedisConfig.cs │ │ ├── DebugConfig.cs │ │ └── ServiceSettings.cs │ ├── Interfaces │ │ ├── IConfig.cs │ │ └── IAppServiceLoader.cs │ ├── Config.cs │ ├── AppServiceLoader.cs │ ├── Extensions │ │ ├── AppServiceLoaderExtensions.cs │ │ └── HostingEnvironmentExtensions.cs │ └── CodeZero.Configuration.csproj ├── CodeZero.Shared.Constants │ ├── Constants │ │ ├── AuthSchemes.cs │ │ ├── Environments.cs │ │ ├── MimeTypes.cs │ │ └── HeaderName.cs │ └── CodeZero.Shared.Constants.csproj ├── CodeZero.StringExtensions │ ├── GlobalUsings.cs │ └── CodeZero.StringExtensions.csproj ├── CodeZero │ └── CodeZero.csproj └── CodeZero.Check │ └── CodeZero.Check.csproj ├── clean.bat ├── clean.sh ├── .vscode ├── tasks.json └── launch.json ├── LICENSE ├── Common.props ├── Directory.Build.props └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasraldin/CodeZero/HEAD/icon.png -------------------------------------------------------------------------------- /tests/WebAPI/Program.cs: -------------------------------------------------------------------------------- 1 | CustomHostBuilder.CreateAsync(WebApplication.CreateBuilder(args), args); -------------------------------------------------------------------------------- /tests/WebAPI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApplicationName": "CodeZero", 3 | "HTTPS_PORT": 443 4 | } -------------------------------------------------------------------------------- /tests/WebAPI/wwwroot/swagger-ui/custom-swagger.css: -------------------------------------------------------------------------------- 1 | .swagger-ui table tbody tr td:first-of-type { 2 | min-width: 12em; 3 | } 4 | -------------------------------------------------------------------------------- /src/CodeZero.Builder/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using CodeZero; 2 | global using CodeZero.Configuration.Models; 3 | global using CodeZero.Logging; -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/Interfaces/IBusinessException.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Exception; 2 | 3 | public interface IBusinessException { } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Data/IDispatchEvents.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Data; 2 | 3 | public interface IDispatchEvents 4 | { 5 | Task DispatchEvents(); 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/Interfaces/IHasErrorCode.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Exception; 2 | 3 | public interface IHasErrorCode 4 | { 5 | string Code { get; } 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/Interfaces/IUserFriendlyException.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Exception; 2 | 3 | public interface IUserFriendlyException : IBusinessException { } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Events/IEventStore.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Events; 2 | 3 | public interface IEventStore 4 | { 5 | void Save(T theEvent) where T : Event; 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Mediator/IHandle.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Mediator; 2 | 3 | public interface IHandle where T : Event 4 | { 5 | Task Handle(T domainEvent); 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/Interfaces/IHasErrorDetails.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Exception; 2 | 3 | public interface IHasErrorDetails 4 | { 5 | string Details { get; } 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Interfaces/IHasDomainEvent.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain; 2 | 3 | public interface IHasDomainEvent 4 | { 5 | List DomainEvents { get; set; } 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Interfaces/IDomainEventDispatcher.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain; 2 | 3 | public interface IDomainEventDispatcher 4 | { 5 | Task Dispatch(Event domainEvent); 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/Interfaces/IHasHttpStatusCode.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Exception; 2 | 3 | public interface IHasHttpStatusCode 4 | { 5 | int HttpStatusCode { get; } 6 | } -------------------------------------------------------------------------------- /tests/WebAPI/Identity/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | //using Microsoft.AspNetCore.Identity; 2 | 3 | //namespace WebAPI.Identity; 4 | 5 | //public class ApplicationUser : IdentityUser 6 | //{ 7 | //} -------------------------------------------------------------------------------- /src/CodeZero.Shared/Logging/Interfaces/IExceptionWithSelfLogging.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Logging; 2 | 3 | public interface IExceptionWithSelfLogging 4 | { 5 | void Log(ILogger logger); 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Interfaces/ICurrentUserService.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Interfaces; 2 | 3 | public interface ICurrentUserService 4 | { 5 | string? UserId { get; } 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Data/IBaseDbContext.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Data; 2 | 3 | public interface IBaseDbContext 4 | { 5 | Task SaveChangesAsync(CancellationToken cancellationToken = default); 6 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/ApiVersioning/IRequestedApiVersion.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.ApiVersioning; 2 | 3 | public interface IRequestedApiVersion 4 | { 5 | string Current { get; } 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/Interfaces/IHasValidationErrors.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Exception; 2 | 3 | public interface IHasValidationErrors 4 | { 5 | IEnumerable ValidationErrors { get; } 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Data/IHandleCreation.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Data; 2 | 3 | /// 4 | /// Handle entity state Added. 5 | /// 6 | public interface IHandleCreation 7 | { 8 | void HandleAdded(); 9 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Exception/ForbiddenAccessException.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Exception; 2 | 3 | public class ForbiddenAccessException : CodeZeroException 4 | { 5 | public ForbiddenAccessException() : base() { } 6 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Data/IHandleDeletion.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Data; 2 | 3 | /// 4 | /// Handle entity state Deleted. 5 | /// 6 | public interface IHandleDeletion 7 | { 8 | void HandleDeleted(); 9 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Data/IHandleModification.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Data; 2 | 3 | /// 4 | /// Handle entity state Modified. 5 | /// 6 | public interface IHandleModification 7 | { 8 | void HandleModified(); 9 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Data/IEntityTracker.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Data; 2 | 3 | /// 4 | /// Handle Change Tracking in EF Core 5 | /// 6 | public interface IEntityTracker : IHandleCreation, IHandleModification, IHandleDeletion { } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/License.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | public partial class License 4 | { 5 | public string Name { get; set; } = default!; 6 | public string Url { get; set; } = default!; 7 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Services/IDomainService.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Services; 2 | 3 | /// 4 | /// This interface can be implemented by all domain services to identify them by convention. 5 | /// 6 | public interface IDomainService { } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/KeyManagement.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | public partial class KeyManagement 4 | { 5 | public int NewKeyLifetime { get; set; } 6 | public bool AutoGenerateKeys { get; set; } 7 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Mediator/IMediatorHandler.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Mediator; 2 | 3 | public interface IMediatorHandler 4 | { 5 | Task PublishEvent(T @event) where T : Event; 6 | Task SendCommand(T command) where T : Command; 7 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/Scopes.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | public partial class Scopes 4 | { 5 | public string ScopeName { get; set; } = default!; 6 | public string ShortDescription { get; set; } = default!; 7 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Data/IHandleVersioned.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Data; 2 | 3 | /// 4 | /// Handling Concurrency Conflicts. 5 | /// Timestamp/RowVersion 6 | /// 7 | public interface IHandleVersioned 8 | { 9 | void HandleVersioned(); 10 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Responses/ValidationIssue.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Responses; 2 | 3 | public class ValidationIssue 4 | { 5 | public string PropertyName { get; set; } = default!; 6 | public List PropertyFailures { get; set; } = default!; 7 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/HttpsRedirectionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | public partial class HttpsRedirectionOptions 4 | { 5 | public int RedirectStatusCode { get; set; } 6 | public int? HttpsPort { get; set; } 7 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Messaging/DomainEvent.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Messaging; 2 | 3 | public abstract class DomainEvent : Event 4 | { 5 | public bool IsPublished { get; set; } 6 | 7 | protected DomainEvent(Guid aggregateId) 8 | { 9 | AggregateId = aggregateId; 10 | } 11 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/Contact.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | public partial class Contact 4 | { 5 | public string Name { get; set; } = default!; 6 | public string Email { get; set; } = default!; 7 | public string Url { get; set; } = default!; 8 | } -------------------------------------------------------------------------------- /tests/WebAPI/Models/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | namespace WebAPI.Models; 2 | 3 | public class WeatherForecast 4 | { 5 | public DateTime Date { get; set; } 6 | public int TemperatureC { get; set; } 7 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 8 | public string? Summary { get; set; } 9 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/ConnectionStrings.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents ConnectionStrings configuration parameters 5 | /// 6 | public partial class ConnectionStrings 7 | { 8 | public string DefaultConnection { get; set; } = default!; 9 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/SeqOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Seq configuration parameters 5 | /// 6 | public partial class SeqOptions 7 | { 8 | public string Endpoint { get; set; } = default!; 9 | public string ApiKey { get; set; } = default!; 10 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Messaging/Message.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Messaging; 2 | 3 | public abstract class Message 4 | { 5 | public string MessageType { get; protected set; } 6 | public Guid AggregateId { get; protected set; } 7 | 8 | protected Message() 9 | { 10 | MessageType = GetType().Name; 11 | } 12 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Text; 2 | global using System.Text.Json; 3 | global using System.Text.Json.Serialization; 4 | global using CodeZero; 5 | global using CodeZero.Configuration; 6 | global using CodeZero.Configuration.Models; 7 | global using CodeZero.Logging; 8 | global using JetBrains.Annotations; -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/HstsOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | public partial class HstsOptions 4 | { 5 | public int MaxAge { get; set; } 6 | public bool IncludeSubDomains { get; set; } 7 | public bool Preload { get; set; } 8 | public string[] ExcludedHosts { get; set; } = default!; 9 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Interfaces/IGeneratesDomainEvents.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain; 2 | 3 | //TODO: Re-consider this interface 4 | public interface IGeneratesDomainEvents 5 | { 6 | IEnumerable GetLocalEvents(); 7 | IEnumerable GetDistributedEvents(); 8 | void ClearLocalEvents(); 9 | void ClearDistributedEvents(); 10 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared.Constants/Constants/AuthSchemes.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero; 2 | 3 | public static partial class AppConst 4 | { 5 | public static partial class AuthSchemes 6 | { 7 | public const string OAuth2 = "oauth2"; 8 | public const string Bearer = "Bearer"; 9 | public const string ApiKey = "ApiKey"; 10 | } 11 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/PaginationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents PaginationOptions configuration parameters 5 | /// 6 | public partial class PaginationOptions 7 | { 8 | public int DefaultPageSize { get; set; } 9 | public int DefaultMaxPageCount { get; set; } 10 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Aggregate/IAggregateRoot.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Aggregate; 2 | 3 | /// 4 | /// Marker interface to represent a aggregate root. 5 | /// Apply this marker interface only to aggregate root entities 6 | /// Repositories will only work with aggregate roots, not their children 7 | /// 8 | public interface IAggregateRoot { } -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/DebugHelper.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | public static class DebugHelper 4 | { 5 | public static bool IsDebug 6 | { 7 | get 8 | { 9 | #pragma warning disable 10 | #if DEBUG 11 | return true; 12 | #endif 13 | return false; 14 | #pragma warning restore 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/IActive.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities; 2 | 3 | /// 4 | /// Make an entity active/passive. 5 | /// 6 | public interface IActive 7 | { 8 | /// 9 | /// True: This entity is active. 10 | /// False: This entity is not active. 11 | /// 12 | bool IsActive { get; } 13 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/IDisplaySequence.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities; 2 | 3 | /// 4 | /// Used to display sequence/order of entities. 5 | /// 6 | public interface IDisplaySequence 7 | { 8 | /// 9 | /// Used to mark an entity sequence. 10 | /// 11 | int? DisplaySequence { get; set; } 12 | } -------------------------------------------------------------------------------- /tests/WebAPI/Domain/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using CodeZero.Domain.Entities; 2 | using CodeZero.Domain.Entities.Auditing; 3 | 4 | namespace WebAPI.Domain; 5 | 6 | /// 7 | /// Base entity. 8 | /// 9 | [Serializable] 10 | public abstract class BaseEntity : FullAuditedEntity, IActive 11 | { 12 | public bool IsActive { get; protected set; } 13 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/ResponseCompressionConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Headers configuration parameters 5 | /// 6 | public class ResponseCompressionConfig 7 | { 8 | public bool EnableForHttps { get; set; } 9 | public string[] MimeTypes { get; set; } = default!; 10 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/ApiVersioning/NullRequestedApiVersion.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.ApiVersioning; 2 | 3 | public class NullRequestedApiVersion : IRequestedApiVersion 4 | { 5 | public static NullRequestedApiVersion Instance { get; set; } = new(); 6 | 7 | public string Current => null!; 8 | 9 | private NullRequestedApiVersion() { } 10 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.ComponentModel; 2 | global using System.ComponentModel.DataAnnotations; 3 | global using System.Reflection; 4 | global using System.Text; 5 | global using System.Text.Json; 6 | global using CodeZero.Exception; 7 | global using CodeZero.Logging; 8 | global using JetBrains.Annotations; 9 | global using Microsoft.Extensions.Logging; -------------------------------------------------------------------------------- /src/CodeZero.Domain/Aggregate/AggregateRoot.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Aggregate; 2 | 3 | [Serializable] 4 | public abstract class AggregateRoot : BasicAggregateRoot, IHasConcurrencyStamp 5 | { 6 | public virtual string ConcurrencyStamp { get; set; } 7 | 8 | protected AggregateRoot() 9 | { 10 | ConcurrencyStamp = Guid.NewGuid().ToString("N"); 11 | } 12 | } -------------------------------------------------------------------------------- /src/CodeZero.StringExtensions/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Collections.Immutable; 2 | global using System.Globalization; 3 | global using System.IO.Compression; 4 | global using System.Net; 5 | global using System.Security.Cryptography; 6 | global using System.Text; 7 | global using System.Text.Json; 8 | global using System.Text.RegularExpressions; 9 | global using JetBrains.Annotations; -------------------------------------------------------------------------------- /src/CodeZero.Domain/Exception/DomainException.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Exception; 2 | 3 | public class DomainException : CodeZeroException 4 | { 5 | public DomainException() { } 6 | 7 | public DomainException(string message) : base(message) { } 8 | 9 | public DomainException(string message, System.Exception innerException) 10 | : base(message, innerException) { } 11 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/CorsSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents CorsSettings configuration parameters 5 | /// 6 | public partial class CorsSettings 7 | { 8 | public string DefaultCorsPolicy { get; set; } = default!; 9 | public List CorsPolicy { get; set; } = default!; 10 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Logging/Interfaces/IHasLogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Logging; 2 | 3 | /// 4 | /// Interface to define a 5 | /// property (see ). 6 | /// 7 | public interface IHasLogLevel 8 | { 9 | /// 10 | /// Log severity. 11 | /// 12 | LogLevel LogLevel { get; set; } 13 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Messaging/Event.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Messaging; 2 | 3 | /// 4 | /// Base DomainEvent 5 | /// 6 | public abstract class Event : Message, INotification 7 | { 8 | [System.ComponentModel.DataAnnotations.Timestamp] 9 | public DateTime Timestamp { get; private set; } 10 | 11 | protected Event() 12 | { 13 | Timestamp = DateTime.UtcNow; 14 | } 15 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Interfaces/IConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CodeZero.Configuration; 4 | 5 | /// 6 | /// Used to retrieve configured TOptions instances. 7 | /// 8 | public interface IConfig 9 | { 10 | /// 11 | /// Gets a section name to load configuration 12 | /// 13 | [JsonIgnore] 14 | T Options { get; set; } 15 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/ExternalServices.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Integrating External Services configuration parameters 5 | /// 6 | public partial class ExternalServices 7 | { 8 | public string Name { get; set; } = default!; 9 | public string Url { get; set; } = default!; 10 | public string ApiKey { get; set; } = default!; 11 | } -------------------------------------------------------------------------------- /clean.bat: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | cls 3 | 4 | ECHO CodeZero by Nasr Aldin - nasr2ldin@gmail.com 5 | ECHO Deleting 'BIN' and 'OBJ' folders 6 | ECHO Version: 1.0.0 7 | ECHO. 8 | 9 | ECHO Deleting... 10 | FOR /d /r . %%d in (bin,obj) DO ( 11 | IF EXIST "%%d" ( 12 | ECHO.Deleting: %%d 13 | rd /s/q "%%d" 14 | ) 15 | ) 16 | 17 | ECHO. 18 | ECHO.BIN and OBJ folders have been successfully deleted. Press any key to exit. 19 | pause > nul -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/Jwt.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Jwt configuration parameters 5 | /// 6 | public partial class Jwt 7 | { 8 | public string Audience { get; set; } = default!; 9 | public string Issuer { get; set; } = default!; 10 | public string SecretKey { get; set; } = default!; 11 | public string ExpireDays { get; set; } = default!; 12 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Config.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace CodeZero.Configuration; 4 | 5 | /// 6 | public class Config : IConfig 7 | { 8 | public Config(IConfiguration configuration) 9 | { 10 | Options = configuration.GetSection(typeof(T).Name).Get(); 11 | } 12 | 13 | /// 14 | public virtual T Options { get; set; } 15 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/HeadersConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Headers configuration parameters 5 | /// 6 | public partial class HeadersConfig 7 | { 8 | public string XFrameOptions { get; set; } = default!; 9 | public string XContentTypeOptions { get; set; } = default!; 10 | public string XssProtection { get; set; } = default!; 11 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/MiniProfilerConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Debug configuration parameters 5 | /// 6 | public partial class MiniProfilerConfig 7 | { 8 | public string RouteBasePath { get; set; } = "/profiler"; 9 | public string Storage { get; set; } = "MemoryCache"; 10 | public string ConnectionString { get; set; } = default!; 11 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Interfaces/IPagedList.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Interfaces; 2 | 3 | /// 4 | /// Paged list interface 5 | /// 6 | public interface IPagedList 7 | { 8 | //int PageIndex { get; } 9 | //int PageSize { get; } 10 | int PageNumber { get; } 11 | int TotalCount { get; } 12 | int TotalPages { get; } 13 | bool HasPreviousPage { get; } 14 | bool HasNextPage { get; } 15 | List Items { get; } 16 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/Language.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Languages configuration parameters 5 | /// 6 | public partial class Language 7 | { 8 | public string Name { get; set; } = default!; 9 | public string Culture { get; set; } = default!; 10 | public bool IsRtl { get; set; } 11 | public bool IsActive { get; set; } 12 | public int DisplaySequence { get; set; } 13 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/CosmosDBSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Azure CosmosDB Settings onfiguration parameters 5 | /// 6 | public partial class CosmosDBSettings 7 | { 8 | public string Account { get; set; } = default!; 9 | public string Key { get; set; } = default!; 10 | public string DatabaseName { get; set; } = default!; 11 | public string ContainerName { get; set; } = default!; 12 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Interfaces/IIdentityService.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Interfaces; 2 | 3 | public interface IIdentityService 4 | { 5 | Task GetUserNameAsync(string userId); 6 | Task IsInRoleAsync(string userId, string role); 7 | Task AuthorizeAsync(string userId, string policyName); 8 | Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password); 9 | Task DeleteUserAsync(string userId); 10 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Logging/HasLogLevelExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Logging; 2 | 3 | public static class HasLogLevelExtensions 4 | { 5 | public static TException WithLogLevel( 6 | [NotNull] this TException exception, 7 | LogLevel logLevel) 8 | where TException : IHasLogLevel 9 | { 10 | ArgumentNullException.ThrowIfNull(exception); 11 | 12 | exception.LogLevel = logLevel; 13 | 14 | return exception; 15 | } 16 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/UnauthorizedException.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Exception; 2 | 3 | public class UnauthorizedException : CodeZeroException 4 | { 5 | public UnauthorizedException(System.Exception inner) : base("Unauthorized", inner) { } 6 | 7 | public static void ThrowIfUnauthorizedServiceCall(System.Exception ex) 8 | { 9 | if (ex.InnerException != null && ex.Message.Contains("Unauthorized")) 10 | throw new UnauthorizedException(ex); 11 | } 12 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/HostedServices.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents HostedServices configuration parameters 5 | /// 6 | public partial class HostedServices 7 | { 8 | public string ServiceName { get; set; } = default!; 9 | public string ServiceClientSecret { get; set; } = default!; 10 | public string ServiceRequestedScopes { get; set; } = default!; 11 | public int ServiceInterval { get; set; } = 5000; 12 | } -------------------------------------------------------------------------------- /tests/WebAPI/Identity/IdentityResultExtensions.cs: -------------------------------------------------------------------------------- 1 | //using CodeZero.Domain.Common.Models; 2 | //using Microsoft.AspNetCore.Identity; 3 | 4 | //namespace WebAPI.Identity; 5 | 6 | //public static class IdentityResultExtensions 7 | //{ 8 | // public static Result ToApplicationResult(this IdentityResult result) 9 | // { 10 | // return result.Succeeded 11 | // ? Result.Success() 12 | // : Result.Failure(result.Errors.Select(e => e.Description)); 13 | // } 14 | //} -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/IHasConcurrencyStamp.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities; 2 | 3 | /// 4 | /// Concurrency control or management the consistency of the data 5 | /// when more than one user is accessing it for different purposes. 6 | /// 7 | public interface IHasConcurrencyStamp 8 | { 9 | /// 10 | /// A random value that must change whenever a user is persisted to the store 11 | /// 12 | string ConcurrencyStamp { get; set; } 13 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/ISoftDelete.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities; 2 | 3 | /// 4 | /// Used to standardize soft deleting entities. 5 | /// Soft-delete entities are not actually deleted, 6 | /// marked as IsDeleted = true in the database, 7 | /// but can not be retrieved to the application. 8 | /// 9 | public interface ISoftDelete 10 | { 11 | /// 12 | /// Used to mark an Entity as 'Deleted'. 13 | /// 14 | bool IsDeleted { get; } 15 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Interfaces/IAppServiceLoader.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | 4 | namespace CodeZero.Configuration; 5 | 6 | /// 7 | /// Load , 8 | /// at runtime. 9 | /// 10 | public interface IAppServiceLoader 11 | { 12 | IWebHostEnvironment Environment { get; set; } 13 | IConfiguration Configuration { get; set; } 14 | string ApplicationName { get; set; } 15 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/ProxySettings.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Enforce Https configuration parameters 5 | /// 6 | public partial class ProxySettings 7 | { 8 | public ForwardedHeadersOptions ForwardedHeadersOptions { get; set; } = default!; 9 | public HstsOptions HstsOptions { get; set; } = default!; 10 | public HttpsRedirectionOptions HttpsRedirectionOptions { get; set; } = default!; 11 | public string RequestBasePath { get; set; } = default!; 12 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/CharExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | public static class CharExtensions 4 | { 5 | public static bool In(this char value, params char[] chars) 6 | { 7 | foreach (char c in chars) 8 | { 9 | if (c == value) 10 | return true; 11 | } 12 | 13 | return false; 14 | } 15 | } 16 | 17 | public static class ByteExtensions 18 | { 19 | public static string ToHexString(this byte[] bytes) 20 | { 21 | return BitConverter.ToString(bytes).Replace("-", ""); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/WebAPI/Services/CurrentUserService.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using CodeZero.Domain.Common.Interfaces; 3 | 4 | namespace WebAPI.Services; 5 | 6 | public class CurrentUserService : ICurrentUserService 7 | { 8 | private readonly IHttpContextAccessor _httpContextAccessor; 9 | 10 | public CurrentUserService(IHttpContextAccessor httpContextAccessor) 11 | { 12 | _httpContextAccessor = httpContextAccessor; 13 | } 14 | 15 | public string? UserId => _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); 16 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Logging/EFCoreLoggingUtils.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Logging; 2 | 3 | public static class EFCoreLoggingUtils 4 | { 5 | public static IDisposable EFQueryScope(this ILogger logger, string queryScopeName) 6 | { 7 | return logger.BeginScope(new Dictionary { { "EFQueries", queryScopeName } }); 8 | } 9 | 10 | public static IDisposable EFQueryScope(this ILogger logger, string queryScopeName) 11 | { 12 | return logger.BeginScope(new Dictionary { { "EFQueries", queryScopeName } }); 13 | } 14 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/Authentication.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Authentication configuration parameters 5 | /// 6 | public partial class Authentication 7 | { 8 | public string Authority { get; set; } = default!; 9 | public string Audience { get; set; } = default!; 10 | public string ClientId { get; set; } = default!; 11 | public string ClientSecret { get; set; } = default!; 12 | public string Scopes { get; set; } = default!; 13 | public bool SaveToken { get; set; } = default!; 14 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/DataProtectionConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents DataProtection configuration parameters 5 | /// 6 | public partial class DataProtectionConfig 7 | { 8 | public bool PersistKeysToRedis { get; set; } = default!; 9 | public string Path { get; set; } = "AppData"; 10 | public string FileSystemDirectoryName { get; set; } = default!; 11 | public string RedisKey { get; set; } = default!; 12 | public KeyManagement KeyManagement { get; set; } = default!; 13 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Messaging/Command.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Messaging; 2 | 3 | public abstract class Command : Message, IRequest 4 | { 5 | [System.ComponentModel.DataAnnotations.Timestamp] 6 | public DateTime Timestamp { get; private set; } 7 | public ValidationResult ValidationResult { get; set; } 8 | 9 | protected Command() 10 | { 11 | Timestamp = DateTime.UtcNow; 12 | ValidationResult = new ValidationResult(); 13 | } 14 | 15 | public virtual bool IsValid() 16 | { 17 | return ValidationResult.IsValid; 18 | } 19 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/SwaggerInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents SwaggerInfo configuration parameters 5 | /// 6 | public partial class SwaggerInfo 7 | { 8 | public string Title { get; set; } = AppServiceLoader.Instance.ApplicationName; 9 | public string Description { get; set; } = $"{AppServiceLoader.Instance.ApplicationName} API Swagger"; 10 | public Contact Contact { get; set; } = default!; 11 | public string TermsOfService { get; set; } = default!; 12 | public License License { get; set; } = default!; 13 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/Filters/RemoveVersionFromParameter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | 4 | namespace CodeZero.Swagger.Filters; 5 | 6 | public class RemoveVersionFromParameter : IOperationFilter 7 | { 8 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 9 | { 10 | var versionParameter = operation?.Parameters? 11 | .SingleOrDefault(p => p.Name == "version"); 12 | 13 | if (versionParameter is not null) 14 | operation?.Parameters.Remove(versionParameter); 15 | } 16 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Events/StoredEvent.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Events; 2 | 3 | public class StoredEvent : Event 4 | { 5 | public StoredEvent(Event theEvent, string data, string user) 6 | { 7 | Id = Guid.NewGuid(); 8 | AggregateId = theEvent.AggregateId; 9 | MessageType = theEvent.MessageType; 10 | Data = data; 11 | User = user; 12 | } 13 | 14 | // EF Constructor 15 | protected StoredEvent() { } 16 | 17 | public Guid Id { get; private set; } 18 | public string Data { get; private set; } = default!; 19 | public string User { get; private set; } = default!; 20 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/ApiVersioning/HttpContextRequestedApiVersion.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace CodeZero.ApiVersioning; 5 | 6 | public class HttpContextRequestedApiVersion : IRequestedApiVersion 7 | { 8 | public string Current => _httpContextAccessor.HttpContext?.GetRequestedApiVersion()?.ToString()!; 9 | 10 | private readonly IHttpContextAccessor _httpContextAccessor; 11 | 12 | public HttpContextRequestedApiVersion(IHttpContextAccessor httpContextAccessor) 13 | { 14 | _httpContextAccessor = httpContextAccessor; 15 | } 16 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Messaging/ResponseMessage.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Messaging; 2 | 3 | public class ResponseMessage : Message 4 | { 5 | public ValidationResult ValidationResult { get; set; } 6 | 7 | public ResponseMessage(ValidationResult validationResult) 8 | { 9 | ValidationResult = validationResult; 10 | } 11 | 12 | public void AddError(string message) 13 | { 14 | AddError(string.Empty, message); 15 | } 16 | 17 | public void AddError(string propertyName, string message) 18 | { 19 | ValidationResult.Errors.Add(new ValidationFailure(propertyName, message)); 20 | } 21 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/Text/Json/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System.Text.Json; 2 | 3 | public static class JsonExtensions 4 | { 5 | public static T FromJson( 6 | this string json, 7 | JsonSerializerOptions jsonOptions = default!) 8 | { 9 | var josnDeserialize = JsonSerializer.Deserialize(json, jsonOptions); 10 | 11 | if (josnDeserialize is not null) 12 | return josnDeserialize; 13 | 14 | return default!; 15 | } 16 | 17 | public static string ToJson(this T obj, JsonSerializerOptions jsonOptions = default!) => JsonSerializer.Serialize(obj, jsonOptions); 18 | } -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Purpose: Basic shell script to remove 'BIN' and 'OBJ' folders 3 | # Author: Nasr Aldin {https://nasraldin.com/} under GPL v2.0+ 4 | # ----------------------------------------------------------------- 5 | 6 | echo CodeZero by Nasr Aldin - nasr2ldin@gmail.com 7 | echo Deleting 'BIN' and 'OBJ' folders 8 | echo Version: 1.0.0 9 | echo 10 | 11 | echo Deleting... 12 | # Find and delete all directory recursively 'BIN' and 'OBJ' folders 13 | find $(pwd) -type d \( -name "bin" -o -name "obj" \); 14 | find $(pwd) -type d \( -name "bin" -o -name "obj" \) -exec rm -rf {} +; 15 | echo BIN and OBJ folders have been successfully deleted. 16 | -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Models/Result.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Models; 2 | 3 | public class Result 4 | { 5 | internal Result(bool succeeded, IEnumerable errors) 6 | { 7 | Succeeded = succeeded; 8 | Errors = errors.ToArray(); 9 | } 10 | 11 | public bool Succeeded { get; set; } 12 | public string[] Errors { get; set; } 13 | 14 | public static Result Success() 15 | { 16 | return new Result(true, System.Array.Empty()); 17 | } 18 | 19 | public static Result Failure(IEnumerable errors) 20 | { 21 | return new Result(false, errors); 22 | } 23 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/CustomCorsPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | public partial class CustomCorsPolicy 4 | { 5 | public string PolicyName { get; set; } = default!; 6 | public bool AllowAnyHeader { get; set; } 7 | public bool AllowAnyMethod { get; set; } 8 | public bool AllowAnyOrigin { get; set; } 9 | public string[] Headers { get; set; } = default!; 10 | public string[] Methods { get; set; } = default!; 11 | public string[] Origins { get; set; } = default!; 12 | public TimeSpan? PreflightMaxAge { get; set; } 13 | public bool SupportsCredentials { get; set; } 14 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/IValidatableEntity.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities; 2 | 3 | /// 4 | /// Interface for all classes that may be validated on their own. 5 | /// 6 | public interface IValidatableEntity 7 | { 8 | /// 9 | /// Validate the entity, returning a validation result. 10 | /// 11 | /// A validation result containing errors - or not. 12 | ValidationResult ValidationResult { get; } 13 | 14 | /// 15 | /// Validate the entity. 16 | /// 17 | /// A validation result true or false. 18 | bool Validate(); 19 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/ApiKeyConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents ApiKey configuration parameters 5 | /// 6 | public partial class ApiKeyConfig 7 | { 8 | public string ApiKey { get; set; } = default!; 9 | public string HeaderName { get; set; } = default!; 10 | public List AllowedClients { get; set; } = default!; 11 | } 12 | 13 | /// 14 | /// Represents allowed ApiKey clients configuration parameters 15 | /// 16 | public partial class AllowedClients 17 | { 18 | public string Name { get; set; } = default!; 19 | public string Description { get; set; } = default!; 20 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Behaviors/RequestLogger.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Behaviours; 2 | 3 | public class RequestLogger : IRequestPreProcessor 4 | where TRequest : notnull 5 | { 6 | private readonly ILogger _logger; 7 | 8 | public RequestLogger(ILogger logger) 9 | { 10 | _logger = logger; 11 | } 12 | 13 | public async Task Process(TRequest request, CancellationToken cancellationToken) 14 | { 15 | var requestName = typeof(TRequest).Name; 16 | 17 | _logger.LogInformation($"{Assembly.GetExecutingAssembly()} Request: {requestName} {request}"); 18 | 19 | await Task.CompletedTask; 20 | } 21 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Helpers/Reflection/TypeHelper.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Helpers; 2 | 3 | /// 4 | /// Some simple type-checking methods used internally. 5 | /// 6 | public static class TypeHelper 7 | { 8 | public static object GetDefaultValue(Type type) 9 | { 10 | if (type.IsValueType) 11 | { 12 | return Activator.CreateInstance(type)!; 13 | } 14 | 15 | return null!; 16 | } 17 | 18 | public static bool IsDefaultValue([CanBeNull] object obj) 19 | { 20 | if (obj is null) 21 | { 22 | return true; 23 | } 24 | 25 | return obj.Equals(GetDefaultValue(obj.GetType())); 26 | } 27 | } -------------------------------------------------------------------------------- /tests/WebAPI/Domain/Entities/Language.cs: -------------------------------------------------------------------------------- 1 | namespace WebAPI.Domain.Entities; 2 | 3 | public class Language : BaseEntity 4 | { 5 | public string Name { get; set; } = default!; 6 | public string Culture { get; set; } = default!; 7 | public string Icon { get; set; } = default!; 8 | public bool IsRtl { get; set; } 9 | 10 | public Language() { } 11 | 12 | public Language( 13 | string name, 14 | string culture, 15 | string? icon, 16 | bool isActive = true, 17 | bool isRtl = false) 18 | { 19 | Name = name; 20 | Culture = culture; 21 | Icon = icon!; 22 | IsActive = isActive; 23 | IsRtl = isRtl; 24 | } 25 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/StringValueAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | /// 4 | /// This attribute is used to represent a string value for a value in an enum. 5 | /// 6 | [AttributeUsage(AttributeTargets.All)] 7 | public class StringValueAttribute : Attribute 8 | { 9 | /// 10 | /// Holds the stringvalue for a value in an enum. 11 | /// 12 | public string StringValue { get; protected set; } 13 | 14 | /// 15 | /// Constructor used to init a StringValue Attribute 16 | /// 17 | /// 18 | public StringValueAttribute(string value) 19 | { 20 | StringValue = value; 21 | } 22 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/CacheConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents cache configuration parameters 5 | /// 6 | public partial class CacheConfig 7 | { 8 | /// 9 | /// Gets or sets the default cache time in minutes 10 | /// 11 | public int DefaultCacheTime { get; set; } = 60; 12 | 13 | /// 14 | /// Gets or sets the short term cache time in minutes 15 | /// 16 | public int ShortTermCacheTime { get; set; } = 3; 17 | 18 | /// 19 | /// Gets or sets the bundled files cache time in minutes 20 | /// 21 | public int BundledFilesCacheTime { get; set; } = 120; 22 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/IEntity.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities; 2 | 3 | /// 4 | /// Defines interface for base entity type. 5 | /// All entities in the system must implement this interface. 6 | /// 7 | /// Type of the primary key of the entity 8 | public interface IEntity 9 | { 10 | /// 11 | /// Unique identifier for this entity. 12 | /// 13 | TKey Id { get; } 14 | 15 | /// 16 | /// Checks if this entity is transient 17 | /// (not persisted to database and it has not an ). 18 | /// 19 | /// True, if this entity is transient 20 | bool IsTransient(); 21 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/Auditing/IFullAudited.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities.Auditing; 2 | 3 | /// 4 | /// This interface ads 5 | /// to for a fully audited entity. 6 | /// 7 | public interface IFullAudited : IAudited, IDeletion { } 8 | 9 | /// 10 | /// Adds navigation properties to interface for user. 11 | /// 12 | /// Type of the user 13 | /// Type of the user primary key 14 | public interface IFullAudited : 15 | IAudited, 16 | IFullAudited, 17 | IDeletion 18 | where TUser : IEntity 19 | { } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/SwaggerConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Swagger configuration parameters 5 | /// 6 | public partial class SwaggerConfig 7 | { 8 | public string RouteTemplate { get; set; } = "swagger/{documentName}/swagger.json"; 9 | public string RoutePrefix { get; set; } = "swagger.json"; 10 | public string UiEndpoint { get; set; } = "swagger"; 11 | public bool EnableAuthentication { get; set; } 12 | public bool EnableApiKey { get; set; } 13 | public string AuthorizationUrl { get; set; } = default!; 14 | public List Scopes { get; set; } = default!; 15 | public int DefaultModelsExpandDepth { get; set; } = 1; 16 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Mediator/MediatorHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace CodeZero.Domain.Mediator; 4 | 5 | public class MediatorHandler : IMediatorHandler 6 | { 7 | private readonly IMediator _mediator; 8 | 9 | public MediatorHandler(IMediator mediator) 10 | { 11 | _mediator = mediator; 12 | } 13 | 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public virtual async Task SendCommand(T command) where T : Command 16 | { 17 | return await _mediator.Send(command); 18 | } 19 | 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | public virtual async Task PublishEvent(T @event) where T : Event 22 | { 23 | await _mediator.Publish(@event); 24 | } 25 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/ServiceInstallers/AddCodeZeroExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.AspNetCore.Builder; 2 | 3 | /// 4 | /// Adds CodeZero services to the host service collection 5 | /// Registration of the dependency in a service container. 6 | /// 7 | public static partial class ServiceCollectionExtensions 8 | { 9 | /// 10 | /// Adds CodeZero services to the host service collection and let the app change 11 | /// the default behavior and set of features through a configure action. 12 | /// 13 | public static WebApplicationBuilder AddCodeZero(this WebApplicationBuilder builder) 14 | { 15 | builder.AddAppServiceLoader(); 16 | builder.AddDefaultServices(); 17 | 18 | return builder; 19 | } 20 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Helpers/HttpContextHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace CodeZero.Helpers; 4 | 5 | /// 6 | /// Provides access to the current , 7 | /// if one is available. 8 | /// 9 | public class HttpContextHelper : IHttpContextHelper 10 | { 11 | public HttpContextHelper(IHttpContextAccessor httpContextAccessor) 12 | { 13 | HttpContextAccessor = httpContextAccessor; 14 | 15 | Current = this; 16 | } 17 | 18 | public IHttpContextAccessor HttpContextAccessor { get; set; } 19 | public static HttpContextHelper Current { get; private set; } = default!; 20 | } 21 | 22 | public interface IHttpContextHelper 23 | { 24 | IHttpContextAccessor HttpContextAccessor { get; set; } 25 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/Auditing/IAudited.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities.Auditing; 2 | 3 | /// 4 | /// This interface is implemented by entities which must be audited. 5 | /// Related properties automatically set when saving/updating 6 | /// objects. 7 | /// 8 | public interface IAudited : ICreation, IModification { } 9 | 10 | /// 11 | /// Adds navigation properties to interface for user. 12 | /// 13 | /// Type of the user 14 | /// Type of the user primary key 15 | public interface IAudited : 16 | IAudited, 17 | ICreation, 18 | IModification 19 | where TUser : IEntity 20 | { } -------------------------------------------------------------------------------- /src/CodeZero.Domain/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.ComponentModel.DataAnnotations.Schema; 2 | global using System.Reflection; 3 | global using System.Text; 4 | global using CodeZero.Domain.Common.Interfaces; 5 | global using CodeZero.Domain.Common.Models; 6 | global using CodeZero.Domain.Common.Security; 7 | global using CodeZero.Domain.Data; 8 | global using CodeZero.Domain.Entities; 9 | global using CodeZero.Domain.Exception; 10 | global using CodeZero.Domain.Messaging; 11 | global using CodeZero.Exception; 12 | global using CodeZero.Helpers; 13 | global using CodeZero.Logging; 14 | global using FluentValidation; 15 | global using FluentValidation.Results; 16 | global using JetBrains.Annotations; 17 | global using MediatR; 18 | global using MediatR.Pipeline; 19 | global using Microsoft.Extensions.Logging; -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/Filters/ReplaceVersionWithExactValueInPath.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | 4 | namespace CodeZero.Swagger.Filters; 5 | 6 | public class ReplaceVersionWithExactValueInPath : IDocumentFilter 7 | { 8 | public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) 9 | { 10 | if (swaggerDoc?.Paths != null) 11 | { 12 | var paths = new OpenApiPaths(); 13 | 14 | foreach (var path in swaggerDoc.Paths) 15 | { 16 | paths.Add(path.Key.Replace("{version}", swaggerDoc.Info.Version, 17 | StringComparison.Ordinal), path.Value); 18 | } 19 | 20 | swaggerDoc.Paths = paths; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/ServiceMedia.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents service media configuration parameters 5 | /// 6 | public partial class MediaConfig 7 | { 8 | public int[] SupportedSizes { get; set; } = default!; 9 | public int MaxBrowserCacheDays { get; set; } 10 | public int MaxCacheDays { get; set; } 11 | public int MaxFileSize { get; set; } 12 | public string CdnBaseUrl { get; set; } = default!; 13 | public string AssetsRequestPath { get; set; } = default!; 14 | public string AssetsPath { get; set; } = default!; 15 | public bool UseTokenizedQueryString { get; set; } 16 | public string[] AllowedFileExtensions { get; set; } = default!; 17 | public string ContentSecurityPolicy { get; set; } = default!; 18 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/MediatorExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | 4 | namespace MediatR; 5 | 6 | public static class MediatorExtensions 7 | { 8 | public static async Task DispatchDomainEvents(this IMediator mediator, DbContext context) 9 | { 10 | var entities = context.ChangeTracker 11 | .Entries>() 12 | .Where(e => e.Entity.DomainEvents.Any()) 13 | .Select(e => e.Entity); 14 | 15 | var domainEvents = entities 16 | .SelectMany(e => e.DomainEvents) 17 | .ToList(); 18 | 19 | entities.ToList().ForEach(e => e.ClearDomainEvents()); 20 | 21 | foreach (var domainEvent in domainEvents) 22 | await mediator.Publish(domainEvent); 23 | } 24 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/ComparableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | /// 4 | /// Extension methods for . 5 | /// 6 | public static class ComparableExtensions 7 | { 8 | /// 9 | /// Checks a value is between a minimum and maximum value. 10 | /// 11 | /// The value to be checked 12 | /// Minimum (inclusive) value 13 | /// Maximum (inclusive) value 14 | public static bool IsBetween( 15 | this T value, 16 | T minInclusiveValue, 17 | T maxInclusiveValue) 18 | where T : IComparable 19 | { 20 | return value.CompareTo(minInclusiveValue) >= 0 && value.CompareTo(maxInclusiveValue) <= 0; 21 | } 22 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/IHasMetaTages.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities; 2 | 3 | /// 4 | /// Represents a Meta Tages 5 | /// 6 | public interface IHasMetaTages 7 | { 8 | /// 9 | /// Gets or sets the meta title 10 | /// 11 | string MetaTitle { get; set; } 12 | 13 | /// 14 | /// Gets or sets the meta keywords 15 | /// 16 | string MetaKeywords { get; set; } 17 | 18 | /// 19 | /// Gets or sets the meta description 20 | /// 21 | string MetaDescription { get; set; } 22 | 23 | /// 24 | /// Gets or sets the meta author 25 | /// 26 | string MetaAuthor { get; set; } 27 | 28 | /// 29 | /// Gets or sets the meta copyright 30 | /// 31 | string MetaCopyright { get; set; } 32 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/Filters/AcceptLanguageHeader.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | 4 | namespace CodeZero.Swagger.Filters; 5 | 6 | public class AcceptLanguageHeader : IOperationFilter 7 | { 8 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 9 | { 10 | if (operation is null || context is null) 11 | return; 12 | 13 | if (operation.Parameters is null) 14 | operation.Parameters = new List(); 15 | 16 | operation.Parameters.Add(new OpenApiParameter 17 | { 18 | Name = AppConst.HeaderName.AcceptLanguage, 19 | In = ParameterLocation.Header, 20 | Required = false, 21 | Schema = new OpenApiSchema 22 | { 23 | Type = "String" 24 | } 25 | }); 26 | } 27 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/AppServiceLoader.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | 4 | namespace CodeZero.Configuration; 5 | 6 | /// 7 | /// Represents the most common app settings ared used in the application 8 | /// 9 | public class AppServiceLoader : IAppServiceLoader 10 | { 11 | public static AppServiceLoader Instance { get; private set; } = default!; 12 | 13 | public AppServiceLoader(IWebHostEnvironment env, IConfiguration configuration) 14 | { 15 | Environment = env; 16 | Configuration = configuration; 17 | ApplicationName = configuration["ApplicationName"] ?? AppDomain.CurrentDomain.FriendlyName; 18 | 19 | Instance = this; 20 | } 21 | 22 | public IWebHostEnvironment Environment { get; set; } 23 | public IConfiguration Configuration { get; set; } 24 | public string ApplicationName { get; set; } 25 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Security/AuthorizeAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Security; 2 | 3 | /// 4 | /// Specifies the class this attribute is applied to requires authorization. 5 | /// 6 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 7 | public class AuthorizeAttribute : Attribute 8 | { 9 | /// 10 | /// Initializes a new instance of the 11 | /// class. 12 | /// 13 | public AuthorizeAttribute() { } 14 | 15 | /// 16 | /// Gets or sets a comma delimited list of roles 17 | /// that are allowed to access the resource. 18 | /// 19 | public string Roles { get; set; } = string.Empty; 20 | 21 | /// 22 | /// Gets or sets the policy name that determines access to the resource. 23 | /// 24 | public string Policy { get; set; } = string.Empty; 25 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Messaging/CommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Messaging; 2 | 3 | public abstract class CommandHandler 4 | { 5 | protected ValidationResult ValidationResult; 6 | 7 | protected CommandHandler() 8 | { 9 | ValidationResult = new ValidationResult(); 10 | } 11 | 12 | protected void AddError(string mensagem) 13 | { 14 | ValidationResult.Errors.Add(new ValidationFailure(string.Empty, mensagem)); 15 | } 16 | 17 | protected async Task Commit(IBaseDbContext context, string message) 18 | { 19 | var result = await context.SaveChangesAsync(CancellationToken.None); 20 | 21 | if (result == 0) AddError(message); 22 | 23 | return ValidationResult; 24 | } 25 | 26 | protected async Task Commit(IBaseDbContext context) 27 | { 28 | return await Commit(context, "There was an error saving data").ConfigureAwait(false); 29 | } 30 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Configuration/Models/ForwardedHeadersOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | public partial class ForwardedHeadersOptions 4 | { 5 | public string ForwardedForHeaderName { get; set; } = default!; 6 | public string ForwardedHostHeaderName { get; set; } = default!; 7 | public string ForwardedProtoHeaderName { get; set; } = default!; 8 | public string OriginalForHeaderName { get; set; } = default!; 9 | public string OriginalHostHeaderName { get; set; } = default!; 10 | public string OriginalProtoHeaderName { get; set; } = default!; 11 | public int? ForwardLimit { get; set; } 12 | public string[] KnownProxies { get; set; } = default!; 13 | public string[] KnownNetworks { get; set; } = default!; 14 | public string[] AllowedHosts { get; set; } = default!; 15 | public bool RequireHeaderSymmetry { get; set; } 16 | public bool AddActiveNetworkInterfaceToKnownNetworks { get; set; } 17 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared.Constants/Constants/Environments.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero; 2 | 3 | public static partial class AppConst 4 | { 5 | public static readonly string ASPNETCORE_ENVIRONMENT = "ASPNETCORE_ENVIRONMENT"; 6 | 7 | /// 8 | /// Environment names 9 | /// 10 | public static partial class Environments 11 | { 12 | // This from Microsoft.AspNetCore.Hosting.Environments 13 | public static readonly string Development = "Development"; 14 | public static readonly string Production = "Production"; 15 | public static readonly string Staging = "Staging"; 16 | 17 | // more env names by CodeZero 18 | public static readonly string Dev = "dev"; 19 | public static readonly string Prod = "prod"; 20 | public static readonly string Stag = "stag"; 21 | public static readonly string Test = "test"; 22 | public static readonly string QA = "qa"; 23 | public static readonly string UAT = "uat"; 24 | } 25 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Behaviors/UnhandledExceptionBehavior.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Behaviours; 2 | 3 | public class UnhandledExceptionBehavior : 4 | IPipelineBehavior 5 | where TRequest : IRequest 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public UnhandledExceptionBehavior(ILogger logger) 10 | { 11 | _logger = logger; 12 | } 13 | 14 | public async Task Handle( 15 | TRequest request, 16 | CancellationToken cancellationToken, 17 | RequestHandlerDelegate next) 18 | { 19 | try 20 | { 21 | return await next(); 22 | } 23 | catch (CodeZeroException ex) 24 | { 25 | var requestName = typeof(TRequest).Name; 26 | 27 | _logger.LogError(ex, $"{Assembly.GetExecutingAssembly()} Request: Unhandled Exception for Request {requestName} {request}"); 28 | 29 | throw; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/NullableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | public static class NullableExtensions 4 | { 5 | /// 6 | /// Check if index.HasValue and index.Value == value 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | public static bool HasValueAndEquals(this T? source, T target) 13 | where T : struct 14 | { 15 | return source.HasValue && source.Value.Equals(target); 16 | } 17 | 18 | /// 19 | /// Check if index.HasValue and index.Value == value 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | public static bool HasValueAndEquals(this T? source, T? target) 26 | where T : struct 27 | { 28 | return source.HasValue && source.Value.Equals(target); 29 | } 30 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/UserFriendlyException.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace CodeZero.Exception; 4 | 5 | /// 6 | /// This exception type is directly shown to the user. 7 | /// 8 | [Serializable] 9 | public class UserFriendlyException : BusinessException, IUserFriendlyException 10 | { 11 | public UserFriendlyException( 12 | string message, 13 | string code = null!, 14 | string details = null!, 15 | System.Exception innerException = null!, 16 | LogLevel logLevel = LogLevel.Warning) 17 | : base( 18 | code, 19 | message, 20 | details, 21 | innerException, 22 | logLevel) 23 | { 24 | Details = details; 25 | } 26 | 27 | /// 28 | /// Constructor for serializing. 29 | /// 30 | public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context) 31 | : base(serializationInfo, context) 32 | { 33 | } 34 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/RedisConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents Redis configuration parameters 5 | /// 6 | public partial class RedisConfig 7 | { 8 | /// 9 | /// Gets or sets Redis connection string. Used when Redis is enabled 10 | /// 11 | public string ConnectionString { get; set; } = default!; 12 | 13 | /// 14 | /// Gets or sets a specific redis database; If you need to use 15 | /// a specific redis database, just set its number here. 16 | /// set NULL if should use the different database 17 | /// for each data type (used by default) 18 | /// 19 | public int? DatabaseId { get; set; } = null; 20 | 21 | /// 22 | /// Gets or sets a value indicating whether we should ignore 23 | /// Redis timeout exception (Enabling this setting increases 24 | /// cache stability but may decrease site performance) 25 | /// 26 | public bool IgnoreTimeoutException { get; set; } = false; 27 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/tests/WebAPI/WebAPI.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/tests/WebAPI/WebAPI.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/tests/WebAPI/WebAPI.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Nasr Aldin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: 5 | // https://go.microsoft.com/fwlink/?linkid=830387 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Launch (WebAPI)", 10 | "type": "coreclr", 11 | "request": "launch", 12 | "preLaunchTask": "build", 13 | "program": "${workspaceFolder}/tests/WebAPI/bin/Debug/net6.0/WebAPI.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/tests/WebAPI", 16 | "stopAtEntry": false, 17 | "serverReadyAction": { 18 | "action": "openExternally", 19 | "pattern": "\\bNow listening on:\\s+https://\\S+:([0-9]+)", 20 | "uriFormat": "https://localhost:%s/swagger" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Models/PaginatedList.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace CodeZero.Domain.Common.Models; 4 | 5 | public class PaginatedList : IPagedList 6 | { 7 | public List Items { get; } 8 | public int PageNumber { get; } 9 | public int TotalPages { get; } 10 | public int TotalCount { get; } 11 | 12 | public PaginatedList(List items, int count, int pageNumber, int pageSize) 13 | { 14 | PageNumber = pageNumber; 15 | TotalPages = (int)Math.Ceiling(count / (double)pageSize); 16 | TotalCount = count; 17 | Items = items; 18 | } 19 | 20 | public bool HasPreviousPage => PageNumber > 1; 21 | 22 | public bool HasNextPage => PageNumber < TotalPages; 23 | 24 | public static async Task> CreateAsync( 25 | IQueryable source, 26 | int pageNumber, 27 | int pageSize) 28 | { 29 | var count = await source.CountAsync(); 30 | var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync(); 31 | 32 | return new PaginatedList(items, count, pageNumber, pageSize); 33 | } 34 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Aggregate/BasicAggregateRoot.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace CodeZero.Domain.Aggregate; 4 | 5 | [Serializable] 6 | public abstract class BasicAggregateRoot : IAggregateRoot, IGeneratesDomainEvents 7 | { 8 | private readonly ICollection _distributedEvents = new Collection(); 9 | private readonly ICollection _localEvents = new Collection(); 10 | 11 | public virtual IEnumerable GetLocalEvents() 12 | { 13 | return _localEvents; 14 | } 15 | 16 | public virtual IEnumerable GetDistributedEvents() 17 | { 18 | return _distributedEvents; 19 | } 20 | 21 | public virtual void ClearLocalEvents() 22 | { 23 | _localEvents.Clear(); 24 | } 25 | 26 | public virtual void ClearDistributedEvents() 27 | { 28 | _distributedEvents.Clear(); 29 | } 30 | 31 | protected virtual void AddLocalEvent(object eventData) 32 | { 33 | _localEvents.Add(eventData); 34 | } 35 | 36 | protected virtual void AddDistributedEvent(object eventData) 37 | { 38 | _distributedEvents.Add(eventData); 39 | } 40 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/Auditing/IDeletion.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities.Auditing; 2 | 3 | /// 4 | /// This interface is implemented by entities which wanted 5 | /// to store deletion information (who and when deleted). 6 | /// is automatically set when deleting 7 | /// . 8 | /// 9 | public interface IDeletion : ISoftDelete 10 | { 11 | /// 12 | /// Which user deleted this entity? 13 | /// 14 | string DeletedBy { get; set; } 15 | 16 | /// 17 | /// Deletion time of this entity. 18 | /// 19 | DateTime? DeletionTime { get; set; } 20 | } 21 | 22 | /// 23 | /// Adds navigation properties to interface for user. 24 | /// 25 | /// Type of the user 26 | /// Type of the user primary key 27 | public interface IDeletion : IDeletion where TUser : IEntity 28 | { 29 | /// 30 | /// Reference to the deleter user of this entity. 31 | /// 32 | TUser DeleterUser { get; set; } 33 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/Auditing/ICreation.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities.Auditing; 2 | 3 | /// 4 | /// This interface is implemented by entities that is wanted 5 | /// to store creation information (who and when created). 6 | /// Creation time and creator user are automatically 7 | /// set when saving to database. 8 | /// 9 | public interface ICreation 10 | { 11 | /// 12 | /// Id of the creator user of this entity. 13 | /// 14 | string CreatedBy { get; set; } 15 | 16 | /// 17 | /// Creation time of this entity. 18 | /// 19 | DateTime CreatedAt { get; set; } 20 | } 21 | 22 | /// 23 | /// Adds navigation properties to interface for user. 24 | /// 25 | /// Type of the user 26 | /// Type of the user primary key 27 | public interface ICreation : ICreation where TUser : IEntity 28 | { 29 | /// 30 | /// Reference to the creator user of this entity. 31 | /// 32 | TUser CreatorUser { get; set; } 33 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/DebugConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents App Debug configuration parameters 5 | /// 6 | public partial class DebugConfig 7 | { 8 | /// 9 | /// Controls the capture of startup errors 10 | /// 11 | public bool CaptureStartupErrors { get; set; } 12 | public bool DetailedErrorsKey { get; set; } 13 | 14 | /// 15 | /// Enables application data to be included in exception messages, logging, etc. 16 | /// 17 | public bool SensitiveDataLogging { get; set; } 18 | public bool EnableDetailedErrors { get; set; } 19 | 20 | /// 21 | /// Gets or sets a value indicating whether to display the full error in production environment. 22 | /// It's ignored (always enabled) in development environment 23 | /// 24 | public bool DisplayFullErrorStack { get; set; } 25 | 26 | /// 27 | /// Gets or sets a value that indicates whether to use MiniProfiler services 28 | /// 29 | public bool UseMiniProfiler { get; set; } 30 | public bool SerilogSelfLog { get; set; } 31 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/Auditing/IModification.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities.Auditing; 2 | 3 | /// 4 | /// This interface is implemented by entities that is wanted 5 | /// to store modification information (who and when modified lastly). 6 | /// Properties are automatically set when updating the . 7 | /// 8 | public interface IModification 9 | { 10 | /// 11 | /// Id of the modifier user of this entity. 12 | /// 13 | string UpdatedBy { get; set; } 14 | 15 | /// 16 | /// The last modified time for this entity. 17 | /// 18 | DateTime UpdatedAt { get; set; } 19 | } 20 | 21 | /// 22 | /// Adds navigation properties to interface for user. 23 | /// 24 | /// Type of the user 25 | /// Type of the user primary key 26 | public interface IModification : IModification where TUser : IEntity 27 | { 28 | /// 29 | /// Reference to the last modifier user of this entity. 30 | /// 31 | TUser ModifierUser { get; set; } 32 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Behaviors/LoggingBehavior.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Behaviours; 2 | 3 | public class LoggingBehavior : IRequestPreProcessor 4 | where TRequest : notnull 5 | { 6 | private readonly ILogger _logger; 7 | private readonly ICurrentUserService _currentUserService; 8 | private readonly IIdentityService _identityService; 9 | 10 | public LoggingBehavior( 11 | ILogger logger, 12 | ICurrentUserService currentUserService, 13 | IIdentityService identityService) 14 | { 15 | _logger = logger; 16 | _currentUserService = currentUserService; 17 | _identityService = identityService; 18 | } 19 | 20 | public async Task Process(TRequest request, CancellationToken cancellationToken) 21 | { 22 | var requestName = typeof(TRequest).Name; 23 | var userId = _currentUserService.UserId ?? string.Empty; 24 | string userName = string.Empty; 25 | 26 | if (!string.IsNullOrEmpty(userId)) 27 | { 28 | userName = await _identityService.GetUserNameAsync(userId); 29 | } 30 | 31 | _logger.LogInformation($"{Assembly.GetExecutingAssembly()} Request: {requestName} {userId} {userName} {request}"); 32 | } 33 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/BusinessException.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace CodeZero.Exception; 4 | 5 | [Serializable] 6 | public class BusinessException : CodeZeroException, 7 | IBusinessException, 8 | IHasErrorCode, 9 | IHasErrorDetails, 10 | IHasLogLevel 11 | { 12 | public string Code { get; set; } = default!; 13 | public string Details { get; set; } = default!; 14 | public LogLevel LogLevel { get; set; } 15 | 16 | public BusinessException( 17 | string code = null!, 18 | string message = null!, 19 | string details = null!, 20 | System.Exception innerException = null!, 21 | LogLevel logLevel = LogLevel.Warning) 22 | : base(message, innerException) 23 | { 24 | Code = code; 25 | Details = details; 26 | LogLevel = logLevel; 27 | } 28 | 29 | /// 30 | /// Constructor for serializing. 31 | /// 32 | public BusinessException(SerializationInfo serializationInfo, StreamingContext context) 33 | : base(serializationInfo, context) 34 | { 35 | } 36 | 37 | public BusinessException WithData(string name, object value) 38 | { 39 | Data[name] = value; 40 | 41 | return this; 42 | } 43 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/ValueObject.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common; 2 | 3 | public abstract class ValueObject where T : ValueObject 4 | { 5 | public override bool Equals(object? obj) 6 | { 7 | var valueObject = obj as T; 8 | 9 | if (obj is null || obj.GetType() != GetType()) 10 | { 11 | return false; 12 | } 13 | 14 | return EqualsCore(valueObject!); 15 | } 16 | 17 | private bool EqualsCore(ValueObject other) 18 | { 19 | return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); 20 | } 21 | 22 | public override int GetHashCode() 23 | { 24 | return GetEqualityComponents().Aggregate(1, 25 | (current, obj) => current * 23 + (obj?.GetHashCode() ?? 0)); 26 | } 27 | 28 | protected abstract IEnumerable GetEqualityComponents(); 29 | 30 | protected static bool EqualOperator(ValueObject left, ValueObject right) 31 | { 32 | if (left is null ^ right is null) 33 | { 34 | return false; 35 | } 36 | 37 | return left?.Equals(right!) != false; 38 | } 39 | 40 | protected static bool NotEqualOperator(ValueObject left, ValueObject right) 41 | { 42 | return !(EqualOperator(left, right)); 43 | } 44 | } -------------------------------------------------------------------------------- /tests/WebAPI/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using WebAPI.Models; 3 | 4 | namespace WebAPI.Controllers; 5 | 6 | [ApiController] 7 | [Route("[controller]")] 8 | public class WeatherForecastController : ControllerBase 9 | { 10 | private static readonly string[] Summaries = new[] 11 | { 12 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 13 | }; 14 | 15 | private readonly ILogger _logger; 16 | 17 | public WeatherForecastController(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | [HttpGet] 23 | [Produces("application/json")] 24 | public IEnumerable Get() 25 | { 26 | _logger.LogInformation("WeatherForecast > Get"); 27 | //_logger.LogInformation($"HttpContextHelper > {HttpContextHelper.Current.HttpContextAccessor.HttpContext?.Request.Path}"); 28 | var data = Enumerable.Range(1, 5).Select(index => new WeatherForecast 29 | { 30 | Date = DateTime.Now.AddDays(index), 31 | TemperatureC = Random.Shared.Next(-20, 55), 32 | Summary = Summaries[Random.Shared.Next(Summaries.Length)] 33 | }) 34 | .ToArray(); 35 | 36 | return data; 37 | } 38 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Extensions/AppServiceLoaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using CodeZero.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | 5 | namespace Microsoft.AspNetCore.Builder; 6 | 7 | /// 8 | /// Adds CodeZero Configuration services to the host service collection 9 | /// 10 | public static partial class AppServiceLoaderExtensions 11 | { 12 | /// 13 | /// Adds CodeZero services to the host service collection 14 | /// and let the app change the default behavior and set of features through a configure action. 15 | /// 16 | public static WebApplicationBuilder AddAppServiceLoader(this WebApplicationBuilder builder) 17 | { 18 | Console.WriteLine("[CodeZero] Adds AppServiceLoader..."); 19 | 20 | // Add Load configuration from appsettings.json 21 | //builder.Services.AddOptions(); 22 | 23 | // Register Core configuration 24 | builder.Services.AddTransient(typeof(IAppServiceLoader), typeof(AppServiceLoader)); 25 | builder.Services.TryAddSingleton(new AppServiceLoader(builder.Environment, builder.Configuration)); 26 | builder.Services.AddTransient(typeof(IConfig<>), typeof(Config<>)); 27 | 28 | return builder; 29 | } 30 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/Collections/Generic/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace System.Collections.Generic; 4 | 5 | /// 6 | /// Extension methods for . 7 | /// 8 | [EditorBrowsable(EditorBrowsableState.Never)] 9 | public static class CollectionExtensions 10 | { 11 | /// 12 | /// Checks whatever given collection object is null or has no item. 13 | /// 14 | public static bool IsNullOrEmpty([CanBeNull] this ICollection source) 15 | { 16 | return source is null || source.Count <= 0; 17 | } 18 | 19 | /// 20 | /// Adds an item to the collection if it's not already in the collection. 21 | /// 22 | /// The collection 23 | /// Item to check and add 24 | /// Type of the items in the collection 25 | /// Returns True if added, returns False if not. 26 | public static bool AddIfNotContains([NotNull] this ICollection source, T item) 27 | { 28 | ArgumentNullException.ThrowIfNull(source); 29 | 30 | if (source.Contains(item)) 31 | { 32 | return false; 33 | } 34 | 35 | source.Add(item); 36 | 37 | return true; 38 | } 39 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/Auditing/CreationAudited.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities.Auditing; 2 | 3 | /// 4 | /// This class can be used to simplify implementing . 5 | /// 6 | /// Type of the primary key of the entity 7 | [Serializable] 8 | public abstract class CreationAudited : BaseEntity, ICreation 9 | { 10 | /// 11 | /// Creator of this entity. 12 | /// 13 | public virtual string CreatedBy { get; set; } = default!; 14 | 15 | /// 16 | /// Creation time of this entity. 17 | /// 18 | public virtual DateTime CreatedAt { get; set; } = DateTime.UtcNow; 19 | } 20 | 21 | /// 22 | /// This class can be used to simplify implementing . 23 | /// 24 | /// Type of the primary key of the entity 25 | /// Type of the user 26 | [Serializable] 27 | public abstract class CreationAudited : 28 | CreationAudited, 29 | ICreation 30 | where TUser : IEntity 31 | { 32 | /// 33 | /// Reference to the creator user of this entity. 34 | /// 35 | [ForeignKey("CreatedBy")] 36 | public virtual TUser CreatorUser { get; set; } = default!; 37 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Responses/BaseResponse.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation.Results; 2 | 3 | namespace CodeZero.Domain.Common.Responses; 4 | 5 | public class BaseResponse 6 | { 7 | public BaseResponse() 8 | { 9 | IsSuccess = false; 10 | } 11 | 12 | public BaseResponse(IEnumerable failures) 13 | { 14 | IsSuccess = false; 15 | ValidationIssues = new List(); 16 | 17 | var propertyNames = failures 18 | .Select(e => e.PropertyName) 19 | .Distinct(); 20 | 21 | foreach (var propertyName in propertyNames) 22 | { 23 | // Each PropertyName get's an array of failures associated with it: 24 | var PropertyFailures = failures 25 | .Where(e => e.PropertyName == propertyName) 26 | .Select(e => e.ErrorMessage) 27 | .ToArray(); 28 | 29 | var propertyFailure = new ValidationIssue 30 | { 31 | PropertyName = propertyName, 32 | PropertyFailures = PropertyFailures.ToList() 33 | }; 34 | 35 | ValidationIssues.Add(propertyFailure); 36 | } 37 | } 38 | 39 | public bool IsSuccess { get; set; } 40 | public string Message { get; set; } = default!; 41 | public IList ValidationIssues { get; set; } = default!; 42 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Behaviors/ValidationBehavior.cs: -------------------------------------------------------------------------------- 1 | using ValidationException = CodeZero.Domain.Exception.ValidationException; 2 | 3 | namespace CodeZero.Domain.Common.Behaviours; 4 | 5 | public class ValidationBehavior : 6 | IPipelineBehavior 7 | where TRequest : IRequest 8 | { 9 | private readonly IEnumerable> _validators; 10 | 11 | public ValidationBehavior(IEnumerable> validators) 12 | { 13 | _validators = validators; 14 | } 15 | 16 | public async Task Handle( 17 | TRequest request, 18 | CancellationToken cancellationToken, 19 | RequestHandlerDelegate next) 20 | { 21 | if (_validators.Any()) 22 | { 23 | var context = new ValidationContext(request); 24 | 25 | var validationResults = await Task.WhenAll( 26 | _validators.Select(v => 27 | v.ValidateAsync(context, cancellationToken))); 28 | 29 | var failures = validationResults 30 | .Where(r => r.Errors.Any()) 31 | .SelectMany(r => r.Errors) 32 | .ToList(); 33 | 34 | if (failures.Any()) 35 | throw new ValidationException(failures); 36 | } 37 | 38 | return await next(); 39 | } 40 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/PoweredBy/PoweredByMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace CodeZero.Middleware; 4 | 5 | /// 6 | /// Adds the X-Powered-By header with values CodeZero. 7 | /// 8 | public class PoweredByMiddleware 9 | { 10 | private readonly RequestDelegate _next; 11 | private readonly IPoweredByMiddlewareOptions _options; 12 | 13 | public PoweredByMiddleware(RequestDelegate next, IPoweredByMiddlewareOptions options) 14 | { 15 | _next = next; 16 | _options = options; 17 | } 18 | 19 | public Task Invoke(HttpContext httpContext) 20 | { 21 | if (_options.Enabled) 22 | { 23 | httpContext.Response.Headers[_options.HeaderName] = _options.HeaderValue; 24 | } 25 | 26 | return _next.Invoke(httpContext); 27 | } 28 | } 29 | 30 | internal class PoweredByMiddlewareOptions : IPoweredByMiddlewareOptions 31 | { 32 | private const string PoweredByHeaderName = AppConst.HeaderName.PoweredBy; 33 | private const string PoweredByHeaderValue = "CodeZero"; 34 | public string HeaderName => PoweredByHeaderName; 35 | public string HeaderValue { get; set; } = PoweredByHeaderValue; 36 | public bool Enabled { get; set; } = true; 37 | } 38 | 39 | public interface IPoweredByMiddlewareOptions 40 | { 41 | bool Enabled { get; set; } 42 | string HeaderName { get; } 43 | string HeaderValue { get; set; } 44 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Extensions/DomainExtensions.cs: -------------------------------------------------------------------------------- 1 | using CodeZero.Domain.Common.Behaviours; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | public static partial class ServiceCollectionExtensions 6 | { 7 | /// 8 | /// Adds Domain Services. 9 | /// 10 | /// The . 11 | /// 12 | public static IServiceCollection AddDomainServices([NotNull] this IServiceCollection services) 13 | { 14 | //services.AddAutoMapper(Assembly.GetExecutingAssembly()); 15 | services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); 16 | services.AddMediatR(Assembly.GetExecutingAssembly()); 17 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehavior<,>)); 18 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(AuthorizationBehavior<,>)); 19 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); 20 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehavior<,>)); 21 | services.AddTransient(typeof(IRequestPreProcessor<>), typeof(LoggingBehavior<>)); 22 | services.AddTransient(typeof(IRequestPreProcessor<>), typeof(RequestLogger<>)); 23 | services.AddTransient(typeof(IPagedList<>), typeof(PaginatedList<>)); 24 | 25 | return services; 26 | } 27 | } -------------------------------------------------------------------------------- /Common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | true 5 | enable 6 | true 7 | true 8 | true 9 | icon.png 10 | 11 | 12 | 13 | $(NoWarn);1591 14 | $(NoWarn);CA1062 15 | 16 | 17 | 18 | $(NoWarn);CS1591 19 | false 20 | 21 | 22 | 23 | 24 | 25 | all 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | 34 | 35 | 36 | True 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Exception; 2 | 3 | /// 4 | /// Represents an error that occurs if a queried object 5 | /// by a particular key is null (not found). 6 | /// 7 | public class NotFoundException : CodeZeroException 8 | { 9 | /// 10 | /// Initializes a new instance of the NotFoundException class 11 | /// with a specified name of the queried object and its key. 12 | /// 13 | /// The value by which the object is queried. 14 | /// Name of the queried object. 15 | public NotFoundException(string key, string objectName) 16 | : base($"\"{objectName}\" ({key}) was not found.") 17 | { 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the NotFoundException class 22 | /// with a specified name of the queried object, its key, 23 | /// and the exception that is the cause of this exception. 24 | /// 25 | /// The value by which the object is queried. 26 | /// Name of the queried object. 27 | /// The exception that is the cause of the current exception. 28 | public NotFoundException(string key, string objectName, System.Exception innerException) 29 | : base($"Queried object {objectName} was not found, Key: {key}", innerException) 30 | { 31 | } 32 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/Antiforgery/UseAntiforgeryExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Antiforgery; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Microsoft.AspNetCore.Builder; 6 | 7 | public static partial class ApplicationBuilderExtensions 8 | { 9 | /// 10 | /// Register the Antiforgery. 11 | /// 12 | /// The . 13 | /// 14 | public static IApplicationBuilder UseAntiforgery([NotNull] this IApplicationBuilder app) 15 | { 16 | IAntiforgery antiforgery = app.ApplicationServices.GetRequiredService(); 17 | 18 | return app.Use(next => context => 19 | { 20 | string path = context.Request.Path.Value!; 21 | 22 | if (string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) || 23 | string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase)) 24 | { 25 | // The request token can be sent as a JavaScript-readable cookie, 26 | // and Angular uses it by default. 27 | var tokens = antiforgery.GetAndStoreTokens(context); 28 | context.Response.Cookies.Append(AppConst.HeaderName.XsrfToken, 29 | tokens.RequestToken!, new CookieOptions() { HttpOnly = false }); 30 | } 31 | 32 | return next(context); 33 | }); 34 | } 35 | } -------------------------------------------------------------------------------- /src/CodeZero.Builder/Microsoft/Configuration/ConfigurationBuilderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Extensions.Configuration; 2 | 3 | public class ConfigurationBuilderOptions 4 | { 5 | /// 6 | /// Used to set user secret id for the application. 7 | /// 8 | public string UserSecretsId { get; set; } = default!; 9 | 10 | /// 11 | /// Default value: "appsettings". 12 | /// 13 | public string FileName { get; set; } = "appsettings"; 14 | 15 | /// 16 | /// Default value: "appsettings.Logs". 17 | /// 18 | public string SerilogFileName { get; set; } = "Logs"; 19 | 20 | /// 21 | /// Default value: "appsettings.Language". 22 | /// 23 | public string LanguageFileName { get; set; } = "Language"; 24 | 25 | /// 26 | /// Environment name. Generally used "Development", "Staging" or "Production". 27 | /// 28 | public string EnvironmentName { get; set; } = default!; 29 | 30 | /// 31 | /// Base path to read the configuration file indicated by . 32 | /// 33 | public string BasePath { get; set; } = default!; 34 | 35 | /// 36 | /// Prefix for the environment variables. 37 | /// 38 | public string EnvironmentVariablesPrefix { get; set; } = default!; 39 | 40 | /// 41 | /// Command line arguments. 42 | /// 43 | public string[] CommandLineArgs { get; set; } = default!; 44 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/SerilogConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.Extensions.Configuration; 3 | using Serilog; 4 | using Serilog.Exceptions; 5 | using Serilog.Exceptions.Core; 6 | using Serilog.Exceptions.EntityFrameworkCore.Destructurers; 7 | 8 | namespace CodeZero.Logging; 9 | 10 | public static class SerilogConfig 11 | { 12 | public static void SetupLogger([NotNull] IConfiguration configuration) 13 | { 14 | Console.WriteLine($"[CodeZero] Loads Serilog Configuration..."); 15 | 16 | var appName = configuration.GetSection("ApplicationName").Value ?? AppDomain.CurrentDomain.FriendlyName; 17 | 18 | Log.Logger = new LoggerConfiguration() 19 | .ReadFrom.Configuration(configuration) 20 | .Enrich.FromLogContext() 21 | .Enrich.WithProperty("Application", appName) 22 | .Enrich.WithProperty("Environment", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")!) 23 | .Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() 24 | .WithDefaultDestructurers() 25 | .WithDestructurers(new[] { new DbUpdateExceptionDestructurer() })) 26 | 27 | // Used to filter out potentially bad data due debugging. 28 | // Very useful when doing Seq dashboards and want to remove logs under debugging session. 29 | #if DEBUG 30 | .Enrich.WithProperty("DebuggerAttached", Debugger.IsAttached) 31 | #endif 32 | .CreateLogger(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.ExceptionServices; 2 | using System.Runtime.InteropServices; 3 | using System.Security; 4 | 5 | namespace System; 6 | 7 | /// 8 | /// Extension methods for class. 9 | /// 10 | public static class ExceptionExtensions 11 | { 12 | /// 13 | /// Uses 14 | /// method to re-throws exception while preserving stack trace. 15 | /// 16 | /// Exception to be re-thrown 17 | public static void ReThrow(this Exception exception) 18 | { 19 | ExceptionDispatchInfo.Capture(exception).Throw(); 20 | } 21 | 22 | /// 23 | /// Try to get a log level from the given 24 | /// if it implements the interface. 25 | /// Otherwise, returns the . 26 | /// 27 | /// 28 | /// 29 | /// 30 | public static LogLevel GetLogLevel( 31 | this Exception exception, 32 | LogLevel defaultLevel = LogLevel.Error) 33 | { 34 | return (exception as IHasLogLevel)?.LogLevel ?? defaultLevel; 35 | } 36 | 37 | public static bool IsFatal(this Exception ex) 38 | { 39 | return 40 | ex is OutOfMemoryException or 41 | SecurityException or 42 | SEHException; 43 | } 44 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/RateLimit/RateLimitingClientIDExtensions.cs: -------------------------------------------------------------------------------- 1 | using AspNetCoreRateLimit; 2 | using Microsoft.Extensions.Configuration; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection; 5 | 6 | public static partial class ServiceCollectionExtensions 7 | { 8 | /// 9 | /// Add IpRateLimiting based on client ID. 10 | /// 11 | /// The . 12 | /// The . 13 | /// 14 | public static IServiceCollection AddRateLimitingClientID( 15 | [NotNull] this IServiceCollection services, 16 | [NotNull] IConfiguration configuration) 17 | { 18 | // load general configuration from appsettings.json 19 | services.Configure(configuration.GetSection("ClientRateLimiting")); 20 | // load ip rules from appsettings.json 21 | services.Configure(configuration.GetSection("ClientRateLimitPolicies")); 22 | // inject counter and rules stores 23 | services.AddInMemoryRateLimiting(); 24 | services.AddSingleton(); 25 | services.AddSingleton(); 26 | // configuration (resolvers, counter key builders) 27 | services.AddSingleton(); 28 | 29 | return services; 30 | } 31 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared.Constants/Constants/MimeTypes.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero; 2 | 3 | public static partial class AppConst 4 | { 5 | /// 6 | /// Collection of MimeType Constants for using to avoid Typos 7 | /// If needed MimeTypes missing feel free to add 8 | /// 9 | public static partial class MimeTypes 10 | { 11 | public const string JSON = "application/json"; 12 | public const string PDF = "application/pdf"; 13 | public const string ZIP = "application/zip"; 14 | public const string ForceDownload = "application/force-download"; 15 | public const string Problem = "application/problem+json"; 16 | public const string Image_JPEG = "image/jpeg"; 17 | public const string Image_PNG = "image/png"; 18 | public const string Image_SVG = "image/svg+xml"; 19 | public const string Audio = "audio/mpeg"; 20 | public const string Video = "video/mp4"; 21 | public const string Multipart_Mixed = "multipart/mixed"; 22 | public const string Multipart_FormData = "multipart/form-data"; 23 | public const string Text = "text/plain"; 24 | public const string Text_UTF8 = "text/plain; charset=utf-8"; 25 | public const string Text_CSS = "text/css"; 26 | public const string Text_CSV = "text/csv"; 27 | public const string Text_HTML = "text/html"; 28 | /// 29 | /// Generic binary data, for unknown binary file. 30 | /// 31 | public const string OctetStream = "application/octet-stream"; 32 | } 33 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/Auditing/AuditedEntity.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities.Auditing; 2 | 3 | /// 4 | /// This class can be used to simplify implementing . 5 | /// 6 | /// Type of the primary key of the entity 7 | [Serializable] 8 | public abstract class AuditedEntity : CreationAudited, IAudited 9 | { 10 | /// 11 | /// Last modifier user of this entity. 12 | /// 13 | public virtual string UpdatedBy { get; set; } = default!; 14 | 15 | /// 16 | /// Last modification date of this entity. 17 | /// 18 | public virtual DateTime UpdatedAt { get; set; } 19 | } 20 | 21 | /// 22 | /// This class can be used to simplify implementing . 23 | /// 24 | /// Type of the primary key of the entity 25 | /// Type of the user 26 | [Serializable] 27 | public abstract class AuditedEntity : 28 | AuditedEntity, 29 | IAudited 30 | where TUser : IEntity 31 | { 32 | /// 33 | /// Reference to the creator user of this entity. 34 | /// 35 | [ForeignKey("CreatedBy")] 36 | public virtual TUser CreatorUser { get; set; } = default!; 37 | 38 | /// 39 | /// Reference to the last modifier user of this entity. 40 | /// 41 | [ForeignKey("UpdatedBy")] 42 | public virtual TUser ModifierUser { get; set; } = default!; 43 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared.Constants/CodeZero.Shared.Constants.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | CodeZero.Shared.Constants 7 | CodeZero.Shared.Constants 8 | 9 | 6.0.1 10 | 6.0.1 11 | false 12 | A set of common constants shared for CodeZero Framework 13 | A set of common constants shared for CodeZero Framework 14 | .net core;boilerplate;modular;best-practices 15 | 16 | Update constants 17 | AnyCPU;ARM64 18 | 19 | 20 | 21 | obj\Debug\net6.0\CodeZero.Shared.Constants.xml 22 | 23 | 24 | 25 | obj\Debug\net6.0\CodeZero.Shared.Constants.xml 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/HeadersExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Microsoft.AspNetCore.Builder; 4 | 5 | public static partial class ApplicationBuilderExtensions 6 | { 7 | /// 8 | /// Register the Headers 9 | /// 10 | /// The . 11 | /// The . 12 | /// 13 | public static IApplicationBuilder UseHeaders( 14 | [NotNull] this IApplicationBuilder app, 15 | [NotNull] IConfiguration configuration) 16 | { 17 | var headersConfig = configuration.GetSection(nameof(HeadersConfig)).Get(); 18 | 19 | app.Use(async (context, next) => 20 | { 21 | if (headersConfig is not null) 22 | { 23 | context.Response.Headers.Add(AppConst.HeaderName.XFrameOptions, headersConfig.XFrameOptions); 24 | context.Response.Headers.Add(AppConst.HeaderName.XssProtection, headersConfig.XssProtection); 25 | context.Response.Headers.Add(AppConst.HeaderName.XContentTypeOptions, headersConfig.XContentTypeOptions); 26 | } 27 | 28 | // Remove version discloser 29 | context.Response.Headers.Remove("X-AspNet-Version"); 30 | context.Response.Headers.Remove("X-AspNetMvc-Version"); 31 | context.Response.Headers.Remove("Server"); 32 | 33 | await next(); 34 | }); 35 | 36 | return app; 37 | } 38 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared.Constants/Constants/HeaderName.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero; 2 | 3 | public static partial class AppConst 4 | { 5 | /// 6 | /// Represent header names 7 | /// 8 | public static partial class HeaderName 9 | { 10 | public const string ApiKey = "Api-Key"; 11 | public const string Authorization = "Authorization"; 12 | public const string Authority = "Authority"; 13 | public const string ApiVersion = "X-Api-Version"; 14 | public const string PoweredBy = "X-Powered-By"; 15 | public const string Csrf = "X-Csrf-Token"; 16 | public const string Xsrf = "X-Xsrf-Token"; 17 | public const string XsrfToken = "Xsrf-Token"; 18 | public const string XssProtection = "X-Xss-Protection"; 19 | public const string XFrameOptions = "X-Frame-Options"; 20 | public const string XContentTypeOptions = "X-Content-Type-Options"; 21 | public const string XForwardedFor = "X-Forwarded-For"; 22 | public const string XForwardedHost = "X-Forwarded-Host"; 23 | public const string XForwardedProto = "X-Forwarded-Proto"; 24 | public const string AcceptLanguage = "Accept-Language"; 25 | public const string ContentLanguage = "Content-Language"; 26 | public const string Accept = "Accept"; 27 | public const string ContentType = "Content-Type"; 28 | public const string Server = "Server"; 29 | public const string LastModified = "Last-Modified"; 30 | public const string UserAgent = "User-Agent"; 31 | public const string XPagination = "X-Pagination"; 32 | } 33 | } -------------------------------------------------------------------------------- /src/CodeZero/CodeZero.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | CodeZero 7 | CodeZero 8 | 9 | 6.0.0 10 | 6.0.0 11 | false 12 | 13 | CodeZero .net 6 Inial release 14 | CodeZero 15 | README.md 16 | .net core;boilerplate;clean architecture;extensions;modular;best-practices 17 | AnyCPU;ARM64 18 | 19 | 20 | 21 | obj\Debug\net6.0\CodeZero.xml 22 | 23 | 24 | 25 | obj\Debug\net6.0\CodeZero.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | True 33 | \ 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Models/ServiceSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Configuration.Models; 2 | 3 | /// 4 | /// Represents the service settings configuration 5 | /// 6 | public partial class ServiceSettings 7 | { 8 | public string DefaultCulture { get; set; } = "en"; 9 | public DefaultApiVersion DefaultApiVersion { get; set; } = new(); 10 | public bool InitializeDatabase { get; set; } 11 | public bool UseAuthentication { get; set; } 12 | public bool UseApiKey { get; set; } 13 | public bool UseLocalization { get; set; } 14 | public bool UseHttpsRedirection { get; set; } 15 | public bool UseRoutingLowercaseUrls { get; set; } 16 | public bool UseDataProtection { get; set; } 17 | public bool UseReverseProxy { get; set; } 18 | public bool UseCors { get; set; } 19 | public bool UseRedis { get; set; } 20 | public bool UseMemoryCache { get; set; } 21 | public bool UseAntiforgery { get; set; } 22 | public bool UseStackExchangeExceptional { get; set; } 23 | public bool AddMvcServices { get; set; } 24 | public bool EnableResponseCompression { get; set; } 25 | public bool EnableContentNegotiation { get; set; } 26 | public bool EnableIpRateLimiting { get; set; } 27 | public bool EnableClientRateLimiting { get; set; } 28 | public bool EnableSwagger { get; set; } 29 | public bool EnableSerilog { get; set; } 30 | public bool EnableSeq { get; set; } 31 | } 32 | 33 | public class DefaultApiVersion 34 | { 35 | public int Major { get; set; } = 1; 36 | public int Minor { get; set; } = 0; 37 | public string Status { get; set; } = default!; 38 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nasr Aldin 4 | https://nasraldin.com 5 | Copyright © 2022, Nasr Aldin 6 | CodeZero 7 | CodeZero is a set of common implementations to help you implementing DDD, CQRS and another facilities for new modern web applications is an open-source project written in .NET Core. 8 | 9 | https://github.com/nasraldin/CodeZero 10 | https://github.com/nasraldin/CodeZero 11 | MIT 12 | Public 13 | 14 | 15 | 16 | True 17 | 18 | 19 | 20 | 21 | true 22 | 23 | 24 | true 25 | 26 | 27 | true 28 | snupkg 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/ContentNegotiationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Extensions.DependencyInjection; 2 | 3 | public static partial class ServiceCollectionExtensions 4 | { 5 | /// 6 | /// Adds Content Negotiation configure. 7 | /// 8 | /// The . 9 | /// 10 | public static IServiceCollection AddContentNegotiation([NotNull] this IServiceCollection services) 11 | { 12 | services.AddControllers(config => 13 | { 14 | // Add XML Content Negotiation 15 | config.RespectBrowserAcceptHeader = true; 16 | 17 | // tells the server that if the client tries to negotiate for 18 | // the media type the server doesn’t support, 19 | // it should return the 406 Not Acceptable status code. 20 | config.ReturnHttpNotAcceptable = true; 21 | 22 | }).AddJsonOptions(jsonOptions => 23 | { 24 | jsonOptions.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; 25 | jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 26 | jsonOptions.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); 27 | jsonOptions.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; 28 | }) 29 | .AddXmlSerializerFormatters() 30 | // support XML formatters 31 | .AddXmlDataContractSerializerFormatters(); 32 | 33 | return services; 34 | } 35 | } -------------------------------------------------------------------------------- /tests/WebAPI/Persistence/ApplicationDbContextInitialiser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using WebAPI.Domain.Entities; 3 | 4 | namespace WebAPI.Persistence; 5 | 6 | public class ApplicationDbContextInitialiser 7 | { 8 | private readonly ILogger _logger; 9 | private readonly ApplicationDbContext _context; 10 | 11 | public ApplicationDbContextInitialiser(ILogger logger, ApplicationDbContext context) 12 | { 13 | _logger = logger; 14 | _context = context; 15 | } 16 | 17 | public async Task InitialiseAsync() 18 | { 19 | try 20 | { 21 | if (!_context.Database.IsSqlite()) 22 | { 23 | await _context.Database.MigrateAsync(); 24 | } 25 | } 26 | catch (Exception ex) 27 | { 28 | _logger.LogError(ex, "An error occurred while initialising the database."); 29 | throw; 30 | } 31 | } 32 | 33 | public async Task SeedAsync() 34 | { 35 | try 36 | { 37 | await TrySeedAsync(); 38 | } 39 | catch (Exception ex) 40 | { 41 | _logger.LogError(ex, "An error occurred while seeding the database."); 42 | throw; 43 | } 44 | } 45 | 46 | public async Task TrySeedAsync() 47 | { 48 | if (!_context.Languages.Any()) 49 | { 50 | await _context.Languages.AddRangeAsync( 51 | new Language("English", "en", string.Empty, false), 52 | new Language("العربية", "ar", string.Empty, true)); 53 | 54 | await _context.SaveChangesAsync(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/CodeZero.Check/CodeZero.Check.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | CodeZero.Check 7 | CodeZero.Check 8 | 9 | 6.0.1 10 | 6.0.1 11 | false 12 | Useful of checks of common guard extensions can help you make checks easier 13 | Useful of checks of common guard extensions can help you make checks easier 14 | .net core;guard;check;best-practices 15 | 16 | Updated the checks and added more validation 17 | AnyCPU;ARM64 18 | 19 | 20 | 21 | obj\Debug\net6.0\CodeZero.Check.xml 22 | 23 | 24 | 25 | obj\Debug\net6.0\CodeZero.Check.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/WebAPI/WebAPI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | enable 7 | enable 8 | 9 | 1.1.1 10 | 1.1.1 11 | 1.1.1 12 | true 13 | $(NoWarn);1591 14 | AnyCPU;ARM64 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/CodeZero.StringExtensions/CodeZero.StringExtensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | CodeZero.StringExtensions 7 | CodeZero.StringExtensions 8 | 9 | 6.0.1 10 | 6.0.1 11 | false 12 | Useful string extensions can help you to manipulation strings 13 | Useful string extensions can help you to manipulation strings 14 | .net core;string extensions;check;best-practices 15 | 16 | Add more extensions 17 | AnyCPU;ARM64 18 | 19 | 20 | 21 | obj\Debug\net6.0\CodeZero.StringExtensions.xml 22 | 23 | 24 | 25 | obj\Debug\net6.0\CodeZero.StringExtensions.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/CodeZero.Shared/CodeZero.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | CodeZero.Shared 7 | CodeZero.Shared 8 | 9 | 6.0.1 10 | 6.0.1 11 | false 12 | A set of common classes shared for CodeZero Framework 13 | A set of common classes shared for CodeZero Framework 14 | .net core;boilerplate;modular;best-practices 15 | 16 | Shared Exception, Logging 17 | AnyCPU;ARM64 18 | 19 | 20 | 21 | obj\Debug\net6.0\CodeZero.Shared.xml 22 | 23 | 24 | 25 | obj\Debug\net6.0\CodeZero.Shared.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/PoweredBy/PoweredByCodeZeroExtensions.cs: -------------------------------------------------------------------------------- 1 | using CodeZero.Middleware; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Microsoft.AspNetCore.Builder; 5 | 6 | public static class PoweredByCodeZeroExtensions 7 | { 8 | /// 9 | /// Configures whether use or not the Header X-Powered-By. 10 | /// Default value is CodeZero. 11 | /// 12 | /// The modular application builder 13 | /// Boolean indicating if the header should be included in the response or not 14 | /// The modular application builder 15 | public static IApplicationBuilder UsePoweredByCodeZero(this IApplicationBuilder app, bool enabled) 16 | { 17 | var options = app.ApplicationServices.GetRequiredService(); 18 | options.Enabled = enabled; 19 | 20 | return app; 21 | } 22 | 23 | /// 24 | /// Configures whether use or not the Header X-Powered-By and its value. 25 | /// Default value is CodeZero. 26 | /// 27 | /// The modular application builder 28 | /// Boolean indicating if the header should be included in the response or not 29 | /// Header's value 30 | /// The modular application builder 31 | public static IApplicationBuilder UsePoweredBy(this IApplicationBuilder app, 32 | bool enabled, string headerValue) 33 | { 34 | var options = app.ApplicationServices.GetRequiredService(); 35 | options.Enabled = enabled; 36 | options.HeaderValue = headerValue; 37 | 38 | return app; 39 | } 40 | } -------------------------------------------------------------------------------- /src/CodeZero.Builder/CodeZero.Builder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | CodeZero.Builder 7 | CodeZero.Builder 8 | 9 | 6.0.1 10 | 6.0.1 11 | false 12 | Initializes a new instance of the WebApplicationBuilder class with preconfigured defaults 13 | Initializes a new instance of the WebApplicationBuilder class with preconfigured defaults 14 | .net core;boilerplate;clean architecture;best-practices 15 | 16 | CodeZero.Builder .net 6 Inial release 17 | AnyCPU;ARM64 18 | 19 | 20 | 21 | obj\Debug\net6.0\CodeZero.Builder.xml 22 | 23 | 24 | 25 | obj\Debug\net6.0\CodeZero.Builder.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/ResponseCompressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.ResponseCompression; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection; 7 | 8 | public static partial class ServiceCollectionExtensions 9 | { 10 | /// 11 | /// Add Response Compression. 12 | /// 13 | /// The . 14 | /// The . 15 | /// 16 | public static IServiceCollection AddResponseCompressionConfig( 17 | [NotNull] this IServiceCollection services, 18 | [NotNull] IConfiguration configuration) 19 | { 20 | var config = configuration.GetSection(nameof(ResponseCompressionConfig)) 21 | .Get(); 22 | 23 | services.AddResponseCompression(options => 24 | { 25 | options.EnableForHttps = config.EnableForHttps; 26 | options.Providers.Add(); 27 | options.Providers.Add(); 28 | 29 | if (config.MimeTypes.Any()) 30 | { 31 | options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(config.MimeTypes); 32 | } 33 | }); 34 | 35 | services.Configure(options => 36 | { 37 | options.Level = CompressionLevel.Optimal; 38 | }); 39 | 40 | services.Configure(options => 41 | { 42 | options.Level = CompressionLevel.Optimal; 43 | }); 44 | 45 | return services; 46 | } 47 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/DomainEventDispatcher.cs: -------------------------------------------------------------------------------- 1 | //using CodeZero.Domain.Mediator; 2 | //using CodeZero.Domain.Messaging; 3 | 4 | //namespace CodeZero.Domain; 5 | 6 | //// https://gist.github.com/jbogard/54d6569e883f63afebc7 7 | //// http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/ 8 | //public class DomainEventDispatcher : IDomainEventDispatcher 9 | //{ 10 | // private readonly IContainer _container; 11 | 12 | // public DomainEventDispatcher(IContainer container) 13 | // { 14 | // _container = container; 15 | // } 16 | 17 | // public Task Dispatch(Event domainEvent) 18 | // { 19 | // var handlerType = typeof(IHandle<>).MakeGenericType(domainEvent.GetType()); 20 | // var wrapperType = typeof(DomainEventHandler<>).MakeGenericType(domainEvent.GetType()); 21 | // var handlers = _container.GetAllInstances(handlerType); 22 | // var wrappedHandlers = handlers 23 | // .Cast() 24 | // .Select(handler => (DomainEventHandler)Activator.CreateInstance(wrapperType, handler)); 25 | 26 | // foreach (var handler in wrappedHandlers) 27 | // { 28 | // handler.Handle(domainEvent); 29 | // } 30 | 31 | // return Task.CompletedTask; 32 | // } 33 | 34 | // private abstract class DomainEventHandler 35 | // { 36 | // public abstract void Handle(Event domainEvent); 37 | // } 38 | 39 | // private sealed class DomainEventHandler : DomainEventHandler where T : Event 40 | // { 41 | // private readonly IHandle _handler; 42 | 43 | // public DomainEventHandler(IHandle handler) 44 | // { 45 | // _handler = handler; 46 | // } 47 | 48 | // public override void Handle(Event domainEvent) 49 | // { 50 | // _handler.Handle((T)domainEvent); 51 | // } 52 | // } 53 | //} -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Behaviors/PerformanceBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace CodeZero.Domain.Common.Behaviours; 4 | 5 | public class PerformanceBehavior : 6 | IPipelineBehavior 7 | where TRequest : IRequest 8 | { 9 | private readonly Stopwatch _timer; 10 | private readonly ILogger _logger; 11 | private readonly ICurrentUserService _currentUserService; 12 | private readonly IIdentityService _identityService; 13 | 14 | public PerformanceBehavior( 15 | ILogger logger, 16 | ICurrentUserService currentUserService, 17 | IIdentityService identityService) 18 | { 19 | _timer = new Stopwatch(); 20 | _logger = logger; 21 | _currentUserService = currentUserService; 22 | _identityService = identityService; 23 | } 24 | 25 | public async Task Handle( 26 | TRequest request, 27 | CancellationToken cancellationToken, 28 | RequestHandlerDelegate next) 29 | { 30 | _timer.Start(); 31 | 32 | var response = await next(); 33 | 34 | _timer.Stop(); 35 | 36 | var elapsedMilliseconds = _timer.ElapsedMilliseconds; 37 | 38 | if (elapsedMilliseconds > 500) 39 | { 40 | var requestName = typeof(TRequest).Name; 41 | var userId = _currentUserService.UserId ?? string.Empty; 42 | var userName = string.Empty; 43 | 44 | if (!string.IsNullOrEmpty(userId)) 45 | { 46 | userName = await _identityService.GetUserNameAsync(userId); 47 | } 48 | 49 | _logger.LogWarning($"{Assembly.GetExecutingAssembly()} Long Running Request: {requestName} ({elapsedMilliseconds} milliseconds) {request} User: {userId} {userName}"); 50 | } 51 | 52 | return response; 53 | } 54 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/AddMvcServicesExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | public static partial class ServiceCollectionExtensions 6 | { 7 | /// 8 | /// Adds MVC framework services. 9 | /// 10 | /// The . 11 | /// The . 12 | /// 13 | public static IServiceCollection AddMvcServices( 14 | [NotNull] this IServiceCollection services, 15 | [NotNull] IConfiguration configuration) 16 | { 17 | services.AddMvc(mvcOptions => 18 | { 19 | //mvcOptions.OutputFormatters.RemoveType(); 20 | //mvcOptions.OutputFormatters.RemoveType(); 21 | mvcOptions.RespectBrowserAcceptHeader = true; 22 | }) 23 | //.AddFluentValidation(fvc => 24 | //{ 25 | // fvc.RegisterValidatorsFromAssemblyContaining(); 26 | //}) 27 | .AddJsonOptions(jsonOptions => 28 | { 29 | jsonOptions.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; 30 | jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 31 | jsonOptions.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); 32 | jsonOptions.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; 33 | }) 34 | //.ConfigureApplicationPartManager(apm => apm.FeatureProviders.Add(new CustomControllerFeatureProvider(AppServiceLoader.Instance.FeatureManager))) 35 | .AddControllersAsServices(); 36 | 37 | return services; 38 | } 39 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/CodeZero.Configuration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | CodeZero.Configuration 7 | CodeZero.Configuration 8 | 9 | 6.0.1 10 | 6.0.1 11 | false 12 | Help to load Configuration easier in your application 13 | Help to load Configuration easier in your application 14 | .net core;clean config;configuration;best-practices 15 | 16 | Update Configurations 17 | AnyCPU;ARM64 18 | 19 | 20 | 21 | obj\Debug\net6.0\CodeZero.Configuration.xml 22 | 23 | 24 | 25 | obj\Debug\net6.0\CodeZero.Configuration.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/RateLimit/RateLimitingClientIPExtensions.cs: -------------------------------------------------------------------------------- 1 | using AspNetCoreRateLimit; 2 | using CodeZero.RateLimit; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection; 6 | 7 | public static partial class ServiceCollectionExtensions 8 | { 9 | /// 10 | /// Add IpRateLimiting based on client IP. 11 | /// 12 | /// The . 13 | /// The . 14 | /// 15 | public static IServiceCollection AddRateLimitingClientIP( 16 | [NotNull] this IServiceCollection services, 17 | [NotNull] IConfiguration configuration) 18 | { 19 | bool.TryParse(configuration["IpRateLimiting:EnableRateLimitingRedis"], out bool enableRateLimitingRedis); 20 | 21 | // load general configuration from appsettings.json 22 | services.Configure(configuration.GetSection("IpRateLimiting")); 23 | // load ip rules from appsettings.json 24 | services.Configure(configuration.GetSection("IpRateLimitPolicies")); 25 | // inject counter and rules stores 26 | services.AddInMemoryRateLimiting(); 27 | 28 | if (enableRateLimitingRedis) 29 | { 30 | services.AddSingleton(); 31 | services.AddSingleton(); 32 | } 33 | else 34 | { 35 | services.AddSingleton(); 36 | services.AddSingleton(); 37 | } 38 | 39 | // configuration (resolvers, counter key builders) 40 | services.AddSingleton(); 41 | 42 | return services; 43 | } 44 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/Localization/LocalizationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Localization; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection; 7 | 8 | public static partial class ServiceCollectionExtensions 9 | { 10 | /// 11 | /// Add Localization 12 | /// 13 | /// The . 14 | /// The . 15 | public static IServiceCollection AddLocalizationServices( 16 | [NotNull] this IServiceCollection services, 17 | [NotNull] IConfiguration configuration) 18 | { 19 | var serviceSettings = configuration.GetSection(nameof(ServiceSettings)).Get() ?? new(); 20 | var language = configuration.GetSection(nameof(Language)).Get(); 21 | var supportedCultures = language?.Select(lang => new CultureInfo(lang.Culture)).ToArray(); 22 | var defaultCulture = new CultureInfo[] { new CultureInfo("en"), new CultureInfo("ar") }; 23 | 24 | services.AddLocalization(); 25 | services.Configure(options => 26 | { 27 | options.DefaultRequestCulture = new RequestCulture(serviceSettings.DefaultCulture); 28 | options.SupportedCultures = supportedCultures ?? defaultCulture; 29 | options.RequestCultureProviders = new List 30 | { 31 | // Order is important, its in which order they will be evaluated 32 | new AcceptLanguageHeaderRequestCultureProvider(), 33 | //new QueryStringRequestCultureProvider(), 34 | //new CookieRequestCultureProvider() 35 | }; 36 | }); 37 | 38 | //services.AddControllers().AddDataAnnotationsLocalization(); 39 | 40 | return services; 41 | } 42 | } -------------------------------------------------------------------------------- /tests/WebAPI/Persistence/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using CodeZero.Domain.Data; 3 | using MediatR; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.ChangeTracking; 6 | using WebAPI.Domain.Entities; 7 | using WebAPI.Persistence.Interceptors; 8 | 9 | namespace WebAPI.Persistence; 10 | 11 | public class ApplicationDbContext : DbContext, IBaseDbContext 12 | { 13 | private readonly IMediator _mediator; 14 | private readonly AuditableEntitySaveChangesInterceptor _auditableEntitySaveChangesInterceptor; 15 | 16 | public ApplicationDbContext( 17 | DbContextOptions options, 18 | IMediator mediator, 19 | AuditableEntitySaveChangesInterceptor auditableEntitySaveChangesInterceptor) 20 | : base(options) 21 | { 22 | ChangeTracker.AutoDetectChangesEnabled = false; 23 | ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; 24 | ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; 25 | ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; 26 | 27 | _mediator = mediator; 28 | _auditableEntitySaveChangesInterceptor = auditableEntitySaveChangesInterceptor; 29 | } 30 | 31 | public virtual DbSet Languages { get; set; } = default!; 32 | 33 | protected override void OnModelCreating(ModelBuilder modelBuilder) 34 | { 35 | modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); 36 | base.OnModelCreating(modelBuilder); 37 | } 38 | 39 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 40 | { 41 | optionsBuilder.AddInterceptors(_auditableEntitySaveChangesInterceptor); 42 | } 43 | 44 | public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) 45 | { 46 | await _mediator.DispatchDomainEvents(this); 47 | 48 | return await base.SaveChangesAsync(cancellationToken); 49 | } 50 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Entities/Auditing/FullAuditedEntity.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Entities.Auditing; 2 | 3 | /// 4 | /// Implements 5 | /// to be a base class for full-audited entities. 6 | /// 7 | /// Type of the primary key of the entity 8 | [Serializable] 9 | public abstract class FullAuditedEntity : AuditedEntity, IFullAudited 10 | { 11 | /// 12 | /// Is this entity Deleted? 13 | /// 14 | public virtual bool IsDeleted { get; protected set; } 15 | 16 | /// 17 | /// Which user deleted this entity? 18 | /// 19 | public virtual string DeletedBy { get; set; } = default!; 20 | 21 | /// 22 | /// Deletion time of this entity. 23 | /// 24 | public virtual DateTime? DeletionTime { get; set; } 25 | } 26 | 27 | /// 28 | /// Implements 29 | /// to be a base class for full-audited entities. 30 | /// 31 | /// Type of the primary key of the entity 32 | /// Type of the user 33 | [Serializable] 34 | public abstract class FullAuditedEntity : 35 | AuditedEntity, 36 | IFullAudited 37 | where TUser : IEntity 38 | { 39 | /// 40 | /// Is this entity Deleted? 41 | /// 42 | public virtual bool IsDeleted { get; protected set; } 43 | 44 | /// 45 | /// Reference to the deleter user of this entity. 46 | /// 47 | [ForeignKey("DeletedBy")] 48 | public virtual TUser DeleterUser { get; set; } = default!; 49 | 50 | /// 51 | /// Which user deleted this entity? 52 | /// 53 | public virtual string DeletedBy { get; set; } = default!; 54 | 55 | /// 56 | /// Deletion time of this entity. 57 | /// 58 | public virtual DateTime? DeletionTime { get; set; } 59 | } -------------------------------------------------------------------------------- /tests/WebAPI/Migrations/20220717185026_Init.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace WebAPI.Migrations 6 | { 7 | public partial class Init : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Languages", 13 | columns: table => new 14 | { 15 | Id = table.Column(type: "INTEGER", nullable: false) 16 | .Annotation("Sqlite:Autoincrement", true), 17 | Name = table.Column(type: "TEXT", nullable: false), 18 | Culture = table.Column(type: "TEXT", nullable: false), 19 | Icon = table.Column(type: "TEXT", nullable: false), 20 | IsRtl = table.Column(type: "INTEGER", nullable: false), 21 | CreatedBy = table.Column(type: "TEXT", nullable: false), 22 | CreatedAt = table.Column(type: "TEXT", nullable: false), 23 | UpdatedBy = table.Column(type: "TEXT", nullable: false), 24 | UpdatedAt = table.Column(type: "TEXT", nullable: false), 25 | IsDeleted = table.Column(type: "INTEGER", nullable: false), 26 | DeletedBy = table.Column(type: "TEXT", nullable: false), 27 | DeletionTime = table.Column(type: "TEXT", nullable: true), 28 | IsActive = table.Column(type: "INTEGER", nullable: false) 29 | }, 30 | constraints: table => 31 | { 32 | table.PrimaryKey("PK_Languages", x => x.Id); 33 | }); 34 | } 35 | 36 | protected override void Down(MigrationBuilder migrationBuilder) 37 | { 38 | migrationBuilder.DropTable( 39 | name: "Languages"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/WebAPI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:25829", 8 | "sslPort": 44338 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Development": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "https://localhost:7115", 28 | "dotnetRunMessages": true 29 | }, 30 | "Staging": { 31 | "commandName": "Project", 32 | "environmentVariables": { 33 | "ASPNETCORE_ENVIRONMENT": "Staging" 34 | }, 35 | "applicationUrl": "https://localhost:7115" 36 | }, 37 | "Production": { 38 | "commandName": "Project", 39 | "environmentVariables": { 40 | "ASPNETCORE_ENVIRONMENT": "Production" 41 | }, 42 | "applicationUrl": "https://localhost:7115" 43 | }, 44 | "Docker": { 45 | "commandName": "Docker", 46 | "launchBrowser": true, 47 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", 48 | "environmentVariables": { 49 | "ASPNETCORE_ENVIRONMENT": "Docker" 50 | }, 51 | "publishAllPorts": true, 52 | "useSSL": true, 53 | "sslPort": 7115 54 | }, 55 | "Guest": { 56 | "commandName": "Project", 57 | "launchBrowser": true, 58 | "launchUrl": "swagger", 59 | "environmentVariables": { 60 | "ASPNETCORE_ENVIRONMENT": "Guest" 61 | }, 62 | "applicationUrl": "https://localhost:7115", 63 | "dotnetRunMessages": true 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/ReverseProxy/UseProxyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.NetworkInformation; 2 | using CodeZero.Utils; 3 | using Microsoft.AspNetCore.HttpOverrides; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace Microsoft.AspNetCore.Builder; 7 | 8 | public static partial class ApplicationBuilderExtensions 9 | { 10 | /// 11 | /// Register Reverse Proxy. 12 | /// 13 | /// The . 14 | /// The . 15 | /// 16 | public static IApplicationBuilder UseProxy( 17 | [NotNull] this IApplicationBuilder app, 18 | [NotNull] IConfiguration configuration) 19 | { 20 | var proxySettings = configuration.GetSection(nameof(ProxySettings)).Get(); 21 | 22 | if (!string.IsNullOrEmpty(proxySettings?.RequestBasePath)) 23 | { 24 | app.Use(async (context, next) => 25 | { 26 | context.Request.PathBase = proxySettings.RequestBasePath; 27 | await next.Invoke().ConfigureAwait(false); 28 | }); 29 | } 30 | 31 | var forwardedOptions = new ForwardedHeadersOptions() 32 | { 33 | ForwardedHeaders = ForwardedHeaders.All 34 | }; 35 | 36 | if (proxySettings?.ForwardedHeadersOptions is not null && 37 | proxySettings.ForwardedHeadersOptions.AddActiveNetworkInterfaceToKnownNetworks) 38 | { 39 | foreach (var network in Network.GetNetworks(NetworkInterfaceType.Ethernet)) 40 | { 41 | forwardedOptions.KnownNetworks.Add(network); 42 | } 43 | } 44 | 45 | app.UseForwardedHeaders(forwardedOptions); 46 | 47 | // The default HSTS value is 30 days. 48 | // You may want to change this for production scenarios, 49 | // see https://aka.ms/aspnetcore-hsts. 50 | app.UseHsts(); 51 | 52 | return app; 53 | } 54 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/Antiforgery/AntiforgeryExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | public static partial class ServiceCollectionExtensions 6 | { 7 | /// 8 | /// Add Register the antiforgery services to be service-aware. 9 | /// 10 | /// The . 11 | /// 12 | public static IServiceCollection AddAntiforgeryConfig([NotNull] this IServiceCollection services) 13 | { 14 | // Adds host and service level antiforgery services. 15 | services.AddAntiforgery(options => 16 | { 17 | // Angular's default header name for sending the XSRF token. 18 | options.HeaderName = AppConst.HeaderName.Xsrf; 19 | options.FormFieldName = "__RequestVerificationToken"; 20 | options.Cookie.Name = $"_{AppServiceLoader.Instance.ApplicationName}Antiforgery"; 21 | 22 | // Don't set the cookie builder 'Path' so that it uses the 'IAuthenticationFeature' value 23 | // set by the pipeline and comming from the request 'PathBase' which already ends with the 24 | // service prefix but may also start by a path related e.g to a virtual folder. 25 | }); 26 | services.AddMvc(options => 27 | { 28 | // ValidateAntiforgery will validate every request, 29 | // whereas AutoValidateAntiforgery will only perform validation 30 | // for unsafe HTTP methods(methods other than GET, HEAD, OPTIONS and TRACE). 31 | ////options.Filters.Add(new ValidateAntiForgeryTokenAttribute()); 32 | 33 | // Forcing AntiForgery Token Validation on by default, it's only in Razor Pages by default 34 | // Load this filter after the MediaSizeFilterLimitAttribute, but before the 35 | // IgnoreAntiforgeryTokenAttribute. refer : https://github.com/aspnet/AspNetCore/issues/10384 36 | options.Filters.Add(typeof(AutoValidateAntiforgeryTokenAttribute), 999); 37 | }); 38 | 39 | return services; 40 | } 41 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/UseSwaggerVersionedExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Microsoft.AspNetCore.Builder; 6 | 7 | public static partial class ApplicationBuilderExtensions 8 | { 9 | /// 10 | /// Register the Swagger generator and the Swagger UI middlewares. 11 | /// 12 | /// The . 13 | /// The . 14 | /// 15 | public static IApplicationBuilder UseSwaggerVersioned( 16 | this IApplicationBuilder app, 17 | //IApiVersionDescriptionProvider provider, 18 | [NotNull] IConfiguration configuration) 19 | { 20 | var provider = app.ApplicationServices.GetRequiredService(); 21 | var sgOptions = configuration.GetSection(nameof(SwaggerConfig)).Get() ?? new(); 22 | 23 | // Enable middleware to serve generated Swagger as a JSON endpoint. 24 | app.UseSwagger(option => { option.RouteTemplate = sgOptions.RouteTemplate; }); 25 | 26 | // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.) 27 | app.UseSwaggerUI(options => 28 | { 29 | // build a swagger endpoint for each discovered API version 30 | for (var i = 0; i < provider.ApiVersionDescriptions.Count; i++) 31 | { 32 | var description = provider.ApiVersionDescriptions[i]; 33 | options.SwaggerEndpoint($"/{sgOptions.UiEndpoint}/{description.GroupName}/{sgOptions.RoutePrefix}", 34 | description.GroupName.ToUpperInvariant()); 35 | } 36 | 37 | options.DefaultModelsExpandDepth(sgOptions.DefaultModelsExpandDepth); 38 | options.DisplayRequestDuration(); 39 | options.EnableValidator(); 40 | options.EnableFilter(); 41 | options.InjectStylesheet("/swagger-ui/custom-swagger.css"); 42 | }); 43 | 44 | return app; 45 | } 46 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Helpers/ObjectHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace CodeZero.Helpers; 6 | 7 | public static class ObjectHelper 8 | { 9 | private static readonly ConcurrentDictionary CachedObjectProperties = new(); 10 | 11 | public static void TrySetProperty( 12 | TObject obj, 13 | Expression> propertySelector, 14 | Func valueFactory, 15 | params Type[] ignoreAttributeTypes) 16 | { 17 | TrySetProperty(obj, propertySelector, x => valueFactory(), ignoreAttributeTypes); 18 | } 19 | 20 | public static void TrySetProperty( 21 | TObject obj, 22 | Expression> propertySelector, 23 | Func valueFactory, 24 | params Type[] ignoreAttributeTypes) 25 | { 26 | var cacheKey = $"{obj?.GetType().FullName}-" + 27 | $"{propertySelector}-" + 28 | $"{(ignoreAttributeTypes != null ? "-" + string.Join("-", ignoreAttributeTypes.Select(x => x.FullName)) : "")}"; 29 | 30 | var property = CachedObjectProperties.GetOrAdd(cacheKey, () => 31 | { 32 | if (propertySelector.Body.NodeType != ExpressionType.MemberAccess) 33 | { 34 | return null!; 35 | } 36 | 37 | var memberExpression = propertySelector.Body.As(); 38 | 39 | var propertyInfo = obj?.GetType().GetProperties().FirstOrDefault(x => 40 | x.Name == memberExpression.Member.Name && 41 | x.GetSetMethod(true) != null); 42 | 43 | if (propertyInfo == null) 44 | { 45 | return null!; 46 | } 47 | 48 | if (ignoreAttributeTypes != null && 49 | ignoreAttributeTypes.Any(ignoreAttribute => propertyInfo.IsDefined(ignoreAttribute, true))) 50 | { 51 | return null!; 52 | } 53 | 54 | return propertyInfo; 55 | }); 56 | 57 | property?.SetValue(obj, valueFactory(obj)); 58 | } 59 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/Filters/SwaggerDefaultValues.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 2 | using Microsoft.OpenApi.Any; 3 | using Microsoft.OpenApi.Models; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | 6 | namespace CodeZero.Swagger.Filters; 7 | 8 | /// 9 | /// Represents the Swagger/Swashbuckle operation filter 10 | /// used to document the implicit API version parameter. 11 | /// 12 | /// This is only required 13 | /// due to bugs in the . 14 | /// Once they are fixed and published, this class can be removed. 15 | public class SwaggerDefaultValues : IOperationFilter 16 | { 17 | /// 18 | /// Applies the filter to the specified operation using the given context. 19 | /// 20 | /// The operation to apply the filter to. 21 | /// The current operation filter context. 22 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 23 | { 24 | if (operation is null || context is null) 25 | { 26 | return; 27 | } 28 | 29 | var apiDescription = context.ApiDescription; 30 | operation.Deprecated |= apiDescription.IsDeprecated(); 31 | 32 | if (operation.Parameters is null) 33 | { 34 | return; 35 | } 36 | 37 | // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 38 | // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 39 | foreach (var parameter in operation.Parameters) 40 | { 41 | var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); 42 | 43 | if (parameter.Description is null) 44 | { 45 | parameter.Description = description.ModelMetadata?.Description; 46 | } 47 | if (parameter.Schema.Default is null && description.DefaultValue is not null) 48 | { 49 | parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString()); 50 | } 51 | 52 | parameter.Required |= description.IsRequired; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/CodeZero.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | latest 6 | CodeZero.Domain 7 | CodeZero.Domain 8 | 9 | 6.0.2 10 | 6.0.2 11 | false 12 | CodeZero.Domain is a set of common implementations to help you implementing DDD, CQRS and another facilities for new modern web applications 13 | CodeZero.Domain is a set of common implementations to help you implementing DDD, CQRS and another facilities for new modern web applications 14 | .net core;ddd;cqrs;modular;best-practices 15 | 16 | CodeZero.Domain .net 6 Inial release 17 | AnyCPU;ARM64 18 | 19 | 20 | 21 | obj\Debug\net6.0\CodeZero.Domain.xml 22 | 23 | 24 | 25 | obj\Debug\net6.0\CodeZero.Domain.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/Localization/UseLocalizationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using Microsoft.AspNetCore.Localization; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace Microsoft.AspNetCore.Builder; 6 | 7 | public static partial class ApplicationBuilderExtensions 8 | { 9 | /// 10 | /// Localization 11 | /// 12 | /// The . 13 | /// The . 14 | /// 15 | public static IApplicationBuilder UseLocalizationServices( 16 | [NotNull] this IApplicationBuilder app, 17 | [NotNull] IConfiguration configuration) 18 | { 19 | var serviceSettings = configuration.GetSection(nameof(ServiceSettings)).Get() ?? new(); 20 | var language = configuration.GetSection(nameof(Language)).Get(); 21 | var supportedCultures = language?.Select(lang => new CultureInfo(lang.Culture)).ToArray()!; 22 | var defaultCulture = new CultureInfo[] { new CultureInfo("en"), new CultureInfo("ar") }; 23 | 24 | app.UseRequestLocalization(new RequestLocalizationOptions 25 | { 26 | DefaultRequestCulture = new RequestCulture(serviceSettings.DefaultCulture), 27 | // Formatting numbers, dates, etc. 28 | SupportedCultures = supportedCultures ?? defaultCulture, 29 | }); 30 | 31 | app.Use(async (context, next) => 32 | { 33 | // Get client prefered language 34 | var userLang = context.Request.Headers[AppConst.HeaderName.AcceptLanguage] 35 | .ToString().Split(',').FirstOrDefault(); 36 | 37 | // Set language 38 | var lang = supportedCultures?.FirstOrDefault(lang => lang.Name == userLang)?.Name ?? 39 | defaultCulture.FirstOrDefault(lang => lang.Name == userLang)?.Name ?? 40 | serviceSettings.DefaultCulture; 41 | 42 | context.Response.Headers.TryAdd(AppConst.HeaderName.ContentLanguage, lang); 43 | // Switch app culture 44 | Thread.CurrentThread.CurrentCulture = new CultureInfo(lang!); 45 | 46 | await next(); 47 | }); 48 | 49 | return app; 50 | } 51 | } -------------------------------------------------------------------------------- /tests/WebAPI/Persistence/Interceptors/AuditableEntitySaveChangesInterceptor.cs: -------------------------------------------------------------------------------- 1 | using CodeZero.Domain.Common.Interfaces; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.ChangeTracking; 4 | using Microsoft.EntityFrameworkCore.Diagnostics; 5 | using WebAPI.Domain; 6 | 7 | namespace WebAPI.Persistence.Interceptors; 8 | 9 | public class AuditableEntitySaveChangesInterceptor : SaveChangesInterceptor 10 | { 11 | private readonly ICurrentUserService _currentUserService; 12 | 13 | public AuditableEntitySaveChangesInterceptor(ICurrentUserService currentUserService) 14 | { 15 | _currentUserService = currentUserService; 16 | } 17 | 18 | public override InterceptionResult SavingChanges( 19 | DbContextEventData eventData, 20 | InterceptionResult result) 21 | { 22 | UpdateEntities(eventData.Context); 23 | 24 | return base.SavingChanges(eventData, result); 25 | } 26 | 27 | public override ValueTask> SavingChangesAsync( 28 | DbContextEventData eventData, 29 | InterceptionResult result, 30 | CancellationToken cancellationToken = default) 31 | { 32 | UpdateEntities(eventData.Context); 33 | 34 | return base.SavingChangesAsync(eventData, result, cancellationToken); 35 | } 36 | 37 | public void UpdateEntities(DbContext? context) 38 | { 39 | if (context == null) return; 40 | 41 | foreach (var entry in context.ChangeTracker.Entries()) 42 | { 43 | if (entry.State == EntityState.Added) 44 | { 45 | entry.Entity.CreatedBy = _currentUserService.UserId!; 46 | entry.Entity.CreatedAt = DateTime.UtcNow; 47 | } 48 | 49 | if (entry.State == EntityState.Added || entry.State == EntityState.Modified || entry.HasChangedOwnedEntities()) 50 | { 51 | entry.Entity.UpdatedBy = _currentUserService.UserId!; 52 | entry.Entity.UpdatedAt = DateTime.UtcNow; 53 | } 54 | } 55 | } 56 | } 57 | 58 | public static class Extensions 59 | { 60 | public static bool HasChangedOwnedEntities(this EntityEntry entry) => 61 | entry.References.Any(r => 62 | r.TargetEntry != null && 63 | r.TargetEntry.Metadata.IsOwned() && 64 | (r.TargetEntry.State == EntityState.Added || r.TargetEntry.State == EntityState.Modified)); 65 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Utils/Network.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Net; 3 | using System.Net.NetworkInformation; 4 | using Microsoft.AspNetCore.HttpOverrides; 5 | 6 | namespace CodeZero.Utils; 7 | 8 | public static class Network 9 | { 10 | public static IEnumerable GetNetworks(NetworkInterfaceType type) 11 | { 12 | foreach (var item in NetworkInterface.GetAllNetworkInterfaces() 13 | // get all operational networks of a given type 14 | .Where(n => n.NetworkInterfaceType == type && n.OperationalStatus == OperationalStatus.Up) 15 | .Select(n => n.GetIPProperties()) // get the IPs 16 | .Where(n => n.GatewayAddresses.Any())) // where the IPs have a gateway defined 17 | { 18 | // get the first cluster-facing IP address 19 | var ipInfo = item.UnicastAddresses.FirstOrDefault(i => i.Address.AddressFamily == 20 | System.Net.Sockets.AddressFamily.InterNetwork); 21 | 22 | if (ipInfo == null) { continue; } 23 | 24 | // convert the mask to bits 25 | var maskBytes = ipInfo.IPv4Mask.GetAddressBytes(); 26 | 27 | if (!BitConverter.IsLittleEndian) 28 | { 29 | Array.Reverse(maskBytes); 30 | } 31 | 32 | var maskBits = new BitArray(maskBytes); 33 | // count the number of "true" bits to get the CIDR mask 34 | var cidrMask = maskBits.Cast().Count(b => b); 35 | // convert my application's ip address to bits 36 | var ipBytes = ipInfo.Address.GetAddressBytes(); 37 | 38 | if (!BitConverter.IsLittleEndian) 39 | { 40 | Array.Reverse(maskBytes); 41 | } 42 | 43 | var ipBits = new BitArray(ipBytes); 44 | // and the bits with the mask to get the start of the range 45 | var maskedBits = ipBits.And(maskBits); 46 | // Convert the masked IP back into an IP address 47 | var maskedIpBytes = new byte[4]; 48 | maskedBits.CopyTo(maskedIpBytes, 0); 49 | 50 | if (!BitConverter.IsLittleEndian) 51 | { 52 | Array.Reverse(maskedIpBytes); 53 | } 54 | 55 | var rangeStartIp = new IPAddress(maskedIpBytes); 56 | 57 | // return the start IP and CIDR mask 58 | yield return new IPNetwork(rangeStartIp, cidrMask); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # CodeZero Packages 4 | CodeZero is a general purpose application framework specially designed for new modern web applications is a open-source project written in .NET Core. 5 | 6 | Central organizing structure, decoupled from frameworks and technology details. 7 | 8 | Built by small components that are developed and tested in isolation. 9 | 10 |
11 | 12 | ## Give a Star! :star: 13 | If you like or are using this project to learn or start your solution, please give it a star. Thanks! 14 | 15 |
16 | 17 | > Hit the `WATCH` button to get the latest CodeZero Template updates. 🤷‍♂️ 18 | 19 |
20 | 21 | > Hit the `FORK` button and show CodeZero Template on your profile. 🌱 22 | 23 |
24 | 25 | ## Technologies 26 | * [C# 10](https://docs.microsoft.com/en-us/dotnet/csharp) 27 | * [.NET 6](https://dotnet.microsoft.com) 28 | 29 |
30 | 31 | ## Branch structure 32 | 33 | Active development happens on the dev branch. This always contains the latest version. The main branch contains the latest versions of the stable codebase. The prod branch contains a codebase for production release. 34 | 35 |
36 | 37 | ## Contributors and Using the GitHub/Gitlab Repository 38 | 39 | You can contributor to the project and enhance the solution or add new futuer. just clon and make a PR to dev branch. 40 | 41 | To get started based on this repository, you need to get a copy locally. You have three options: fork, clone, or download. Most of the time, you probably just want to download. 42 | 43 | You should **download the repository**, unblock the zip file, and extract it to a new folder if you just want to play with the project or you wish to use it as the starting point for an application. 44 | 45 | You should **fork this repository** only if you plan on submitting a pull request. Or if you'd like to keep a copy of a snapshot of the repository in your own GitHub account. 46 | 47 | You should **clone this repository** if you're one of the contributors and you have commit access to it. Otherwise you probably want one of the other options. 48 | 49 | ## Support 50 | If you are having problems, please let us know by use the [issue tracker](https://github.com/nasraldin/codezero-template/issues) for that. fill free to support us to request a feature or enhance code or bug report. 51 | * Fix errors. 52 | * Refactoring. 53 | * Build the Front End. 54 | * Submit issues and bugs. 55 | 56 | ## License 57 | 58 | This project is licensed with the [MIT license](LICENSE). 59 | -------------------------------------------------------------------------------- /src/CodeZero.Shared/Logging/ErrorCode.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Logging; 2 | 3 | /// 4 | /// CodeZero common errors 5 | /// 6 | public static partial class ErrorCode 7 | { 8 | public static readonly ErrorId NotModified = new(304, "Not Modified"); 9 | public static readonly ErrorId Redirect = new(307, "Redirect"); 10 | public static readonly ErrorId BadRequest = new(400, "Bad Request"); 11 | public static readonly ErrorId Unauthorized = new(401, "Unauthorized"); 12 | public static readonly ErrorId PaymentRequired = new(402, "Payment Required"); 13 | public static readonly ErrorId Forbidden = new(403, "Forbidden"); 14 | public static readonly ErrorId NotFound = new(404, "Not Found"); 15 | public static readonly ErrorId MethodNotAllowed = new(405, "Method Not Allowed"); 16 | public static readonly ErrorId RequestTimeout = new(408, "Request Timeout"); 17 | public static readonly ErrorId Conflict = new(409, "Conflict"); 18 | public static readonly ErrorId PayloadTooLarge = new(413, "Payload Too Large"); 19 | public static readonly ErrorId URITooLong = new(414, "URI Too Long"); 20 | public static readonly ErrorId ExpectationFailed = new(417, "Expectation Failed"); 21 | public static readonly ErrorId RequestExpired = new(419, "Request Expired"); 22 | public static readonly ErrorId Locked = new(423, "Locked"); 23 | public static readonly ErrorId TooManyRequests = new(429, "Too Many Requests"); 24 | public static readonly ErrorId NoResponse = new(444, "No Response"); 25 | public static readonly ErrorId Unavailable = new(451, "Unavailable"); 26 | public static readonly ErrorId ClientClosedRequest = new(460, "Client Closed Request"); 27 | public static readonly ErrorId InvalidToken = new(498, "Invalid Token"); 28 | public static readonly ErrorId TokenRequired = new(499, "Token Required"); 29 | public static readonly ErrorId InternalServerError = new(500, "Internal Server Error"); 30 | public static readonly ErrorId NotImplemented = new(501, "Not Implemented"); 31 | public static readonly ErrorId BadGateway = new(502, "Bad Gateway"); 32 | public static readonly ErrorId ServiceUnavailable = new(503, "Service Unavailable"); 33 | public static readonly ErrorId GatewayTimeout = new(504, "Gateway Timeout"); 34 | public static readonly ErrorId NotSupported = new(505, "Not Supported"); 35 | public static readonly ErrorId LoopDetected = new(508, "Loop Detected"); 36 | public static readonly ErrorId UnknownError = new(520, "Unknown Error"); 37 | public static readonly ErrorId ConnectionTimedOut = new(522, "Connection Timed Out"); 38 | } -------------------------------------------------------------------------------- /src/CodeZero.Configuration/Extensions/HostingEnvironmentExtensions.cs: -------------------------------------------------------------------------------- 1 | using CodeZero; 2 | 3 | namespace Microsoft.Extensions.Hosting; 4 | 5 | public static class HostingEnvironmentExtensions 6 | { 7 | /// 8 | /// Checks if the current host environment name is dev 9 | /// 10 | /// 11 | /// True if the environment name is dev, otherwise false. 12 | public static bool IsDev(this IHostEnvironment hostEnvironment) 13 | { 14 | return hostEnvironment.IsEnvironment(AppConst.Environments.Dev); 15 | } 16 | 17 | /// 18 | /// Checks if the current host environment name is prod 19 | /// 20 | /// 21 | /// True if the environment name is prod, otherwise false. 22 | public static bool IsProd(this IHostEnvironment hostEnvironment) 23 | { 24 | return hostEnvironment.IsEnvironment(AppConst.Environments.Prod); 25 | } 26 | 27 | /// 28 | /// Checks if the current host environment name is stag 29 | /// 30 | /// 31 | /// True if the environment name is stag, otherwise false. 32 | public static bool IsStag(this IHostEnvironment hostEnvironment) 33 | { 34 | return hostEnvironment.IsEnvironment(AppConst.Environments.Stag); 35 | } 36 | 37 | /// 38 | /// Checks if the current host environment name qa dev 39 | /// 40 | /// 41 | /// True if the environment name is qa, otherwise false. 42 | public static bool IsQA(this IHostEnvironment hostEnvironment) 43 | { 44 | return hostEnvironment.IsEnvironment(AppConst.Environments.QA); 45 | } 46 | 47 | /// 48 | /// Checks if the current host environment name is uat 49 | /// 50 | /// 51 | /// True if the environment name is uat, otherwise false. 52 | public static bool IsUAT(IHostEnvironment hostEnvironment) 53 | { 54 | return hostEnvironment.IsEnvironment(AppConst.Environments.UAT); 55 | } 56 | 57 | /// 58 | /// Checks if the current host environment name is test 59 | /// 60 | /// 61 | /// True if the environment name is test, otherwise false. 62 | public static bool IsTest(IHostEnvironment hostEnvironment) 63 | { 64 | return hostEnvironment.IsEnvironment(AppConst.Environments.Test); 65 | } 66 | } -------------------------------------------------------------------------------- /src/CodeZero.Builder/Microsoft/Configuration/ConfigurationHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Extensions.Configuration; 2 | 3 | public static class ConfigurationHelper 4 | { 5 | public static IConfigurationRoot BuildConfiguration( 6 | ConfigurationBuilderOptions options = null!, 7 | Action builderAction = null!) 8 | { 9 | options ??= new ConfigurationBuilderOptions(); 10 | 11 | if (string.IsNullOrEmpty(options.BasePath)) 12 | { 13 | options.BasePath = Directory.GetCurrentDirectory(); 14 | } 15 | 16 | if (string.IsNullOrEmpty(options.EnvironmentName)) 17 | { 18 | options.EnvironmentName = Environment.GetEnvironmentVariable(AppConst.ASPNETCORE_ENVIRONMENT)!; 19 | } 20 | 21 | Console.WriteLine("[CodeZero] Loads environment variables..."); 22 | var builder = new ConfigurationBuilder() 23 | .SetBasePath(options.BasePath) 24 | .AddJsonFile($"{options.FileName}.json", optional: false, reloadOnChange: true) 25 | .AddJsonFile($"{options.FileName}.{options.EnvironmentName}.json", optional: false, reloadOnChange: true) 26 | .AddJsonFile($"{options.FileName}.{options.SerilogFileName}.json", optional: true, reloadOnChange: true) 27 | .AddJsonFile($"{options.FileName}.{options.LanguageFileName}.json", optional: true, reloadOnChange: true); 28 | 29 | Console.ForegroundColor = ConsoleColor.Green; 30 | Console.WriteLine("[CodeZero] Environment variables loaded."); 31 | Console.ResetColor(); 32 | 33 | if ((options.EnvironmentName == AppConst.Environments.Development || 34 | options.EnvironmentName == AppConst.Environments.Dev) && 35 | options.UserSecretsId is not null) 36 | { 37 | Console.WriteLine("[CodeZero] Check UserSecrets..."); 38 | builder.AddUserSecrets(options.UserSecretsId); 39 | Console.ForegroundColor = ConsoleColor.Green; 40 | Console.WriteLine("[CodeZero] UserSecrets loaded."); 41 | } 42 | 43 | if (!string.IsNullOrEmpty(options.EnvironmentVariablesPrefix)) 44 | { 45 | builder.AddEnvironmentVariables(options.EnvironmentVariablesPrefix); 46 | } 47 | 48 | if (options.CommandLineArgs is not null && options.CommandLineArgs.Any()) 49 | { 50 | builder.AddCommandLine(options.CommandLineArgs); 51 | } 52 | 53 | Console.ForegroundColor = ConsoleColor.Green; 54 | Console.WriteLine("[CodeZero] Configuration Build Done!."); 55 | Console.ResetColor(); 56 | 57 | builderAction?.Invoke(builder); 58 | 59 | return builder.Build(); 60 | } 61 | } -------------------------------------------------------------------------------- /tests/WebAPI/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | using WebAPI.Persistence; 7 | 8 | #nullable disable 9 | 10 | namespace WebAPI.Migrations 11 | { 12 | [DbContext(typeof(ApplicationDbContext))] 13 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 14 | { 15 | protected override void BuildModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder.HasAnnotation("ProductVersion", "6.0.7"); 19 | 20 | modelBuilder.Entity("WebAPI.Domain.Entities.Language", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd() 24 | .HasColumnType("INTEGER"); 25 | 26 | b.Property("CreatedAt") 27 | .HasColumnType("TEXT"); 28 | 29 | b.Property("CreatedBy") 30 | .IsRequired() 31 | .HasColumnType("TEXT"); 32 | 33 | b.Property("Culture") 34 | .IsRequired() 35 | .HasColumnType("TEXT"); 36 | 37 | b.Property("DeletedBy") 38 | .IsRequired() 39 | .HasColumnType("TEXT"); 40 | 41 | b.Property("DeletionTime") 42 | .HasColumnType("TEXT"); 43 | 44 | b.Property("Icon") 45 | .IsRequired() 46 | .HasColumnType("TEXT"); 47 | 48 | b.Property("IsActive") 49 | .HasColumnType("INTEGER"); 50 | 51 | b.Property("IsDeleted") 52 | .HasColumnType("INTEGER"); 53 | 54 | b.Property("IsRtl") 55 | .HasColumnType("INTEGER"); 56 | 57 | b.Property("Name") 58 | .IsRequired() 59 | .HasColumnType("TEXT"); 60 | 61 | b.Property("UpdatedAt") 62 | .HasColumnType("TEXT"); 63 | 64 | b.Property("UpdatedBy") 65 | .IsRequired() 66 | .HasColumnType("TEXT"); 67 | 68 | b.HasKey("Id"); 69 | 70 | b.ToTable("Languages"); 71 | }); 72 | #pragma warning restore 612, 618 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/CodeZero.Domain/Exception/EntityNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Exception; 2 | 3 | /// 4 | /// This exception is thrown if an entity excepted to be found but not found. 5 | /// 6 | public class EntityNotFoundException : CodeZeroException 7 | { 8 | /// 9 | /// Type of the entity. 10 | /// 11 | public Type EntityType { get; set; } = default!; 12 | 13 | /// 14 | /// Id of the Entity. 15 | /// 16 | public object Id { get; set; } = default!; 17 | 18 | /// 19 | /// Creates a new object. 20 | /// 21 | public EntityNotFoundException() 22 | { 23 | } 24 | 25 | /// 26 | /// Creates a new object. 27 | /// 28 | public EntityNotFoundException(Type entityType) 29 | : this(entityType, null!, null!) 30 | { 31 | } 32 | 33 | /// 34 | /// Creates a new object. 35 | /// 36 | public EntityNotFoundException(Type entityType, object id) 37 | : this(entityType, id, null!) 38 | { 39 | } 40 | 41 | /// 42 | /// Creates a new object. 43 | /// 44 | public EntityNotFoundException(Type entityType, object id, System.Exception innerException) 45 | : base( 46 | id is null 47 | ? $"There is no such an entity given given id. Entity type: {entityType.FullName}" 48 | : $"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", 49 | innerException) 50 | { 51 | EntityType = entityType; 52 | Id = id!; 53 | } 54 | 55 | /// 56 | /// Creates a new object. 57 | /// 58 | public EntityNotFoundException(string name, object key) 59 | : base($"Entity \"{name}\" ({key}) was not found.") 60 | { 61 | } 62 | 63 | /// 64 | /// Creates a new object. 65 | /// 66 | /// Exception message 67 | public EntityNotFoundException(string message) 68 | : base(message) 69 | { 70 | } 71 | 72 | /// 73 | /// Creates a new object. 74 | /// 75 | /// Exception message 76 | /// Inner exception 77 | public EntityNotFoundException(string message, System.Exception innerException) 78 | : base(message, innerException) 79 | { 80 | } 81 | } -------------------------------------------------------------------------------- /tests/WebAPI/Migrations/20220717185026_Init.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using WebAPI.Persistence; 8 | 9 | #nullable disable 10 | 11 | namespace WebAPI.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | [Migration("20220717185026_Init")] 15 | partial class Init 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder.HasAnnotation("ProductVersion", "6.0.7"); 21 | 22 | modelBuilder.Entity("WebAPI.Domain.Entities.Language", b => 23 | { 24 | b.Property("Id") 25 | .ValueGeneratedOnAdd() 26 | .HasColumnType("INTEGER"); 27 | 28 | b.Property("CreatedAt") 29 | .HasColumnType("TEXT"); 30 | 31 | b.Property("CreatedBy") 32 | .IsRequired() 33 | .HasColumnType("TEXT"); 34 | 35 | b.Property("Culture") 36 | .IsRequired() 37 | .HasColumnType("TEXT"); 38 | 39 | b.Property("DeletedBy") 40 | .IsRequired() 41 | .HasColumnType("TEXT"); 42 | 43 | b.Property("DeletionTime") 44 | .HasColumnType("TEXT"); 45 | 46 | b.Property("Icon") 47 | .IsRequired() 48 | .HasColumnType("TEXT"); 49 | 50 | b.Property("IsActive") 51 | .HasColumnType("INTEGER"); 52 | 53 | b.Property("IsDeleted") 54 | .HasColumnType("INTEGER"); 55 | 56 | b.Property("IsRtl") 57 | .HasColumnType("INTEGER"); 58 | 59 | b.Property("Name") 60 | .IsRequired() 61 | .HasColumnType("TEXT"); 62 | 63 | b.Property("UpdatedAt") 64 | .HasColumnType("TEXT"); 65 | 66 | b.Property("UpdatedBy") 67 | .IsRequired() 68 | .HasColumnType("TEXT"); 69 | 70 | b.HasKey("Id"); 71 | 72 | b.ToTable("Languages"); 73 | }); 74 | #pragma warning restore 612, 618 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/CorsConfigExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | public static partial class ServiceCollectionExtensions 6 | { 7 | /// 8 | /// Adds CORS. 9 | /// 10 | /// The . 11 | /// The . 12 | /// 13 | public static IServiceCollection AddCorsConfig( 14 | [NotNull] this IServiceCollection services, 15 | [NotNull] IConfiguration configuration) 16 | { 17 | var corsConfig = configuration.GetSection(nameof(CorsSettings)).Get(); 18 | 19 | if (corsConfig is null) 20 | { 21 | throw new CodeZeroException($"Configure {nameof(CorsSettings)} settings in appsettings.Environment.json"); 22 | } 23 | 24 | if (!corsConfig.CorsPolicy.Any()) 25 | return services; 26 | 27 | if (corsConfig.CorsPolicy.Any(cp => string.IsNullOrEmpty(cp.PolicyName))) 28 | throw new CodeZeroException("PolicyName can't be null!"); 29 | 30 | services.AddCors(options => 31 | { 32 | corsConfig.CorsPolicy.ForEach(policy => 33 | { 34 | options.AddPolicy(name: policy.PolicyName, 35 | builder => 36 | { 37 | // check for Cors allow specific 38 | if (policy.AllowAnyHeader) 39 | builder.AllowAnyHeader(); 40 | if (policy.AllowAnyMethod) 41 | builder.AllowAnyMethod(); 42 | if (policy.AllowAnyOrigin) 43 | builder.AllowAnyOrigin(); 44 | 45 | // not any 46 | if (!policy.AllowAnyHeader && policy.Headers is not null) 47 | builder.WithHeaders(policy.Headers); 48 | 49 | if (!policy.AllowAnyMethod && policy.Methods is not null) 50 | builder.WithHeaders(policy.Methods); 51 | 52 | if (!policy.AllowAnyOrigin && policy.Origins is not null) 53 | builder.WithHeaders(policy.Origins); 54 | 55 | if (policy.SupportsCredentials) 56 | builder.AllowCredentials(); 57 | 58 | if (policy.PreflightMaxAge.HasValue) 59 | builder.SetPreflightMaxAge(policy.PreflightMaxAge.Value).AllowCredentials(); 60 | }); 61 | }); 62 | }); 63 | 64 | return services; 65 | } 66 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Libraries/Swagger/ApiVersioning/AddApiVersioningExtensions.cs: -------------------------------------------------------------------------------- 1 | using CodeZero.ApiVersioning; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Versioning; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection; 7 | 8 | public static partial class ServiceCollectionExtensions 9 | { 10 | /// 11 | /// Adds API versioning, API explorer service. 12 | /// 13 | /// The . 14 | /// The 15 | /// 16 | public static IServiceCollection AddCodeZeroApiVersioning( 17 | [NotNull] this IServiceCollection services, 18 | [NotNull] IConfiguration config) 19 | { 20 | services.AddTransient(); 21 | services.AddSingleton(NullRequestedApiVersion.Instance); 22 | 23 | var apiVer = config.GetSection(nameof(DefaultApiVersion)).Get() ?? new(); 24 | 25 | // Add API versioning 26 | services.AddApiVersioning(options => 27 | { 28 | // Specify the default api version 29 | options.DefaultApiVersion = new ApiVersion(apiVer.Major, apiVer.Minor, apiVer.Status); 30 | 31 | // Reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" 32 | options.ReportApiVersions = true; 33 | 34 | // Assume that the caller wants the default version if they don't specify 35 | options.AssumeDefaultVersionWhenUnspecified = true; 36 | 37 | // Read the version number from the accept header 38 | ////options.ApiVersionReader = new MediaTypeApiVersionReader(); 39 | 40 | // Read the version number from the header 41 | options.ApiVersionReader = new HeaderApiVersionReader(AppConst.HeaderName.ApiVersion); 42 | }); 43 | 44 | // Adds an API explorer 45 | services.AddVersionedApiExplorer(options => 46 | { 47 | options.DefaultApiVersion = new ApiVersion(apiVer.Major, apiVer.Minor, apiVer.Status); 48 | 49 | // Add the versioned api explorer, which also adds IApiVersionDescriptionProvider service 50 | // Note: the specified format code will format the version as "'v'major[.minor][-status]" 51 | options.GroupNameFormat = "'v'VVV"; 52 | 53 | options.AssumeDefaultVersionWhenUnspecified = true; 54 | 55 | // Note: this option is only necessary when versioning by url segment. 56 | // the SubstitutionFormat can also be used to control the format of the API version in route templates 57 | options.SubstituteApiVersionInUrl = true; 58 | }); 59 | 60 | return services; 61 | } 62 | } -------------------------------------------------------------------------------- /tests/WebAPI/Identity/IdentityService.cs: -------------------------------------------------------------------------------- 1 | //using CodeZero.Domain.Common.Interfaces; 2 | //using CodeZero.Domain.Common.Models; 3 | //using Microsoft.AspNetCore.Identity; 4 | //using Microsoft.EntityFrameworkCore; 5 | 6 | //namespace WebAPI.Identity; 7 | 8 | //public class IdentityService : IIdentityService 9 | //{ 10 | // private readonly UserManager _userManager; 11 | // //private readonly IUserClaimsPrincipalFactory _userClaimsPrincipalFactory; 12 | // //private readonly IAuthorizationService _authorizationService; 13 | 14 | // public IdentityService( 15 | // UserManager userManager 16 | // //IUserClaimsPrincipalFactory userClaimsPrincipalFactory, 17 | // //IAuthorizationService authorizationService 18 | // ) 19 | // { 20 | // _userManager = userManager; 21 | // //_userClaimsPrincipalFactory = userClaimsPrincipalFactory; 22 | // //_authorizationService = authorizationService; 23 | // } 24 | 25 | // public async Task GetUserNameAsync(string userId) 26 | // { 27 | // var user = await _userManager.Users.FirstAsync(u => u.Id == userId); 28 | 29 | // return user.UserName; 30 | // } 31 | 32 | // public async Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password) 33 | // { 34 | // var user = new ApplicationUser 35 | // { 36 | // UserName = userName, 37 | // Email = userName, 38 | // }; 39 | 40 | // var result = await _userManager.CreateAsync(user, password); 41 | 42 | // return (result.ToApplicationResult(), user.Id); 43 | // } 44 | 45 | // public async Task IsInRoleAsync(string userId, string role) 46 | // { 47 | // var user = _userManager.Users.SingleOrDefault(u => u.Id == userId); 48 | 49 | // return user != null && await _userManager.IsInRoleAsync(user, role); 50 | // } 51 | 52 | // public async Task AuthorizeAsync(string userId, string policyName) 53 | // { 54 | // var user = _userManager.Users.SingleOrDefault(u => u.Id == userId); 55 | 56 | // if (user == null) 57 | // { 58 | // return false; 59 | // } 60 | 61 | // //var principal = await _userClaimsPrincipalFactory.CreateAsync(user); 62 | 63 | // //var result = await _authorizationService.AuthorizeAsync(principal, policyName); 64 | 65 | // return await Task.FromResult(true); 66 | // } 67 | 68 | // public async Task DeleteUserAsync(string userId) 69 | // { 70 | // var user = _userManager.Users.SingleOrDefault(u => u.Id == userId); 71 | 72 | // return user != null ? await DeleteUserAsync(user) : Result.Success(); 73 | // } 74 | 75 | // public async Task DeleteUserAsync(ApplicationUser user) 76 | // { 77 | // var result = await _userManager.DeleteAsync(user); 78 | 79 | // return result.ToApplicationResult(); 80 | // } 81 | //} -------------------------------------------------------------------------------- /src/CodeZero.Shared/System/Collections/Generic/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System.Collections.Generic; 2 | 3 | /// 4 | /// Extension methods for . 5 | /// 6 | public static class DictionaryExtensions 7 | { 8 | public static T? GetValueOrDefault(this IDictionary source, TKey key) 9 | where TKey : notnull 10 | { 11 | return GetValueOrDefault(source, key, defaultValue: default); 12 | } 13 | 14 | public static T? GetValueOrDefault( 15 | this IDictionary source, 16 | TKey key, 17 | T? defaultValue) 18 | where TKey : notnull 19 | { 20 | if (source is null) throw new ArgumentNullException(nameof(source)); 21 | if (key is null) throw new ArgumentNullException(nameof(key)); 22 | if (source.TryGetValue(key, out var item)) 23 | { 24 | return item; 25 | } 26 | 27 | return defaultValue; 28 | } 29 | 30 | /// 31 | /// Gets a value from the dictionary with given key. Returns default value if can not find. 32 | /// 33 | /// Dictionary to check and get 34 | /// Key to find the value 35 | /// A factory method used to create the value 36 | /// if not found in the dictionary 37 | /// Type of the key 38 | /// Type of the value 39 | /// Value if found, default if can not found. 40 | public static TValue GetOrAdd( 41 | this IDictionary dictionary, 42 | TKey key, 43 | Func factory) 44 | { 45 | if (dictionary is null) throw new ArgumentNullException(nameof(dictionary)); 46 | if (key is null) throw new ArgumentNullException(nameof(key)); 47 | if (dictionary.TryGetValue(key, out var obj)) 48 | { 49 | return obj; 50 | } 51 | 52 | return dictionary[key] = factory(key); 53 | } 54 | 55 | /// 56 | /// Gets a value from the dictionary with given key. 57 | /// Returns default value if can not find. 58 | /// 59 | /// Dictionary to check and get 60 | /// Key to find the value 61 | /// A factory method used to create the value 62 | /// if not found in the dictionary 63 | /// Type of the key 64 | /// Type of the value 65 | /// Value if found, default if can not found. 66 | public static TValue GetOrAdd( 67 | this IDictionary dictionary, 68 | TKey key, 69 | Func factory) 70 | { 71 | return dictionary.GetOrAdd(key, k => factory()); 72 | } 73 | } -------------------------------------------------------------------------------- /src/CodeZero.ServiceInstaller/Extensions/DataProtectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Microsoft.AspNetCore.DataProtection; 3 | using Microsoft.AspNetCore.DataProtection.KeyManagement; 4 | using Microsoft.AspNetCore.DataProtection.XmlEncryption; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection.Extensions; 7 | using Microsoft.Extensions.Options; 8 | using StackExchange.Redis; 9 | 10 | namespace Microsoft.Extensions.DependencyInjection; 11 | 12 | public static partial class ServiceCollectionExtensions 13 | { 14 | /// 15 | /// Add DataProtection. 16 | /// 17 | /// The . 18 | /// The . 19 | /// 20 | public static IServiceCollection AddDataProtectionConfig( 21 | [NotNull] this IServiceCollection services, 22 | [NotNull] IConfiguration configuration) 23 | { 24 | var dpConfig = configuration.GetSection(nameof(DataProtectionConfig)).Get(); 25 | var redisConfig = configuration.GetSection(nameof(RedisConfig)).Get(); 26 | 27 | if (dpConfig is null) 28 | { 29 | throw new CodeZeroException($"Configure {nameof(DataProtectionConfig)} settings in appsettings.Environment.json"); 30 | } 31 | 32 | // Remove any previously registered options setups. 33 | services.RemoveAll>(); 34 | services.RemoveAll>(); 35 | 36 | // The 'FileSystemXmlRepository' will create the directory, but only if it is not overridden. 37 | var directory = new DirectoryInfo(Path.Combine(dpConfig.Path, dpConfig.FileSystemDirectoryName)); 38 | 39 | // Adds app level data protection services. 40 | var builder = services.AddDataProtection().SetApplicationName(Assembly.GetExecutingAssembly().FullName!); 41 | 42 | if (dpConfig.PersistKeysToRedis) 43 | { 44 | if (redisConfig is null) 45 | { 46 | throw new CodeZeroException($"Add your {nameof(RedisConfig)} to appsettings json."); 47 | } 48 | 49 | ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(redisConfig.ConnectionString); 50 | builder.PersistKeysToStackExchangeRedis(redis, dpConfig.RedisKey); 51 | } 52 | else 53 | { 54 | builder.PersistKeysToFileSystem(directory) 55 | .AddKeyManagementOptions(options => 56 | { 57 | options.NewKeyLifetime = TimeSpan.FromDays(dpConfig.KeyManagement.NewKeyLifetime); 58 | options.AutoGenerateKeys = dpConfig.KeyManagement.AutoGenerateKeys; 59 | options.XmlEncryptor ??= new NullXmlEncryptor(); 60 | }); 61 | } 62 | 63 | return services; 64 | } 65 | } -------------------------------------------------------------------------------- /src/CodeZero.Builder/Builder/CodeZeroHostBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Serilog; 6 | 7 | namespace Microsoft.Extensions.Hosting; 8 | 9 | /// 10 | /// CodeZero Host Builder: A program initialization configuration parameters. 11 | /// 12 | public static class CodeZeroHostBuilder 13 | { 14 | public static WebApplicationBuilder CreateAsync( 15 | WebApplicationBuilder webApplication, 16 | ConfigurationBuilderOptions options = null!) 17 | { 18 | var configuration = BuildConfiguration(options); 19 | webApplication.WebHost.UseConfiguration(configuration); 20 | webApplication.AddCodeZero(); 21 | 22 | // Load ServiceSettings 23 | var serviceSettings = configuration.GetSection(nameof(ServiceSettings)).Get() ?? new(); 24 | var debugConfig = configuration.GetSection(nameof(DebugConfig)).Get() ?? new(); 25 | 26 | webApplication.WebHost.UseKestrel(webHostBuilder => 27 | { 28 | webHostBuilder.AddServerHeader = false; 29 | }); 30 | webApplication.WebHost.CaptureStartupErrors(debugConfig.CaptureStartupErrors); 31 | webApplication.WebHost.UseSetting(WebHostDefaults.DetailedErrorsKey, 32 | debugConfig.DetailedErrorsKey.ToString().ToLower()); 33 | 34 | // Add Serilog configuration 35 | if (serviceSettings.EnableSerilog) 36 | { 37 | webApplication.Host.UseSerilog(); 38 | SerilogConfig.SetupLogger(configuration); 39 | } 40 | 41 | return webApplication; 42 | } 43 | 44 | public static IConfiguration BuildConfiguration(ConfigurationBuilderOptions options) 45 | { 46 | Console.ResetColor(); 47 | Console.ForegroundColor = ConsoleColor.Yellow; 48 | Console.WriteLine(Assembly.GetExecutingAssembly().FullName? 49 | .Replace(".Builder", "") 50 | .Replace("Culture=neutral, PublicKeyToken=null", "by Nasr Aldin")); 51 | Console.ResetColor(); 52 | Thread.CurrentThread.Name = $"{AppDomain.CurrentDomain.FriendlyName} Main thread"; 53 | Console.WriteLine($"[CodeZero] CurrentThread: {Thread.CurrentThread.Name}"); 54 | Console.WriteLine("[CodeZero] Build Configuration..."); 55 | 56 | if (!File.Exists(Path.Combine(Directory.GetCurrentDirectory(), Path.GetFileName($"{options.FileName}.{options.EnvironmentName}.json")))) 57 | { 58 | Console.ForegroundColor = ConsoleColor.Red; 59 | Console.WriteLine($"{options.FileName}.{options.EnvironmentName}.json FileNotFound"); 60 | throw new FileNotFoundException($"{options.FileName}.{options.EnvironmentName}.json file is not existing. " + 61 | $"Please check your {options.FileName}.{options.EnvironmentName}.json is exist or create new one from appsettings.Template.json"); 62 | } 63 | 64 | return ConfigurationHelper.BuildConfiguration(options); 65 | } 66 | } -------------------------------------------------------------------------------- /src/CodeZero.Shared/Exception/CodeZeroException.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace System; 4 | 5 | /// 6 | /// Base exception type for those are thrown 7 | /// by CodeZero system for app specific exceptions. 8 | /// 9 | [Serializable] 10 | public class CodeZeroException : Exception 11 | { 12 | private readonly string _resourceName = default!; 13 | private readonly IList _validationErrors = default!; 14 | 15 | /// 16 | public CodeZeroException() { } 17 | 18 | /// 19 | public CodeZeroException(string message) 20 | : base(message) 21 | { 22 | } 23 | 24 | /// 25 | public CodeZeroException(string message, Exception innerException) 26 | : base(message, innerException) 27 | { 28 | } 29 | 30 | public CodeZeroException( 31 | string message, 32 | string resourceName, 33 | IList validationErrors) 34 | : base(message) 35 | { 36 | _resourceName = resourceName; 37 | _validationErrors = validationErrors; 38 | } 39 | 40 | public CodeZeroException( 41 | string message, 42 | string resourceName, 43 | IList validationErrors, 44 | Exception innerException) 45 | : base(message, innerException) 46 | { 47 | _resourceName = resourceName; 48 | _validationErrors = validationErrors; 49 | } 50 | 51 | /// 52 | /// Constructor should be protected for unsealed classes, private for sealed classes. 53 | // (The Serializer invokes this constructor through reflection, so it can be private) 54 | protected CodeZeroException(SerializationInfo info, StreamingContext context) 55 | : base(info, context) 56 | { 57 | } 58 | 59 | /// 60 | /// Initializes a new instance of the Exception class with a specified error message. 61 | /// 62 | /// The exception message format. 63 | /// The exception message arguments. 64 | public CodeZeroException(string messageFormat, params object[] args) 65 | : base(string.Format(messageFormat, args)) 66 | { 67 | } 68 | 69 | public string ResourceName 70 | { 71 | get { return _resourceName; } 72 | } 73 | 74 | public IList ValidationErrors 75 | { 76 | get { return _validationErrors; } 77 | } 78 | 79 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 80 | { 81 | ArgumentNullException.ThrowIfNull(info); 82 | 83 | info.AddValue("ResourceName", ResourceName); 84 | 85 | // Note: if "List" isn't serializable you may need to work out another 86 | // method of adding your list, this is just for show... 87 | info.AddValue("ValidationErrors", ValidationErrors, typeof(IList)); 88 | 89 | // MUST call through to the base class to let it save its own state 90 | base.GetObjectData(info, context); 91 | } 92 | } -------------------------------------------------------------------------------- /src/CodeZero.Domain/Common/Behaviors/AuthorizationBehavior.cs: -------------------------------------------------------------------------------- 1 | namespace CodeZero.Domain.Common.Behaviours; 2 | 3 | public class AuthorizationBehavior : 4 | IPipelineBehavior 5 | where TRequest : IRequest 6 | { 7 | private readonly ICurrentUserService _currentUserService; 8 | private readonly IIdentityService _identityService; 9 | 10 | public AuthorizationBehavior( 11 | ICurrentUserService currentUserService, 12 | IIdentityService identityService) 13 | { 14 | _currentUserService = currentUserService; 15 | _identityService = identityService; 16 | } 17 | 18 | public async Task Handle( 19 | TRequest request, 20 | CancellationToken cancellationToken, 21 | RequestHandlerDelegate next) 22 | { 23 | var authorizeAttributes = request.GetType().GetCustomAttributes(); 24 | 25 | if (authorizeAttributes.Any()) 26 | { 27 | // Must be authenticated user 28 | if (_currentUserService.UserId is null) 29 | { 30 | throw new UnauthorizedAccessException(); 31 | } 32 | 33 | // Role-based authorization 34 | var authorizeAttributesWithRoles = authorizeAttributes.Where(a => !string.IsNullOrWhiteSpace(a.Roles)); 35 | 36 | if (authorizeAttributesWithRoles.Any()) 37 | { 38 | var authorized = false; 39 | 40 | foreach (var roles in authorizeAttributesWithRoles.Select(a => a.Roles.Split(','))) 41 | { 42 | foreach (var role in roles) 43 | { 44 | var isInRole = await _identityService.IsInRoleAsync(_currentUserService.UserId, role.Trim()); 45 | 46 | if (isInRole) 47 | { 48 | authorized = true; 49 | break; 50 | } 51 | } 52 | } 53 | 54 | // Must be a member of at least one role in roles 55 | if (!authorized) 56 | { 57 | throw new ForbiddenAccessException(); 58 | } 59 | } 60 | 61 | // Policy-based authorization 62 | var authorizeAttributesWithPolicies = authorizeAttributes.Where(a => !string.IsNullOrWhiteSpace(a.Policy)); 63 | 64 | if (authorizeAttributesWithPolicies.Any()) 65 | { 66 | foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy)) 67 | { 68 | var authorized = await _identityService.AuthorizeAsync(_currentUserService.UserId, policy); 69 | 70 | if (!authorized) 71 | { 72 | throw new ForbiddenAccessException(); 73 | } 74 | } 75 | } 76 | } 77 | 78 | // User is authorized / authorization not required 79 | return await next(); 80 | } 81 | } --------------------------------------------------------------------------------