Chapter 6 Getting Additional Resources

Chapter 6 Getting Additional Resources

As of now, we can continue with GET requests by adding additional actions to our controller. Moreover, we are going to create one more controller for the Employee resource and implement an additional action in it.
截至目前,我们可以通过向控制器添加其他操作来继续处理 GET 请求。此外,我们将为Employee资源再创建一个控制器,并在其中实现一个附加操作。

6.1 Getting a Single Resource From the Database

Let’s start by modifying the ICompanyRepository interface:
让我们从修改 ICompanyRepository 接口开始:

// ICompanyRepository.cs

using System;
using System.Collections.Generic;
using Entities.Models;

namespace Contracts
{
    public interface ICompanyRepository
    {
        IEnumerable<Company> GetAllCompanies(bool trackChanges);
        Company GetCompany(Guid companyId, bool trackChanges);
    }

}

Then, we are going to implement this interface in the CompanyRepository.cs file:
然后,我们将在CompanyRepository.cs文件中实现此接口

// CompanyRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Contracts;
using Entities;
using Entities.Models;

namespace Repository
{
    public class CompanyRepository : RepositoryBase<Company>, ICompanyRepository
    {
        public CompanyRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }
        public IEnumerable<Company> GetAllCompanies(bool trackChanges) =>
            FindAll(trackChanges).OrderBy(c=>c.Name).ToList();

        public Company GetCompany(Guid companyId, bool trackChanges) => 
            FindByCondition(c => 
            c.Id.Equals(companyId), trackChanges)
            .SingleOrDefault();
    }
}

Finally, let’s change the CompanyController class:
最后,让我们更改 CompanyController 类:

//CompaniesController.cs

        [HttpGet("{id}")]
        public IActionResult GetCompany(Guid id)
        {
            var company = _repository.Company.GetCompany(id, trackChanges: false);
            if (company == null)
            {
                _logger.LogInfo($"Company with id: {id} doesn't exist in the database.");
                return NotFound();
            }
            else
            {
                var companyDto = _mapper.Map<CompanyDto>(company);
                return Ok(companyDto);
            }
        }

The route for this action is /api/companies/id and that’s because the /api/companies part applies from the root route (on top of the controller) and the id part is applied from the action attribute [HttpGet(“{id}“)].
此操作的路由是 /api/companies/id,这是因为/api/companies部分从根路由(在控制器顶部)和 ID 部分从操作属性应用[HttpGet(“{id}“)]。

So, our action returns IActionResult, like the previous one, and we fetch a single company from the database. If it doesn’t exist, we use the NotFound method to return a 404 status code. From this example, we can see that ASP.NET Core provides us with a variety of semantical methods that state what we can use them for, just by reading their names. The Ok method is for the good result (status code 200) and the NotFound method is for the NotFound result (status code 404).

因此,我们的操作返回 IActionResult,就像上一个一样,我们从数据库中获取单个公司。如果它不存在,我们使用 NotFound 方法返回 404 状态代码。从这个例子中,我们可以看到 ASP.NET Core为我们提供了多种语义方法,只需读取它们的名称即可说明我们可以将它们用于什么目的。Ok 方法用于良好结果(状态代码 200),NotFound 方法用于 NotFound 结果(状态代码 404)。

If a company exists in the database, we just map it to the CompanyDto type and return it to the client.
如果数据库中存在一家公司,我们只需将其映射到 CompanyDto键入并将其返回给客户端。

Let’s use Postman to send valid and invalid requests towards our API:
让我们使用 Postman 向我们的 API 发送有效和无效的请求:
https://localhost:5001/api/companies/3d490a70-94ce-4d15-9494-5248280c2ce3

Invalid request:
无效请求:

https://localhost:5001/api/companies/3d490a70-94ce-4d15-9494-5248280c2ce2

6.2 Parent/Child Relationships in WebAPI

Up until now, we have been working only with the company, which is a parent (principal) entity in our API. But for each company, we have a related employee (dependent entity). Every employee must be related to a certain company and we are going to create our URIs in that manner.
到目前为止,我们一直只与公司合作,该公司是我们API中的父(主体)实体。但是对于每个公司,我们都有一个相关的员工(依赖实体)。每个员工都必须与某个公司相关,我们将以这种方式创建我们的 URI。

That said, let’s create a new controller and name it EmployeesController:
也就是说,让我们创建一个新的控制器并命名它EmployeesController:

//EmployeesController.cs

using AutoMapper;
using Contracts;
using Microsoft.AspNetCore.Mvc;

