This commit is contained in:
Frede Hundewadt 2023-12-11 14:05:07 +01:00
parent 1ee695e924
commit 6c13b606ef
43 changed files with 595 additions and 114 deletions

View file

@ -31,6 +31,13 @@ namespace Contracts;
public interface ICompanyRepository
{
IEnumerable<Company> GetAllCompanies(bool trackChanges);
Company GetCompany(Guid companyId, bool trackChanges);
Company? GetCompany(Guid companyId, bool trackChanges);
void CreateCompany(Company company);
IEnumerable<Company> GetByIds(IEnumerable<Guid> ids, bool trackChanges);
void DeleteCompany(Company company, bool trackChanges);
}

View file

@ -31,5 +31,7 @@ namespace Contracts;
public interface IEmployeeRepository
{
IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges);
Employee GetEmployee(Guid companyId, Guid employeeId, bool trackChanges);
Employee? GetEmployee(Guid companyId, Guid employeeId, bool trackChanges);
void CreateEmployeeForCompany(Guid companyId, Employee employee);
void DeleteEmployee(Employee employee);
}

View file

@ -31,18 +31,17 @@ namespace Entities;
public class Company
{
[Key]
public Guid CompanyId { get; set; }
[Column("CompanyId")] public Guid Id { get; set; }
[Required(ErrorMessage = "Company name is a required field.")]
[MaxLength(60, ErrorMessage = "Maximum length for the Name is 60 characters.")]
public string? Name { get; set; }
[Required(ErrorMessage = "Company address is a required field.")]
[MaxLength(60, ErrorMessage = "Maximum length for the Address is 60 characters")]
public string? Address { get; set; }
public string? Country { get; set; }
public ICollection<Employee>? Employees { get; set; }
public ICollection<Employee>? Employees { get; set; }
}

View file

@ -31,14 +31,14 @@ namespace Entities;
public class Employee
{
[Key]
public Guid EmployeeId { get; set; }
[Column("EmployeeId")]
public Guid Id { get; set; }
[Required(ErrorMessage = "Employee name is a required field.")]
[MaxLength(30, ErrorMessage = "Maximum length for the Name is 30 characters.")]
public string? Name { get; set; }
[Required(ErrorMessage = "Age is a required field.")]
[Required(ErrorMessage = "Age is a required field.")]
public int Age { get; set; }
[Required(ErrorMessage = "Position is a required field.")]
@ -47,5 +47,6 @@ public class Employee
[ForeignKey(nameof(Company))]
public Guid CompanyId { get; set; }
public Company? Company { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace Entities.Exceptions;
public abstract class BadRequestException : Exception
{
protected BadRequestException(string message) : base(message) { }
}

View file

@ -0,0 +1,6 @@
namespace Entities.Exceptions;
public sealed class CollectionByIdsBadRequestException : BadRequestException
{
public CollectionByIdsBadRequestException(): base("Collection count mismatch"){}
}

View file

@ -0,0 +1,6 @@
namespace Entities.Exceptions;
public sealed class CompanyCollectionBadRequest : BadRequestException
{
public CompanyCollectionBadRequest():base("Company collection is null"){}
}

View file

@ -26,9 +26,10 @@
namespace Entities.Exceptions;
public class CompanyNotFoundException : NotFoundException
public sealed class CompanyNotFoundException : NotFoundException
{
public CompanyNotFoundException(Guid companyId) : base($"The company with id {companyId} vas not found.")
public CompanyNotFoundException(Guid companyId) :
base($"The company with id {companyId} vas not found.")
{
}
}

View file

@ -26,7 +26,7 @@
namespace Entities.Exceptions;
public class EmployeeNotFoundException : NotFoundException
public sealed class EmployeeNotFoundException : NotFoundException
{
public EmployeeNotFoundException(Guid employeeId)
: base($"Employee with id: {employeeId} doesn't exist in the database.")

View file

@ -0,0 +1,6 @@
namespace Entities.Exceptions;
public sealed class IdParametersBadRequestException : BadRequestException
{
public IdParametersBadRequestException():base("Parameter ids is null"){}
}

View file

@ -35,13 +35,24 @@ internal class CompanyRepository : RepositoryBase<Company>, ICompanyRepository
{
}
public IEnumerable<Company> GetAllCompanies(bool trackChanges)
{
return FindAll(trackChanges).OrderBy(c => c.Name).ToList();
}
public Company GetCompany(Guid companyId, bool trackChanges)
public Company? GetCompany(Guid companyId, bool trackChanges)
{
return FindByCondition(c => c.CompanyId.Equals(companyId), trackChanges).SingleOrDefault();
return FindByCondition(c => c.Id.Equals(companyId), trackChanges).SingleOrDefault();
}
public void CreateCompany(Company company) => Create(company);
public IEnumerable<Company> GetByIds(IEnumerable<Guid> ids, bool trackChanges) =>
FindByCondition(x => ids.Contains(x.Id), trackChanges).ToList();
public void DeleteCompany(Company company, bool trackChanges) => Delete(company);
}

View file

@ -38,14 +38,14 @@ public class CompanyConfiguration : IEntityTypeConfiguration<Company>
(
new Company
{
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
Name = "IT_Solutions Ltd",
Address = "583 Wall Dr. Gwynn Oak, MD 21207",
Country = "USA"
},
new Company
{
CompanyId = new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"),
Name = "Admin_Solutions Ltd",
Address = "312 Forest Avenue, BF 923",
Country = "USA"

View file

@ -38,28 +38,29 @@ public class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
(
new Employee
{
EmployeeId = new Guid("12341234-1111-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("80abbca8-664d-4b20-b5de-024705497d4a"),
Name = "Sam Raiden",
Age = 26,
Position = "Software developer",
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc")
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870")
},
new Employee
{
EmployeeId = new Guid("12341234-2222-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"),
Name = "Jana McLeaf",
Age = 30,
Position = "Software developer",
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc")
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870")
},
new Employee
{
EmployeeId = new Guid("56785678-1111-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"),
Name = "Kane Miller",
Age = 35,
Position = "Administrator",
CompanyId = new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc")
CompanyId = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3")
}
);
}
}
}

View file

@ -35,12 +35,24 @@ public class EmployeeRepository : RepositoryBase<Employee>, IEmployeeRepository
{
}
public IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges) =>
FindByCondition(e => e.CompanyId.Equals(companyId), trackChanges)
.OrderBy(e => e.Name)
.ToList();
public Employee GetEmployee(Guid companyId, Guid employeeId, bool trackChanges) =>
FindByCondition(e => e.CompanyId.Equals(companyId) && e.EmployeeId.Equals(employeeId), trackChanges)
public Employee? GetEmployee(Guid companyId, Guid employeeId, bool trackChanges) =>
FindByCondition(e => e.CompanyId.Equals(companyId) && e.Id.Equals(employeeId), trackChanges)
.SingleOrDefault();
public void CreateEmployeeForCompany(Guid companyId, Employee employee)
{
employee.CompanyId = companyId;
Create(employee);
}
public void DeleteEmployee(Employee employee) => Delete(employee);
}

