Ultimate ASP.NET Core Web API 6 GETTING ADDITIONAL RESOURCES

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

6.1 从数据库中获取单个资源

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

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 文件中实现这个接口:

using Contracts;
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();
    }
}

Then, we have to modify the ICompanyService interface:
然后,我们必须修改 ICompanyService 接口:

using Shared;

namespace Service.Contracts
{
    public interface ICompanyService
    {
        IEnumerable<CompanyDto> GetAllCompanies(bool trackChanges);
        CompanyDto GetCompany(Guid companyId, bool trackChanges);
    }
}

And of course, we have to implement this interface in the CompanyService class:
当然,我们必须在 CompanyService 类中实现此接口:

using AutoMapper;
using Contracts;
using Service.Contracts;
using Shared;

namespace Service
{
    internal sealed class CompanyService : ICompanyService
    {
        private readonly IRepositoryManager _repository;
        private readonly ILoggerManager _logger;
        private readonly IMapper _mapper;
        public CompanyService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
        {
            _repository = repository;
            _logger = logger;
            _mapper = mapper;
        }

        public IEnumerable<CompanyDto> GetAllCompanies(bool trackChanges)
        {
            var companies = _repository.Company.GetAllCompanies(trackChanges);
            var companiesDto = _mapper.Map<IEnumerable<CompanyDto>>(companies);
            return companiesDto;
        }

        public CompanyDto GetCompany(Guid id, bool trackChanges)
        {
            var company = _repository.Company.GetCompany(id, trackChanges);
            //Check if the company is null
            var companyDto = _mapper.Map<CompanyDto>(company);
            return companyDto;
        }
    }
}

So, we are calling the repository method that fetches a single company from the database, maps the result to companyDto, and returns it. You can also see the comment about the null checks, which we are going to solve just in a minute.
因此,我们调用了 repository 方法,该方法从数据库中获取单个公司,将结果映射到 companyDto,然后返回它。您还可以查看有关 null 检查的注释,我们稍后将解决该问题。

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

using Microsoft.AspNetCore.Mvc;
using Service.Contracts;

namespace CompanyEmployees.Presentation.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CompaniesController : ControllerBase
    {
        private readonly IServiceManager _service;
        public CompaniesController(IServiceManager service) => _service = service;

        [HttpGet]
        public IActionResult GetCompanies()
        {
            // throw new Exception("Exception");
            var companies = _service.CompanyService.GetAllCompanies(trackChanges: false);
            return Ok(companies);
        }

        [HttpGet("{id:guid}")]
        public IActionResult GetCompany(Guid id)
        {
            var company = _service.CompanyService.GetCompany(id, trackChanges: false);
            return Ok(company);
        }

    }
}

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:guid}“)]. You can also see that we are using a route constraint (:guid part) where we explicitly state that our id parameter is of the GUID type. We can use many different constraints like int, double, long, float, datetime, bool, length, minlength, maxlength, and many others.
此作的路由是 /api/companies/id,这是因为 /api/companies 部分从根路由(在控制器的顶部)应用,而 id 部分从作属性 [HttpGet(“{id:guid}”)] 应用。您还可以看到,我们正在使用路由约束(:guid 部分),其中我们显式声明我们的 id 参数是 GUID 类型。我们可以使用许多不同的约束,如 int、double、long、float、datetime、bool、length、minlength、maxlength 等。

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

alt text

Great. This works as expected. But, what if someone uses an invalid id parameter?
伟大。这按预期工作。但是,如果有人使用了无效的 id 参数怎么办?

6.1.1 Handling Invalid Requests in a Service Layer‌

6.1.1 处理服务层中的无效请求

As you can see, in our service method, we have a comment stating that the result returned from the repository could be null, and this is something we have to handle. We want to return the NotFound response to the client but without involving our controller’s actions. We are going to keep them nice and clean as they already are.
如你所见,在我们的 service 方法中,我们有一条注释,指出从存储库返回的结果可能是 null,这是我们必须处理的事情。我们希望将 NotFound 响应返回给客户端,但不涉及控制器的作。我们将保持它们已经的良好和干净。