[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
    private readonly IRepositoryManager _repository; 
    private readonly ILoggerManager _logger;
    private readonly IMapper _mapper;

    public EmployeesController(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
    {
        _repository = repository; _logger = logger; _mapper = mapper;
    }
}

We are familiar with this code, but our main route is a bit different. As we said, a single employee can’t exist without a company entity and this is exactly what are we exposing through this URI. To get an employee or employees from the database, we have to specify the companyId parameter, and that is something all actions will have in common. For that reason, we have specified this route as our root route.
我们熟悉此代码,但我们的主要路由有点不同。正如我们所说,没有公司实体,单个员工就无法存在,这是我们究竟通过这个 URI 公开了什么。要从数据库中获取一个或多个员工,我们必须指定 companyId 参数,这是所有操作的共同点。因此,我们已将此路由指定为根路由。

Before we create an action to fetch all the employees per company, we have to modify the IEmployeeRepository interface:

在我们创建一个操作来获取每个公司的所有员工之前,我们必须修改 IEmployeeRepository 接口:


//IEmployeeRepository.cs
using System;
using System.Collections.Generic;
using Entities.Models;

namespace Contracts
{
    public interface IEmployeeRepository
    {
        IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges);
    }
}

After interface modification, we are going to modify the EmployeeRepository class:

接口修改后,我们将修改EmployeeRepository类:

//EmployeeRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Contracts;
using Entities;
using Entities.Models;

namespace Repository
{
    public class EmployeeRepository : RepositoryBase<Employee>, IEmployeeRepository
    {
        public EmployeeRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }

        public IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges) => 
            FindByCondition(e => 
            e.CompanyId.Equals(companyId), trackChanges).
            OrderBy(e => e.Name);
    }
}

Finally, let’s modify the Employees controller:
最后,让我们修改Employees控制器:

//EmployeesController.cs

using System;
using AutoMapper;
using Contracts;
using Microsoft.AspNetCore.Mvc;

[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
    private readonly IRepositoryManager _repository; 
    private readonly ILoggerManager _logger;
    private readonly IMapper _mapper;

    public EmployeesController(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
    {
        _repository = repository; _logger = logger; _mapper = mapper;
    }


    [HttpGet]
    public IActionResult GetEmployeesForCompany(Guid companyId)
    {
        var company = _repository.Company.GetCompany(companyId, trackChanges: false); 
        if (company == null)
        {
            _logger.LogInfo($"Company with id: {companyId} doesn't exist in the database.");
            return NotFound();
        }
        var employeesFromDb = _repository.Employee.GetEmployees(companyId, trackChanges: false);
        return Ok(employeesFromDb);
    }
}

This code is pretty straightforward — nothing we haven’t seen so far — but we need to explain just one thing. As you can see, we have the companyId parameter in our action and this parameter will be mapped from the main route. For that reason, we didn’t place it in the [HttpGet] attribute as we did with the GetCompany action.
这段代码非常简单——到目前为止我们还没有看到——但我们只需要解释一件事。如您所见,我们的操作中有 companyId 参数,此参数将从主路由映射。出于这个原因,我们没有像使用 GetCompany 操作那样将其放在 [HttpGet] 属性中。

But, we know that something is wrong here because we are using a model in our response and not a data transfer object. To fix that, let’s add another class in the DataTransferObjects folder:

但是,我们知道这里有问题,因为我们在响应中使用了模型而不是数据传输对象。为了解决这个问题,让我们在 DataTransferObjects 文件夹中添加另一个类:

//EmployeeDto.cs

using System;

namespace Entities.DataTransferObjects
{
    public class EmployeeDto
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Position { get; set; }
    }
}

After that, let’s create another mapping rule:
之后,让我们创建另一个映射规则:

//MappingProfile.cs

using AutoMapper;
using Entities.DataTransferObjects;
using Entities.Models;

namespace CompanyEmployees
{
    public class MappingProfile:Profile
    {
        public MappingProfile() {
            CreateMap<Company, CompanyDto>().ForMember(c => 
            c.FullAddress, opt => opt.MapFrom(x => string.Join(' ', x.Address, x.Country)));

            CreateMap<Employee, EmployeeDto>();
        }
    }
}

Finally, let’s modify our action:
最后,让我们修改我们的操作:

//EmployeesController.cs

using System;
using System.Collections.Generic;
using AutoMapper;
using Contracts;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Mvc;