View file

@ -15,8 +15,4 @@
<ProjectReference Include="..\Entities\Entities.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Configuration\" />
</ItemGroup>
</Project>

View file

@ -31,6 +31,17 @@ namespace Service.Contracts
public interface ICompanyService
{
IEnumerable<CompanyDto> GetAllCompanies(bool trackChanges);
CompanyDto GetCompany(Guid companyId, bool trackChanges);
CompanyDto CreateCompany(CompanyCreateDto? company);
IEnumerable<CompanyDto> GetByIds(IEnumerable<Guid> ids, bool trackChanges);
(IEnumerable<CompanyDto> companies, string ids) CreateCompanyCollection(IEnumerable<CompanyCreateDto> companyCollection);
void DeleteCompany(Guid companyId, bool trackChanges);
void UpdateCompany(Guid companyId, CompanyUpdateDto? companyUpdate, bool trackChanges);
}
}

View file

@ -24,6 +24,7 @@
// <summary></summary>
// ***********************************************************************
using Entities;
using Shared.DataTransferObjects;
namespace Service.Contracts;
@ -32,4 +33,13 @@ public interface IEmployeeService
{
IEnumerable<EmployeeDto> GetEmployees(Guid companyId, bool trackChanges);
EmployeeDto GetEmployee(Guid companyId, Guid employeeId, bool trackChanges);
EmployeeDto CreateEmployeeForCompany(Guid companyId, EmployeeCreateDto? employeeCreateDto, bool trackChanges);
void DeleteEmployeeForCompany(Guid companyId, Guid employeeId, bool trackChanges);
void UpdateEmployeeForCompany(Guid companyId, Guid employeeId, EmployeeUpdateDto employeeUpdate,
bool companyTrackChanges, bool employeeTrackChanges);
(EmployeeUpdateDto employeeToPatch, Employee employeeEntity) GetEmployeeForPatch(Guid companyId, Guid employeeId, bool companyTrackChanges, bool employeeTrackChanges);
void SaveChangesForPatch(EmployeeUpdateDto employeeToPatch, Employee employeeEntity);
}