So, what we are going to do is to create custom exceptions that we can call from the service methods and interrupt the flow. Then our error handling middleware can catch the exception, process the response, and return it to the client. This is a great way of handling invalid requests inside a service layer without having additional checks in our controllers.
因此,我们要做的是创建自定义异常,我们可以从服务方法中调用这些异常并中断流。然后我们的错误处理中间件可以捕获异常,处理响应,并将其返回给客户端。这是在服务层内处理无效请求的好方法,而无需在我们的控制器中进行额外的检查。

That said, let’s start with a new Exceptions folder creation inside the Entities project. Since, in this case, we are going to create a not found response, let’s create a new NotFoundException class inside that folder:
也就是说,让我们从 Entities 项目中创建新的 Exceptions 文件夹开始。由于在本例中,我们将创建一个未找到的响应,因此让我们在该文件夹中创建一个新的 NotFoundException 类:

namespace Entities.Exceptions
{
    public abstract class NotFoundException : Exception
    {
        protected NotFoundException(string message) : base(message) { }
    }
}

This is an abstract class, which will be a base class for all the individual not found exception classes. It inherits from the Exception class to represent the errors that happen during application execution. Since in our current case, we are handling the situation where we can’t find the company in the database, we are going to create a new CompanyNotFoundException class in the same Exceptions folder:
这是一个抽象类,它将成为所有单个 not found 异常类的基类。它继承自 Exception 类,以表示应用程序执行期间发生的错误。由于在当前情况下,我们正在处理在数据库中找不到公司的情况,因此我们将在同一个 Exceptions 文件夹中创建一个新的 CompanyNotFoundException 类:

namespace Entities.Exceptions
{
    public sealed class CompanyNotFoundException : NotFoundException
    {
        public CompanyNotFoundException(Guid companyId) : base($"The company with id: {companyId} doesn't exist in the database.") { }
    }
}

Right after that, we can remove the comment in the GetCompany method and throw this exception:
紧接着,我们可以删除 GetCompany 方法中的注释并引发以下异常:

using AutoMapper;
using Contracts;
using Entities.Exceptions;
using Service.Contracts;
using Shared;

namespace Service
{
    internal sealed class CompanyService : ICompanyService
    {
        private readonly IRepositoryManager _repository;
        private readonly ILoggerManager _logger;
        private readonly IMapper _mapper;
        public CompanyService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
        {
            _repository = repository;
            _logger = logger;
            _mapper = mapper;
        }

        public IEnumerable<CompanyDto> GetAllCompanies(bool trackChanges)
        {
            var companies = _repository.Company.GetAllCompanies(trackChanges);
            var companiesDto = _mapper.Map<IEnumerable<CompanyDto>>(companies);
            return companiesDto;
        }

        //public CompanyDto GetCompany(Guid id, bool trackChanges)
        //{
        //    var company = _repository.Company.GetCompany(id, trackChanges);
        //    //Check if the company is null
        //    var companyDto = _mapper.Map<CompanyDto>(company);
        //    return companyDto;
        //}

        public CompanyDto GetCompany(Guid id, bool trackChanges)
        {
            var company = _repository.Company.GetCompany(id, trackChanges);
            if (company is null) throw new CompanyNotFoundException(id);
            var companyDto = _mapper.Map<CompanyDto>(company);
            return companyDto;
        }
    }
}

Finally, we have to modify our error middleware because we don’t want to return the 500 error message to our clients for every custom error we throw from the service layer.
最后,我们必须修改我们的错误中间件,因为我们不想为我们从服务层抛出的每个自定义错误返回 500 错误消息给客户端。

So, let’s modify the ExceptionMiddlewareExtensions class in the main project:
因此,让我们修改主项目中的 ExceptionMiddlewareExtensions 类:

using Contracts;
using Entities.ErrorModel;
using Entities.Exceptions;
using Microsoft.AspNetCore.Diagnostics;

namespace CompanyEmployees.Extensions
{
    public static class ExceptionMiddlewareExtensions
    {
        public static void ConfigureExceptionHandler(this WebApplication app, ILoggerManager logger)
        {
            app.UseExceptionHandler(appError =>
            {
                appError.Run(async context =>
                {
                    context.Response.ContentType = "application/json";
                    var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                    if (contextFeature != null)
                    {
                        context.Response.StatusCode = contextFeature.Error
                        switch
                        {
                            NotFoundException => StatusCodes.Status404NotFound,
                            _ => StatusCodes.Status500InternalServerError
                        };
                        logger.LogError($"Something went wrong: {contextFeature.Error}");
                        await context.Response.WriteAsync(new ErrorDetails()
                        {
                            StatusCode = context.Response.StatusCode,
                            Message = contextFeature.Error.Message,
                        }.ToString());
                    }
                });
            });
        }
    }
}

We remove the hardcoded StatusCode setup and add the part where we populate it based on the type of exception we throw in our service layer. We are also dynamically populating the Message property of the ErrorDetails object that we return as the response.
我们删除硬编码的 StatusCode 设置,并添加部分,根据我们在服务层中抛出的异常类型来填充它。我们还动态填充作为响应返回的 ErrorDetails 对象的 Message 属性。

Additionally, you can see the advantage of using the base abstract exception class here (NotFoundException in this case). We are not checking for the specific class implementation but the base type. This allows us to have multiple not found classes that inherit from the NotFoundException class and this middleware will know that we want to return the NotFound response to the client.
此外,您可以在此处看到使用基抽象异常类的优势(在本例中为 NotFoundException)。我们检查的不是特定的类实现,而是基类型。这允许我们拥有多个从 NotFoundException 类继承的未找到的类,并且此中间件将知道我们想要将 NotFound 响应返回给客户端。

Excellent. Now, we can start the app and send the invalid request:
非常好。现在,我们可以启动应用程序并发送无效请求:

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

alt text

We can see the status code we require and also the response object with proper StatusCode and Message properties. Also, if you inspect the log message, you will see that we are logging a correct message.
我们可以看到所需的状态代码,还可以看到具有适当 StatusCode 和 Message 属性的响应对象。此外,如果您检查日志消息,您将看到我们记录的消息正确无误。

With this approach, we have perfect control of all the exceptional cases in our app. We have that control due to global error handler implementation. For now, we only handle the invalid id sent from the client, but we will handle more exceptional cases in the rest of the project.
通过这种方法,我们可以完美地控制应用程序中的所有特殊情况。由于全局错误处理程序的实现,我们拥有了这种控制权。目前,我们只处理客户端发送的无效 ID,但我们将在项目的其余部分处理更多异常情况。

In our tests for a published app, the regular request sent from Postman took 7ms and the exceptional one took 14ms. So you can see how fast the response is.
在我们对已发布应用程序的测试中,从 Postman 发送的常规请求需要 7 毫秒,特殊请求需要 14 毫秒。所以你可以看到响应有多快。

Of course, we are using exceptions only for these exceptional cases (Company not found, Employee not found...) and not throwing them all over the application. So, if you follow the same strategy, you will not face any performance issues.
当然,我们只对这些特殊情况(未找到公司、未找到员工等)使用异常,而不是在整个应用程序中抛出它们。因此,如果您遵循相同的策略,您将不会遇到任何性能问题。

Lastly, if you have an application where you have to throw custom exceptions more often and maybe impact your performance, we are going to provide an alternative to exceptions in the first bonus chapter of this book (Chapter 32).
最后,如果您的应用程序必须更频繁地引发自定义异常,并且可能会影响您的性能,我们将在本书的附1章(第 32 章)中提供异常的替代方案。