[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
    private readonly IRepositoryManager _repository; 
    private readonly ILoggerManager _logger;
    private readonly IMapper _mapper;

    public EmployeesController(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
    {
        _repository = repository; _logger = logger; _mapper = mapper;
    }


    //[HttpGet]
    //public IActionResult GetEmployeesForCompany(Guid companyId)
    //{
    //    var company = _repository.Company.GetCompany(companyId, trackChanges: false); 
    //    if (company == null)
    //    {
    //        _logger.LogInfo($"Company with id: {companyId} doesn't exist in the database.");
    //        return NotFound();
    //    }
    //    var employeesFromDb = _repository.Employee.GetEmployees(companyId, trackChanges: false);
    //    return Ok(employeesFromDb);
    //}

    [HttpGet]
    public IActionResult GetEmployeesForCompany(Guid companyId)
    {
        var company = _repository.Company.GetCompany(companyId, trackChanges: false);
        if (company == null)
        {
            _logger.LogInfo($"Company with id: {companyId} doesn't exist in the database."); 
            return NotFound();
        }
        var employeesFromDb = _repository.Employee.GetEmployees(companyId, trackChanges: false); 
        var employeesDto = _mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);
        return Ok(employeesDto);
    }
}

That done, we can send a request with a valid companyId:
完成后,我们可以发送具有有效公司 ID 的请求:

https://localhost:5001/api/companies/c9d4c053-49b6-410c-bc78-2d54a9991870/employees

And with an invalid companyId:
发送无效公司ID :

https://localhost:5001/api/companies/c9d4c053-49b6-410c-bc78-2d54a9991873/employees

Alt text

Excellent. Let’s continue by fetching a single employee.
非常好。让我们继续获取一个员工。

6.3 Getting a Single Employee for Company

So, as we did in previous sections, let’s start with an interface modification:
因此,正如我们在前面的部分中所做的那样,让我们从接口修改开始:

//IEmployeeRepository.cs

using System;
using System.Collections.Generic;
using Entities.Models;

namespace Contracts
{

    public interface IEmployeeRepository
    {
        IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges);
        Employee GetEmployee(Guid companyId, Guid id, bool trackChanges);
    }
}

Now, let’s implement this method in the EmployeeRepository class:
现在,让我们在 EmployeeRepository 类中实现此方法:

//EmployeeRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Contracts;
using Entities;
using Entities.Models;

namespace Repository
{
    public class EmployeeRepository : RepositoryBase<Employee>, IEmployeeRepository
    {
        public EmployeeRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }

        public IEnumerable<Employee> GetEmployees(Guid companyId, bool trackChanges) => 
            FindByCondition(e => 
            e.CompanyId.Equals(companyId), trackChanges)
            .OrderBy(e => e.Name);

        public Employee GetEmployee(Guid companyId, Guid id, bool trackChanges) => 
            FindByCondition(e => 
            e.CompanyId.Equals(companyId) && e.Id.Equals(id), trackChanges)
            .SingleOrDefault();
    }
}

Finally, let’s modify the EmployeeController class:
最后,让我们修改 EmployeeController 类:

//EmployeesController.cs

using System;
using System.Collections.Generic;
using AutoMapper;
using Contracts;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Mvc;

[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase
{
    ......
    ......

    [HttpGet("{id}")]
    public IActionResult GetEmployeeForCompany(Guid companyId, Guid id)
    {
        var company = _repository.Company.GetCompany(companyId, trackChanges: false);
        if (company == null)
        {
            _logger.LogInfo($"Company with id: {companyId} doesn't exist in the database.");
            return NotFound();
        }
        var employeeDb = _repository.Employee.GetEmployee(companyId, id, trackChanges: false);
        if (employeeDb == null)
        {
            _logger.LogInfo($"Employee with id: {id} doesn't exist in the database.");
            return NotFound();
        }
        var employee = _mapper.Map<EmployeeDto>(employeeDb);
        return Ok(employee);
    }
}

Excellent.
非常好。

We can test this action by using already created requests from the Bonus 2-CompanyEmployeesRequests.postman_collection.json file placed in the folder with the exercise files:
我们可以使用来自 Bonus 2-CompanyEmployeesRequests.postman_collection.json 文件中已创建的请求来测试此操作,该文件放置在包含练习文件的文件夹中:

https://localhost:5001/api/companies/c9d4c053-49b6-410c-bc78-2d54a9991870/employees/86dba8c0-d178-41E7-938C-ED49778FB52A

Alt text

When we send the request with an invalid company or employee id:
当我们使用无效的公司或员工ID 发送请求时:

https://localhost:5001/api/companies/c9d4c053-49b6-410c-bc78-2d54a9991870/employees/86dba8c0-d178-41E7-938C-ED49778FB52C

Alt text

Our results are pretty self-explanatory.
我们的结果是不言自明的。

Until now, we have received only JSON formatted responses from our API. But what if we want to support some other format, like XML for example?
到目前为止,我们只收到来自 API 的 JSON 格式响应。但是,如果我们想支持其他格式,例如XML,该怎么办?

Well, in the next chapter we are going to learn more about Content Negotiation and enabling different formats for our responses.
好吧,在下一章中,我们将了解有关内容协商和为我们的响应启用不同格式的更多信息。

发表评论