View file

@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Entities\Entities.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>

View file

@ -26,6 +26,7 @@
using AutoMapper;
using Contracts;
using Entities;
using Entities.Exceptions;
using Service.Contracts;
using Shared.DataTransferObjects;
@ -57,10 +58,95 @@ internal sealed class CompanyService : ICompanyService
public CompanyDto GetCompany(Guid companyId, bool trackChanges)
{
var company = _repository.Company.GetCompany(companyId, trackChanges);
if (company == null) throw new CompanyNotFoundException(companyId);
var companyDto = _mapper.Map<CompanyDto>(company);
return companyDto;
switch (company)
{
case null:
throw new CompanyNotFoundException(companyId);
default:
{
var companyDto = _mapper.Map<CompanyDto>(company);
return companyDto;
}
}
}
public CompanyDto CreateCompany(CompanyCreateDto? company)
{
var companyEntity = _mapper.Map<Company>(company);
_repository.Company.CreateCompany(companyEntity);
_repository.Save();
var result = _mapper.Map<CompanyDto>(companyEntity);
return result;
}
public IEnumerable<CompanyDto> GetByIds(IEnumerable<Guid> ids, bool trackChanges)
{
switch (ids)
{
case null:
throw new IdParametersBadRequestException();
}
var entities = _repository.Company.GetByIds(ids, trackChanges);
if (ids.Count() != entities.Count())
throw new CollectionByIdsBadRequestException();
var result = _mapper.Map<IEnumerable<CompanyDto>>(entities);
return result;
}
public (IEnumerable<CompanyDto> companies, string ids) CreateCompanyCollection(IEnumerable<CompanyCreateDto> companyCollection)
{
if (companyCollection is null)
{
throw new CollectionByIdsBadRequestException();
}
var entities = _mapper.Map<IEnumerable<Company>>(companyCollection);
foreach (var company in entities)
{
_repository.Company.CreateCompany(company);
}
_repository.Save();
var result = _mapper.Map<IEnumerable<CompanyDto>>(entities);
var ids = string.Join(",", entities.Select(c => c.Id));
return (result, ids);
}
public void DeleteCompany(Guid companyId, bool trackChanges)
{
var company = _repository.Company.GetCompany(companyId, trackChanges);
switch (company)
{
case null:
throw new CompanyNotFoundException(companyId);
default:
_repository.Company.DeleteCompany(company, trackChanges);
_repository.Save();
break;
}
}
public void UpdateCompany(Guid companyId, CompanyUpdateDto? companyUpdate, bool trackChanges)
{
var companyEntity = _repository.Company.GetCompany(companyId, trackChanges);
switch (companyEntity)
{
case null:
throw new CompanyNotFoundException(companyId);
default:
_mapper.Map(companyUpdate, companyEntity);
_repository.Save();
break;
}
}
}

View file