6.2 Parent/Child Relationships in Web API

6.2 Web API 中的父/子关系

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 in the Presentation project and name it EmployeesController:
也就是说,让我们在 Presentation 项目中创建一个新控制器,并将其命名为 EmployeesController:

using Microsoft.AspNetCore.Mvc;
using Service.Contracts;

namespace CompanyEmployees.Presentation.Controllers
{
    [Route("api/companies/{companyId}/employees")]
    [ApiController]
    public class EmployeesController : ControllerBase
    {
        private readonly IServiceManager _service;
        public EmployeesController(IServiceManager service) => _service = service;
    }
}

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 we are 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 接口:

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 类:

using Contracts;
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).ToList();
    }
}

Then, before we start adding code to the service layer, we are going to create a new DTO. Let’s name it EmployeeDto and add it to the Shared/DataTransferObjects folder:
然后,在我们开始向服务层添加代码之前,我们将创建一个新的 DTO。让我们将其命名为 EmployeeDto 并将其添加到 Shared/DataTransferObjects 文件夹中:

namespace Shared.DataTransferObjects
{
    public record CompanyDto(Guid Id, string Name, string FullAddress);
}

Since we want to return this DTO to the client, we have to create a mapping rule inside the MappingProfile class:
由于我们想将此 DTO 返回给客户端,因此我们必须在 MappingProfile 类中创建一个映射规则:

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

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

Now, we can modify the IEmployeeService interface:
现在,我们可以修改 IEmployeeService 接口:

using Shared.DataTransferObjects;

namespace Service.Contracts
{
    public interface IEmployeeService { 
        IEnumerable<EmployeeDto> GetEmployees(Guid companyId, bool trackChanges); 
    }
}

And of course, we have to implement this interface in the EmployeeService class:
当然,我们必须在 EmployeeService 类中实现这个接口:

using AutoMapper;
using Contracts;
using Entities.Exceptions;
using Service.Contracts;
using Shared.DataTransferObjects;

namespace Service
{
    internal sealed class EmployeeService : IEmployeeService
    {
        private readonly IRepositoryManager _repository;
        private readonly ILoggerManager _logger;
        private readonly IMapper _mapper;
        public EmployeeService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
        {
            _repository = repository;
            _logger = logger;
            _mapper = mapper;
        }

        public IEnumerable<EmployeeDto> GetEmployees(Guid companyId, bool trackChanges)
        {
            var company = _repository.Company.GetCompany(companyId, trackChanges);
            if (company is null) throw new CompanyNotFoundException(companyId);
            var employeesFromDb = _repository.Employee.GetEmployees(companyId, trackChanges);
            var employeesDto = _mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);
            return employeesDto;
        }
    }
}

Here, we first fetch the company entity from the database. If it doesn’t exist, we return the NotFound response to the client. If it does, we fetch all the employees for that company, map them to the collection of EmployeeDto and return it to the caller.
在这里,我们首先从数据库中获取 company 实体。如果不存在,我们将 NotFound 响应返回给客户端。如果是这样,我们将获取该公司的所有员工,将它们映射到 EmployeeDto 的集合,并将其返回给调用方。

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

[HttpGet]
public IActionResult GetEmployeesForCompany(Guid companyId)
{
    var employees = _service.EmployeeService.GetEmployees(companyId, trackChanges: false);
    return Ok(employees);
}

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] 属性中。

That done, we can send a request with a valid companyId:
完成后,我们可以发送具有有效 companyId:
https://localhost:5001/api/companies/c9d4c053-49b6-410c-bc78-2d54a9991870/employees

alt text

And with an invalid companyId:
并且使用无效的 companyId:
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

6.3 为公司招聘一名员工

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

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 类中实现此方法:

using Contracts;
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).ToList();
        public Employee GetEmployee(Guid companyId, Guid id, bool trackChanges) => FindByCondition(e => e.CompanyId.Equals(companyId) && e.Id.Equals(id), trackChanges).SingleOrDefault();

    }
}

