├── .config └── dotnet-tools.json ├── .gitignore ├── Apiresources.Application ├── Apiresources.Application.csproj ├── Behaviours │ └── ValidationBehaviour.cs ├── DTOs │ └── Email │ │ └── EmailRequest.cs ├── Enums │ └── Roles.cs ├── Exceptions │ ├── ApiException.cs │ └── ValidationException.cs ├── Features │ ├── Departments │ │ └── Queries │ │ │ └── GetDepartments │ │ │ ├── GetDepartmentsQuery.cs │ │ │ └── GetDepartmentsViewModel.cs │ ├── Employees │ │ └── Queries │ │ │ └── GetEmployees │ │ │ ├── GetEmployeesQuery.cs │ │ │ ├── GetEmployeesViewModel.cs │ │ │ └── PagedEmployeesQuery.cs │ ├── Positions │ │ ├── Commands │ │ │ ├── CreatePosition │ │ │ │ ├── CreatePositionCommand.cs │ │ │ │ ├── CreatePositionCommandValidator.cs │ │ │ │ └── InsertMockPositionCommand.cs │ │ │ ├── DeletePositionById │ │ │ │ └── DeletePositionByIdCommand.cs │ │ │ └── UpdatePosition │ │ │ │ ├── UpdatePositionCommand.cs │ │ │ │ └── UpdatePositionCommandValidator.cs │ │ └── Queries │ │ │ ├── GetPositionById │ │ │ └── GetPositionByIdQuery.cs │ │ │ └── GetPositions │ │ │ ├── GetPositionsQuery.cs │ │ │ ├── GetPositionsViewModel.cs │ │ │ └── PagedPositionsQuery.cs │ └── SalaryRanges │ │ └── Queries │ │ └── GetSalaryRanges │ │ ├── GetSalaryRangesQuery.cs │ │ └── GetSalaryRangesViewModel.cs ├── Helpers │ ├── DataShapeHelper.cs │ └── ModelHelper.cs ├── Interfaces │ ├── IDataShapeHelper.cs │ ├── IDateTimeService.cs │ ├── IEmailService.cs │ ├── IGenericRepositoryAsync.cs │ ├── IMockService.cs │ ├── IModelHelper.cs │ └── Repositories │ │ ├── IDepartmentRepositoryAsync.cs │ │ ├── IEmployeeRepositoryAsync.cs │ │ ├── IPositionRepositoryAsync.cs │ │ └── ISalaryRangeRepositoryAsync.cs ├── Mappings │ └── GeneralProfile.cs ├── MyTemplate.vstemplate ├── Parameters │ ├── Column.cs │ ├── ListParameter.cs │ ├── PagingParameter.cs │ ├── QueryParameter.cs │ ├── RecordsCount.cs │ ├── Search.cs │ └── SortOrder.cs ├── ServiceExtensions.cs ├── Using.cs ├── Wrappers │ ├── PagedDataTableResponse.cs │ ├── PagedResponse.cs │ └── Response.cs └── __TemplateIcon.ico ├── Apiresources.Domain ├── Apiresources.Domain.csproj ├── Common │ ├── AuditableBaseEntity.cs │ └── BaseEntity.cs ├── Entities │ ├── Department.cs │ ├── Employee.cs │ ├── Entity.cs │ ├── Position.cs │ └── SalaryRange.cs ├── Enums │ └── Gender.cs ├── MyTemplate.vstemplate ├── Settings │ ├── JWTSettings.cs │ └── MailSettings.cs ├── Using.cs └── __TemplateIcon.ico ├── Apiresources.Infrastructure.Persistence ├── Apiresources.Infrastructure.Persistence.csproj ├── Contexts │ ├── ApplicationDbContext.cs │ └── ApplicationDbContextHelpers.cs ├── Extensions │ └── DbFunctionsExtensions.cs ├── MyTemplate.vstemplate ├── Repositories │ ├── DepartmentRepositoryAsync.cs │ ├── EmployeeRepositoryAsync.cs │ ├── GenericRepositoryAsync.cs │ ├── PositionRepositoryAsync.cs │ └── SalaryRangeRepositoryAsync.cs ├── SeedData │ └── DbInitializer.cs ├── ServiceRegistration.cs ├── Using.cs └── __TemplateIcon.ico ├── Apiresources.Infrastructure.Shared ├── Apiresources.Infrastructure.Shared.csproj ├── Mock │ ├── EmployeeBogusConfig.cs │ ├── PositionInsertBogusConfig.cs │ └── PositionSeedBogusConfig.cs ├── MyTemplate.vstemplate ├── ServiceRegistration.cs ├── Services │ ├── DatabaseSeeder.cs │ ├── DateTimeService.cs │ ├── EmailService.cs │ └── MockService.cs ├── Using.cs └── __TemplateIcon.ico ├── Apiresources.WebApi ├── Apiresources.WebApi.csproj ├── Apiresources.WebApi.xml ├── Controllers │ ├── BaseApiController.cs │ ├── MetaController.cs │ └── v1 │ │ ├── DepartmentsController.cs │ │ ├── EmployeesController.cs │ │ ├── PositionsController.cs │ │ └── SalaryRangesController.cs ├── Extensions │ ├── AppExtensions.cs │ ├── AuthorizationConsts.cs │ └── ServiceExtensions.cs ├── Middlewares │ └── ErrorHandlerMiddleware.cs ├── Models │ └── Metadata.cs ├── MyTemplate.vstemplate ├── Program.cs ├── Properties │ ├── PublishProfiles │ │ └── FolderProfile.pubxml │ └── launchSettings.json ├── Using.cs ├── __TemplateIcon.ico ├── appsettings.Development.json └── appsettings.json ├── OnionAPI.vstemplate ├── database-centrics-vs-domain-centric-architecture.drawio └── zip_template_onion_api.bat /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "csharpier": { 6 | "version": "0.30.5", 7 | "commands": [ 8 | "dotnet-csharpier" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TemplateOnionAPI.zip -------------------------------------------------------------------------------- /Apiresources.Application/Apiresources.Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 1.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Apiresources.Application/Behaviours/ValidationBehaviour.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Behaviours 2 | { 3 | public class ValidationBehavior : IPipelineBehavior 4 | where TRequest : IRequest 5 | { 6 | // A collection of validators that will be used to validate the request. 7 | private readonly IEnumerable> _validators; 8 | 9 | // Constructor that takes in a collection of validators. 10 | public ValidationBehavior(IEnumerable> validators) 11 | { 12 | _validators = validators; 13 | } 14 | 15 | // This method is called when a request is being handled by the pipeline. It first checks if there are any validators to use. If there are, it uses them to validate the request. If there are any validation failures, it throws a ValidationException. If all validations pass, it calls the next handler in the pipeline. 16 | public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) 17 | { 18 | if (_validators.Any()) 19 | { 20 | // Create a new ValidationContext with the request object. 21 | var context = new ValidationContext(request); 22 | 23 | // Use all validators to validate the request asynchronously. 24 | var validationResults = await Task.WhenAll(_validators.Select(validator => validator.ValidateAsync(context, cancellationToken))); 25 | 26 | // Extract any validation failures from the validation results. 27 | var failures = validationResults.SelectMany(validationResult => validationResult.Errors) 28 | .Where(validationResult => validationResult != null) 29 | .ToList(); 30 | 31 | // If there are any validation failures, throw a ValidationException with the list of failures. 32 | if (failures.Any()) 33 | throw new Exceptions.ValidationException(failures); 34 | } 35 | 36 | // Call the next handler in the pipeline and return its result. 37 | return await next(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Apiresources.Application/DTOs/Email/EmailRequest.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.DTOs.Email 2 | { 3 | // Represents an email request with properties for recipient address, subject line, message body, and sender address 4 | public class EmailRequest 5 | { 6 | // Recipient address of the email 7 | public string To { get; set; } 8 | // Subject line of the email 9 | public string Subject { get; set; } 10 | // Message body of the email 11 | public string Body { get; set; } 12 | // Sender address of the email 13 | public string From { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Apiresources.Application/Enums/Roles.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Enums 2 | { 3 | /// 4 | /// Defines different roles within an organization. 5 | /// 6 | public enum Roles 7 | { 8 | /// 9 | /// The highest level of access and control, with full responsibility for the entire organization. 10 | /// 11 | SuperAdmin, 12 | 13 | /// 14 | /// A high-level position responsible for managing a specific department or team within the organization. 15 | /// 16 | Admin, 17 | 18 | /// 19 | /// A mid-level position responsible for supervising and managing a team of employees within the organization. 20 | /// 21 | Manager, 22 | 23 | /// 24 | /// A low-level position responsible for performing daily tasks related to their specific job function within the organization. 25 | /// 26 | Employee 27 | } 28 | } -------------------------------------------------------------------------------- /Apiresources.Application/Exceptions/ApiException.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Exceptions { 2 | /// 3 | /// Custom exception class for API-related errors. 4 | /// 5 | public class ApiException : Exception { 6 | /// 7 | /// Initializes a new instance of the class with default values. 8 | /// 9 | public ApiException() : base() { 10 | } 11 | 12 | /// 13 | /// Initializes a new instance of the class with a specified error message. 14 | /// 15 | /// The error message that describes the cause of the exception. 16 | public ApiException(string message) : base(message) { 17 | } 18 | 19 | /// 20 | /// Initializes a new instance of the class with a specified error message and arguments. 21 | /// 22 | /// The error message that describes the cause of the exception. 23 | /// An object array that contains zero or more objects to format into the error message string. 24 | public ApiException(string message, params object[] args) : base(String.Format(CultureInfo.CurrentCulture, message, args)) { 25 | } 26 | 27 | /// 28 | /// Initializes a new instance of the class with a specified error message and an inner exception. 29 | /// 30 | /// The error message that describes the cause of the exception. 31 | /// The exception that caused the current exception, or null if no inner exception is present. 32 | public ApiException(string message, Exception innerException) : base(message, innerException) { 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Apiresources.Application/Exceptions/ValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Exceptions 2 | { 3 | /// 4 | /// Represents a validation exception that occurs when one or more validation failures have occurred. 5 | /// 6 | public class ValidationException : Exception 7 | { 8 | /// 9 | /// Initializes a new instance of the class with default values. 10 | /// 11 | public ValidationException() : base("One or more validation failures have occurred.") 12 | { 13 | Errors = new List(); 14 | } 15 | 16 | /// 17 | /// Gets a list of error messages that describe the validation failures. 18 | /// 19 | public List Errors { get; } 20 | 21 | /// 22 | /// Initializes a new instance of the class with a specified message. 23 | /// 24 | /// The error message that explains the reason for the exception. 25 | public ValidationException(string message) : base(message) 26 | { 27 | } 28 | 29 | /// 30 | /// Initializes a new instance of the class with a specified message and inner exception. 31 | /// 32 | /// The error message that explains the reason for the exception. 33 | /// The exception that is the cause of the current exception, or null if no inner exception is specified. 34 | public ValidationException(string message, Exception innerException) : base(message, innerException) 35 | { 36 | } 37 | 38 | /// 39 | /// Initializes a new instance of the class with a list of validation failures. 40 | /// 41 | /// A collection of validation failure objects that describe the errors. 42 | public ValidationException(IEnumerable failures) 43 | : this() 44 | { 45 | foreach (var failure in failures) 46 | { 47 | Errors.Add(failure.ErrorMessage); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Apiresources.Application/Features/Departments/Queries/GetDepartments/GetDepartmentsQuery.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Departments.Queries.GetDepartments 2 | { 3 | /// 4 | /// GetAllDepartmentsQuery - handles media IRequest 5 | /// BaseRequestParameter - contains paging parameters 6 | /// To add filter/search parameters, add search properties to the body of this class 7 | /// 8 | public class GetDepartmentsQuery : ListParameter, IRequest> 9 | { 10 | } 11 | 12 | public class GetAllDepartmentsQueryHandler : IRequestHandler> 13 | { 14 | private readonly IDepartmentRepositoryAsync _repository; 15 | private readonly IModelHelper _modelHelper; 16 | private readonly IMapper _mapper; 17 | 18 | /// 19 | /// Constructor for GetAllDepartmentsQueryHandler class. 20 | /// 21 | /// IDepartmentRepositoryAsync object. 22 | /// IModelHelper object. 23 | /// 24 | /// GetAllDepartmentsQueryHandler object. 25 | /// 26 | public GetAllDepartmentsQueryHandler(IDepartmentRepositoryAsync repository, IModelHelper modelHelper, IMapper mapper) 27 | { 28 | _repository = repository; 29 | _modelHelper = modelHelper; 30 | _mapper = mapper; 31 | } 32 | 33 | /// 34 | /// Handles the GetDepartmentsQuery request and returns a PagedResponse containing the requested data. 35 | /// 36 | /// The GetDepartmentsQuery request. 37 | /// The cancellation token. 38 | /// A PagedResponse containing the requested data. 39 | public async Task> Handle(GetDepartmentsQuery request, CancellationToken cancellationToken) 40 | { 41 | string fields = _modelHelper.GetModelFields(); 42 | string defaultOrderByColumn = "Name"; 43 | 44 | string orderBy = string.Empty; 45 | 46 | // if the request orderby is not null 47 | if (!string.IsNullOrEmpty(request.OrderBy)) 48 | { 49 | // check to make sure order by field is valid and in the view model 50 | orderBy = _modelHelper.ValidateModelFields(request.OrderBy); 51 | } 52 | 53 | // if the order by is invalid 54 | if (string.IsNullOrEmpty(orderBy)) 55 | { 56 | //default fields from view model 57 | orderBy = defaultOrderByColumn; 58 | } 59 | 60 | var data = await _repository.GetAllShapeAsync(orderBy, fields); 61 | 62 | // automap to ViewModel 63 | var viewModel = _mapper.Map>(data); 64 | 65 | return viewModel; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Departments/Queries/GetDepartments/GetDepartmentsViewModel.cs: -------------------------------------------------------------------------------- 1 | // Defines a namespace for the project's features 2 | namespace $safeprojectname$.Features.Departments.Queries.GetDepartments 3 | { 4 | // Defines a view model class for getting departments 5 | public class GetDepartmentsViewModel 6 | { 7 | // Id property for department 8 | public Guid Id { get; set; } 9 | 10 | // Name property for department 11 | public string Name { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Employees/Queries/GetEmployees/GetEmployeesQuery.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Employees.Queries.GetEmployees 2 | { 3 | /// 4 | /// GetAllEmployeesQuery - handles media IRequest 5 | /// BaseRequestParameter - contains paging parameters 6 | /// To add filter/search parameters, add search properties to the body of this class 7 | /// 8 | public class GetEmployeesQuery : QueryParameter, IRequest>> 9 | { 10 | //examples: 11 | public string LastName { get; set; } 12 | 13 | public string FirstName { get; set; } 14 | public string Email { get; set; } 15 | public string EmployeeNumber { get; set; } 16 | public string PositionTitle { get; set; } 17 | 18 | public ListParameter ShapeParameter { get; set; } 19 | } 20 | 21 | public class GetAllEmployeesQueryHandler : IRequestHandler>> 22 | { 23 | private readonly IEmployeeRepositoryAsync _repository; 24 | private readonly IModelHelper _modelHelper; 25 | 26 | /// 27 | /// Constructor for GetAllEmployeesQueryHandler class. 28 | /// 29 | /// IEmployeeRepositoryAsync object. 30 | /// IModelHelper object. 31 | /// 32 | /// GetAllEmployeesQueryHandler object. 33 | /// 34 | public GetAllEmployeesQueryHandler(IEmployeeRepositoryAsync employeeRepository, IModelHelper modelHelper) 35 | { 36 | _repository = employeeRepository; 37 | _modelHelper = modelHelper; 38 | } 39 | 40 | /// 41 | /// Handles the GetEmployeesQuery request and returns a PagedResponse containing the requested data. 42 | /// 43 | /// The GetEmployeesQuery request. 44 | /// The cancellation token. 45 | /// A PagedResponse containing the requested data. 46 | public async Task>> Handle(GetEmployeesQuery request, CancellationToken cancellationToken) 47 | { 48 | var objRequest = request; 49 | //filtered fields security 50 | if (!string.IsNullOrEmpty(objRequest.Fields)) 51 | { 52 | //limit to fields in view model 53 | objRequest.Fields = _modelHelper.ValidateModelFields(objRequest.Fields); 54 | } 55 | else 56 | { 57 | //default fields from view model 58 | objRequest.Fields = _modelHelper.GetModelFields(); 59 | } 60 | // query based on filter 61 | var qryResult = await _repository.GetEmployeeResponseAsync(objRequest); 62 | var data = qryResult.data; 63 | RecordsCount recordCount = qryResult.recordsCount; 64 | 65 | // response wrapper 66 | return new PagedResponse>(data, objRequest.PageNumber, objRequest.PageSize, recordCount); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Employees/Queries/GetEmployees/GetEmployeesViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Employees.Queries.GetEmployees 2 | { 3 | /// 4 | /// Represents a view model for the GetEmployees query. 5 | /// 6 | public class GetEmployeesViewModel 7 | { 8 | /// 9 | /// Gets or sets the ID of the employee. 10 | /// 11 | public Guid Id { get; set; } 12 | 13 | /// 14 | /// Gets or sets the first name of the employee. 15 | /// 16 | public string FirstName { get; set; } 17 | 18 | /// 19 | /// Gets or sets the middle name of the employee. 20 | /// 21 | public string MiddleName { get; set; } 22 | 23 | /// 24 | /// Gets or sets the last name of the employee. 25 | /// 26 | public string LastName { get; set; } 27 | 28 | /// 29 | /// Gets or sets the birthday of the employee. 30 | /// 31 | public DateTime Birthday { get; set; } 32 | 33 | /// 34 | /// Gets or sets the email address of the employee. 35 | /// 36 | public string Email { get; set; } 37 | 38 | /// 39 | /// Gets or sets the gender of the employee. 40 | /// 41 | public Gender Gender { get; set; } 42 | 43 | /// 44 | /// Gets or sets the employee number. 45 | /// 46 | public string EmployeeNumber { get; set; } 47 | 48 | /// 49 | /// Gets or sets the prefix of the employee's name. 50 | /// 51 | public string Prefix { get; set; } 52 | 53 | /// 54 | /// Gets or sets the phone number of the employee. 55 | /// 56 | public string Phone { get; set; } 57 | 58 | /// 59 | /// Gets or sets the position held by the employee. 60 | /// 61 | public virtual Position Position { get; set; } 62 | 63 | /// 64 | /// Gets or sets the salary of the employee. 65 | /// 66 | public decimal Salary { get; set; } 67 | } 68 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Employees/Queries/GetEmployees/PagedEmployeesQuery.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Employees.Queries.GetEmployees 2 | { 3 | // This class represents a query for paginated employee data. 4 | public partial class PagedEmployeesQuery : QueryParameter, IRequest>> 5 | { 6 | // The page number to retrieve. 0-indexed. 7 | public int Draw { get; set; } 8 | 9 | // The index of the first record to retrieve in the current data set. 10 | public int Start { get; set; } 11 | 12 | // The number of records to retrieve per page. 13 | public int Length { get; set; } 14 | 15 | // The order in which to sort the retrieved records. 16 | public IList Order { get; set; } 17 | 18 | // The search criteria for filtering the retrieved records. 19 | public Search Search { get; set; } 20 | 21 | // The fields to include in the retrieved records. 22 | public IList Columns { get; set; } 23 | } 24 | 25 | // This class handles requests for paginated employee data. 26 | public class PageEmployeeQueryHandler : IRequestHandler>> 27 | { 28 | // The repository used to retrieve employee data. 29 | private readonly IEmployeeRepositoryAsync _repository; 30 | 31 | // Helper methods for working with models. 32 | private readonly IModelHelper _modelHelper; 33 | 34 | /// 35 | /// Constructor for PageEmployeeQueryHandler class. 36 | /// 37 | /// IEmployeeRepositoryAsync object. 38 | /// IModelHelper object. 39 | public PageEmployeeQueryHandler(IEmployeeRepositoryAsync repository, IModelHelper modelHelper) 40 | { 41 | _repository = repository; 42 | _modelHelper = modelHelper; 43 | } 44 | 45 | /// 46 | /// Handles the PagedEmployeesQuery request and returns a PagedDataTableResponse. 47 | /// 48 | /// The PagedEmployeesQuery request. 49 | /// The cancellation token. 50 | /// A PagedDataTableResponse. 51 | public async Task>> Handle(PagedEmployeesQuery request, CancellationToken cancellationToken) 52 | { 53 | var objRequest = request; 54 | 55 | // Convert the page number and page size from the query parameters. 56 | objRequest.PageNumber = (request.Start / request.Length) + 1; 57 | objRequest.PageSize = request.Length; 58 | 59 | // Convert the order parameter to an ORDER BY clause. 60 | var colOrder = request.Order[0]; 61 | switch (colOrder.Column) 62 | { 63 | case 0: 64 | objRequest.OrderBy = colOrder.Dir == "asc" ? "LastName" : "LastName DESC"; 65 | break; 66 | 67 | case 1: 68 | objRequest.OrderBy = colOrder.Dir == "asc" ? "FirstName" : "FirstName DESC"; 69 | break; 70 | 71 | case 2: 72 | objRequest.OrderBy = colOrder.Dir == "asc" ? "Email" : "Email DESC"; 73 | break; 74 | 75 | case 3: 76 | objRequest.OrderBy = colOrder.Dir == "asc" ? "EmployeeNumber" : "EmployeeNumber DESC"; 77 | break; 78 | 79 | case 4: 80 | objRequest.OrderBy = colOrder.Dir == "asc" ? "Position.PositionTitle" : "Position.PositionTitle DESC"; 81 | break; 82 | } 83 | 84 | // If no fields were specified, use the default fields from the view model. 85 | if (string.IsNullOrEmpty(objRequest.Fields)) 86 | { 87 | objRequest.Fields = _modelHelper.GetModelFields(); 88 | } 89 | 90 | // Retrieve the paginated employee data based on the query parameters. 91 | var qryResult = await _repository.GetPagedEmployeeResponseAsync(objRequest); 92 | var data = qryResult.data; 93 | RecordsCount recordCount = qryResult.recordsCount; 94 | 95 | // Return a PagedDataTableResponse containing the retrieved data. 96 | return new PagedDataTableResponse>(data, request.Draw, recordCount); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Commands/CreatePosition/CreatePositionCommand.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Commands.CreatePosition 2 | { 3 | // This class represents a command to create a new position. 4 | public partial class CreatePositionCommand : IRequest> 5 | { 6 | // The title of the position being created. 7 | public string PositionTitle { get; set; } 8 | 9 | // A unique number assigned to the position being created. 10 | public string PositionNumber { get; set; } 11 | 12 | // A description of the position being created. 13 | public string PositionDescription { get; set; } 14 | 15 | // The ID of the department that the position belongs to. 16 | public Guid DepartmentId { get; set; } 17 | 18 | // The ID of the salary range associated with the position being created. 19 | public Guid SalaryRangeId { get; set; } 20 | 21 | } 22 | 23 | // This class handles the logic for creating a new position. 24 | public class CreatePositionCommandHandler : IRequestHandler> 25 | { 26 | // A repository to interact with the database for positions. 27 | private readonly IPositionRepositoryAsync _repository; 28 | 29 | // An object mapper to convert between different data types. 30 | private readonly IMapper _mapper; 31 | 32 | // Constructor that injects the position repository and mapper into the handler. 33 | public CreatePositionCommandHandler(IPositionRepositoryAsync repository, IMapper mapper) 34 | { 35 | _repository = repository; 36 | _mapper = mapper; 37 | } 38 | 39 | // This method is called when a new position creation command is issued. 40 | public async Task> Handle(CreatePositionCommand request, CancellationToken cancellationToken) 41 | { 42 | // Maps the incoming command to a Position object using the mapper. 43 | var position = _mapper.Map(request); 44 | 45 | // Adds the new position to the database asynchronously. 46 | await _repository.AddAsync(position); 47 | 48 | // Returns a response containing the ID of the newly created position. 49 | return new Response(position.Id); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Commands/CreatePosition/CreatePositionCommandValidator.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Commands.CreatePosition 2 | { 3 | /// 4 | /// Validator for the CreatePositionCommand. 5 | /// 6 | public class CreatePositionCommandValidator : AbstractValidator 7 | { 8 | private readonly IPositionRepositoryAsync _repository; 9 | 10 | /// 11 | /// Initializes a new instance of the CreatePositionCommandValidator class. 12 | /// 13 | /// The repository to use for validation. 14 | public CreatePositionCommandValidator(IPositionRepositoryAsync repository) 15 | { 16 | _repository = repository; 17 | 18 | // Rule for validating the PositionNumber property. 19 | RuleFor(p => p.PositionNumber) 20 | .NotEmpty().WithMessage("{PropertyName} is required.") 21 | .NotNull() 22 | .MaximumLength(50).WithMessage("{PropertyName} must not exceed 50 characters.") 23 | .MustAsync(IsUniquePositionNumber).WithMessage("{PropertyName} already exists."); 24 | 25 | // Rule for validating the PositionTitle property. 26 | RuleFor(p => p.PositionTitle) 27 | .NotEmpty().WithMessage("{PropertyName} is required.") 28 | .NotNull() 29 | .MaximumLength(50).WithMessage("{PropertyName} must not exceed 50 characters."); 30 | } 31 | 32 | /// 33 | /// Checks if a position number is unique by querying the repository. 34 | /// 35 | /// The position number to check. 36 | /// A cancellation token that can be used to cancel the operation. 37 | /// True if the position number is unique, false otherwise. 38 | private async Task IsUniquePositionNumber(string positionNumber, CancellationToken cancellationToken) 39 | { 40 | return await _repository.IsUniquePositionNumberAsync(positionNumber); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Commands/CreatePosition/InsertMockPositionCommand.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Commands.CreatePosition 2 | { 3 | // Represents a command to insert mock position data into the database 4 | public partial class InsertMockPositionCommand : IRequest> 5 | { 6 | // The number of rows to insert into the database 7 | public int RowCount { get; set; } 8 | } 9 | 10 | // Handles requests for inserting mock position data 11 | public class SeedPositionCommandHandler : IRequestHandler> 12 | { 13 | // Repository used for interacting with position data in the database 14 | private readonly IPositionRepositoryAsync _repository; 15 | 16 | // Repository used for interacting with department data in the database 17 | private readonly IDepartmentRepositoryAsync _repositoryDepartment; 18 | 19 | // Repository used for interacting with salary range data in the database 20 | private readonly ISalaryRangeRepositoryAsync _repositorySalaryRange; 21 | 22 | // Constructor for SeedPositionCommandHandler, which injects the necessary repositories 23 | public SeedPositionCommandHandler(IPositionRepositoryAsync repository, IDepartmentRepositoryAsync departmentRepository, ISalaryRangeRepositoryAsync repositorySalaryRange) 24 | { 25 | _repository = repository; 26 | _repositoryDepartment = departmentRepository; 27 | _repositorySalaryRange = repositorySalaryRange; 28 | } 29 | 30 | // Handle method for processing the InsertMockPositionCommand request 31 | public async Task> Handle(InsertMockPositionCommand request, CancellationToken cancellationToken) 32 | { 33 | // Get all departments from the database 34 | var departments = await _repositoryDepartment.GetAllAsync(); 35 | 36 | // Get all salary ranges from the database 37 | var salaryRanges = await _repositorySalaryRange.GetAllAsync(); 38 | 39 | // Seed the position data into the database with the specified number of rows 40 | await _repository.SeedDataAsync(request.RowCount, departments, salaryRanges); 41 | 42 | // Return a response containing the number of rows inserted 43 | return new Response(request.RowCount); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Commands/DeletePositionById/DeletePositionByIdCommand.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Commands.DeletePositionById 2 | { 3 | // Represents a command to delete a position by its ID. 4 | public class DeletePositionByIdCommand : IRequest> 5 | { 6 | // The ID of the position to be deleted. 7 | public Guid Id { get; set; } 8 | 9 | // Represents the handler for deleting a position by its ID. 10 | public class DeletePositionByIdCommandHandler : IRequestHandler> 11 | { 12 | // The repository used to access and manipulate position data. 13 | private readonly IPositionRepositoryAsync _repository; 14 | 15 | // Constructor that initializes the command handler with the given repository. 16 | public DeletePositionByIdCommandHandler(IPositionRepositoryAsync repository) 17 | { 18 | _repository = repository; 19 | } 20 | 21 | // Handles the command by deleting the specified position from the repository. 22 | public async Task> Handle(DeletePositionByIdCommand command, CancellationToken cancellationToken) 23 | { 24 | // Retrieves the position with the specified ID from the repository. 25 | var entity = await _repository.GetByIdAsync(command.Id); 26 | if (entity == null) throw new ApiException($"Position Not Found."); 27 | // Deletes the retrieved position from the repository. 28 | await _repository.DeleteAsync(entity); 29 | // Returns a response indicating the successful deletion of the position. 30 | return new Response(entity.Id); 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Commands/UpdatePosition/UpdatePositionCommand.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Commands.UpdatePosition 2 | { 3 | // Define a command class for updating a position 4 | public class UpdatePositionCommand : IRequest> 5 | { 6 | public Guid Id { get; set; } // Unique identifier of the position to be updated 7 | public string PositionTitle { get; set; } // New title for the position 8 | public string PositionNumber { get; set; } // New number for the position (optional) 9 | public string PositionDescription { get; set; } // New description for the position 10 | public Guid DepartmentId { get; set; } // ID of the department to which the position belongs 11 | public Guid SalaryRangeId { get; set; } // ID of the salary range for the position 12 | 13 | // Define a handler class for processing the update command 14 | public class UpdatePositionCommandHandler : IRequestHandler> 15 | { 16 | private readonly IPositionRepositoryAsync _repository; // Repository for accessing positions 17 | 18 | // Constructor to inject the repository 19 | public UpdatePositionCommandHandler(IPositionRepositoryAsync positionRepository) 20 | { 21 | _repository = positionRepository; 22 | } 23 | 24 | // Handle method to process the update command 25 | public async Task> Handle(UpdatePositionCommand command, CancellationToken cancellationToken) 26 | { 27 | var position = await _repository.GetByIdAsync(command.Id); // Get the position by ID 28 | 29 | if (position == null) 30 | { 31 | throw new ApiException($"Position Not Found."); // Throw an exception if the position is not found 32 | } 33 | else 34 | { 35 | // Update the position with the new values from the command 36 | position.PositionTitle = command.PositionTitle; 37 | position.PositionDescription = command.PositionDescription; 38 | 39 | await _repository.UpdateAsync(position); // Save the updated position to the repository 40 | 41 | return new Response(position.Id); // Return a response containing the ID of the updated position 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Commands/UpdatePosition/UpdatePositionCommandValidator.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Commands.CreatePosition 2 | { 3 | // Validator class for the UpdatePositionCommand 4 | public class UpdatePositionCommandValidator : AbstractValidator 5 | { 6 | // Constructor to inject the IPositionRepositoryAsync dependency 7 | public UpdatePositionCommandValidator(IPositionRepositoryAsync repository) 8 | { 9 | // Rule to validate that PositionNumber is not empty, null and does not exceed 50 characters. 10 | RuleFor(p => p.PositionNumber) 11 | .NotEmpty().WithMessage("{PropertyName} is required.") // Validates that the PositionNumber property is not empty 12 | .NotNull() // Validates that the PositionNumber property is not null 13 | .MaximumLength(50).WithMessage("{PropertyName} must not exceed 50 characters."); // Validates that the PositionNumber property does not exceed 50 characters 14 | // Rule to validate that PositionTitle is not empty, null and does not exceed 50 characters. 15 | RuleFor(p => p.PositionTitle) 16 | .NotEmpty().WithMessage("{PropertyName} is required.") // Validates that the PositionTitle property is not empty 17 | .NotNull() // Validates that the PositionTitle property is not null 18 | .MaximumLength(50).WithMessage("{PropertyName} must not exceed 50 characters."); // Validates that the PositionTitle property does not exceed 50 characters 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Queries/GetPositionById/GetPositionByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Queries.GetPositionById 2 | { 3 | // Represents a query to get a position by its ID 4 | public class GetPositionByIdQuery : IRequest> 5 | { 6 | // The ID of the position to retrieve 7 | public Guid Id { get; set; } 8 | 9 | // Handles the GetPositionByIdQuery request and retrieves the corresponding Position entity from the repository. 10 | public class GetPositionByIdQueryHandler : IRequestHandler> 11 | { 12 | private readonly IPositionRepositoryAsync _repository; 13 | 14 | // Constructor that initializes the repository 15 | public GetPositionByIdQueryHandler(IPositionRepositoryAsync repository) 16 | { 17 | _repository = repository; 18 | } 19 | // Handles the request and returns a response containing the Position entity if it exists, otherwise throws an ApiException. 20 | public async Task> Handle(GetPositionByIdQuery query, CancellationToken cancellationToken) 21 | { 22 | var entity = await _repository.GetByIdAsync(query.Id); 23 | if (entity == null) throw new ApiException($"Position Not Found."); 24 | return new Response(entity); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Queries/GetPositions/GetPositionsQuery.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Queries.GetPositions 2 | { 3 | /// 4 | /// Definition of GetPositionsQuery class that inherits from QueryParameter and implements IRequest>> 5 | /// 6 | public class GetPositionsQuery : QueryParameter, IRequest>> 7 | { 8 | /// 9 | /// Property to hold the position number as a string. 10 | /// 11 | public string PositionNumber { get; set; } 12 | 13 | /// 14 | /// Property to hold the position title as a string. 15 | /// 16 | public string PositionTitle { get; set; } 17 | 18 | /// 19 | /// Property to hold the department as a string. 20 | /// 21 | public string Department { get; set; } 22 | } 23 | 24 | /// 25 | /// Definition of GetAllPositionsQueryHandler class that implements IRequestHandler>> 26 | /// 27 | public class GetAllPositionsQueryHandler : IRequestHandler>> 28 | { 29 | private readonly IPositionRepositoryAsync _repository; 30 | private readonly IModelHelper _modelHelper; 31 | 32 | /// 33 | /// Constructor for GetAllPositionsQueryHandler that takes in an IPositionRepositoryAsync and IModelHelper. 34 | /// 35 | /// IPositionRepositoryAsync object. 36 | /// IModelHelper object. 37 | public GetAllPositionsQueryHandler(IPositionRepositoryAsync repository, IModelHelper modelHelper) 38 | { 39 | _repository = repository; 40 | _modelHelper = modelHelper; 41 | } 42 | 43 | /// 44 | /// Handle method that takes in a GetPositionsQuery and CancellationToken and returns a PagedResponse>. 45 | /// 46 | /// GetPositionsQuery object. 47 | /// CancellationToken object. 48 | /// PagedResponse> object. 49 | public async Task>> Handle(GetPositionsQuery request, CancellationToken cancellationToken) 50 | { 51 | var objRequest = request; 52 | // verify request fields are valid field and exist in the DTO 53 | if (!string.IsNullOrEmpty(objRequest.Fields)) 54 | { 55 | //limit to fields in view model 56 | objRequest.Fields = _modelHelper.ValidateModelFields(objRequest.Fields); 57 | } 58 | if (string.IsNullOrEmpty(objRequest.Fields)) 59 | { 60 | //default fields from view model 61 | objRequest.Fields = _modelHelper.GetModelFields(); 62 | } 63 | // verify orderby a valid field and exist in the DTO GetPositionsViewModel 64 | if (!string.IsNullOrEmpty(objRequest.OrderBy)) 65 | { 66 | //limit to fields in view model 67 | objRequest.OrderBy = _modelHelper.ValidateModelFields(objRequest.OrderBy); 68 | } 69 | 70 | // query based on filter 71 | var qryResult = await _repository.GetPositionReponseAsync(objRequest); 72 | var data = qryResult.data; 73 | RecordsCount recordCount = qryResult.recordsCount; 74 | // response wrapper 75 | return new PagedResponse>(data, objRequest.PageNumber, objRequest.PageSize, recordCount); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Queries/GetPositions/GetPositionsViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Queries.GetPositions 2 | { 3 | // ViewModel class for representing positions. 4 | public class GetPositionsViewModel 5 | { 6 | // Unique identifier for each position. 7 | public Guid Id { get; set; } 8 | 9 | // Title of the position (e.g., Senior Developer). 10 | public string PositionTitle { get; set; } 11 | 12 | // Identifier number associated with a specific position. 13 | public string PositionNumber { get; set; } 14 | 15 | // Detailed description of the responsibilities and requirements for this position. 16 | public string PositionDescription { get; set; } 17 | 18 | // Associated department that holds the position. 19 | public virtual Department Department { get; set; } 20 | 21 | // Salary range assigned to the position. 22 | public virtual SalaryRange SalaryRange { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/Positions/Queries/GetPositions/PagedPositionsQuery.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.Positions.Queries.GetPositions 2 | { 3 | public partial class PagedPositionsQuery : QueryParameter, IRequest>> 4 | { 5 | 6 | //strong type input parameters 7 | public int Draw { get; set; } //page number 8 | 9 | public int Start { get; set; } //Paging first record indicator. This is the start point in the current data set (0 index based - i.e. 0 is the first record). 10 | public int Length { get; set; } //page size 11 | public IList Order { get; set; } //Order by 12 | public Search Search { get; set; } //search criteria 13 | public IList Columns { get; set; } //select fields 14 | } 15 | 16 | public class PagePositionQueryHandler : IRequestHandler>> 17 | { 18 | private readonly IPositionRepositoryAsync _repository; 19 | private readonly IModelHelper _modelHelper; 20 | 21 | public PagePositionQueryHandler(IPositionRepositoryAsync repository, IMapper mapper, IModelHelper modelHelper) 22 | { 23 | _repository = repository; 24 | _modelHelper = modelHelper; 25 | } 26 | 27 | public async Task>> Handle(PagedPositionsQuery request, CancellationToken cancellationToken) 28 | { 29 | var objRequest = request; 30 | 31 | // Draw map to PageNumber 32 | objRequest.PageNumber = (request.Start / request.Length) + 1; 33 | // Length map to PageSize 34 | objRequest.PageSize = request.Length; 35 | 36 | // Map order > OrderBy 37 | var colOrder = request.Order[0]; 38 | switch (colOrder.Column) 39 | { 40 | case 0: 41 | objRequest.OrderBy = colOrder.Dir == "asc" ? "PositionNumber" : "PositionNumber DESC"; 42 | break; 43 | 44 | case 1: 45 | objRequest.OrderBy = colOrder.Dir == "asc" ? "PositionTitle" : "PositionTitle DESC"; 46 | break; 47 | 48 | case 2: 49 | objRequest.OrderBy = colOrder.Dir == "asc" ? "Department.Name" : "Department.Name DESC"; 50 | break; 51 | } 52 | 53 | if (string.IsNullOrEmpty(objRequest.Fields)) 54 | { 55 | //default fields from view model 56 | objRequest.Fields = _modelHelper.GetModelFields(); 57 | } 58 | // query based on filter 59 | var qryResult = await _repository.PagedPositionReponseAsync(objRequest); 60 | var data = qryResult.data; 61 | RecordsCount recordCount = qryResult.recordsCount; 62 | 63 | // response wrapper 64 | return new PagedDataTableResponse>(data, request.Draw, recordCount); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/SalaryRanges/Queries/GetSalaryRanges/GetSalaryRangesQuery.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Features.SalaryRanges.Queries.GetSalaryRanges 2 | { 3 | /// 4 | /// GetAllSalaryRangesQuery - handles media IRequest 5 | /// BaseRequestParameter - contains paging parameters 6 | /// To add filter/search parameters, add search properties to the body of this class 7 | /// 8 | public class GetSalaryRangesQuery : ListParameter, IRequest> 9 | { 10 | } 11 | 12 | public class GetAllSalaryRangesQueryHandler : IRequestHandler> 13 | { 14 | private readonly ISalaryRangeRepositoryAsync _repository; 15 | private readonly IModelHelper _modelHelper; 16 | private readonly IMapper _mapper; 17 | 18 | /// 19 | /// Constructor for GetAllSalaryRangesQueryHandler class. 20 | /// 21 | /// ISalaryRangeRepositoryAsync object. 22 | /// IModelHelper object. 23 | /// 24 | /// GetAllSalaryRangesQueryHandler object. 25 | /// 26 | public GetAllSalaryRangesQueryHandler(ISalaryRangeRepositoryAsync repository, IModelHelper modelHelper, IMapper mapper) 27 | { 28 | _repository = repository; 29 | _modelHelper = modelHelper; 30 | _mapper = mapper; 31 | } 32 | 33 | /// 34 | /// Handles the GetSalaryRangesQuery request and returns a PagedResponse containing the requested data. 35 | /// 36 | /// The GetSalaryRangesQuery request. 37 | /// The cancellation token. 38 | /// A PagedResponse containing the requested data. 39 | public async Task> Handle(GetSalaryRangesQuery request, CancellationToken cancellationToken) 40 | { 41 | string fields = _modelHelper.GetModelFields(); 42 | string defaultOrderByColumn = "Name"; 43 | 44 | string orderBy = string.Empty; 45 | 46 | // if the request orderby is not null 47 | if (!string.IsNullOrEmpty(request.OrderBy)) 48 | { 49 | // check to make sure order by field is valid and in the view model 50 | orderBy = _modelHelper.ValidateModelFields(request.OrderBy); 51 | } 52 | 53 | // if the order by is invalid 54 | if (string.IsNullOrEmpty(orderBy)) 55 | { 56 | //default fields from view model 57 | orderBy = defaultOrderByColumn; 58 | } 59 | 60 | var data = await _repository.GetAllShapeAsync(orderBy, fields); 61 | 62 | // automap to ViewModel 63 | var viewModel = _mapper.Map>(data); 64 | 65 | return viewModel; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Apiresources.Application/Features/SalaryRanges/Queries/GetSalaryRanges/GetSalaryRangesViewModel.cs: -------------------------------------------------------------------------------- 1 | // Define a namespace for the feature's queries and views 2 | namespace $safeprojectname$.Features.SalaryRanges.Queries.GetSalaryRanges 3 | { 4 | // Define a view model class to represent the salary range data 5 | public class GetSalaryRangesViewModel 6 | { 7 | // Define properties for the ID, name, minimum and maximum salaries of the salary range 8 | /// 9 | /// The unique identifier for this salary range 10 | /// 11 | public Guid Id { get; set; } 12 | 13 | /// 14 | /// The name of this salary range 15 | /// 16 | public string Name { get; set; } 17 | 18 | /// 19 | /// The minimum salary in this range 20 | /// 21 | public decimal MinSalary { get; set; } 22 | 23 | /// 24 | /// The maximum salary in this range 25 | /// 26 | public decimal MaxSalary { get; set; } 27 | 28 | } 29 | } -------------------------------------------------------------------------------- /Apiresources.Application/Helpers/DataShapeHelper.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Helpers 2 | { 3 | public class DataShapeHelper : IDataShapeHelper 4 | { 5 | // A collection of PropertyInfo objects that represents all the properties of type T. 6 | public PropertyInfo[] Properties { get; set; } 7 | 8 | public DataShapeHelper() 9 | { 10 | // Get all public instance properties of type T. 11 | Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); 12 | } 13 | 14 | // Returns a collection of Entity objects that contain only the specified fields from the given entities. 15 | public IEnumerable ShapeData(IEnumerable entities, string fieldsString) 16 | { 17 | // Get the required properties based on the fieldsString parameter. 18 | var requiredProperties = GetRequiredProperties(fieldsString); 19 | 20 | // Fetch the data and return it as a collection of Entity objects. 21 | return FetchData(entities, requiredProperties); 22 | } 23 | 24 | public async Task> ShapeDataAsync(IEnumerable entities, string fieldsString) 25 | { 26 | // Get the required properties based on the fieldsString parameter. 27 | var requiredProperties = GetRequiredProperties(fieldsString); 28 | 29 | // Fetch the data and return it as a collection of Entity objects using Task.Run(). 30 | return await Task.Run(() => FetchData(entities, requiredProperties)); 31 | } 32 | 33 | // Returns an Entity object that contains only the specified fields from the given entity. 34 | public Entity ShapeData(T entity, string fieldsString) 35 | { 36 | // Get the required properties based on the fieldsString parameter. 37 | var requiredProperties = GetRequiredProperties(fieldsString); 38 | 39 | // Fetch and return the data for a single entity. 40 | return FetchDataForEntity(entity, requiredProperties); 41 | } 42 | 43 | // Returns a collection of PropertyInfo objects that represent the specified fields in type T. 44 | private IEnumerable GetRequiredProperties(string fieldsString) 45 | { 46 | var requiredProperties = new List(); 47 | 48 | if (!string.IsNullOrWhiteSpace(fieldsString)) 49 | { 50 | // Split the fieldsString parameter by commas and remove any empty entries. 51 | var fields = fieldsString.Split(',', StringSplitOptions.RemoveEmptyEntries); 52 | 53 | foreach (var field in fields) 54 | { 55 | // Find the PropertyInfo object that matches the current field name, ignoring case. 56 | var property = Properties.FirstOrDefault(pi => pi.Name.Equals(field.Trim(), StringComparison.InvariantCultureIgnoreCase)); 57 | 58 | if (property == null) 59 | continue; 60 | 61 | requiredProperties.Add(property); 62 | } 63 | } 64 | else 65 | { 66 | // If no fieldsString parameter was provided, return all properties in type T. 67 | requiredProperties = Properties.ToList(); 68 | } 69 | 70 | return requiredProperties; 71 | } 72 | 73 | // Returns a collection of Entity objects that contain only the specified fields from the given entities. 74 | private IEnumerable FetchData(IEnumerable entities, IEnumerable requiredProperties) 75 | { 76 | var shapedData = new List(); 77 | 78 | foreach (var entity in entities) 79 | { 80 | // Fetch and add the data for each entity to the shapedData collection. 81 | var shapedObject = FetchDataForEntity(entity, requiredProperties); 82 | shapedData.Add(shapedObject); 83 | } 84 | 85 | return shapedData; 86 | } 87 | 88 | // Returns an Entity object that contains only the specified fields from the given entity. 89 | private Entity FetchDataForEntity(T entity, IEnumerable requiredProperties) 90 | { 91 | var shapedObject = new Entity(); 92 | 93 | foreach (var property in requiredProperties) 94 | { 95 | // Get the value of the current property from the entity and add it to the shapedObject. 96 | var objectPropertyValue = property.GetValue(entity); 97 | shapedObject.TryAdd(property.Name, objectPropertyValue); 98 | } 99 | 100 | return shapedObject; 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /Apiresources.Application/Helpers/ModelHelper.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Helpers 2 | { 3 | public class ModelHelper : IModelHelper 4 | { 5 | /// 6 | /// Checks if specified field names exist in a model class and returns them as a comma-separated string. 7 | /// 8 | /// The type of the model class. 9 | /// A comma-separated string containing the field names to check. 10 | /// A comma-separated string containing only the field names that exist in the model class. 11 | public string ValidateModelFields(string fields) 12 | { 13 | // Initialize an empty string to store the return value. 14 | string retString = string.Empty; 15 | 16 | // Define binding flags to include instance, non-public, and public properties of the model class. 17 | var bindingFlags = System.Reflection.BindingFlags.Instance | 18 | System.Reflection.BindingFlags.NonPublic | 19 | System.Reflection.BindingFlags.Public; 20 | 21 | // Get a list of property names from the model class using the specified binding flags. 22 | var listFields = typeof(T).GetProperties(bindingFlags).Select(f => f.Name).ToList(); 23 | 24 | // Split the input fields string into an array. 25 | string[] arrayFields = fields.Split(','); 26 | 27 | // Iterate through each field in the array. 28 | foreach (var field in arrayFields) 29 | { 30 | // Trim any leading or trailing whitespace from the field name and check if it exists in the list of model properties. 31 | if (listFields.Contains(field.Trim(), StringComparer.OrdinalIgnoreCase)) 32 | retString += field + ","; // If the field exists, add it to the return string. 33 | }; 34 | 35 | // Return the comma-separated string containing only the existing fields. 36 | return retString; 37 | } 38 | 39 | /// 40 | /// Returns a comma-separated string containing all of the property names in a model class. 41 | /// 42 | /// The type of the model class. 43 | /// A comma-separated string containing the property names of the model class. 44 | public string GetModelFields() 45 | { 46 | // Initialize an empty string to store the return value. 47 | string retString = string.Empty; 48 | 49 | // Define binding flags to include instance, non-public, and public properties of the model class. 50 | var bindingFlags = System.Reflection.BindingFlags.Instance | 51 | System.Reflection.BindingFlags.NonPublic | 52 | System.Reflection.BindingFlags.Public; 53 | 54 | // Get a list of property names from the model class using the specified binding flags. 55 | var listFields = typeof(T).GetProperties(bindingFlags).Select(f => f.Name).ToList(); 56 | 57 | // Iterate through each field in the list and add it to the return string. 58 | foreach (string field in listFields) 59 | { 60 | retString += field + ","; 61 | } 62 | 63 | // Return the comma-separated string containing all of the fields. 64 | return retString; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/IDataShapeHelper.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces 2 | { 3 | /// 4 | /// Interface for data shape helper that provides methods to shape data based on provided fields. 5 | /// 6 | public interface IDataShapeHelper 7 | { 8 | /// 9 | /// Shapes the data for a collection of entities based on the provided fields string. 10 | /// 11 | /// The collection of entities to shape. 12 | /// A comma-separated string representing the fields to include in the shaped data. 13 | /// An IEnumerable of Entity objects that represent the shaped data. 14 | IEnumerable ShapeData(IEnumerable entities, string fieldsString); 15 | 16 | /// 17 | /// Asynchronously shapes the data for a collection of entities based on the provided fields string. 18 | /// 19 | /// The collection of entities to shape. 20 | /// A comma-separated string representing the fields to include in the shaped data. 21 | /// A Task that will return an IEnumerable of Entity objects that represent the shaped data. 22 | Task> ShapeDataAsync(IEnumerable entities, string fieldsString); 23 | 24 | /// 25 | /// Shapes the data for a single entity based on the provided fields string. 26 | /// 27 | /// The entity to shape. 28 | /// A comma-separated string representing the fields to include in the shaped data. 29 | /// An Entity object that represents the shaped data. 30 | Entity ShapeData(T entity, string fieldsString); 31 | } 32 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/IDateTimeService.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces 2 | { 3 | // Defines an interface for a service that provides access to the current UTC date and time. 4 | public interface IDateTimeService 5 | { 6 | // Gets the current UTC date and time. 7 | DateTime NowUtc { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/IEmailService.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces 2 | { 3 | // Define an interface for sending email messages. 4 | public interface IEmailService 5 | { 6 | // Asynchronously send an email message using the provided request object. 7 | Task SendAsync(EmailRequest request); 8 | } 9 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/IGenericRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces 2 | { 3 | /// 4 | /// Interface for a generic repository that can handle CRUD operations and more on any entity of type T. 5 | /// 6 | public interface IGenericRepositoryAsync where T : class 7 | { 8 | /// 9 | /// Retrieves an entity with the given ID from the database asynchronously. 10 | /// 11 | /// The ID of the entity to retrieve. 12 | /// A task that represents the asynchronous operation and returns the retrieved entity. 13 | Task GetByIdAsync(Guid id); 14 | 15 | /// 16 | /// Retrieves all entities from the database asynchronously. 17 | /// 18 | /// A task that represents the asynchronous operation and returns a collection of all entities. 19 | Task> GetAllAsync(); 20 | 21 | /// 22 | /// Adds a new entity to the database asynchronously. 23 | /// 24 | /// The entity to add. 25 | /// A task that represents the asynchronous operation and returns the added entity. 26 | Task AddAsync(T entity); 27 | 28 | /// 29 | /// Updates an existing entity in the database asynchronously. 30 | /// 31 | /// The updated entity. 32 | /// A task that represents the asynchronous operation. 33 | Task UpdateAsync(T entity); 34 | 35 | /// 36 | /// Deletes an entity from the database asynchronously. 37 | /// 38 | /// The entity to delete. 39 | /// A task that represents the asynchronous operation. 40 | Task DeleteAsync(T entity); 41 | 42 | /// 43 | /// Inserts a collection of entities into the database asynchronously. 44 | /// 45 | /// The collection of entities to insert. 46 | /// A task that represents the asynchronous operation. 47 | Task BulkInsertAsync(IEnumerable entities); 48 | 49 | /// 50 | /// Retrieves a paged response of all entities from the database asynchronously. 51 | /// 52 | /// The page number to retrieve. 53 | /// The size of each page. 54 | /// A task that represents the asynchronous operation and returns a collection of entities on the specified page. 55 | Task> GetPagedReponseAsync(int pageNumber, int pageSize); 56 | 57 | /// 58 | /// Retrieves a paged response of all entities from the database asynchronously with advanced filtering, sorting, and projection options. 59 | /// 60 | /// The page number to retrieve. 61 | /// The size of each page. 62 | /// The field or fields by which to sort the results. 63 | /// The fields to include in the projection of the results. 64 | /// An expression representing a filter condition for the results. 65 | /// A task that represents the asynchronous operation and returns a collection of entities on the specified page that meet the filter criteria. 66 | Task> GetPagedAdvancedReponseAsync(int pageNumber, int pageSize, string orderBy, string fields, ExpressionStarter predicate); 67 | 68 | /// 69 | /// Retrieves all entities from the database asynchronously with advanced sorting and projection options. 70 | /// 71 | /// The field or fields by which to sort the results. 72 | /// The fields to include in the projection of the results. 73 | /// A task that represents the asynchronous operation and returns a collection of all entities with the specified sorting and projection options. 74 | Task> GetAllShapeAsync(string orderBy, string fields); 75 | } 76 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/IMockService.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces 2 | { 3 | /// 4 | /// Defines a mock service for generating data. 5 | /// 6 | public interface IMockService 7 | { 8 | /// 9 | /// Generates positions based on the provided parameters. 10 | /// 11 | /// Number of rows to generate. 12 | /// Departments to include in generated data. 13 | /// Salary ranges to include in generated data. 14 | /// A list of positions. 15 | List GetPositions(int rowCount, IEnumerable departments, IEnumerable salaryRanges); 16 | 17 | /// 18 | /// Generates employees based on the provided parameters. 19 | /// 20 | /// Number of rows to generate. 21 | /// A list of employees. 22 | List GetEmployees(int rowCount); 23 | 24 | /// 25 | /// Seeds positions with predefined data. 26 | /// 27 | /// Number of rows to seed. 28 | /// A list of seeded positions. 29 | List SeedPositions(int rowCount); 30 | } 31 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/IModelHelper.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces 2 | { 3 | /// 4 | /// Interface for model helper. 5 | /// 6 | public interface IModelHelper 7 | { 8 | /// 9 | /// Gets the fields of a model. 10 | /// 11 | /// The type of the model. 12 | /// The fields of the model as a string. 13 | string GetModelFields(); 14 | 15 | /// 16 | /// Validates the fields of a model. 17 | /// 18 | /// The type of the model. 19 | /// The fields to validate as a string. 20 | /// A validation message if there are any errors, or null if all fields are valid. 21 | string ValidateModelFields(string fields); 22 | } 23 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/Repositories/IDepartmentRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | // Defines an asynchronous repository interface for the Department entity 2 | namespace $safeprojectname$.Interfaces.Repositories 3 | { 4 | public interface IDepartmentRepositoryAsync : IGenericRepositoryAsync 5 | { 6 | // Methods inherited from IGenericRepositoryAsync will be available here, such as AddAsync, UpdateAsync, and DeleteAsync. 7 | } 8 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/Repositories/IEmployeeRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces.Repositories 2 | { 3 | /// 4 | /// Interface for retrieving paged employee response asynchronously. 5 | /// 6 | public interface IEmployeeRepositoryAsync : IGenericRepositoryAsync 7 | { 8 | /// 9 | /// Retrieves a list of employees based on the provided query parameters asynchronously. 10 | /// 11 | /// The request parameters. 12 | /// A task that represents the asynchronous operation and returns a tuple containing the list of employees and the total number of records. 13 | Task<(IEnumerable data, RecordsCount recordsCount)> GetEmployeeResponseAsync(GetEmployeesQuery requestParameters); 14 | 15 | /// 16 | /// Retrieves a paged list of employees based on the provided query parameters asynchronously. 17 | /// 18 | /// The request parameters. 19 | /// A task that represents the asynchronous operation and returns a tuple containing the paged list of employees and the total number of records. 20 | Task<(IEnumerable data, RecordsCount recordsCount)> GetPagedEmployeeResponseAsync(PagedEmployeesQuery requestParameters); 21 | } 22 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/Repositories/IPositionRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces.Repositories 2 | { 3 | /// 4 | /// Repository interface for Position entity with asynchronous methods. 5 | /// 6 | public interface IPositionRepositoryAsync : IGenericRepositoryAsync 7 | { 8 | /// 9 | /// Checks if the given position number is unique in the database. 10 | /// 11 | /// Position number to check for uniqueness. 12 | /// 13 | /// Task indicating whether the position number is unique. 14 | /// 15 | Task IsUniquePositionNumberAsync(string positionNumber); 16 | 17 | /// 18 | /// Seeds initial data into the Position repository. The seed operation will create a specified 19 | /// number of records in the database, each with a randomly generated name and birthdate. 20 | /// 21 | /// Number of rows to seed. 22 | /// 23 | /// Task indicating the completion of seeding. 24 | /// 25 | Task SeedDataAsync(int rowCount, IEnumerable departments, IEnumerable salaryRanges); 26 | 27 | /// 28 | /// Retrieves a list of Position records based on the provided query parameters. 29 | /// 30 | /// Parameters for the query. 31 | /// 32 | /// Task containing the list of Position records and the total number of records found. 33 | /// 34 | Task<(IEnumerable data, RecordsCount recordsCount)> GetPositionReponseAsync(GetPositionsQuery requestParameters); 35 | 36 | /// 37 | /// Retrieves a paged list of Position records based on the provided query parameters. 38 | /// 39 | /// Parameters for the query. 40 | /// 41 | /// Task containing the paged list of Position records and the total number of records found. 42 | /// 43 | Task<(IEnumerable data, RecordsCount recordsCount)> PagedPositionReponseAsync(PagedPositionsQuery requestParameters); 44 | } 45 | } -------------------------------------------------------------------------------- /Apiresources.Application/Interfaces/Repositories/ISalaryRangeRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Interfaces.Repositories 2 | { 3 | /// 4 | /// Represents a repository for performing asynchronous operations on salary ranges. 5 | /// 6 | public interface ISalaryRangeRepositoryAsync : IGenericRepositoryAsync 7 | { 8 | // Interface members are defined in the base interface, which is IGenericRepositoryAsync 9 | } 10 | } -------------------------------------------------------------------------------- /Apiresources.Application/Mappings/GeneralProfile.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Mappings 2 | { 3 | // Defines a mapping profile for general mappings between entities and view models. 4 | public class GeneralProfile : Profile 5 | { 6 | // Initializes a new instance of the GeneralProfile class. 7 | public GeneralProfile() 8 | { 9 | // Maps an Employee entity to a GetEmployeesViewModel, and vice versa. 10 | CreateMap().ReverseMap(); 11 | 12 | // Maps a Position entity to a GetPositionsViewModel, and vice versa. 13 | CreateMap().ReverseMap(); 14 | // Maps a Department entity to a GetDepartmentsViewModel, and vice versa. 15 | CreateMap().ReverseMap(); 16 | 17 | // Maps a SalaryRange entity to a GetSalaryRangesViewModel, and vice versa. 18 | CreateMap().ReverseMap(); 19 | // Maps a CreatePositionCommand to a Position entity. 20 | CreateMap(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Apiresources.Application/Parameters/Column.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Parameters 2 | { 3 | public class Column // Represents a column in a table or grid that can be sorted and filtered. 4 | { 5 | public string Data { get; set; } // The data value to display in this column. This could be the name of a field in a database table, for example. 6 | 7 | public string Name { get; set; } // A human-readable name for this column. This will be displayed in the user interface. 8 | 9 | public bool Searchable { get; set; } // Indicates whether the user can search for values in this column. If true, the application should provide a search box or other mechanism to allow users to filter the data based on the values in this column. 10 | 11 | public bool Orderable { get; set; } // Indicates whether the user can sort the data in this column. If true, the application should provide a way for users to click on the column header to sort the data in ascending or descending order. 12 | 13 | public Search Search { get; set; } // An object that represents any search criteria applied to this column. This could be used to filter the data based on specific values or ranges of values. 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Apiresources.Application/Parameters/ListParameter.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Parameters 2 | { 3 | // Represents a parameter for filtering and sorting lists of data. 4 | public class ListParameter 5 | { 6 | // Gets or sets the name of the property to use when ordering the list. 7 | public virtual string OrderBy { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Apiresources.Application/Parameters/PagingParameter.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Parameters 2 | { 3 | // Defines a class for paging parameters that can be used to control how data is paginated. 4 | public class PagingParameter 5 | { 6 | // Maximum page size allowed (set to 200). 7 | private const int maxPageSize = 200; 8 | 9 | // Gets or sets the current page number, defaulting to 1 if not provided. 10 | public int PageNumber { get; set; } = 1; 11 | 12 | // Gets or sets the current page size. If a value greater than the maximum page size is provided, 13 | // it will be limited to the maximum page size instead of throwing an error. 14 | private int _pageSize = 10; 15 | public int PageSize 16 | { 17 | get { return _pageSize; } 18 | set { _pageSize = (value > maxPageSize) ? maxPageSize : value; } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Apiresources.Application/Parameters/QueryParameter.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Parameters 2 | { 3 | /// 4 | /// Represents a query parameter for filtering and sorting data. 5 | /// Inherits from PagingParameter to include pagination properties. 6 | /// 7 | public class QueryParameter : PagingParameter 8 | { 9 | /// 10 | /// Gets or sets the field name(s) to order the results by. 11 | /// 12 | public virtual string OrderBy { get; set; } 13 | 14 | /// 15 | /// Gets or sets the fields to include in the query results. 16 | /// 17 | public virtual string Fields { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /Apiresources.Application/Parameters/RecordsCount.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Parameters 2 | { 3 | /// 4 | /// Represents a class containing records count information. 5 | /// 6 | public class RecordsCount 7 | { 8 | /// 9 | /// Gets or sets the number of filtered records. 10 | /// 11 | public int RecordsFiltered { get; set; } 12 | /// 13 | /// Gets or sets the total number of records. 14 | /// 15 | public int RecordsTotal { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /Apiresources.Application/Parameters/Search.cs: -------------------------------------------------------------------------------- 1 | // This namespace contains classes and other elements related to project parameters. 2 | namespace $safeprojectname$.Parameters 3 | { 4 | // The Search class represents search-related parameters. 5 | public class Search 6 | { 7 | // The Value property is a string representing the value of the search query. 8 | public string Value { get; set; } 9 | 10 | // The Regex property is a boolean indicating whether to use regular expressions for the search. 11 | public bool Regex { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Apiresources.Application/Parameters/SortOrder.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Parameters 2 | { 3 | // Represents the sort order for a column in a data grid. 4 | public class SortOrder 5 | { 6 | // The index of the column to sort. 7 | public int Column { get; set; } 8 | 9 | // The direction to sort, either "asc" or "desc". 10 | public string Dir { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Apiresources.Application/ServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using $safeprojectname$.Behaviours; 2 | using $safeprojectname$.Helpers; 3 | using $safeprojectname$.Interfaces; 4 | using $ext_projectname$.Domain.Entities; 5 | using FluentValidation; 6 | using MediatR; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using System.Reflection; 9 | 10 | namespace $safeprojectname$ 11 | { 12 | public static class ServiceExtensions 13 | { 14 | public static void AddApplicationLayer(this IServiceCollection services) 15 | { 16 | services.AddAutoMapper(Assembly.GetExecutingAssembly()); 17 | services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); 18 | services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(Assembly.GetExecutingAssembly())); 19 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); 20 | services.AddScoped, DataShapeHelper>(); 21 | services.AddScoped, DataShapeHelper>(); 22 | services.AddScoped(); 23 | // * use Scutor to register generic IDataShapeHelper interface for DI and specifying the lifetime of dependencies 24 | services.Scan(selector => selector 25 | .FromCallingAssembly() 26 | .AddClasses(classSelector => classSelector.AssignableTo(typeof(IDataShapeHelper<>))) 27 | .AsImplementedInterfaces() 28 | .WithTransientLifetime()); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Apiresources.Application/Using.cs: -------------------------------------------------------------------------------- 1 | global using AutoMapper; 2 | global using FluentValidation; 3 | global using FluentValidation.Results; 4 | global using LinqKit; 5 | global using MediatR; 6 | global using Microsoft.Extensions.DependencyInjection; 7 | global using $safeprojectname$.Behaviours; 8 | global using $safeprojectname$.DTOs.Email; 9 | global using $safeprojectname$.Exceptions; 10 | global using $safeprojectname$.Features.Departments.Queries.GetDepartments; 11 | global using $safeprojectname$.Features.Employees.Queries.GetEmployees; 12 | global using $safeprojectname$.Features.Positions.Commands.CreatePosition; 13 | global using $safeprojectname$.Features.Positions.Commands.UpdatePosition; 14 | global using $safeprojectname$.Features.Positions.Queries.GetPositions; 15 | global using $safeprojectname$.Features.SalaryRanges.Queries.GetSalaryRanges; 16 | global using $safeprojectname$.Helpers; 17 | global using $safeprojectname$.Interfaces; 18 | global using $safeprojectname$.Interfaces.Repositories; 19 | global using $safeprojectname$.Parameters; 20 | global using $safeprojectname$.Wrappers; 21 | global using $ext_projectname$.Domain.Entities; 22 | global using $ext_projectname$.Domain.Enums; 23 | global using System; 24 | global using System.Collections.Generic; 25 | global using System.Globalization; 26 | global using System.Linq; 27 | global using System.Reflection; 28 | global using System.Threading; 29 | global using System.Threading.Tasks; -------------------------------------------------------------------------------- /Apiresources.Application/Wrappers/PagedDataTableResponse.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Wrappers 2 | { 3 | // A response object that includes data and paged information for a DataTable. 4 | public class PagedDataTableResponse : Response 5 | { 6 | // The number of times the request has been processed. 7 | public int Draw { get; set; } 8 | 9 | // The number of records filtered based on search criteria. 10 | public int RecordsFiltered { get; set; } 11 | 12 | // The total number of records in the table before filtering. 13 | public int RecordsTotal { get; set; } 14 | 15 | // Constructor for PagedDataTableResponse object. Initializes the response with data, page number, and record counts. 16 | public PagedDataTableResponse(T data, int pageNumber, RecordsCount recordsCount) 17 | { 18 | this.Draw = pageNumber; 19 | this.RecordsFiltered = recordsCount.RecordsFiltered; 20 | this.RecordsTotal = recordsCount.RecordsTotal; 21 | this.Data = data; 22 | this.Message = null; 23 | this.Succeeded = true; 24 | this.Errors = null; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Apiresources.Application/Wrappers/PagedResponse.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Wrappers 2 | { 3 | // PagedResponse class that inherits from Response class and represents a paged response with data and pagination information. 4 | public class PagedResponse : Response 5 | { 6 | // Gets or sets the current page number of the response. 7 | public virtual int PageNumber { get; set; } 8 | 9 | // Gets the size of each page in the response. 10 | public int PageSize { get; set; } 11 | 12 | // Gets the total number of records that were filtered based on some criteria. 13 | public int RecordsFiltered { get; set; } 14 | 15 | // Gets the total number of records available before any filtering was applied. 16 | public int RecordsTotal { get; set; } 17 | 18 | // Initializes a new instance of the PagedResponse class with the specified data and pagination information. 19 | public PagedResponse(T data, int pageNumber, int pageSize, RecordsCount recordsCount) 20 | { 21 | this.PageNumber = pageNumber; 22 | this.PageSize = pageSize; 23 | this.RecordsFiltered = recordsCount.RecordsFiltered; 24 | this.RecordsTotal = recordsCount.RecordsTotal; 25 | this.Data = data; 26 | this.Message = null; 27 | this.Succeeded = true; 28 | this.Errors = null; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Apiresources.Application/Wrappers/Response.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Wrappers 2 | { 3 | public class Response 4 | { 5 | /// 6 | /// Initializes a new instance of the Response class. 7 | /// 8 | public Response() 9 | { 10 | } 11 | 12 | /// 13 | /// Initializes a new instance of the Response class with the specified data and message. 14 | /// 15 | /// The data to return in the response. 16 | /// A message to accompany the data, if any. 17 | public Response(T data, string message = null) 18 | { 19 | Succeeded = true; 20 | Message = message; 21 | Data = data; 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the Response class with the specified error message. 26 | /// 27 | /// The error message to return in the response. 28 | public Response(string message) 29 | { 30 | Succeeded = false; 31 | Message = message; 32 | } 33 | 34 | /// 35 | /// Indicates whether the operation was successful or not. 36 | /// 37 | public bool Succeeded { get; set; } 38 | 39 | /// 40 | /// A message to accompany the response, if any. 41 | /// 42 | public string Message { get; set; } 43 | 44 | /// 45 | /// A list of error messages, if any. 46 | /// 47 | public List Errors { get; set; } 48 | 49 | /// 50 | /// The data returned in the response. 51 | /// 52 | public T Data { get; set; } 53 | } 54 | } -------------------------------------------------------------------------------- /Apiresources.Application/__TemplateIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workcontrolgit/TemplateOnionAPI/a3c83a62548301165a685de06aafda3b40d9b169/Apiresources.Application/__TemplateIcon.ico -------------------------------------------------------------------------------- /Apiresources.Domain/Apiresources.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | 1.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Apiresources.Domain/Common/AuditableBaseEntity.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Common 2 | { 3 | // Abstract base class for entities that support auditing 4 | public abstract class AuditableBaseEntity : BaseEntity 5 | { 6 | // The username of the user who created this entity 7 | public string CreatedBy { get; set; } 8 | // The timestamp when this entity was created 9 | public DateTime Created { get; set; } 10 | // The username of the user who last modified this entity (if applicable) 11 | public string LastModifiedBy { get; set; } 12 | // The timestamp when this entity was last modified (if applicable) 13 | public DateTime? LastModified { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Apiresources.Domain/Common/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Common 2 | { 3 | /// 4 | /// Base class for all entities that have an ID property. 5 | /// 6 | public abstract class BaseEntity 7 | { 8 | /// 9 | /// Unique identifier for this entity. 10 | /// 11 | public virtual Guid Id { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Apiresources.Domain/Entities/Department.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Entities 2 | { 3 | // Represents a department in the organization. 4 | public class Department : AuditableBaseEntity 5 | { 6 | // Gets or sets the name of the department. 7 | public string Name { get; set; } 8 | 9 | // Navigation property for related positions. 10 | // This property allows for easy access to all positions associated with this department. 11 | public virtual ICollection Positions { get; set; } 12 | 13 | // Initializes a new instance of the Department class, creating an empty collection for positions. 14 | public Department() 15 | { 16 | Positions = new HashSet(); 17 | } 18 | 19 | // Additional properties (e.g., Name, ManagerId, etc.) can be added here 20 | } 21 | } -------------------------------------------------------------------------------- /Apiresources.Domain/Entities/Employee.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Entities 2 | { 3 | public class Employee : AuditableBaseEntity // Inheriting from a base entity that includes audit information such as created/modified dates and user IDs. 4 | { 5 | public string FirstName { get; set; } // The first name of the employee. 6 | public string MiddleName { get; set; } // The middle name of the employee, if applicable. 7 | public string LastName { get; set; } // The last name of the employee. 8 | // Foreign Key for Position 9 | public Guid PositionId { get; set; } // A unique identifier for the position that the employee holds. 10 | // Navigation Property for Position 11 | public virtual Position Position { get; set; } // A reference to the Position entity that the employee is associated with. This allows you to retrieve information about the employee's position without having to make additional database queries. 12 | // Salary of the Employee 13 | public decimal Salary { get; set; } // The monthly salary of the employee. 14 | 15 | public DateTime Birthday { get; set; } // The date of birth for the employee. 16 | public string Email { get; set; } // The email address for the employee. 17 | public Gender Gender { get; set; } // An enumeration representing the gender of the employee. 18 | public string EmployeeNumber { get; set; } // A unique identifier for the employee within your organization. 19 | public string Prefix { get; set; } // Any prefixes or titles that should be displayed before the employee's name, such as "Dr." or "Mr." 20 | public string Phone { get; set; } // The phone number for the employee. 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Apiresources.Domain/Entities/Entity.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Entities 2 | { 3 | public class Entity : DynamicObject, IXmlSerializable, IDictionary 4 | { 5 | // Private member variables for root name and expando dictionary 6 | private readonly string _root = "Entity"; 7 | private readonly IDictionary _expando = null; 8 | 9 | // Constructor initializes expando dictionary 10 | public Entity() 11 | { 12 | _expando = new ExpandoObject(); 13 | } 14 | 15 | // Override TryGetMember to try getting member value from expando dictionary 16 | public override bool TryGetMember(GetMemberBinder binder, out object result) 17 | { 18 | if (_expando.TryGetValue(binder.Name, out object value)) 19 | { 20 | result = value; 21 | return true; 22 | } 23 | 24 | return base.TryGetMember(binder, out result); 25 | } 26 | 27 | // Override TrySetMember to try setting member value in expando dictionary 28 | public override bool TrySetMember(SetMemberBinder binder, object value) 29 | { 30 | _expando[binder.Name] = value; 31 | 32 | return true; 33 | } 34 | 35 | // Implement GetSchema method for IXmlSerializable interface (not implemented) 36 | public XmlSchema GetSchema() 37 | { 38 | throw new NotImplementedException(); 39 | } 40 | 41 | // Implement ReadXml method for IXmlSerializable interface 42 | public void ReadXml(XmlReader reader) 43 | { 44 | reader.ReadStartElement(_root); 45 | 46 | while (!reader.Name.Equals(_root)) 47 | { 48 | string typeContent; 49 | Type underlyingType; 50 | var name = reader.Name; 51 | 52 | reader.MoveToAttribute("type"); 53 | typeContent = reader.ReadContentAsString(); 54 | underlyingType = Type.GetType(typeContent); 55 | reader.MoveToContent(); 56 | _expando[name] = reader.ReadElementContentAs(underlyingType, null); 57 | } 58 | } 59 | 60 | // Implement WriteXml method for IXmlSerializable interface 61 | public void WriteXml(XmlWriter writer) 62 | { 63 | foreach (var key in _expando.Keys) 64 | { 65 | var value = _expando[key]; 66 | WriteLinksToXml(key, value, writer); 67 | } 68 | } 69 | 70 | // Helper method to write links to XML 71 | private void WriteLinksToXml(string key, object value, XmlWriter writer) 72 | { 73 | writer.WriteStartElement(key); 74 | writer.WriteString(value.ToString()); 75 | writer.WriteEndElement(); 76 | } 77 | 78 | // Implement Add method for IDictionary interface 79 | public void Add(string key, object value) 80 | { 81 | _expando.Add(key, value); 82 | } 83 | 84 | // Implement ContainsKey method for IDictionary interface 85 | public bool ContainsKey(string key) 86 | { 87 | return _expando.ContainsKey(key); 88 | } 89 | 90 | // Implement Keys property for IDictionary interface 91 | public ICollection Keys 92 | { 93 | get { return _expando.Keys; } 94 | } 95 | 96 | // Implement Remove method for IDictionary interface 97 | public bool Remove(string key) 98 | { 99 | return _expando.Remove(key); 100 | } 101 | 102 | // Implement TryGetValue method for IDictionary interface 103 | public bool TryGetValue(string key, out object value) 104 | { 105 | return _expando.TryGetValue(key, out value); 106 | } 107 | 108 | // Implement Values property for IDictionary interface 109 | public ICollection Values 110 | { 111 | get { return _expando.Values; } 112 | } 113 | 114 | // Implement indexer for IDictionary interface 115 | public object this[string key] 116 | { 117 | get 118 | { 119 | return _expando[key]; 120 | } 121 | set 122 | { 123 | _expando[key] = value; 124 | } 125 | } 126 | 127 | // Implement Add method for ICollection> interface 128 | public void Add(KeyValuePair item) 129 | { 130 | _expando.Add(item); 131 | } 132 | 133 | // Implement Clear method for ICollection> interface 134 | public void Clear() 135 | { 136 | _expando.Clear(); 137 | } 138 | 139 | // Implement Contains method for ICollection> interface 140 | public bool Contains(KeyValuePair item) 141 | { 142 | return _expando.Contains(item); 143 | } 144 | 145 | // Implement CopyTo method for ICollection> interface 146 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 147 | { 148 | _expando.CopyTo(array, arrayIndex); 149 | } 150 | 151 | // Implement Count property for ICollection> interface 152 | public int Count 153 | { 154 | get { return _expando.Count; } 155 | } 156 | 157 | // Implement IsReadOnly property for ICollection> interface 158 | public bool IsReadOnly 159 | { 160 | get { return _expando.IsReadOnly; } 161 | } 162 | 163 | // Implement Remove method for ICollection> interface 164 | public bool Remove(KeyValuePair item) 165 | { 166 | return _expando.Remove(item); 167 | } 168 | 169 | // Implement GetEnumerator method for IEnumerable> interface 170 | public IEnumerator> GetEnumerator() 171 | { 172 | return _expando.GetEnumerator(); 173 | } 174 | 175 | // Implement GetEnumerator method for IEnumerable interface 176 | IEnumerator IEnumerable.GetEnumerator() 177 | { 178 | return GetEnumerator(); 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /Apiresources.Domain/Entities/Position.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Entities 2 | { 3 | public class Position : AuditableBaseEntity 4 | { 5 | // Property representing the title of the position. 6 | public string PositionTitle { get; set; } 7 | 8 | // Property representing the number of the position. 9 | public string PositionNumber { get; set; } 10 | 11 | // Property representing a description of the position. 12 | public string PositionDescription { get; set; } 13 | 14 | // Foreign key property for the associated Department entity. 15 | public Guid DepartmentId { get; set; } 16 | 17 | // Navigation property for the associated Department entity. 18 | public virtual Department Department { get; set; } 19 | 20 | // Navigation property for related Employee entities. 21 | public virtual ICollection Employees { get; set; } 22 | 23 | // Foreign key property for the associated SalaryRange entity. 24 | public Guid SalaryRangeId { get; set; } 25 | 26 | // Navigation property for the associated SalaryRange entity. 27 | public virtual SalaryRange SalaryRange { get; set; } 28 | 29 | // Default constructor that initializes the Employees collection. 30 | public Position() 31 | { 32 | Employees = new HashSet(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Apiresources.Domain/Entities/SalaryRange.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Entities 2 | { 3 | // Represents a salary range for a position 4 | public class SalaryRange : AuditableBaseEntity 5 | { 6 | // Minimum salary value within this range 7 | public decimal MinSalary { get; set; } 8 | 9 | // Maximum salary value within this range 10 | public decimal MaxSalary { get; set; } 11 | 12 | // Name or additional details about the salary range 13 | public string Name { get; set; } 14 | 15 | // Navigation property back to Position, indicating which positions fall within this salary range 16 | public virtual ICollection Positions { get; set; } 17 | 18 | // Initializes a new instance of the SalaryRange class with an empty collection of Positions 19 | public SalaryRange() 20 | { 21 | Positions = new HashSet(); 22 | } 23 | 24 | // Additional properties or methods can be added here to further define the behavior and functionality of this entity 25 | } 26 | } -------------------------------------------------------------------------------- /Apiresources.Domain/Enums/Gender.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Enums 2 | { 3 | /// 4 | /// Represents the gender of a person. 5 | /// 6 | public enum Gender 7 | { 8 | /// 9 | /// Indicates that the person is male. 10 | /// 11 | Male, 12 | 13 | /// 14 | /// Indicates that the person is female. 15 | /// 16 | Female 17 | } 18 | } -------------------------------------------------------------------------------- /Apiresources.Domain/MyTemplate.vstemplate: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apiresources.Domain 4 | for entities and the common models 5 | CSharp 6 | 7 | 8 | 1000 9 | true 10 | Apiresources.Domain 11 | true 12 | Enabled 13 | true 14 | true 15 | __TemplateIcon.ico 16 | 17 | 18 | 19 | 20 | AuditableBaseEntity.cs 21 | BaseEntity.cs 22 | 23 | 24 | Department.cs 25 | Employee.cs 26 | Entity.cs 27 | Position.cs 28 | SalaryRange.cs 29 | 30 | 31 | Gender.cs 32 | 33 | 34 | JWTSettings.cs 35 | MailSettings.cs 36 | 37 | Using.cs 38 | 39 | 40 | -------------------------------------------------------------------------------- /Apiresources.Domain/Settings/JWTSettings.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Settings 2 | { 3 | /// 4 | /// Represents settings for JWT (JSON Web Token) authentication. 5 | /// 6 | public class JWTSettings 7 | { 8 | /// 9 | /// Gets or sets the key used to sign the token. 10 | /// 11 | public string Key { get; set; } 12 | 13 | /// 14 | /// Gets or sets the issuer of the token (i.e., the entity that created it). 15 | /// 16 | public string Issuer { get; set; } 17 | 18 | /// 19 | /// Gets or sets the audience for the token (i.e., the entities that are allowed to use it). 20 | /// 21 | public string Audience { get; set; } 22 | 23 | /// 24 | /// Gets or sets the duration of the token in minutes. 25 | /// 26 | public double DurationInMinutes { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /Apiresources.Domain/Settings/MailSettings.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Settings 2 | { 3 | // Represents mail settings for an application. 4 | public class MailSettings 5 | { 6 | // Email address to send mail from. 7 | public string EmailFrom { get; set; } 8 | 9 | // SMTP server host name or IP address. 10 | public string SmtpHost { get; set; } 11 | 12 | // SMTP server port number. 13 | public int SmtpPort { get; set; } 14 | 15 | // Username for authentication with SMTP server. 16 | public string SmtpUser { get; set; } 17 | 18 | // Password for authentication with SMTP server. 19 | public string SmtpPass { get; set; } 20 | 21 | // Display name to appear in email headers. 22 | public string DisplayName { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /Apiresources.Domain/Using.cs: -------------------------------------------------------------------------------- 1 | global using $safeprojectname$.Common; 2 | global using $safeprojectname$.Enums; 3 | global using System; 4 | global using System.Collections; 5 | global using System.Collections.Generic; 6 | global using System.Dynamic; 7 | global using System.Xml; 8 | global using System.Xml.Schema; 9 | global using System.Xml.Serialization; -------------------------------------------------------------------------------- /Apiresources.Domain/__TemplateIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workcontrolgit/TemplateOnionAPI/a3c83a62548301165a685de06aafda3b40d9b169/Apiresources.Domain/__TemplateIcon.ico -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Apiresources.Infrastructure.Persistence.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 1.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Contexts/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Contexts 2 | { 3 | public partial class ApplicationDbContext : DbContext 4 | { 5 | private readonly IDateTimeService _dateTime; // Service for getting current date and time 6 | private readonly ILoggerFactory _loggerFactory; // Factory for creating logger instances 7 | 8 | public ApplicationDbContext(DbContextOptions options, 9 | IDateTimeService dateTime, // Injected service for getting current date and time 10 | ILoggerFactory loggerFactory // Injected factory for creating logger instances 11 | ) : base(options) 12 | { 13 | ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; // Disable tracking of query results in change tracker 14 | _dateTime = dateTime; 15 | _loggerFactory = loggerFactory; 16 | } 17 | 18 | public DbSet Departments { get; set; } // Entity set for departments 19 | public DbSet Positions { get; set; } // Entity set for positions 20 | public DbSet Employees { get; set; } // Entity set for employees 21 | public DbSet SalaryRanges { get; set; } // Entity set for salary ranges 22 | 23 | public override Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) 24 | { 25 | foreach (var entry in ChangeTracker.Entries()) 26 | { 27 | switch (entry.State) 28 | { 29 | case EntityState.Added: // If entity is being added to database 30 | entry.Entity.Created = _dateTime.NowUtc; // Set created date and time to current UTC time using injected service 31 | break; 32 | 33 | case EntityState.Modified: // If entity is being modified in database 34 | entry.Entity.LastModified = _dateTime.NowUtc; // Set last modified date and time to current UTC time using injected service 35 | break; 36 | } 37 | } 38 | return base.SaveChangesAsync(cancellationToken); // Call base method to save changes asynchronously with specified cancellation token 39 | } 40 | 41 | protected override void OnModelCreating(ModelBuilder modelBuilder) 42 | { 43 | base.OnModelCreating(modelBuilder); 44 | 45 | // Configuring data model using EF Core Fluent API, using helper class 46 | ApplicationDbContextHelpers.DatabaseModelCreating(modelBuilder); 47 | } 48 | 49 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 50 | { 51 | optionsBuilder.UseLoggerFactory(_loggerFactory) // Use specified logger factory for logging operations 52 | .EnableSensitiveDataLogging() // Enable sensitive data logging for debugging purposes 53 | .EnableDetailedErrors(); // Enable detailed error messages when an exception occurs during database operation 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Contexts/ApplicationDbContextHelpers.cs: -------------------------------------------------------------------------------- 1 | internal static class ApplicationDbContextHelpers 2 | { 3 | /// 4 | /// Configures the model builder for the application database context. 5 | /// 6 | /// The model builder. 7 | public static void DatabaseModelCreating(ModelBuilder modelBuilder) 8 | { 9 | // Configure Department entity 10 | modelBuilder.Entity(entity => 11 | { 12 | // Set properties for the entity 13 | entity.Property(e => e.Id).ValueGeneratedNever(); // Id is never generated by the database 14 | entity.Property(e => e.CreatedBy).HasMaxLength(100); // Maximum length of CreatedBy property 15 | entity.Property(e => e.LastModifiedBy).HasMaxLength(100); // Maximum length of LastModifiedBy property 16 | entity.Property(e => e.Name).HasMaxLength(250); // Maximum length of Name property 17 | }); 18 | 19 | // Configure Employee entity 20 | modelBuilder.Entity(entity => 21 | { 22 | // Create index for PositionId property 23 | entity.HasIndex(e => e.PositionId, "IX_Employees_PositionId"); 24 | 25 | // Set properties for the entity 26 | entity.Property(e => e.Id).ValueGeneratedNever(); // Id is never generated by the database 27 | entity.Property(e => e.CreatedBy).HasMaxLength(100); // Maximum length of CreatedBy property 28 | entity.Property(e => e.Email).HasMaxLength(250); // Maximum length of Email property 29 | entity.Property(e => e.EmployeeNumber).HasMaxLength(100); // Maximum length of EmployeeNumber property 30 | entity.Property(e => e.FirstName).HasMaxLength(100); // Maximum length of FirstName property 31 | entity.Property(e => e.LastModifiedBy).HasMaxLength(100); // Maximum length of LastModifiedBy property 32 | entity.Property(e => e.LastName).HasMaxLength(100); // Maximum length of LastName property 33 | entity.Property(e => e.MiddleName).HasMaxLength(100); // Maximum length of MiddleName property 34 | entity.Property(e => e.Phone).HasMaxLength(100); // Maximum length of Phone property 35 | entity.Property(e => e.Salary).HasColumnType("decimal(18, 2)"); // Salary is a decimal with precision 18 and scale 2 36 | entity.Property(e => e.Prefix).HasMaxLength(100); // Maximum length of Prefix property 37 | 38 | // Configure relationship between Employee and Position entities 39 | entity.HasOne(d => d.Position).WithMany(p => p.Employees).HasForeignKey(d => d.PositionId); 40 | }); 41 | 42 | // Configure Position entity 43 | modelBuilder.Entity(entity => 44 | { 45 | // Create index for DepartmentId property 46 | entity.HasIndex(e => e.DepartmentId, "IX_Positions_DepartmentId"); 47 | 48 | // Create index for SalaryRangeId property 49 | entity.HasIndex(e => e.SalaryRangeId, "IX_Positions_SalaryRangeId"); 50 | 51 | // Set properties for the entity 52 | entity.Property(e => e.Id).ValueGeneratedNever(); // Id is never generated by the database 53 | entity.Property(e => e.CreatedBy).HasMaxLength(100); // Maximum length of CreatedBy property 54 | entity.Property(e => e.LastModifiedBy).HasMaxLength(100); // Maximum length of LastModifiedBy property 55 | entity.Property(e => e.PositionDescription) 56 | .IsRequired() 57 | .HasMaxLength(1000); // Required and maximum length of PositionDescription property 58 | entity.Property(e => e.PositionNumber) 59 | .IsRequired() 60 | .HasMaxLength(100); // Required and maximum length of PositionNumber property 61 | entity.Property(e => e.PositionTitle) 62 | .IsRequired() 63 | .HasMaxLength(250); // Required and maximum length of PositionTitle property 64 | 65 | // Configure relationship between Position and Department entities 66 | entity.HasOne(d => d.Department).WithMany(p => p.Positions).HasForeignKey(d => d.DepartmentId); 67 | 68 | // Configure relationship between Position and SalaryRange entities 69 | entity.HasOne(d => d.SalaryRange).WithMany(p => p.Positions).HasForeignKey(d => d.SalaryRangeId); 70 | }); 71 | 72 | // Configure SalaryRange entity 73 | modelBuilder.Entity(entity => 74 | { 75 | // Set properties for the entity 76 | entity.Property(e => e.Id).ValueGeneratedNever(); // Id is never generated by the database 77 | entity.Property(e => e.CreatedBy).HasMaxLength(100); // Maximum length of CreatedBy property 78 | entity.Property(e => e.Name).HasMaxLength(250); // Maximum length of Name property 79 | entity.Property(e => e.LastModifiedBy).HasMaxLength(100); // Maximum length of LastModifiedBy property 80 | entity.Property(e => e.MaxSalary).HasColumnType("decimal(18, 2)"); // MaxSalary is a decimal with precision 18 and scale 2 81 | entity.Property(e => e.MinSalary).HasColumnType("decimal(18, 2)"); // MinSalary is a decimal with precision 18 and scale 2 82 | }); 83 | } 84 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Extensions/DbFunctionsExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Extensions 2 | { 3 | internal class DbFunctionsExtensions 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/MyTemplate.vstemplate: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apiresources.Infrastructure.Persistence 4 | for application-specific database access 5 | CSharp 6 | 7 | 8 | 1000 9 | true 10 | Apiresources.Infrastructure.Persistence 11 | true 12 | Enabled 13 | true 14 | true 15 | __TemplateIcon.ico 16 | 17 | 18 | 19 | 20 | ApplicationDbContext.cs 21 | ApplicationDbContextHelpers.cs 22 | 23 | 24 | DepartmentRepositoryAsync.cs 25 | EmployeeRepositoryAsync.cs 26 | GenericRepositoryAsync.cs 27 | PositionRepositoryAsync.cs 28 | SalaryRangeRepositoryAsync.cs 29 | 30 | 31 | DbInitializer.cs 32 | 33 | ServiceRegistration.cs 34 | Using.cs 35 | 36 | 37 | -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Repositories/DepartmentRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Repositories 2 | { 3 | /// 4 | /// Represents a repository for asynchronous operations on the Department entity. 5 | /// 6 | public class DepartmentRepositoryAsync : GenericRepositoryAsync, IDepartmentRepositoryAsync 7 | { 8 | /// 9 | /// Initializes a new instance of the DepartmentRepositoryAsync class with the provided database context. 10 | /// 11 | /// The application's DbContext. 12 | public DepartmentRepositoryAsync(ApplicationDbContext dbContext) : base(dbContext) 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Repositories/EmployeeRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Repositories 2 | { 3 | public class EmployeeRepositoryAsync : GenericRepositoryAsync, IEmployeeRepositoryAsync 4 | { 5 | private readonly DbSet _repository; 6 | private readonly IDataShapeHelper _dataShaper; 7 | 8 | /// 9 | /// Constructor for EmployeeRepositoryAsync class. 10 | /// 11 | /// IDataShapeHelper object. 12 | /// IMockService object. 13 | /// 14 | /// 15 | /// 16 | public EmployeeRepositoryAsync(ApplicationDbContext dbContext, 17 | IDataShapeHelper dataShaper) : base(dbContext) 18 | { 19 | _repository = dbContext.Set(); 20 | _dataShaper = dataShaper; 21 | } 22 | 23 | /// 24 | /// Retrieves a paged list of employees based on the provided query parameters. 25 | /// 26 | /// The query parameters used to filter and page the data. 27 | /// A tuple containing the paged list of employees and the total number of records. 28 | public async Task<(IEnumerable data, RecordsCount recordsCount)> GetEmployeeResponseAsync(GetEmployeesQuery requestParameters) 29 | { 30 | //searchable fields 31 | var lastName = requestParameters.LastName; 32 | var firstName = requestParameters.FirstName; 33 | var email = requestParameters.Email; 34 | var employeeNumber = requestParameters.EmployeeNumber; 35 | var positionTitle = requestParameters.PositionTitle; 36 | 37 | var pageNumber = requestParameters.PageNumber; 38 | var pageSize = requestParameters.PageSize; 39 | var orderBy = requestParameters.OrderBy; 40 | var fields = requestParameters.Fields; 41 | 42 | int recordsTotal, recordsFiltered; 43 | 44 | // Setup IQueryable 45 | var result = _repository 46 | .AsNoTracking() 47 | .AsExpandable(); 48 | 49 | // Count records total 50 | recordsTotal = await result.CountAsync(); 51 | 52 | // filter data 53 | FilterByColumn(ref result, lastName, firstName, email, positionTitle, employeeNumber); 54 | 55 | // Count records after filter 56 | recordsFiltered = await result.CountAsync(); 57 | 58 | //set Record counts 59 | var recordsCount = new RecordsCount 60 | { 61 | RecordsFiltered = recordsFiltered, 62 | RecordsTotal = recordsTotal 63 | }; 64 | 65 | // set order by 66 | if (!string.IsNullOrWhiteSpace(orderBy)) 67 | { 68 | result = result.OrderBy(orderBy); 69 | } 70 | 71 | //limit query fields 72 | if (!string.IsNullOrWhiteSpace(fields)) 73 | { 74 | result = result.Select("new(" + fields + ")"); 75 | } 76 | // paging 77 | result = result 78 | .Skip((pageNumber - 1) * pageSize) 79 | .Take(pageSize); 80 | 81 | // retrieve data to list 82 | var resultData = await result.ToListAsync(); 83 | 84 | // shape data 85 | var shapeData = _dataShaper.ShapeData(resultData, fields); 86 | 87 | return (shapeData, recordsCount); 88 | } 89 | 90 | /// 91 | /// Retrieves a paged list of employees based on the provided query parameters. 92 | /// 93 | /// The query parameters used to filter and page the data. 94 | /// A tuple containing the paged list of employees and the total number of records. 95 | public async Task<(IEnumerable data, RecordsCount recordsCount)> GetPagedEmployeeResponseAsync(PagedEmployeesQuery requestParameters) 96 | { 97 | //searchable fields 98 | 99 | var pageNumber = requestParameters.PageNumber; 100 | var pageSize = requestParameters.PageSize; 101 | var orderBy = requestParameters.OrderBy; 102 | var fields = requestParameters.Fields; 103 | 104 | int recordsTotal, recordsFiltered; 105 | 106 | // Setup IQueryable 107 | var result = _repository 108 | .AsNoTracking() 109 | .AsExpandable(); 110 | 111 | // Count records total 112 | recordsTotal = await result.CountAsync(); 113 | 114 | // filter data 115 | FilterByColumn(ref result, requestParameters.Search.Value); 116 | 117 | // Count records after filter 118 | recordsFiltered = await result.CountAsync(); 119 | 120 | //set Record counts 121 | var recordsCount = new RecordsCount 122 | { 123 | RecordsFiltered = recordsFiltered, 124 | RecordsTotal = recordsTotal 125 | }; 126 | 127 | // set order by 128 | if (!string.IsNullOrWhiteSpace(orderBy)) 129 | { 130 | result = result.OrderBy(orderBy); 131 | } 132 | 133 | //limit query fields 134 | if (!string.IsNullOrWhiteSpace(fields)) 135 | { 136 | result = result.Select("new(" + fields + ")"); 137 | } 138 | // paging 139 | result = result 140 | .Skip((pageNumber - 1) * pageSize) 141 | .Take(pageSize); 142 | 143 | // retrieve data to list 144 | var resultData = await result.ToListAsync(); 145 | 146 | // shape data 147 | var shapeData = _dataShaper.ShapeData(resultData, fields); 148 | 149 | return (shapeData, recordsCount); 150 | } 151 | 152 | /// 153 | /// Filters an IQueryable of employees based on the provided parameters. 154 | /// 155 | /// The IQueryable of employees to filter. 156 | /// The employee title to filter by. 157 | /// The last name to filter by. 158 | /// The first name to filter by. 159 | /// The email to filter by. 160 | private void FilterByColumn(ref IQueryable qry, string lastName, string firstName, string email, string positionTitle, string employeeNumber) 161 | { 162 | if (!qry.Any()) 163 | return; 164 | 165 | if (string.IsNullOrEmpty(lastName) && string.IsNullOrEmpty(firstName) && string.IsNullOrEmpty(email) && string.IsNullOrEmpty(positionTitle) && string.IsNullOrEmpty(employeeNumber)) 166 | return; 167 | 168 | var predicate = PredicateBuilder.New(); 169 | 170 | if (!string.IsNullOrEmpty(lastName)) 171 | predicate = predicate.Or(p => p.LastName.ToLower().Contains(lastName.ToLower().Trim())); 172 | 173 | if (!string.IsNullOrEmpty(firstName)) 174 | predicate = predicate.Or(p => p.FirstName.ToLower().Contains(firstName.ToLower().Trim())); 175 | 176 | if (!string.IsNullOrEmpty(email)) 177 | predicate = predicate.Or(p => p.Email.ToLower().Contains(email.ToLower().Trim())); 178 | 179 | if (!string.IsNullOrEmpty(employeeNumber)) 180 | predicate = predicate.Or(p => p.EmployeeNumber.ToLower().Contains(employeeNumber.ToLower().Trim())); 181 | 182 | if (!string.IsNullOrEmpty(positionTitle)) 183 | predicate = predicate.Or(p => p.Position.PositionTitle.ToLower().Contains(positionTitle.ToLower().Trim())); 184 | 185 | qry = qry.Where(predicate); 186 | } 187 | 188 | /// 189 | /// Filters an IQueryable of employees based on the provided parameters. 190 | /// 191 | /// The IQueryable of employees to filter. 192 | /// The employee title to filter by. 193 | private void FilterByColumn(ref IQueryable qry, string keyword) 194 | { 195 | if (!qry.Any()) 196 | return; 197 | 198 | if (string.IsNullOrEmpty(keyword)) 199 | return; 200 | 201 | var predicate = PredicateBuilder.New(); 202 | 203 | predicate = predicate.Or(p => p.LastName.ToLower().Contains(keyword.ToLower().Trim())); 204 | predicate = predicate.Or(p => p.FirstName.ToLower().Contains(keyword.ToLower().Trim())); 205 | predicate = predicate.Or(p => p.Email.ToLower().Contains(keyword.ToLower().Trim())); 206 | predicate = predicate.Or(p => p.EmployeeNumber.ToLower().Contains(keyword.ToLower().Trim())); 207 | predicate = predicate.Or(p => p.Position.PositionTitle.ToLower().Contains(keyword.ToLower().Trim())); 208 | 209 | qry = qry.Where(predicate); 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Repositories/GenericRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Repository 2 | { 3 | public class GenericRepositoryAsync : IGenericRepositoryAsync where T : class 4 | { 5 | private readonly ApplicationDbContext _dbContext; 6 | 7 | public GenericRepositoryAsync(ApplicationDbContext dbContext) 8 | { 9 | _dbContext = dbContext; 10 | } 11 | 12 | public virtual async Task GetByIdAsync(Guid id) 13 | { 14 | return await _dbContext.Set().FindAsync(id); 15 | } 16 | 17 | public async Task> GetAllAsync() 18 | { 19 | return await _dbContext 20 | .Set() 21 | .ToListAsync(); 22 | } 23 | 24 | public async Task AddAsync(T entity) 25 | { 26 | await _dbContext.Set().AddAsync(entity); 27 | await _dbContext.SaveChangesAsync(); 28 | return entity; 29 | } 30 | 31 | public async Task UpdateAsync(T entity) 32 | { 33 | _dbContext.Entry(entity).State = EntityState.Modified; 34 | await _dbContext.SaveChangesAsync(); 35 | } 36 | 37 | public async Task DeleteAsync(T entity) 38 | { 39 | _dbContext.Set().Remove(entity); 40 | await _dbContext.SaveChangesAsync(); 41 | } 42 | 43 | public async Task BulkInsertAsync(IEnumerable entities) 44 | { 45 | // Bulk Insert Extension https://entityframework-extensions.net/bulk-insert 46 | await _dbContext.BulkInsertAsync(entities); 47 | 48 | // if DB does not support bulk insert use the code below 49 | //foreach (T row in entities) 50 | //{ 51 | // await this.AddAsync(row); 52 | //} 53 | } 54 | 55 | public async Task> GetPagedReponseAsync(int pageNumber, int pageSize) 56 | { 57 | return await _dbContext 58 | .Set() 59 | .Skip((pageNumber - 1) * pageSize) 60 | .Take(pageSize) 61 | .AsNoTracking() 62 | .ToListAsync(); 63 | } 64 | 65 | public async Task> GetPagedAdvancedReponseAsync(int pageNumber, int pageSize, string orderBy, string fields, ExpressionStarter predicate) 66 | { 67 | return await _dbContext 68 | .Set() 69 | .Skip((pageNumber - 1) * pageSize) 70 | .Take(pageSize) 71 | .Select("new(" + fields + ")") 72 | .OrderBy(orderBy) 73 | .AsNoTracking() 74 | .Where(predicate) 75 | .ToListAsync(); 76 | } 77 | 78 | public async Task> GetAllShapeAsync(string orderBy, string fields) 79 | { 80 | return await _dbContext 81 | .Set() 82 | .Select("new(" + fields + ")") 83 | .OrderBy(orderBy) 84 | .AsNoTracking() 85 | .ToListAsync(); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Repositories/PositionRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Repositories 2 | { 3 | // PositionRepositoryAsync handles data operations for Position entities. 4 | public class PositionRepositoryAsync : GenericRepositoryAsync, IPositionRepositoryAsync 5 | { 6 | private readonly ApplicationDbContext _dbContext; // Database context for interacting with database 7 | private readonly DbSet _repository; // DbSet for Position entity 8 | private readonly IDataShapeHelper _dataShaper; // Helper for shaping data 9 | private readonly IMockService _mockData; // Mock service for generating dummy data 10 | 11 | // Constructor to initialize the repository with required services 12 | public PositionRepositoryAsync(ApplicationDbContext dbContext, 13 | IDataShapeHelper dataShaper, IMockService mockData) : base(dbContext) 14 | { 15 | _dbContext = dbContext; 16 | _repository = dbContext.Set(); 17 | _dataShaper = dataShaper; 18 | _mockData = mockData; 19 | } 20 | 21 | // Checks for the uniqueness of the position number in the Position DbSet 22 | public async Task IsUniquePositionNumberAsync(string positionNumber) 23 | { 24 | return await _repository 25 | .AllAsync(p => p.PositionNumber != positionNumber); 26 | } 27 | 28 | // Seeds the database with mock Position data 29 | public async Task SeedDataAsync(int rowCount, IEnumerable departments, IEnumerable salaryRanges) 30 | { 31 | await this.BulkInsertAsync(_mockData.GetPositions(rowCount, departments, salaryRanges)); 32 | 33 | return true; // Returns true upon successful seed 34 | } 35 | 36 | // Retrieves paged and filtered position data 37 | public async Task<(IEnumerable data, RecordsCount recordsCount)> PagedPositionReponseAsync(PagedPositionsQuery requestParameters) 38 | { 39 | var pageNumber = requestParameters.PageNumber; 40 | var pageSize = requestParameters.PageSize; 41 | var orderBy = requestParameters.OrderBy; 42 | var fields = requestParameters.Fields; 43 | 44 | int recordsTotal, recordsFiltered; 45 | 46 | // Setup IQueryable for querying Positions 47 | var result = _repository 48 | .AsNoTracking() 49 | .AsExpandable(); 50 | 51 | // Count total records 52 | recordsTotal = await result.CountAsync(); 53 | 54 | // Filter data based on search value 55 | FilterByColumn(ref result, requestParameters.Search.Value); 56 | 57 | // Count records after applying filters 58 | recordsFiltered = await result.CountAsync(); 59 | 60 | // Set record counts 61 | var recordsCount = new RecordsCount 62 | { 63 | RecordsFiltered = recordsFiltered, 64 | RecordsTotal = recordsTotal 65 | }; 66 | 67 | // Apply ordering 68 | if (!string.IsNullOrWhiteSpace(orderBy)) 69 | { 70 | result = result.OrderBy(orderBy); 71 | } 72 | 73 | // Select specific fields 74 | if (!string.IsNullOrWhiteSpace(fields)) 75 | { 76 | result = result.Select("new(" + fields + ")"); 77 | } 78 | // Apply pagination 79 | result = result 80 | .Skip((pageNumber - 1) * pageSize) 81 | .Take(pageSize); 82 | 83 | // Retrieve data into list 84 | var resultData = await result.ToListAsync(); 85 | // Shape data based on user selection 86 | var shapeData = _dataShaper.ShapeData(resultData, fields); 87 | 88 | return (shapeData, recordsCount); 89 | } 90 | 91 | // Retrieves position data based on detailed query parameters 92 | public async Task<(IEnumerable data, RecordsCount recordsCount)> GetPositionReponseAsync(GetPositionsQuery requestParameters) 93 | { 94 | var positionNumber = requestParameters.PositionNumber; 95 | var positionTitle = requestParameters.PositionTitle; 96 | var department = requestParameters.Department; 97 | 98 | var pageNumber = requestParameters.PageNumber; 99 | var pageSize = requestParameters.PageSize; 100 | var orderBy = requestParameters.OrderBy; 101 | var fields = requestParameters.Fields; 102 | 103 | int recordsTotal, recordsFiltered; 104 | 105 | // Setup IQueryable for querying Positions 106 | var result = _repository 107 | .AsNoTracking() 108 | .AsExpandable(); 109 | 110 | // Count total records 111 | recordsTotal = await result.CountAsync(); 112 | 113 | // Filter data based on specific columns 114 | FilterByColumn(ref result, positionNumber, positionTitle, department); 115 | 116 | // Count records after applying filters 117 | recordsFiltered = await result.CountAsync(); 118 | 119 | // Set record counts 120 | var recordsCount = new RecordsCount 121 | { 122 | RecordsFiltered = recordsFiltered, 123 | RecordsTotal = recordsTotal 124 | }; 125 | 126 | // Apply ordering 127 | if (!string.IsNullOrWhiteSpace(orderBy)) 128 | { 129 | result = result.OrderBy(orderBy); 130 | } 131 | 132 | // Select specific fields 133 | if (!string.IsNullOrWhiteSpace(fields)) 134 | { 135 | result = result.Select("new(" + fields + ")"); 136 | } 137 | // Apply pagination 138 | result = result 139 | .Skip((pageNumber - 1) * pageSize) 140 | .Take(pageSize); 141 | 142 | // Retrieve data into list 143 | var resultData = await result.ToListAsync(); 144 | // Shape data based on user selection 145 | var shapeData = _dataShaper.ShapeData(resultData, fields); 146 | 147 | return (shapeData, recordsCount); 148 | } 149 | 150 | // Filters the query based on position number, title, and department 151 | private void FilterByColumn(ref IQueryable qry, string positionNumber, string positionTitle, string department) 152 | { 153 | if (!qry.Any()) 154 | return; 155 | 156 | if (string.IsNullOrEmpty(positionTitle) && string.IsNullOrEmpty(positionNumber) && string.IsNullOrEmpty(department)) 157 | return; 158 | 159 | var predicate = PredicateBuilder.New(); 160 | 161 | if (!string.IsNullOrEmpty(positionNumber)) 162 | predicate = predicate.Or(p => p.PositionNumber.Contains(positionNumber.Trim())); 163 | 164 | if (!string.IsNullOrEmpty(positionTitle)) 165 | predicate = predicate.Or(p => p.PositionTitle.Contains(positionTitle.Trim())); 166 | 167 | if (!string.IsNullOrEmpty(department)) 168 | predicate = predicate.Or(p => p.Department.Name.Contains(department.Trim())); 169 | 170 | qry = qry.Where(predicate); // Apply filtering 171 | } 172 | 173 | // Filters the query based on a keyword 174 | private void FilterByColumn(ref IQueryable qry, string keyword) 175 | { 176 | if (!qry.Any()) 177 | return; 178 | 179 | if (string.IsNullOrEmpty(keyword)) 180 | return; 181 | 182 | var predicate = PredicateBuilder.New(); 183 | 184 | if (!string.IsNullOrEmpty(keyword)) 185 | predicate = predicate.Or(p => p.PositionNumber.Contains(keyword.Trim())); 186 | predicate = predicate.Or(p => p.PositionTitle.Contains(keyword.Trim())); 187 | predicate = predicate.Or(p => p.Department.Name.Contains(keyword.Trim())); 188 | 189 | qry = qry.Where(predicate); // Apply filtering 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Repositories/SalaryRangeRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Repositories 2 | { 3 | // Repository class for handling operations related to the SalaryRange entity asynchronously 4 | public class SalaryRangeRepositoryAsync : GenericRepositoryAsync, ISalaryRangeRepositoryAsync 5 | { 6 | // Entity framework set for interacting with the SalaryRange entities in the database 7 | private readonly DbSet _repository; 8 | 9 | // Constructor for the SalaryRangeRepositoryAsync class 10 | // Takes in the application's database context and passes it to the base class constructor 11 | public SalaryRangeRepositoryAsync(ApplicationDbContext dbContext) : base(dbContext) 12 | { 13 | // Initialize the _repository field by associating it with the SalaryRange set in the database context 14 | _repository = dbContext.Set(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/SeedData/DbInitializer.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.SeedData 2 | { 3 | // Static class for database initialization 4 | public static class DbInitializer 5 | { 6 | // Method to seed data into the database 7 | public static void SeedData(ApplicationDbContext dbContext) 8 | { 9 | // Create an instance of DatabaseSeeder 10 | var databaseSeeder = new DatabaseSeeder(); 11 | 12 | // Insert departments data in bulk 13 | dbContext.BulkInsert(databaseSeeder.Departments); 14 | 15 | // Insert salary ranges data in bulk 16 | dbContext.BulkInsert(databaseSeeder.SalaryRanges); 17 | 18 | // Insert positions data in bulk 19 | dbContext.BulkInsert(databaseSeeder.Positions); 20 | 21 | // Insert employees data in bulk 22 | dbContext.BulkInsert(databaseSeeder.Employees); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/ServiceRegistration.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$ 2 | { 3 | public static class ServiceRegistration 4 | { 5 | public static void AddPersistenceInfrastructure(this IServiceCollection services, IConfiguration configuration) 6 | { 7 | if (configuration.GetValue("UseInMemoryDatabase")) 8 | { 9 | services.AddDbContext(options => 10 | options.UseInMemoryDatabase("ApplicationDb")); 11 | } 12 | else 13 | { 14 | services.AddDbContext(options => 15 | options.UseSqlServer( 16 | configuration.GetConnectionString("DefaultConnection"), 17 | b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); 18 | } 19 | 20 | #region Repositories 21 | 22 | // * use Scutor to register generic repository interface for DI and specifying the lifetime of dependencies 23 | services.Scan(selector => selector 24 | .FromCallingAssembly() 25 | .AddClasses(classSelector => classSelector.AssignableTo(typeof(IGenericRepositoryAsync<>))) 26 | .AsImplementedInterfaces() 27 | .WithTransientLifetime() 28 | ); 29 | 30 | #endregion Repositories 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/Using.cs: -------------------------------------------------------------------------------- 1 | global using EFCore.BulkExtensions; 2 | global using FluentValidation; 3 | global using LinqKit; 4 | global using Microsoft.EntityFrameworkCore; 5 | global using Microsoft.Extensions.Configuration; 6 | global using Microsoft.Extensions.DependencyInjection; 7 | global using Microsoft.Extensions.Logging; 8 | global using $ext_projectname$.Application.Features.Employees.Queries.GetEmployees; 9 | global using $ext_projectname$.Application.Features.Positions.Queries.GetPositions; 10 | global using $ext_projectname$.Application.Interfaces; 11 | global using $ext_projectname$.Application.Interfaces.Repositories; 12 | global using $ext_projectname$.Application.Parameters; 13 | global using $ext_projectname$.Domain.Common; 14 | global using $ext_projectname$.Domain.Entities; 15 | global using $safeprojectname$.Contexts; 16 | global using $safeprojectname$.Repository; 17 | global using $ext_projectname$.Infrastructure.Shared.Services; 18 | global using System; 19 | global using System.Collections.Generic; 20 | global using System.Linq; 21 | global using System.Linq.Dynamic.Core; 22 | global using System.Threading; 23 | global using System.Threading.Tasks; -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Persistence/__TemplateIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workcontrolgit/TemplateOnionAPI/a3c83a62548301165a685de06aafda3b40d9b169/Apiresources.Infrastructure.Persistence/__TemplateIcon.ico -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Apiresources.Infrastructure.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | 1.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Mock/EmployeeBogusConfig.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Mock 2 | { 3 | // Class to create mock Employee data using AutoFaker 4 | public class EmployeeBogusConfig : AutoFaker 5 | { 6 | // Constructor to define mock rules 7 | public EmployeeBogusConfig() 8 | { 9 | // Set a consistent random seed for reproducibility 10 | Randomizer.Seed = new Random(8675309); 11 | 12 | // Rule for generating a random unique identifier for Employee Id 13 | RuleFor(p => p.Id, f => Guid.NewGuid()); 14 | 15 | // Rule for generating a random FirstName 16 | RuleFor(p => p.FirstName, f => f.Name.FirstName()); 17 | 18 | // Rule for generating a random MiddleName 19 | RuleFor(p => p.MiddleName, f => f.Name.FirstName()); 20 | 21 | // Rule for generating a random LastName 22 | RuleFor(p => p.LastName, f => f.Name.LastName()); 23 | 24 | // Rule for generating a random prefix (e.g., Mr., Ms.) 25 | RuleFor(p => p.Prefix, f => f.Name.Prefix()); 26 | 27 | // Rule for generating an email address using FirstName and LastName 28 | RuleFor(p => p.Email, (f, p) => f.Internet.Email(p.FirstName, p.LastName)); 29 | 30 | // Rule for generating a birthdate at least 18 years in the past 31 | RuleFor(p => p.Birthday, f => f.Date.Past(18)); 32 | 33 | // Rule for randomly selecting a value from the Gender enum 34 | RuleFor(p => p.Gender, f => f.PickRandom()); 35 | 36 | // Rule for generating a random EmployeeNumber using EAN-13 format 37 | RuleFor(p => p.EmployeeNumber, f => f.Commerce.Ean13()); 38 | 39 | // Rule for generating a phone number with a specific format 40 | RuleFor(p => p.Phone, f => f.Phone.PhoneNumber("(###)-###-####")); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Mock/PositionInsertBogusConfig.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Mock 2 | { 3 | // This class configures bogus data generation for the Position entity using Faker. 4 | public class PositionInsertBogusConfig : Faker 5 | { 6 | // Constructor that takes in collections of departments and salary ranges 7 | // to aid in generating realistic test data. 8 | public PositionInsertBogusConfig(IEnumerable departments, IEnumerable salaryRanges) 9 | { 10 | // Assign a new Guid for the Id property for each generated Position. 11 | RuleFor(o => o.Id, f => Guid.NewGuid()); 12 | 13 | // Generate a random job title for the PositionTitle property. 14 | RuleFor(o => o.PositionTitle, f => f.Name.JobTitle()); 15 | 16 | // Generate a random 13-digit EAN number for the PositionNumber property. 17 | RuleFor(o => o.PositionNumber, f => f.Commerce.Ean13()); 18 | 19 | // Create a random description associated with a job for PositionDescription. 20 | RuleFor(o => o.PositionDescription, f => f.Name.JobDescriptor()); 21 | 22 | // Pick a random Department's Id from the list provided for DepartmentId. 23 | RuleFor(o => o.DepartmentId, f => f.PickRandom(departments).Id); 24 | 25 | // Pick a random SalaryRange's Id from the list provided for SalaryRangeId. 26 | RuleFor(o => o.SalaryRangeId, f => f.PickRandom(salaryRanges).Id); 27 | 28 | // Generate a random past date within the last year for the Created property. 29 | RuleFor(o => o.Created, f => f.Date.Past(1)); 30 | 31 | // Generate a random full name for the CreatedBy property. 32 | RuleFor(o => o.CreatedBy, f => f.Name.FullName()); 33 | 34 | // Generate a random recent date within the last day for the LastModified property. 35 | RuleFor(o => o.LastModified, f => f.Date.Recent(1)); 36 | 37 | // Generate a random full name for the LastModifiedBy property. 38 | RuleFor(o => o.LastModifiedBy, f => f.Name.FullName()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Mock/PositionSeedBogusConfig.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Mock 2 | { 3 | // Class `PositionSeedBogusConfig` inherits from `AutoFaker` 4 | public class PositionSeedBogusConfig : AutoFaker 5 | { 6 | // Constructor to define the rules and seed for generating fake `Position` data 7 | public PositionSeedBogusConfig() 8 | { 9 | // Set the seed for random data generation for consistency in data 10 | Randomizer.Seed = new Random(8675309); 11 | 12 | // Rule to assign a unique identifier to `Id` property 13 | RuleFor(m => m.Id, f => Guid.NewGuid()); 14 | 15 | // Rule to generate a job title and assign to `PositionTitle` property 16 | RuleFor(o => o.PositionTitle, f => f.Name.JobTitle()); 17 | 18 | // Rule to generate a 13-digit EAN code and assign to `PositionNumber` property 19 | RuleFor(o => o.PositionNumber, f => f.Commerce.Ean13()); 20 | 21 | // Rule to generate a job description and assign to `PositionDescription` property 22 | RuleFor(o => o.PositionDescription, f => f.Name.JobDescriptor()); 23 | 24 | // Rule to generate a past date within the last year for the `Created` property 25 | RuleFor(o => o.Created, f => f.Date.Past(1)); 26 | 27 | // Rule to generate a full name and assign to the `CreatedBy` property 28 | RuleFor(o => o.CreatedBy, f => f.Name.FullName()); 29 | 30 | // Rule to generate a recent date within the last day for the `LastModified` property 31 | RuleFor(o => o.LastModified, f => f.Date.Recent(1)); 32 | 33 | // Rule to generate a full name and assign to the `LastModifiedBy` property 34 | RuleFor(o => o.LastModifiedBy, f => f.Name.FullName()); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/MyTemplate.vstemplate: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apiresources.Infrastructure.Shared 4 | for common services such as Mail Service, Date Time Service, Mock, and so on 5 | CSharp 6 | 7 | 8 | 1000 9 | true 10 | Apiresources.Infrastructure.Shared 11 | true 12 | Enabled 13 | true 14 | true 15 | __TemplateIcon.ico 16 | 17 | 18 | 19 | 20 | EmployeeBogusConfig.cs 21 | PositionInsertBogusConfig.cs 22 | PositionSeedBogusConfig.cs 23 | 24 | 25 | DatabaseSeeder.cs 26 | DateTimeService.cs 27 | EmailService.cs 28 | MockService.cs 29 | 30 | ServiceRegistration.cs 31 | Using.cs 32 | 33 | 34 | -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/ServiceRegistration.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$ 2 | { 3 | public static class ServiceRegistration 4 | { 5 | public static void AddSharedInfrastructure(this IServiceCollection services, IConfiguration _config) 6 | { 7 | services.Configure(_config.GetSection("MailSettings")); 8 | services.AddTransient(); 9 | services.AddTransient(); 10 | services.AddTransient(); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Services/DatabaseSeeder.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Services 2 | { 3 | public class DatabaseSeeder 4 | { 5 | // Public properties for storing generated data collections 6 | public IReadOnlyCollection Departments { get; } 7 | public IReadOnlyCollection Employees { get; } 8 | public IReadOnlyCollection Positions { get; } 9 | public IReadOnlyCollection SalaryRanges { get; } 10 | 11 | // Initializing default row counts for the generated data 12 | public DatabaseSeeder(int rowDepartments = 10, 13 | int rowSalaryRanges = 5, 14 | int rowPositions = 100, 15 | int rowEmployees = 1000, 16 | int seedValue = 1969) 17 | { 18 | // Generate collections based on the specified number of rows and seed value 19 | Departments = GenerateDepartments(rowDepartments, seedValue); 20 | SalaryRanges = GenerateSalaryRanges(rowSalaryRanges, seedValue); 21 | Positions = GeneratePositions(rowPositions, seedValue, Departments, SalaryRanges); 22 | Employees = GenerateEmployees(rowEmployees, seedValue, Positions); 23 | } 24 | 25 | private static IReadOnlyCollection GenerateSalaryRanges(int rowCount, int seedValue) 26 | { 27 | // Setup faker for generating SalaryRange data with specific rules 28 | var faker = new Faker() 29 | .UseSeed(seedValue) 30 | .RuleFor(r => r.Id, f => Guid.NewGuid()) 31 | .RuleFor(r => r.Name, f => f.Name.JobDescriptor()) 32 | .RuleFor(r => r.MinSalary, f => f.Random.Number(30000, 40000)) 33 | .RuleFor(r => r.MaxSalary, f => f.Random.Number(80000, 100000)) 34 | .RuleFor(r => r.Created, f => f.Date.Past(f.Random.Number(1, 5), DateTime.Now)) 35 | .RuleFor(r => r.CreatedBy, f => f.Internet.UserName()); 36 | 37 | // Generate and return the specified number of salary range entries 38 | return faker.Generate(rowCount); 39 | } 40 | 41 | private static IReadOnlyCollection GenerateDepartments(int rowCount, int seedValue) 42 | { 43 | // Setup faker for generating Department data with specific rules 44 | var faker = new Faker() 45 | .UseSeed(seedValue) 46 | .RuleFor(r => r.Id, f => Guid.NewGuid()) 47 | .RuleFor(r => r.Name, f => f.Commerce.Department()) 48 | .RuleFor(r => r.Created, f => f.Date.Past(f.Random.Number(1, 5), DateTime.Now)) 49 | .RuleFor(r => r.CreatedBy, f => f.Internet.UserName()); 50 | 51 | // Generate and return the specified number of department entries 52 | return faker.Generate(rowCount); 53 | } 54 | 55 | private static IReadOnlyCollection GenerateEmployees(int rowCount, int seedValue, IEnumerable positions) 56 | { 57 | // Setup faker for generating Employee data with specific rules, using position information 58 | var faker = new Faker() 59 | .UseSeed(seedValue) 60 | .RuleFor(r => r.Id, f => Guid.NewGuid()) 61 | .RuleFor(r => r.Gender, f => f.PickRandom()) 62 | .RuleFor(r => r.EmployeeNumber, f => f.Commerce.Ean13()) 63 | .RuleFor(r => r.Salary, f => f.Random.Number(20000, 110000)) 64 | .RuleFor(r => r.Prefix, (f, r) => f.Name.Prefix((Name.Gender)r.Gender)) 65 | .RuleFor(r => r.FirstName, (f, r) => f.Name.FirstName((Name.Gender)r.Gender)) 66 | .RuleFor(r => r.MiddleName, (f, r) => f.Name.FirstName((Name.Gender)r.Gender)) 67 | .RuleFor(r => r.LastName, (f, r) => f.Name.LastName((Name.Gender)r.Gender)) 68 | .RuleFor(r => r.Birthday, f => f.Person.DateOfBirth) 69 | .RuleFor(r => r.Email, (f, p) => f.Internet.Email(p.FirstName, p.LastName)) 70 | .RuleFor(r => r.Phone, f => f.Phone.PhoneNumber("(###)-###-####")) 71 | .RuleFor(r => r.PositionId, f => f.PickRandom(positions).Id) 72 | .RuleFor(r => r.Created, f => f.Date.Past(f.Random.Number(1, 5), DateTime.Now)) 73 | .RuleFor(r => r.CreatedBy, f => f.Internet.UserName()); 74 | 75 | // Generate and return the specified number of employee entries 76 | return faker.Generate(rowCount); 77 | } 78 | 79 | private static IReadOnlyCollection GeneratePositions( 80 | int rowCount, int seedValue, 81 | IEnumerable departments, 82 | IEnumerable salaryRanges) 83 | { 84 | // Setup faker for generating Position data with specific rules, using department and salary range information 85 | var random = new Random(); 86 | var faker = new Faker() 87 | .UseSeed(seedValue) 88 | .RuleFor(r => r.Id, f => Guid.NewGuid()) 89 | .RuleFor(o => o.PositionTitle, f => f.Name.JobTitle()) 90 | .RuleFor(o => o.PositionNumber, f => f.Commerce.Ean13()) 91 | .RuleFor(o => o.PositionDescription, f => f.Lorem.Paragraphs(2)) 92 | .RuleFor(r => r.DepartmentId, f => f.PickRandom(departments).Id) 93 | .RuleFor(r => r.SalaryRangeId, f => f.PickRandom(salaryRanges).Id) 94 | .RuleFor(r => r.Created, f => f.Date.Past(f.Random.Number(1, 5), DateTime.Now)) 95 | .RuleFor(r => r.CreatedBy, f => f.Internet.UserName()); 96 | 97 | // Generate, deduplicate by department and salary range, and return the specified number of position entries 98 | return faker.Generate(rowCount) 99 | .GroupBy(r => new { r.DepartmentId, r.SalaryRangeId }) 100 | .Select(r => r.First()) 101 | .ToList(); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Services/DateTimeService.cs: -------------------------------------------------------------------------------- 1 | // Define a namespace for organizing code and avoiding naming conflicts 2 | namespace $safeprojectname$.Services 3 | { 4 | // Define a public class named DateTimeService that implements the IDateTimeService interface 5 | public class DateTimeService : IDateTimeService 6 | { 7 | // Implement a read-only property NowUtc that returns the current date and time in UTC 8 | public DateTime NowUtc => DateTime.UtcNow; 9 | } 10 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Services/EmailService.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Services 2 | { 3 | // Service for sending emails 4 | public class EmailService : IEmailService 5 | { 6 | // Mail settings injected from configuration 7 | public MailSettings _mailSettings { get; } 8 | 9 | // Logger for logging error messages or information 10 | public ILogger _logger { get; } 11 | 12 | // Constructor that accepts mail settings and a logger 13 | public EmailService(IOptions mailSettings, ILogger logger) 14 | { 15 | // Assign mail settings 16 | _mailSettings = mailSettings.Value; 17 | // Assign logger 18 | _logger = logger; 19 | } 20 | 21 | // Asynchronous method to send an email 22 | public async Task SendAsync(EmailRequest request) 23 | { 24 | try 25 | { 26 | // Create a new email message 27 | var email = new MimeMessage(); 28 | // Set the sender email address, use default if not provided 29 | email.Sender = MailboxAddress.Parse(request.From ?? _mailSettings.EmailFrom); 30 | // Add recipient email address 31 | email.To.Add(MailboxAddress.Parse(request.To)); 32 | // Assign subject to the email 33 | email.Subject = request.Subject; 34 | 35 | // Create a new body builder for the email body 36 | var builder = new BodyBuilder(); 37 | // Set the HTML body of the email 38 | builder.HtmlBody = request.Body; 39 | // Assign the body to the email message 40 | email.Body = builder.ToMessageBody(); 41 | 42 | // Initialize an SMTP client 43 | using var smtp = new SmtpClient(); 44 | // Connect to the SMTP server with StartTls 45 | smtp.Connect(_mailSettings.SmtpHost, _mailSettings.SmtpPort, SecureSocketOptions.StartTls); 46 | // Authenticate with the SMTP server 47 | smtp.Authenticate(_mailSettings.SmtpUser, _mailSettings.SmtpPass); 48 | 49 | // Send the email asynchronously 50 | await smtp.SendAsync(email); 51 | // Disconnect from the SMTP server 52 | smtp.Disconnect(true); 53 | } 54 | catch (System.Exception ex) 55 | { 56 | // Log the error if sending fails 57 | _logger.LogError(ex.Message, ex); 58 | // Throw a custom API exception 59 | throw new ApiException(ex.Message); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Services/MockService.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Services 2 | { 3 | // MockService class implementing IMockService interface 4 | public class MockService : IMockService 5 | { 6 | 7 | // Method to get a list of generated Position objects 8 | // rowCount: Number of Position objects to generate 9 | // departments: Collection of Department objects to associate with Positions 10 | // salaryRanges: Collection of SalaryRange objects to associate with Positions 11 | public List GetPositions(int rowCount, IEnumerable departments, IEnumerable salaryRanges) 12 | { 13 | // Create a new faker instance with specified departments and salary ranges 14 | var faker = new PositionInsertBogusConfig(departments, salaryRanges); 15 | // Generate and return a list of Position objects 16 | return faker.Generate(rowCount); 17 | } 18 | 19 | // Method to get a list of generated Employee objects 20 | // rowCount: Number of Employee objects to generate 21 | public List GetEmployees(int rowCount) 22 | { 23 | // Create a new faker instance for Employee objects 24 | var faker = new EmployeeBogusConfig(); 25 | // Generate and return a list of Employee objects 26 | return faker.Generate(rowCount); 27 | } 28 | 29 | // Method to seed a list of Position objects 30 | // rowCount: Number of Position objects to generate 31 | public List SeedPositions(int rowCount) 32 | { 33 | // Create a new faker instance for seeding Position objects 34 | var faker = new PositionSeedBogusConfig(); 35 | // Generate and return a list of seeded Position objects 36 | return faker.Generate(rowCount); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/Using.cs: -------------------------------------------------------------------------------- 1 | global using AutoBogus; 2 | global using Bogus; 3 | global using Bogus.DataSets; 4 | global using MailKit.Net.Smtp; 5 | global using MailKit.Security; 6 | global using Microsoft.Extensions.Configuration; 7 | global using Microsoft.Extensions.DependencyInjection; 8 | global using Microsoft.Extensions.Logging; 9 | global using Microsoft.Extensions.Options; 10 | global using MimeKit; 11 | global using $ext_projectname$.Application.DTOs.Email; 12 | global using $ext_projectname$.Application.Exceptions; 13 | global using $ext_projectname$.Application.Interfaces; 14 | global using $ext_projectname$.Domain.Entities; 15 | global using $ext_projectname$.Domain.Enums; 16 | global using $ext_projectname$.Domain.Settings; 17 | global using $ext_projectname$.Infrastructure.Shared.Mock; 18 | global using $ext_projectname$.Infrastructure.Shared.Services; 19 | global using System; 20 | global using System.Collections.Generic; 21 | global using System.Linq; 22 | global using System.Threading.Tasks; -------------------------------------------------------------------------------- /Apiresources.Infrastructure.Shared/__TemplateIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workcontrolgit/TemplateOnionAPI/a3c83a62548301165a685de06aafda3b40d9b169/Apiresources.Infrastructure.Shared/__TemplateIcon.ico -------------------------------------------------------------------------------- /Apiresources.WebApi/Apiresources.WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | Fuji Nguyen 6 | workcontrolgit 7 | https://github.com/workcontrolgit 8 | Public 9 | https://github.com/workcontrolgit 10 | 1.0.0 11 | 12 | 13 | 14 | $safeprojectname$.xml 15 | 1701;1702;1591 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | all 36 | runtime; build; native; contentfiles; analyzers; buildtransitive 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Apiresources.WebApi/Apiresources.WebApi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $safeprojectname$ 5 | 6 | 7 | 8 | 9 | GET: api/controller 10 | 11 | 12 | 13 | 14 | 15 | 16 | GET: api/controller 17 | 18 | 19 | 20 | 21 | 22 | 23 | GET api/controller/5 24 | 25 | 26 | 27 | 28 | 29 | 30 | POST api/controller 31 | 32 | 33 | 34 | 35 | 36 | 37 | Bulk insert fake data by specifying number of rows 38 | 39 | 40 | 41 | 42 | 43 | 44 | Support Ngx-DataTables https://medium.com/scrum-and-coke/angular-11-pagination-of-zillion-rows-45d8533538c0 45 | 46 | 47 | 48 | 49 | 50 | 51 | PUT api/controller/5 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | DELETE api/controller/5 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Apiresources.WebApi/Controllers/BaseApiController.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Controllers 2 | { 3 | // Attribute to indicate that this is an API Controller 4 | [ApiController] 5 | // Define the routing for the controller, including API versioning 6 | [Route("api/v{version:apiVersion}/[controller]")] 7 | // Define an abstract base class for API controllers that inherit ControllerBase 8 | public abstract class BaseApiController : ControllerBase 9 | { 10 | // Private field to hold the IMediator instance 11 | private IMediator _mediator; 12 | 13 | // Protected property to lazily initialize the IMediator instance using the service provider 14 | protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService(); 15 | } 16 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Controllers/MetaController.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Controllers 2 | { 3 | // MetaController inherits from BaseApiController and handles API requests related to metadata 4 | public class MetaController : BaseApiController 5 | { 6 | // An HTTP GET endpoint at "/info" that returns information about the application 7 | [HttpGet("/info")] 8 | public ActionResult Info() 9 | { 10 | // Get the assembly information for the current application 11 | var assembly = typeof(Program).Assembly; 12 | 13 | // Retrieve the last write time of the assembly file, representing the last update time 14 | var lastUpdate = System.IO.File.GetLastWriteTime(assembly.Location); 15 | 16 | // Retrieve the product version of the assembly 17 | var version = FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion; 18 | 19 | // Return an OK response with the version and last updated timestamp of the application 20 | return Ok($"Version: {version}, Last Updated: {lastUpdate}"); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Controllers/v1/DepartmentsController.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Controllers.v1 2 | { 3 | [ApiVersion("1.0")] 4 | public class DepartmentsController : BaseApiController 5 | { 6 | /// 7 | /// Gets a list of employees based on the specified filter. 8 | /// 9 | /// The filter used to get the list of employees. 10 | /// A list of employees. 11 | [HttpGet] 12 | public async Task Get([FromQuery] GetDepartmentsQuery filter) 13 | { 14 | return Ok(await Mediator.Send(filter)); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Controllers/v1/EmployeesController.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Controllers.v1 2 | { 3 | [ApiVersion("1.0")] 4 | public class EmployeesController : BaseApiController 5 | { 6 | 7 | /// 8 | /// Gets a list of employees based on the specified filter. 9 | /// 10 | /// The filter used to get the list of employees. 11 | /// A list of employees. 12 | [HttpGet] 13 | public async Task Get([FromQuery] GetEmployeesQuery filter) 14 | { 15 | return Ok(await Mediator.Send(filter)); 16 | } 17 | 18 | /// 19 | /// Retrieves a paged list of employees. 20 | /// Support Ngx-DataTables https://medium.com/scrum-and-coke/angular-11-pagination-of-zillion-rows-45d8533538c0 21 | /// 22 | /// The query parameters for the paged list. 23 | /// A paged list of employees. 24 | [HttpPost] 25 | [Route("Paged")] 26 | public async Task Paged(PagedEmployeesQuery query) 27 | { 28 | return Ok(await Mediator.Send(query)); 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Controllers/v1/PositionsController.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Controllers.v1 2 | { 3 | [ApiVersion("1.0")] 4 | public class PositionsController : BaseApiController 5 | { 6 | 7 | /// 8 | /// Gets a list of positions based on the provided filter. 9 | /// 10 | /// The filter used to query the positions. 11 | /// A list of positions. 12 | [HttpGet] 13 | public async Task Get([FromQuery] GetPositionsQuery filter) 14 | { 15 | return Ok(await Mediator.Send(filter)); 16 | } 17 | 18 | /// 19 | /// Gets a position by its Id. 20 | /// 21 | /// The Id of the position. 22 | /// The position with the specified Id. 23 | [HttpGet("{id}")] 24 | //[Authorize] 25 | public async Task Get(Guid id) 26 | { 27 | return Ok(await Mediator.Send(new GetPositionByIdQuery { Id = id })); 28 | } 29 | 30 | /// 31 | /// Creates a new position. 32 | /// 33 | /// The command containing the data for the new position. 34 | /// A 201 Created response containing the newly created position. 35 | [HttpPost] 36 | //[Authorize] 37 | [ProducesResponseType(StatusCodes.Status201Created)] 38 | [ProducesResponseType(StatusCodes.Status400BadRequest)] 39 | public async Task Post(CreatePositionCommand command) 40 | { 41 | var resp = await Mediator.Send(command); 42 | return CreatedAtAction(nameof(Post), resp); 43 | } 44 | 45 | /// 46 | /// Sends an InsertMockPositionCommand to the mediator. 47 | /// 48 | /// The command to be sent. 49 | /// The result of the command. 50 | [HttpPost] 51 | [Route("AddMock")] 52 | //[Authorize] 53 | public async Task AddMock(InsertMockPositionCommand command) 54 | { 55 | return Ok(await Mediator.Send(command)); 56 | } 57 | 58 | /// 59 | /// Retrieves a paged list of positions. 60 | /// 61 | /// The query parameters for the paged list. 62 | /// A paged list of positions. 63 | [HttpPost] 64 | //[Authorize] 65 | [Route("Paged")] 66 | public async Task Paged(PagedPositionsQuery query) 67 | { 68 | return Ok(await Mediator.Send(query)); 69 | } 70 | 71 | /// 72 | /// Updates a position with the given id using the provided command. 73 | /// 74 | /// The id of the position to update. 75 | /// The command containing the updated information. 76 | /// The updated position. 77 | [HttpPut("{id}")] 78 | //[Authorize(Policy = AuthorizationConsts.ManagerPolicy)] 79 | public async Task Put(Guid id, UpdatePositionCommand command) 80 | { 81 | if (id != command.Id) 82 | { 83 | return BadRequest(); 84 | } 85 | return Ok(await Mediator.Send(command)); 86 | } 87 | 88 | /// 89 | /// Deletes a position by its Id. 90 | /// 91 | /// The Id of the position to delete. 92 | /// The result of the deletion. 93 | [HttpDelete("{id}")] 94 | //[Authorize(Policy = AuthorizationConsts.AdminPolicy)] 95 | public async Task Delete(Guid id) 96 | { 97 | return Ok(await Mediator.Send(new DeletePositionByIdCommand { Id = id })); 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Controllers/v1/SalaryRangesController.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Controllers.v1 2 | { 3 | [ApiVersion("1.0")] 4 | public class SalaryRangesController : BaseApiController 5 | { 6 | /// 7 | /// Gets a list of employees based on the specified filter. 8 | /// 9 | /// The filter used to get the list of employees. 10 | /// A list of employees. 11 | [HttpGet] 12 | public async Task Get([FromQuery] GetSalaryRangesQuery filter) 13 | { 14 | return Ok(await Mediator.Send(filter)); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Extensions/AppExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Extensions 2 | { 3 | // Static class containing extension methods for IApplicationBuilder 4 | public static class AppExtensions 5 | { 6 | // Extension method to configure and use Swagger for API documentation 7 | public static void UseSwaggerExtension(this IApplicationBuilder app) 8 | { 9 | // Enables middleware to serve generated Swagger as a JSON endpoint 10 | app.UseSwagger(); 11 | 12 | // Enables middleware to serve Swagger UI at the specified endpoint 13 | app.UseSwaggerUI(c => 14 | { 15 | // Configures the Swagger endpoint with a name and location of the JSON file 16 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "CleanArchitecture.$safeprojectname$"); 17 | }); 18 | } 19 | 20 | // Extension method to add custom middleware for error handling 21 | public static void UseErrorHandlingMiddleware(this IApplicationBuilder app) 22 | { 23 | // Uses the ErrorHandlerMiddleware to process exceptions and generate appropriate responses 24 | app.UseMiddleware(); 25 | } 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Extensions/AuthorizationConsts.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Extensions 2 | { 3 | // This class defines authorization policy constants for the application. 4 | public class AuthorizationConsts 5 | { 6 | // Constant for the Admin role policy 7 | public const string AdminPolicy = "AdminPolicy"; 8 | 9 | // Constant for the Manager role policy 10 | public const string ManagerPolicy = "ManagerPolicy"; 11 | 12 | // Constant for the Employee role policy 13 | public const string EmployeePolicy = "EmployeePolicy"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Apiresources.WebApi/Extensions/ServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Extensions 2 | { 3 | public static class ServiceExtensions 4 | { 5 | // Extension method to add Swagger documentation to the service collection 6 | public static void AddSwaggerExtension(this IServiceCollection services) 7 | { 8 | services.AddSwaggerGen(c => 9 | { 10 | c.SwaggerDoc("v1", new OpenApiInfo 11 | { 12 | Version = "v1", 13 | Title = "Clean Architecture - $safeprojectname$", 14 | Description = "This Api will be responsible for overall data distribution and authorization.", 15 | Contact = new OpenApiContact 16 | { 17 | Name = "Jane Doe", 18 | Email = "jdoe@janedoe.com", 19 | Url = new Uri("https://janedoe.com/contact"), 20 | } 21 | }); 22 | // Add security definition for JWT Bearer 23 | c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme 24 | { 25 | Name = "Authorization", 26 | In = ParameterLocation.Header, 27 | Type = SecuritySchemeType.ApiKey, 28 | Scheme = "Bearer", 29 | BearerFormat = "JWT", 30 | Description = "Input your Bearer token in this format - Bearer {your token here} to access this API", 31 | }); 32 | // Add security requirement to enforce Bearer token use 33 | c.AddSecurityRequirement(new OpenApiSecurityRequirement 34 | { 35 | { 36 | new OpenApiSecurityScheme 37 | { 38 | Reference = new OpenApiReference 39 | { 40 | Type = ReferenceType.SecurityScheme, 41 | Id = "Bearer", 42 | }, 43 | Scheme = "Bearer", 44 | Name = "Bearer", 45 | In = ParameterLocation.Header, 46 | }, new List() 47 | }, 48 | }); 49 | }); 50 | } 51 | 52 | // Extension method to add and configure controllers with JSON options 53 | public static void AddControllersExtension(this IServiceCollection services) 54 | { 55 | services.AddControllers() 56 | .AddJsonOptions(options => 57 | { 58 | // Configure JSON serializer to use camelCase 59 | options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; 60 | options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 61 | }); 62 | } 63 | 64 | // Extension method to configure CORS with a policy to allow any origin, header, and method 65 | public static void AddCorsExtension(this IServiceCollection services) 66 | { 67 | services.AddCors(options => 68 | { 69 | options.AddPolicy("AllowAll", 70 | builder => 71 | { 72 | builder.AllowAnyOrigin() 73 | .AllowAnyHeader() 74 | .AllowAnyMethod(); 75 | }); 76 | }); 77 | } 78 | 79 | // Extension method to add API versioning and configure the API version explorer 80 | public static void AddVersionedApiExplorerExtension(this IServiceCollection services) 81 | { 82 | var apiVersioningBuilder = services.AddApiVersioning(options => 83 | { 84 | options.ReportApiVersions = true; 85 | options.DefaultApiVersion = new ApiVersion(1, 0); 86 | options.AssumeDefaultVersionWhenUnspecified = true; 87 | options.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), 88 | new HeaderApiVersionReader("x-api-version"), 89 | new MediaTypeApiVersionReader("x-api-version")); 90 | }); 91 | apiVersioningBuilder.AddApiExplorer(options => 92 | { 93 | options.GroupNameFormat = "'v'VVV"; 94 | options.SubstituteApiVersionInUrl = true; 95 | }); 96 | } 97 | 98 | // Extension method to add API versioning for the service 99 | public static void AddApiVersioningExtension(this IServiceCollection services) 100 | { 101 | services.AddApiVersioning(config => 102 | { 103 | config.DefaultApiVersion = new ApiVersion(1, 0); 104 | config.AssumeDefaultVersionWhenUnspecified = true; 105 | config.ReportApiVersions = true; 106 | }); 107 | } 108 | 109 | // Extension method to set up JWT authentication 110 | public static void AddJWTAuthentication(this IServiceCollection services, IConfiguration configuration) 111 | { 112 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 113 | .AddJwtBearer(options => 114 | { 115 | options.RequireHttpsMetadata = false; 116 | options.Authority = configuration["Sts:ServerUrl"]; 117 | options.Audience = configuration["Sts:Audience"]; 118 | }); 119 | } 120 | 121 | // Extension method to add authorization policies based on roles 122 | public static void AddAuthorizationPolicies(this IServiceCollection services, IConfiguration configuration) 123 | { 124 | string admin = configuration["ApiRoles:AdminRole"], 125 | manager = configuration["ApiRoles:ManagerRole"], employee = configuration["ApiRoles:EmployeeRole"]; 126 | 127 | services.AddAuthorization(options => 128 | { 129 | options.AddPolicy(AuthorizationConsts.AdminPolicy, policy => policy.RequireRole(admin)); 130 | options.AddPolicy(AuthorizationConsts.ManagerPolicy, policy => policy.RequireRole(manager, admin)); 131 | options.AddPolicy(AuthorizationConsts.EmployeePolicy, policy => policy.RequireRole(employee, manager, admin)); 132 | }); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Apiresources.WebApi/Middlewares/ErrorHandlerMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Middlewares 2 | { 3 | public class ErrorHandlerMiddleware 4 | { 5 | // Next delegate/middleware in the pipeline 6 | private readonly RequestDelegate _next; 7 | // Logger for ErrorHandlerMiddleware 8 | private readonly ILogger _logger; 9 | 10 | // Constructor to initialize ErrorHandlerMiddleware with the next delegate and logger 11 | public ErrorHandlerMiddleware(RequestDelegate next, ILogger logger) 12 | { 13 | _next = next; 14 | _logger = logger; 15 | } 16 | 17 | // Method to handle incoming HTTP requests and catch exceptions 18 | public async Task Invoke(HttpContext context) 19 | { 20 | try 21 | { 22 | // Call the next delegate/middleware in the pipeline 23 | await _next(context); 24 | } 25 | catch (Exception error) 26 | { 27 | var response = context.Response; 28 | response.ContentType = "application/json"; // Set the response content type to JSON 29 | 30 | // Create a response model indicating failure and capturing the error message 31 | var responseModel = new Response() { Succeeded = false, Message = error?.Message }; 32 | 33 | // Determine the status code based on the error type 34 | switch (error) 35 | { 36 | case ApiException: 37 | // Custom application error, set status code to 400 38 | response.StatusCode = (int)HttpStatusCode.BadRequest; 39 | break; 40 | 41 | case ValidationException e: 42 | // Custom application error with validation errors, set status code to 400 43 | response.StatusCode = (int)HttpStatusCode.BadRequest; 44 | responseModel.Errors = e.Errors; // Capture validation errors 45 | break; 46 | 47 | case KeyNotFoundException: 48 | // Not found error, set status code to 404 49 | response.StatusCode = (int)HttpStatusCode.NotFound; 50 | break; 51 | 52 | default: 53 | // Unhandled error, set status code to 500 54 | response.StatusCode = (int)HttpStatusCode.InternalServerError; 55 | break; 56 | } 57 | 58 | // Log the exception message using ILogger 59 | _logger.LogError(error.Message); 60 | 61 | // Serialize the response model to a JSON string 62 | var result = JsonSerializer.Serialize(responseModel); 63 | 64 | // Write the serialized response to the HTTP response 65 | await response.WriteAsync(result); 66 | } 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Models/Metadata.cs: -------------------------------------------------------------------------------- 1 | namespace $safeprojectname$.Models 2 | { 3 | // Represents metadata information 4 | public class Metadata 5 | 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/MyTemplate.vstemplate: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apiresources.WebApi 4 | for controllers to expose REST API resources and endpoints 5 | CSharp 6 | 7 | 8 | 1000 9 | true 10 | Apiresources.WebApi 11 | true 12 | Enabled 13 | true 14 | true 15 | __TemplateIcon.ico 16 | 17 | 18 | 19 | 20 | 21 | FolderProfile.pubxml 22 | 23 | launchSettings.json 24 | 25 | 26 | 27 | DepartmentsController.cs 28 | EmployeesController.cs 29 | PositionsController.cs 30 | SalaryRangesController.cs 31 | 32 | BaseApiController.cs 33 | MetaController.cs 34 | 35 | 36 | AppExtensions.cs 37 | AuthorizationConsts.cs 38 | ServiceExtensions.cs 39 | 40 | 41 | ErrorHandlerMiddleware.cs 42 | 43 | 44 | Metadata.cs 45 | 46 | Apiresources.WebApi.xml 47 | appsettings.Development.json 48 | appsettings.json 49 | Program.cs 50 | Using.cs 51 | 52 | 53 | -------------------------------------------------------------------------------- /Apiresources.WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | // Set up a try block to handle any exceptions during startup 2 | try 3 | { 4 | // Create a WebApplication builder with command-line arguments 5 | var builder = WebApplication.CreateBuilder(args); 6 | // Configure and initialize Serilog for logging 7 | Log.Logger = new LoggerConfiguration() 8 | .ReadFrom.Configuration(builder.Configuration) 9 | .Enrich.FromLogContext() 10 | .CreateLogger(); 11 | 12 | // Use Serilog as the logging provider 13 | builder.Host.UseSerilog(Log.Logger); 14 | 15 | // Log information about application startup 16 | Log.Information("Application startup services registration"); 17 | 18 | // Register application services 19 | builder.Services.AddApplicationLayer(); 20 | builder.Services.AddPersistenceInfrastructure(builder.Configuration); 21 | builder.Services.AddSharedInfrastructure(builder.Configuration); 22 | builder.Services.AddSwaggerExtension(); 23 | builder.Services.AddControllersExtension(); 24 | // Configure CORS policies 25 | builder.Services.AddCorsExtension(); 26 | // Add Health Checks service 27 | builder.Services.AddHealthChecks(); 28 | // Set up API security with JWT 29 | builder.Services.AddJWTAuthentication(builder.Configuration); 30 | builder.Services.AddAuthorizationPolicies(builder.Configuration); 31 | // Add API versioning extension 32 | builder.Services.AddApiVersioningExtension(); 33 | // Add API explorer for Swagger 34 | builder.Services.AddMvcCore().AddApiExplorer(); 35 | // Add versioned API explorer extension 36 | builder.Services.AddVersionedApiExplorerExtension(); 37 | // Build the application 38 | var app = builder.Build(); 39 | // Log information about middleware registration 40 | Log.Information("Application startup middleware registration"); 41 | 42 | // Environment-specific configuration 43 | if (app.Environment.IsDevelopment()) 44 | { 45 | // Use Developer Exception Page in development 46 | app.UseDeveloperExceptionPage(); 47 | // Ensure the database is created and seed initial data during development 48 | using (var scope = app.Services.CreateScope()) 49 | { 50 | var dbContext = scope.ServiceProvider.GetRequiredService(); 51 | // Ensure the database is created and seed data if new 52 | if (dbContext.Database.EnsureCreated()) 53 | { 54 | DbInitializer.SeedData(dbContext); 55 | } 56 | } 57 | } 58 | else 59 | { 60 | // Use Exception Handler and HSTS in non-development environments 61 | app.UseExceptionHandler("/Error"); 62 | app.UseHsts(); 63 | } 64 | // Log HTTP requests using Serilog 65 | app.UseSerilogRequestLogging(); 66 | // Redirect HTTP requests to HTTPS 67 | app.UseHttpsRedirection(); 68 | // Configure request routing 69 | app.UseRouting(); 70 | // Enable configured CORS policy ("AllowAll" in this case) 71 | app.UseCors("AllowAll"); 72 | // Use Authentication middleware 73 | app.UseAuthentication(); 74 | // Use Authorization middleware 75 | app.UseAuthorization(); 76 | // Enable Swagger for API documentation 77 | app.UseSwaggerExtension(); 78 | // Use custom error handling middleware 79 | app.UseErrorHandlingMiddleware(); 80 | // Configure Health Checks endpoint 81 | app.UseHealthChecks("/health"); 82 | // Map controllers for endpoints 83 | app.MapControllers(); 84 | // Log information that the application is starting 85 | Log.Information("Application Starting"); 86 | // Run the application 87 | app.Run(); 88 | } 89 | // Catch any exception that occurs during startup 90 | catch (Exception ex) 91 | { 92 | // Log warning with exception details 93 | Log.Warning(ex, "An error occurred starting the application"); 94 | } 95 | // Ensure the log is flushed properly 96 | finally 97 | { 98 | Log.CloseAndFlush(); 99 | } 100 | -------------------------------------------------------------------------------- /Apiresources.WebApi/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | FileSystem 9 | FileSystem 10 | Release 11 | Any CPU 12 | 13 | True 14 | False 15 | 0c542137-d103-4825-b636-c4e9d8e7efc9 16 | bin\Release\netcoreapp3.1\publish\ 17 | False 18 | 19 | -------------------------------------------------------------------------------- /Apiresources.WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57712", 7 | "sslPort": 44378 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | }, 19 | "sqlDebugging": false, 20 | "nativeDebugging": false 21 | }, 22 | "$safeprojectname$": { 23 | "commandName": "Project", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | }, 29 | "applicationUrl": "https://localhost:9001;http://localhost:5000" 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/Using.cs: -------------------------------------------------------------------------------- 1 | global using Asp.Versioning; 2 | global using MediatR; 3 | global using Microsoft.AspNetCore.Authentication.JwtBearer; 4 | global using Microsoft.AspNetCore.Builder; 5 | global using Microsoft.AspNetCore.Hosting; 6 | global using Microsoft.AspNetCore.Http; 7 | global using Microsoft.AspNetCore.Mvc; 8 | global using Microsoft.Extensions.Configuration; 9 | global using Microsoft.Extensions.DependencyInjection; 10 | global using Microsoft.Extensions.Hosting; 11 | global using Microsoft.Extensions.Logging; 12 | global using Microsoft.OpenApi.Models; 13 | global using $ext_projectname$.Application; 14 | global using $ext_projectname$.Application.Exceptions; 15 | global using $ext_projectname$.Application.Features.Departments.Queries.GetDepartments; 16 | global using $ext_projectname$.Application.Features.Employees.Queries.GetEmployees; 17 | global using $ext_projectname$.Application.Features.Positions.Commands.CreatePosition; 18 | global using $ext_projectname$.Application.Features.Positions.Commands.DeletePositionById; 19 | global using $ext_projectname$.Application.Features.Positions.Commands.UpdatePosition; 20 | global using $ext_projectname$.Application.Features.Positions.Queries.GetPositionById; 21 | global using $ext_projectname$.Application.Features.Positions.Queries.GetPositions; 22 | global using $ext_projectname$.Application.Features.SalaryRanges.Queries.GetSalaryRanges; 23 | global using $ext_projectname$.Application.Wrappers; 24 | global using $ext_projectname$.Infrastructure.Persistence; 25 | global using $ext_projectname$.Infrastructure.Persistence.Contexts; 26 | global using $ext_projectname$.Infrastructure.Persistence.SeedData; 27 | global using $ext_projectname$.Infrastructure.Shared; 28 | global using $safeprojectname$.Extensions; 29 | global using $safeprojectname$.Middlewares; 30 | global using Serilog; 31 | global using System; 32 | global using System.Collections.Generic; 33 | global using System.Diagnostics; 34 | global using System.Net; 35 | global using System.Text.Json; 36 | global using System.Threading.Tasks; 37 | -------------------------------------------------------------------------------- /Apiresources.WebApi/__TemplateIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workcontrolgit/TemplateOnionAPI/a3c83a62548301165a685de06aafda3b40d9b169/Apiresources.WebApi/__TemplateIcon.ico -------------------------------------------------------------------------------- /Apiresources.WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "MailSettings": { 3 | "EmailFrom": "info@codewithmukesh.com", 4 | "SmtpHost": "smtp.ethereal.email", 5 | "SmtpPort": 587, 6 | "SmtpUser": "doyle.sauer@ethereal.email", 7 | "SmtpPass": "6X4wBQQYgU14F23VYc", 8 | "DisplayName": "Mukesh Murugan" 9 | } 10 | } -------------------------------------------------------------------------------- /Apiresources.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft.EntityFrameworkCore.Database.Command": "Information" 6 | } 7 | }, 8 | "UseInMemoryDatabase": false, 9 | "ConnectionStrings": { 10 | "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=$ext_projectname$Db;Integrated Security=True;MultipleActiveResultSets=True" 11 | }, 12 | "Serilog": { 13 | "Using": [ ], 14 | "MinimumLevel": { 15 | "Default": "Debug", 16 | "Override": { 17 | "Microsoft": "Warning", 18 | "Microsoft.EntityFrameworkCore.Database.Command": "Warning", 19 | "System": "Warning" 20 | } 21 | }, 22 | "WriteTo": [ 23 | { "Name": "Console" }, 24 | { 25 | "Name": "Logger", 26 | "Args": { 27 | "configureLogger": { 28 | "Filter": [ 29 | { 30 | "Name": "ByIncludingOnly", 31 | "Args": { 32 | "expression": "@l = 'Error' or @l = 'Fatal' or @l = 'Warning'" 33 | } 34 | } 35 | ], 36 | "WriteTo": [ 37 | { 38 | "Name": "File", 39 | "Args": { 40 | "path": "Logs/Error/error_.log", 41 | "outputTemplate": "{Timestamp:o} [{Level:u3}] ({SourceContext}) {Message}{NewLine}{Exception}", 42 | "rollingInterval": "Day", 43 | "retainedFileCountLimit": 7 44 | } 45 | } 46 | ] 47 | } 48 | } 49 | }, 50 | { 51 | "Name": "Logger", 52 | "Args": { 53 | "configureLogger": { 54 | "Filter": [ 55 | { 56 | "Name": "ByIncludingOnly", 57 | "ApiRoles": null, 58 | "Args": { 59 | "expression": "@l = 'Information'" 60 | } 61 | } 62 | ], 63 | "WriteTo": [ 64 | { 65 | "Name": "File", 66 | "Args": { 67 | "path": "Logs/Info/info_.log", 68 | "outputTemplate": "{Timestamp:o} [{Level:u3}] ({SourceContext}) {Message}{NewLine}{Exception}", 69 | "rollingInterval": "Day", 70 | "retainedFileCountLimit": 7 71 | } 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | ], 78 | "Properties": { 79 | "ApplicationName": "Serilog.$ext_projectname$" 80 | } 81 | }, 82 | "MailSettings": { 83 | "EmailFrom": "info@janedoe.com", 84 | "SmtpHost": "smtp.janedoe.com", 85 | "SmtpPort": 587, 86 | "SmtpUser": "Jane.Doe@janedoe.email", 87 | "SmtpPass": "6X4wBQQYgU14F23VYc", 88 | "DisplayName": "Jane Doe" 89 | }, 90 | "JWTSettings": { 91 | "Key": "C1CF4B7DC4C4175B6618DE4F55CA4", 92 | "Issuer": "CoreIdentity", 93 | "Audience": "CoreIdentityUser", 94 | "DurationInMinutes": 60 95 | }, 96 | "Sts": { 97 | "ServerUrl": "https://localhost:44310", 98 | "Audience": "app.api.employeeprofile" 99 | }, 100 | "ApiRoles": { 101 | "EmployeeRole": "Employee", 102 | "ManagerRole": "Manager", 103 | "AdminRole": "HRAdmin" 104 | }, 105 | "AllowedHosts": "*" 106 | } -------------------------------------------------------------------------------- /OnionAPI.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | OnionApi Template (ASP.NET Core, EntityFramework, Swagger, Linq, Bogus) 4 | A project template for building ASP.NET WebAPI with Core layer, Application layer, Infrastructure layer and WebAPI layer 5 | CSharp 6 | 7 | 8 | 1000 9 | true 10 | MyOnionApi 11 | true 12 | Enabled 13 | true 14 | __TemplateIcon.ico 15 | 16 | 17 | 18 | 19 | 20 | Apiresources.WebApi\MyTemplate.vstemplate 21 | 22 | 23 | 24 | 25 | Apiresources.Application\MyTemplate.vstemplate 26 | 27 | 28 | Apiresources.Domain\MyTemplate.vstemplate 29 | 30 | 31 | 32 | 33 | Apiresources.Infrastructure.Persistence\MyTemplate.vstemplate 34 | 35 | 36 | Apiresources.Infrastructure.Shared\MyTemplate.vstemplate 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /database-centrics-vs-domain-centric-architecture.drawio: -------------------------------------------------------------------------------- 1 | 7ZnbbpswAIafJperMBAgl0loukqdVK1au146tgFvgJFxGrKnnwkmiTE9qTm0am8i+/cB+/98zsCZZtUFh0Xyg2GSDmwLVwMnHNi2H9jytxZWjeD6oBFiTnEj7Qg39B9RoqXUBcWk1DIKxlJBC11ELM8JEpoGOWdLPVvEUv2rBYyJIdwgmJrqHcUiadRgaG3174TGSftlYKmUDLaZlVAmELPljuScD5wpZ0w0oayakrT2rvWlKTd7JHXTME5y8ZICd5fWNLwaZ/cX1e2t//un547Cb05TywNMF6rDqrFi1Toga5Fmy8hkmVBBbgqI6pSlxC21RGSpjAEZhGXREIhoReRHJxFN0ylLGV9X5MxmM3s6lbrZctWZB8IFqXYk1ZMLwjIi+EpmUamjpoAaVK3Fyy0ht+WQ7NDZiFCNinhT8dY4GVDevcJH+4P6ONSN9E0jnT4jnYMZCT6okcDSnQQ9Y9Lus9I+mJWuYWUIBZxDaV3X0lJw9nezwNm6jXLhKup8WRXXS/xZlLIlSiAXZ7itrx9Fx248JAF2a53lQq30oI43X9/JGdhzx/P2BMbrgBmZYIIeLt6hsAx7sUhljBApS4ON7KfoANH8yllOOmYrCaY0zmUUSfeI1Ce1a1TubWOVkFGM08focbbIcT1tQqtlphoFehjug1RnMQKeSQr0T6EDofIMVJNFSfM1JeuKxRR9YlquTqtnWm3WwKPA8g1Yvy6/8LR43FPjCY64sUcR8RDq21mwP5pb1n4sdl1Xs/j0p1AQGJ7qA49xkbCY5TC9YqxQbv4hQqzU8IMLwXSvSUXFb1W8Dt/X4bOhioXVTlK4aiM5Htd3r+3skcqM1p1Zp2NYJus2gadIlGzBEXmiu2qEE6xd4UxenKRQ0Af9Rtdnvip6zahsyuOcu3NEQB4ToUp1EG6a8QaqI3OmyC63C4hyeH+gN3Dvd1L6QWsgn6P+ZtCNz8+vMfsbEG+7E1pH5QZeMUG/uD3f6qPsVJs70UHvQO5IX8HewTXfMkwOWQZpfkCrIQki9ILTl4HCQwGZR3t6cOlsJu/gnQCYj4Djokjl2VZQZgL5NEfoYaCjOvkNB5gvOteclLKHX6Q0Uie/7ID+R57Tvb1FUWQj9JLFDntzb7ivfaez2Dk9G49/zLc323xevia8pKUgOTLRvI8JxJma307oDo8zoboHBqetQrvbHnFCtWewHXCXecSh5LFAYsE/ADsHHAme7/odeKNDwZPR7R+YzX13+y+wc/4f -------------------------------------------------------------------------------- /zip_template_onion_api.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set "source_folder=C:\apps\TemplateOnionAPI" 3 | set "zip_file=C:\apps\VSIXTemplateOnionAPI\VSIXTemplateOnionAPI\ProjectTemplates\TemplateOnionAPI.zip" 4 | 5 | echo Zipping folder: %source_folder% 6 | echo Saving zip file as: %zip_file% 7 | 8 | powershell Compress-Archive -Path "%source_folder%\*" -DestinationPath "%zip_file%" -Force 9 | 10 | echo Zip operation completed. 11 | --------------------------------------------------------------------------------