@ -26,6 +26,7 @@
using AutoMapper;
using Contracts;
using Entities;
using Entities.Exceptions;
using Service.Contracts;
using Shared.DataTransferObjects;
@ -34,9 +35,9 @@ namespace Service;
internal sealed class EmployeeService : IEmployeeService
{
public readonly IRepositoryManager _repository;
public readonly ILoggerManager _logger;
public readonly IMapper _mapper;
private readonly IRepositoryManager _repository;
private readonly ILoggerManager _logger;
private readonly IMapper _mapper;
public EmployeeService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
{
@ -45,33 +46,145 @@ internal sealed class EmployeeService : IEmployeeService
_mapper = mapper;
}
public IEnumerable<EmployeeDto> GetEmployees(Guid companyId, bool trackChanges)
{
var company = _repository.Company.GetCompany(companyId, trackChanges);
if (company == null)
switch (company)
{
throw new CompanyNotFoundException(companyId);
case null:
throw new CompanyNotFoundException(companyId);
}
var employeesFromDb = _repository.Employee.GetEmployees(companyId, trackChanges);
var employeesDto = _mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);
return employeesDto;
var result = _mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);
return result;
}
public EmployeeDto GetEmployee(Guid companyId, Guid employeeId, bool trackChanges)
{
var company = _repository.Company.GetCompany(companyId, trackChanges);
if (company == null)
switch (company)
{
case null:
throw new CompanyNotFoundException(companyId);
}
var employeeDb = _repository.Employee.GetEmployee(companyId, employeeId, trackChanges);
switch (employeeDb)
{
case null:
throw new EmployeeNotFoundException(employeeId);
default:
{
var employee = _mapper.Map<EmployeeDto>(employeeDb);
return employee;
}
}
}
public EmployeeDto CreateEmployeeForCompany(Guid companyId, EmployeeCreateDto? employeeCreateDto, bool trackChanges)
{
var company = _repository.Company.GetCompany(companyId, trackChanges);
switch (company)
{
case null:
throw new CompanyNotFoundException(companyId);
}
var employeeEntity = _mapper.Map<Employee>(employeeCreateDto);
_repository.Employee.CreateEmployeeForCompany(companyId, employeeEntity);
_repository.Save();
var result = _mapper.Map<EmployeeDto>(employeeEntity);
return result;
}
public void DeleteEmployeeForCompany(Guid companyId, Guid employeeId, bool trackChanges)
{
var company = _repository.Company.GetCompany(companyId, trackChanges);
switch (company)
{
case null:
throw new CompanyNotFoundException(companyId);
default:
{
var employee = _repository.Employee.GetEmployee(companyId, employeeId, trackChanges);
switch (employee)
{
case null:
throw new EmployeeNotFoundException(employeeId);
default:
_repository.Employee.DeleteEmployee(employee);
_repository.Save();
break;
}
break;
}
}
}
public void UpdateEmployeeForCompany(Guid companyId, Guid employeeId, EmployeeUpdateDto employeeUpdate,
bool companyTrackChanges, bool employeeTrackChanges)
{
var company = _repository.Company.GetCompany(companyId, companyTrackChanges);
switch (company)
{
case null:
throw new CompanyNotFoundException(companyId);
default:
{
var employee = _repository.Employee.GetEmployee(companyId, employeeId, employeeTrackChanges);
switch (employee)
{
case null:
throw new EmployeeNotFoundException(employeeId);
default:
_mapper.Map(employeeUpdate, employee);
_repository.Save();
break;
}
break;
}
}
}
public (EmployeeUpdateDto employeeToPatch, Employee employeeEntity) GetEmployeeForPatch(Guid companyId,
Guid employeeId, bool companyTrackChanges, bool employeeTrackChanges)
{
var company = _repository.Company.GetCompany(companyId, companyTrackChanges);
if (company is null)
{
throw new CompanyNotFoundException(companyId);
}
var employeeDb = _repository.Employee.GetEmployee(companyId, employeeId, trackChanges);
if (employeeDb == null)
var employeeEntity = _repository.Employee.GetEmployee(companyId, employeeId, employeeTrackChanges);
if (employeeEntity is null)
{
throw new EmployeeNotFoundException(employeeId);
}
var employee = _mapper.Map<EmployeeDto>(employeeDb);
return employee;
var employeeToPatch = _mapper.Map<EmployeeUpdateDto>(employeeEntity);
return (employeeToPatch, employeeEntity);
}
public void SaveChangesForPatch(EmployeeUpdateDto employeeToPatch, Employee employeeEntity)
{
_mapper.Map(employeeToPatch, employeeEntity);
_repository.Save();
}
}

View file

@ -26,4 +26,4 @@
namespace Shared.DataTransferObjects;
public record CompanyInputDto(string Name, string Address, string Country);
public record CompanyCreateDto(string Name, string Address, string Country, IEnumerable<EmployeeCreateDto> Employees);

View file

@ -28,8 +28,8 @@ namespace Shared.DataTransferObjects
{
public record CompanyDto
{
public Guid CompanyId { get; init; }
public string Name { get; init; } = "";
public string FullAddress { get; init; } = "";
};
public Guid Id { get; init; }
public string? Name { get; init; }
public string? FullAddress { get; init; }
}
}

View file

@ -0,0 +1,3 @@
namespace Shared.DataTransferObjects;
public record CompanyUpdateDto(string Name, string Address, string Country, IEnumerable<EmployeeCreateDto> Employees);

View file

@ -26,4 +26,4 @@
namespace Shared.DataTransferObjects;
public record EmployeeInputDto();
public record EmployeeCreateDto(string Name, int Age, string Position);

View file