Next, let’s add another exception class in the Entities/Exceptions folder:
接下来,让我们在 Entities/Exceptions 文件夹中添加另一个异常类:

namespace Entities.Exceptions
{
    public class EmployeeNotFoundException : NotFoundException
    {
        public EmployeeNotFoundException(Guid employeeId) : base($"Employee with id: {employeeId} doesn't exist in the database.") { }
    }
}

We will soon see why do we need this class.
我们很快就会明白为什么我们需要这个类。

To continue, we have to modify the IEmployeeService interface:
要继续,我们必须修改 IEmployeeService 接口:

using Shared.DataTransferObjects;

namespace Service.Contracts
{
    public interface IEmployeeService { 
        IEnumerable<EmployeeDto> GetEmployees(Guid companyId, bool trackChanges);
        EmployeeDto GetEmployee(Guid companyId, Guid id, bool trackChanges);
    }
}

And implement this new method in the EmployeeService class:
并在 EmployeeService 类中实现这个新方法:

using AutoMapper;
using Contracts;
using Entities.Exceptions;
using Service.Contracts;
using Shared.DataTransferObjects;

namespace Service
{
    internal sealed class EmployeeService : IEmployeeService
    {
        private readonly IRepositoryManager _repository;
        private readonly ILoggerManager _logger;
        private readonly IMapper _mapper;
        public EmployeeService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
        {
            _repository = repository;
            _logger = logger;
            _mapper = mapper;
        }

        public IEnumerable<EmployeeDto> GetEmployees(Guid companyId, bool trackChanges)
        {
            var company = _repository.Company.GetCompany(companyId, trackChanges);
            if (company is null) 
                throw new CompanyNotFoundException(companyId);
            var employeesFromDb = _repository.Employee.GetEmployees(companyId, trackChanges);
            var employeesDto = _mapper.Map<IEnumerable<EmployeeDto>>(employeesFromDb);
            return employeesDto;
        }

        public EmployeeDto GetEmployee(Guid companyId, Guid id, bool trackChanges)
        {
            var company = _repository.Company.GetCompany(companyId, trackChanges);
            if (company is null) 
                throw new CompanyNotFoundException(companyId);
            var employeeDb = _repository.Employee.GetEmployee(companyId, id, trackChanges);
            if (employeeDb is null) 
                throw new EmployeeNotFoundException(id);
            var employee = _mapper.Map<EmployeeDto>(employeeDb);
            return employee;
        }
    }
}

This is also a pretty clear code and we can see the reason for creating a new exception class.
这也是一个非常清晰的代码,我们可以看到创建新的异常类的原因。

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

using Microsoft.AspNetCore.Mvc;
using Service.Contracts;

namespace CompanyEmployees.Presentation.Controllers
{
    [Route("api/companies/{companyId}/employees")]
    [ApiController]
    public class EmployeesController : ControllerBase
    {
        private readonly IServiceManager _service;
        public EmployeesController(IServiceManager service) => _service = service;

        [HttpGet]
        public IActionResult GetEmployeesForCompany(Guid companyId)
        {
            var employees = _service.EmployeeService.GetEmployees(companyId, trackChanges: false);
            return Ok(employees);
        }

        [HttpGet("{id:guid}")]
        public IActionResult GetEmployeeForCompany(Guid companyId, Guid id)
        {
            var employee = _service.EmployeeService.GetEmployee(companyId, id, trackChanges: false);
            return Ok(employee);
        }
    }
}

Excellent. You can see how clear our action is.
非常好。你可以看到我们的行动是多么明确。

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

alt text

Our responses are pretty self-explanatory, which makes for a good user experience.
我们的回答不言自明,这带来了良好的用户体验。

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.
那么,在下一章中,我们将了解有关 Content Negotiation 和为我们的响应启用不同格式的更多信息。

Leave a Reply

Your email address will not be published. Required fields are marked *