While creating our company, we created the DTO object required for the CreateCompany
action. So, for employee creation, we are going to do the same thing:
namespace Shared.DataTransferObjects; public record EmployeeForCreationDto(string Name, int Age, string Position);
We don’t have the Id property because we will create it on the server side. Additionally, we don’t have the CompanyId
property because we accept that parameter through the route: [Route(“api/companies/{companyId}/employees”)]. The next step is to modify the IEmployeeRepository
interface:
public interface IEmployeeRepository { IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges); Employee GetEmployee(Guid companyId, Guid id, bool trackChanges); void CreateEmployeeForCompany(Guid companyId, Employee employee); }
Of course, we have to implement this interface:
public void CreateEmployeeForCompany(Guid companyId, Employee employee) { employee.CompanyId = companyId; Create(employee); }
We will accept the employee DTO object in our action and send it to this service method. But from this service method, we send an employee object to this repository method. Therefore, we must create an additional mapping rule in the MappingProfile
class:
CreateMap<EmployeeForCreationDto, Employee>();
The next step is IEmployeeService
modification:
public interface IEmployeeService { IEnumerable<EmployeeDto> GetEmployees(Guid companyId, bool trackChanges); EmployeeDto GetEmployee(Guid companyId, Guid id, bool trackChanges); EmployeeDto CreateEmployeeForCompany(Guid companyId, EmployeeForCreationDto employeeForCreation, bool trackChanges); }
And implement this new method in EmployeeService
:
public EmployeeDto CreateEmployeeForCompany(Guid companyId, EmployeeForCreationDto employeeForCreation, bool trackChanges) { var company = _repository.Company.GetCompany(companyId, trackChanges); if (company is null) throw new CompanyNotFoundException(companyId); var employeeEntity = _mapper.Map<Employee>(employeeForCreation); _repository.Employee.CreateEmployeeForCompany(companyId, employeeEntity); _repository.Save(); var employeeToReturn = _mapper.Map<EmployeeDto>(employeeEntity); return employeeToReturn; }
We have to check whether that company exists in the database because there is no point in creating an employee for a company that does not exist. After that, we map the DTO to an entity, call the repository methods to create a new employee, map back the entity to the DTO, and return it to the caller.
Now, we can add a new action in the EmployeesController
:
[HttpPost] public IActionResult CreateEmployeeForCompany(Guid companyId, [FromBody] EmployeeForCreationDto employee) { if (employee is null) return BadRequest("EmployeeForCreationDto object is null"); var employeeToReturn = _service.EmployeeService.CreateEmployeeForCompany(companyId, employee, trackChanges: false); return CreatedAtRoute("GetEmployeeForCompany", new { companyId, id = employeeToReturn.Id }, employeeToReturn); }
As we can see, the main difference between this action and the CreateCompany
action (if we exclude the fact that we are working with different DTOs) is the return statement, which now has two parameters for the anonymous object. For this to work, we have to modify the HTTP attribute above the GetEmployeeForCompany
action:
[HttpGet("{id:guid}", Name = "GetEmployeeForCompany")] public IActionResult GetEmployeeForCompany(Guid companyId, Guid id)
Let’s give this a try using the id from the company we created:
curl --location 'https://localhost:5001/api/companies/034d516a-e683-4c90-6789-08dcfa6b1471/employees' --header 'Content-Type: application/json' --data '{ "name": "Martin Geil", "age": 29, "position": "Marketing expert" }'
Excellent. A new employee was created. If we take a look at the Headers
tab, we’ll see a link to fetch our newly created employee.
If you copy that link and send another request with it, you will get this employee for sure:
curl --location 'https://localhost:5001/api/companies/034d516a-e683-4c90-6789-08dcfa6b1471/employees/f4945adb-2d3d-4c39-9303-08dcfa727e49'