@ -28,7 +28,7 @@ namespace Shared.DataTransferObjects;
public record EmployeeDto
{
public Guid EmployeeId { get; init; }
public Guid Id { get; init; }
public string Name { get; init; } = "";
public int Age { get; init; }
public string Position { get; init; } = "";

View file

@ -0,0 +1,3 @@
namespace Shared.DataTransferObjects;
public record EmployeeUpdateDto(string Name, int Age, string Position);

View file

@ -2,10 +2,13 @@
using Microsoft.AspNetCore.Mvc;
using Service.Contracts;
using Shared.DataTransferObjects;
using Ultimate.Presentation.ModelBinders;
namespace Ultimate.Presentation.Controllers
{
[Route("api/v3/companies")]
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly IServiceManager _service;
@ -15,19 +18,71 @@ namespace Ultimate.Presentation.Controllers
_service = service;
}
[HttpGet]
public IActionResult GetCompanies()
{
var companies = _service.CompanyService.GetAllCompanies(trackChanges: false);
return Ok(companies);
}
[HttpGet("{id:guid}")]
[HttpGet("{id:guid}", Name = "GetCompanyById")]
public IActionResult GetCompany(Guid id) {
{
var company = _service.CompanyService.GetCompany(id, trackChanges: false);
return Ok(company);
}}
}
[HttpPost]
public IActionResult CreateCompany([FromBody] CompanyCreateDto? company)
{
if (company is null)
{
return BadRequest("Object cannot be null");
}
var result = _service.CompanyService.CreateCompany(company);
return CreatedAtRoute("GetCompanyById", new { id = result.Id }, result);
}
[HttpGet("collection/({ids})", Name = "CompanyCollection")]
public IActionResult GetCompanyCollection([ModelBinder(BinderType = typeof(ArrayModelBinder))] IEnumerable<Guid> ids)
{
var companies = _service.CompanyService.GetByIds(ids, trackChanges: false);
return Ok(companies);
}
[HttpPost("collection")]
public IActionResult CreateCompanyCollection([FromBody] IEnumerable<CompanyCreateDto> collection)
{
var result = _service.CompanyService.CreateCompanyCollection(collection);
return CreatedAtRoute("CompanyCollection", new { result.ids }, result.companies);
}
[HttpDelete("{companyId:guid}", Name = "DeleteCompany")]
public IActionResult DeleteCompany(Guid companyId)
{
_service.CompanyService.DeleteCompany(companyId, trackChanges: false);
return NoContent();
}
[HttpPut("{companyId:guid}", Name = "UpdateCompany")]
public IActionResult UpdateCompany(Guid companyId, [FromBody] CompanyUpdateDto? company)
{
if (company is null)
{
return BadRequest("Company update data is null");
}
_service.CompanyService.UpdateCompany(companyId, company, trackChanges:true);
return NoContent();
}
}

View file

@ -24,12 +24,14 @@
// <summary></summary>
// ***********************************************************************
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using Service.Contracts;
using Shared.DataTransferObjects;
namespace Ultimate.Presentation.Controllers;
[Route("api/v3/companies/{companyId}/employees")]
[Route("api/companies/{companyId}/employees")]
public class EmployeesController : ControllerBase
{
private readonly IServiceManager _service;
@ -39,6 +41,7 @@ public class EmployeesController : ControllerBase
_service = service;
}
[HttpGet]
public IActionResult GetEmployeesForCompany(Guid companyId)
{
@ -46,10 +49,63 @@ public class EmployeesController : ControllerBase
return Ok(employees);
}
[HttpGet("{employeeId:guid}")]
[HttpGet("{employeeId:guid}", Name = "GetEmployeeForCompany")]
public IActionResult GetEmployeeForCompany(Guid companyId, Guid employeeId)
{
var employee = _service.EmployeeService.GetEmployee(companyId, employeeId, trackChanges: false);
return Ok(employee);
}
[HttpPost]
public IActionResult CreateEmployeeForCompany(Guid companyId, [FromBody] EmployeeCreateDto? employee)
{
if (employee == null)
{
return BadRequest("Employee is null");
}
var result = _service.EmployeeService.CreateEmployeeForCompany(companyId, employee, trackChanges: false);
return CreatedAtRoute("GetEmployeeForCompany", new { companyId, employeeId = result.Id }, result);
}
[HttpDelete("{employeeId:guid", Name = "DeleteEmployeeForCompany")]
public IActionResult DeleteEmployeeForCompany(Guid companyId, Guid employeeId)
{
_service.EmployeeService.DeleteEmployeeForCompany(companyId, employeeId, trackChanges:false);
return NoContent();
}
[HttpPut("{employeeId:guid", Name = "UpdateEmployeeForCompany")]
public IActionResult UpdateEmployeeForCompany(Guid companyId, Guid employeeId,
[FromBody] EmployeeUpdateDto? employee)
{
if (employee is null)
{
return BadRequest("Employee data object is null");
}
_service.EmployeeService.UpdateEmployeeForCompany(companyId, employeeId, employee, companyTrackChanges:false, employeeTrackChanges:true);
return NoContent();
}
[HttpPatch("{employeeId:guid", Name = "PatchEmployeeForCompany")]
public IActionResult PatchEmployeeForCompany(Guid companyId, Guid employeeId,
[FromBody] JsonPatchDocument<EmployeeUpdateDto>? patchDoc)
{
if (patchDoc is null)
return BadRequest("patchDoc object is null");
var (employeeToPatch, employeeEntity) = _service.EmployeeService
.GetEmployeeForPatch(companyId, employeeId, companyTrackChanges: false, employeeTrackChanges: true);
patchDoc.ApplyTo(employeeToPatch);
_service.EmployeeService.SaveChangesForPatch(employeeToPatch, employeeEntity);
return NoContent();
}
}

View file

@ -0,0 +1,39 @@
using System.ComponentModel;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Ultimate.Presentation.ModelBinders;
public class ArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (!bindingContext.ModelMetadata.IsEnumerableType)
{
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
var providedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
if (string.IsNullOrEmpty(providedValue))
{
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}
var genericType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
var converter = TypeDescriptor.GetConverter(genericType);
var objectArray = providedValue
.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => converter.ConvertFromString(x.Trim()))
.ToArray();
var guidArray = Array.CreateInstance(genericType, objectArray.Length);
objectArray.CopyTo(guidArray, 0);
bindingContext.Model = guidArray;
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.CompletedTask;
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
</ItemGroup>

2
Ultimate.sln.DotSettings Normal file
View file

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -70,6 +70,6 @@ public class CsvOutputFormatter : TextOutputFormatter
private static void FormatCsv(StringBuilder buffer, CompanyDto company)
{
buffer.AppendLine($"{company.CompanyId},\"{company.Name}\",\"{company.FullAddress}\"");
buffer.AppendLine($"{company.Id},\"{company.Name}\",\"{company.FullAddress}\"");
}
}

View file

@ -41,17 +41,22 @@ public static class ExceptionMiddlewareExtensions
appError.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
context.Response.StatusCode = contextFeature.Error switch
{
NotFoundException => StatusCodes.Status404NotFound,
BadRequestException => StatusCodes.Status400BadRequest,
_ => StatusCodes.Status500InternalServerError
};
logger.LogError($"Exception occurred: {contextFeature.Error}");
await context.Response.WriteAsync(new ErrorDetails
{
StatusCode = context.Response.StatusCode,

View file

@ -51,12 +51,15 @@ public static class ServiceExtensions
public static void ConfigureLoggerService(this IServiceCollection services) =>
services.AddSingleton<ILoggerManager, LoggerManager>();
public static void ConfigureRepositoryManager(this IServiceCollection services) =>
services.AddScoped<IRepositoryManager, RepositoryManager>();
public static void ConfigureServiceManager(this IServiceCollection services) =>
services.AddScoped<IServiceManager, ServiceManager>();
public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration configuration) =>
services.AddDbContext<RepositoryContext>(options =>
options.UseMySql(
@ -64,6 +67,7 @@ public static class ServiceExtensions
new MariaDbServerVersion(configuration.GetValue("MariaDbVersion", "11.0.2")))
);
public static IMvcBuilder AddCustomCsvFormatter(this IMvcBuilder builder) =>
builder.AddMvcOptions(config =>
config.OutputFormatters.Add(new CsvOutputFormatter()));

View file

@ -34,9 +34,16 @@ public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Company, CompanyDto>().ForMember(c => c.FullAddress, opt =>
opt.MapFrom(x => string.Join(' ', x.Address, x.Country)));
CreateMap<Company, CompanyDto>()
.ForMember(c => c.FullAddress, opt =>
opt.MapFrom(x => string.Join(' ', x.Address, x.Country)));
CreateMap<Employee, EmployeeDto>();
CreateMap<CompanyCreateDto, Company>();
CreateMap<EmployeeCreateDto, Employee>();
CreateMap<EmployeeUpdateDto, Employee>().ReverseMap();
}
}

View file

@ -11,7 +11,7 @@ using Repository;
namespace Ultimate.Migrations
{
[DbContext(typeof(RepositoryContext))]
[Migration("20230611100310_DatabaseCreate")]
[Migration("20230912082832_DatabaseCreate")]
partial class DatabaseCreate
{
/// <inheritdoc />
@ -24,9 +24,10 @@ namespace Ultimate.Migrations
modelBuilder.Entity("Entities.Company", b =>
{
b.Property<Guid>("CompanyId")
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
.HasColumnType("char(36)")
.HasColumnName("CompanyId");
b.Property<string>("Address")
.IsRequired()
@ -41,16 +42,17 @@ namespace Ultimate.Migrations
.HasMaxLength(60)
.HasColumnType("varchar(60)");
b.HasKey("CompanyId");
b.HasKey("Id");
b.ToTable("Companies");
});
modelBuilder.Entity("Entities.Employee", b =>
{
b.Property<Guid>("EmployeeId")
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
.HasColumnType("char(36)")
.HasColumnName("EmployeeId");
b.Property<int>("Age")
.HasColumnType("int");
@ -68,7 +70,7 @@ namespace Ultimate.Migrations
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.HasKey("EmployeeId");
b.HasKey("Id");
b.HasIndex("CompanyId");

View file

@ -11,8 +11,8 @@ using Repository;
namespace Ultimate.Migrations
{
[DbContext(typeof(RepositoryContext))]
[Migration("20230611104028_SeedTestData")]
partial class SeedTestData
[Migration("20230912082930_InitialData")]
partial class InitialData
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -24,9 +24,10 @@ namespace Ultimate.Migrations
modelBuilder.Entity("Entities.Company", b =>
{
b.Property<Guid>("CompanyId")
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
.HasColumnType("char(36)")
.HasColumnName("CompanyId");
b.Property<string>("Address")
.IsRequired()
@ -41,21 +42,21 @@ namespace Ultimate.Migrations
.HasMaxLength(60)
.HasColumnType("varchar(60)");
b.HasKey("CompanyId");
b.HasKey("Id");
b.ToTable("Companies");
b.HasData(
new
{
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
Address = "583 Wall Dr. Gwynn Oak, MD 21207",
Country = "USA",
Name = "IT_Solutions Ltd"
},
new
{
CompanyId = new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"),
Address = "312 Forest Avenue, BF 923",
Country = "USA",
Name = "Admin_Solutions Ltd"
@ -64,9 +65,10 @@ namespace Ultimate.Migrations
modelBuilder.Entity("Entities.Employee", b =>
{
b.Property<Guid>("EmployeeId")
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
.HasColumnType("char(36)")
.HasColumnName("EmployeeId");
b.Property<int>("Age")
.HasColumnType("int");
@ -84,7 +86,7 @@ namespace Ultimate.Migrations
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.HasKey("EmployeeId");
b.HasKey("Id");
b.HasIndex("CompanyId");
@ -93,25 +95,25 @@ namespace Ultimate.Migrations
b.HasData(
new
{
EmployeeId = new Guid("12341234-1111-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("80abbca8-664d-4b20-b5de-024705497d4a"),
Age = 26,
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"),
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
Name = "Sam Raiden",
Position = "Software developer"
},
new
{
EmployeeId = new Guid("12341234-2222-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"),
Age = 30,
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"),
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
Name = "Jana McLeaf",
Position = "Software developer"
},
new
{
EmployeeId = new Guid("56785678-1111-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"),
Age = 35,
CompanyId = new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc"),
CompanyId = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"),
Name = "Kane Miller",
Position = "Administrator"
});

View file

@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace Ultimate.Migrations
{
/// <inheritdoc />
public partial class SeedTestData : Migration
public partial class InitialData : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@ -18,8 +18,8 @@ namespace Ultimate.Migrations
columns: new[] { "CompanyId", "Address", "Country", "Name" },
values: new object[,]
{
{ new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"), "583 Wall Dr. Gwynn Oak, MD 21207", "USA", "IT_Solutions Ltd" },
{ new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc"), "312 Forest Avenue, BF 923", "USA", "Admin_Solutions Ltd" }
{ new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"), "312 Forest Avenue, BF 923", "USA", "Admin_Solutions Ltd" },
{ new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"), "583 Wall Dr. Gwynn Oak, MD 21207", "USA", "IT_Solutions Ltd" }
});
migrationBuilder.InsertData(
@ -27,9 +27,9 @@ namespace Ultimate.Migrations
columns: new[] { "EmployeeId", "Age", "CompanyId", "Name", "Position" },
values: new object[,]
{
{ new Guid("12341234-1111-eeee-eeee-aaaabbbbcccc"), 26, new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"), "Sam Raiden", "Software developer" },
{ new Guid("12341234-2222-eeee-eeee-aaaabbbbcccc"), 30, new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"), "Jana McLeaf", "Software developer" },
{ new Guid("56785678-1111-eeee-eeee-aaaabbbbcccc"), 35, new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc"), "Kane Miller", "Administrator" }
{ new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"), 35, new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"), "Kane Miller", "Administrator" },
{ new Guid("80abbca8-664d-4b20-b5de-024705497d4a"), 26, new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"), "Sam Raiden", "Software developer" },
{ new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"), 30, new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"), "Jana McLeaf", "Software developer" }
});
}
@ -39,27 +39,27 @@ namespace Ultimate.Migrations
migrationBuilder.DeleteData(
table: "Employees",
keyColumn: "EmployeeId",
keyValue: new Guid("12341234-1111-eeee-eeee-aaaabbbbcccc"));
keyValue: new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"));
migrationBuilder.DeleteData(
table: "Employees",
keyColumn: "EmployeeId",
keyValue: new Guid("12341234-2222-eeee-eeee-aaaabbbbcccc"));
keyValue: new Guid("80abbca8-664d-4b20-b5de-024705497d4a"));
migrationBuilder.DeleteData(
table: "Employees",
keyColumn: "EmployeeId",
keyValue: new Guid("56785678-1111-eeee-eeee-aaaabbbbcccc"));
keyValue: new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"));
migrationBuilder.DeleteData(
table: "Companies",
keyColumn: "CompanyId",
keyValue: new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"));
keyValue: new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"));
migrationBuilder.DeleteData(
table: "Companies",
keyColumn: "CompanyId",
keyValue: new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc"));
keyValue: new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"));
}
}
}

View file

@ -21,9 +21,10 @@ namespace Ultimate.Migrations
modelBuilder.Entity("Entities.Company", b =>
{
b.Property<Guid>("CompanyId")
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
.HasColumnType("char(36)")
.HasColumnName("CompanyId");
b.Property<string>("Address")
.IsRequired()
@ -38,21 +39,21 @@ namespace Ultimate.Migrations
.HasMaxLength(60)
.HasColumnType("varchar(60)");
b.HasKey("CompanyId");
b.HasKey("Id");
b.ToTable("Companies");
b.HasData(
new
{
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
Address = "583 Wall Dr. Gwynn Oak, MD 21207",
Country = "USA",
Name = "IT_Solutions Ltd"
},
new
{
CompanyId = new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"),
Address = "312 Forest Avenue, BF 923",
Country = "USA",
Name = "Admin_Solutions Ltd"
@ -61,9 +62,10 @@ namespace Ultimate.Migrations
modelBuilder.Entity("Entities.Employee", b =>
{
b.Property<Guid>("EmployeeId")
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
.HasColumnType("char(36)")
.HasColumnName("EmployeeId");
b.Property<int>("Age")
.HasColumnType("int");
@ -81,7 +83,7 @@ namespace Ultimate.Migrations
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.HasKey("EmployeeId");
b.HasKey("Id");
b.HasIndex("CompanyId");
@ -90,25 +92,25 @@ namespace Ultimate.Migrations
b.HasData(
new
{
EmployeeId = new Guid("12341234-1111-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("80abbca8-664d-4b20-b5de-024705497d4a"),
Age = 26,
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"),
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
Name = "Sam Raiden",
Position = "Software developer"
},
new
{
EmployeeId = new Guid("12341234-2222-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"),
Age = 30,
CompanyId = new Guid("12341234-aaaa-eeee-eeee-aaaabbbbcccc"),
CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
Name = "Jana McLeaf",
Position = "Software developer"
},
new
{
EmployeeId = new Guid("56785678-1111-eeee-eeee-aaaabbbbcccc"),
Id = new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"),
Age = 35,
CompanyId = new Guid("56785678-aaaa-eeee-eeee-aaaabbbbcccc"),
CompanyId = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"),
Name = "Kane Miller",
Position = "Administrator"
});

View file

@ -1,5 +1,8 @@
using Contracts;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Options;
using NLog;
using Ultimate.Extensions;
@ -15,16 +18,23 @@ builder.Services.ConfigureServiceManager();
builder.Services.ConfigureSqlContext(builder.Configuration);
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
builder.Services.AddControllers(config =>
{
config.RespectBrowserAcceptHeader = true;
config.ReturnHttpNotAcceptable = true;
config.InputFormatters.Insert(0, GetJsonPatchInputFormatter());
})
.AddXmlDataContractSerializerFormatters()
.AddCustomCsvFormatter()
.AddApplicationPart(typeof(Ultimate.Presentation.AssemblyReference).Assembly);
var app = builder.Build();
var logger = app.Services.GetRequiredService<ILoggerManager>();
app.ConfigureExceptionHandler(logger);
@ -48,3 +58,17 @@ app.UseAuthorization();
app.MapControllers();
app.Run();
return;
NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter() =>
new ServiceCollection()
.AddLogging()
.AddMvc()
.AddNewtonsoftJson()
.Services
.BuildServiceProvider()
.GetRequiredService<IOptions<MvcOptions>>()
.Value
.InputFormatters
.OfType<NewtonsoftJsonPatchInputFormatter>()
.First();

View file

@ -13,7 +13,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "weatherforecast",
"launchUrl": "",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>