Ultimate ASP.NET Core Web API 32 BONUS 1 – RESPONSE PERFORMANCE IMPROVEMENTS

32 BONUS 1 - RESPONSE PERFORMANCE IMPROVEMENTS
32 奖励 1 - 响应性能改进

As mentioned in section 6.1.1, we will show you an alternative way of handling error responses. To repeat, with custom exceptions, we have great control of returning error responses to the client due to the global error handler, which is pretty fast if we use it correctly. Also, the code is pretty clean and straightforward since we don’t have to care about the return types and additional validation in the service methods.‌
如 6.1.1 节所述,我们将向您展示一种处理错误响应的替代方法。重复一遍,对于自定义异常,由于全局错误处理程序,我们可以很好地控制将错误响应返回给客户端,如果我们正确使用它,这将非常快。此外,代码非常简洁明了,因为我们不必关心服务方法中的返回类型和其他验证。

Even though some libraries enable us to write custom responses, for example, OneOf, we still like to create our abstraction logic, which is tested by us and fast. Additionally, we want to show you the whole creation process for such a flow.
尽管一些库允许我们编写自定义响应,例如 OneOf,但我们仍然喜欢创建我们的抽象逻辑,它由我们测试并且快速。此外,我们还想向您展示此类流程的整个创建过程。

For this example, we will use an existing project from part 6 and modify it to implement our API Response flow.
在此示例中,我们将使用 第 6 部分中的现有项目,并对其进行修改以实现我们的 API 响应流。

32.1 Adding Response Classes to the Project

32.1 向项目添加响应类

Let’s start with the API response model classes.‌
让我们从 API 响应模型类开始。

The first thing we are going to do is create a new Responses folder in the Entities project. Inside that folder, we are going to add our first class:
我们要做的第一件事是在 Entities 项目中创建一个新的 Responses 文件夹。在该文件夹中,我们将添加我们的第一个类:

public abstract class ApiBaseResponse { public bool Success { get; set; } protected ApiBaseResponse(bool success) => Success = success; }

This is an abstract class, which will be the main return type for all of our methods where we have to return a successful result or an error result. It also contains a single Success property stating whether the action was successful or not.
这是一个抽象类,它将是我们所有方法的主要返回类型,我们必须返回成功结果或错误结果。它还包含一个 Success 属性,用于说明作是否成功。

Now, if our result is successful, we are going to create only one class in the same folder:
现在,如果我们的结果成功,我们将只在同一个文件夹中创建一个类:

public sealed class ApiOkResponse<TResult> : ApiBaseResponse { public TResult Result { get; set; } public ApiOkResponse(TResult result) :base(true) { Result = result; } }

We are going to use this class as a return type for a successful result. It inherits from the ApiBaseResponse and populates the Success property to true through the constructor. It also contains a single Result property of type TResult. We will store our concrete result in this property, and since we can have different result types in different methods, this property is a generic one.
我们将使用这个类作为成功结果的返回类型。它继承自 ApiBaseResponse,并通过构造函数将 Success 属性填充为 true。它还包含一个 TResult 类型的 Result 属性。我们将具体结果存储在此属性中,由于我们可以在不同的方法中具有不同的结果类型,因此此属性是通用属性。

That’s all regarding the successful responses. Let’s move one to the error classes.
这就是关于成功响应的全部内容。让我们将一个移动到 error 类。

For the error responses, we will follow the same structure as we have for the exception classes. So, we will have base abstract classes for NotFound or BadRequest or any other error responses, and then concrete implementations for these classes like CompanyNotFound or CompanyBadRequest, etc.
对于错误响应,我们将遵循与异常类相同的结构。因此,我们将为 NotFound 或 BadRequest 或任何其他错误响应提供基本抽象类,然后为这些类提供具体实现,例如 CompanyNotFound 或 CompanyBadRequest 等。

That said, let’s use the same folder to create an abstract error class:
也就是说,让我们使用相同的文件夹来创建一个抽象错误类:

public abstract class ApiNotFoundResponse : ApiBaseResponse { public string Message { get; set; } public ApiNotFoundResponse(string message) : base(false) { Message = message; } }

This class also inherits from the ApiBaseResponse, populates the Success property to false, and has a single Message property for the error message.
此类还继承自 ApiBaseResponse,将 Success 属性填充为 false,并且具有错误消息的单个 Message 属性。

In the same manner, we can create the ApiBadRequestResponse class:
以同样的方式,我们可以创建 ApiBadRequestResponse 类:

public abstract class ApiBadRequestResponse : ApiBaseResponse { public string Message { get; set; } public ApiBadRequestResponse(string message) : base(false) { Message = message; } }

This is the same implementation as the previous one. The important thing to notice is that both of these classes are abstract.
这与上一个实现相同。需要注意的重要一点是,这两个类都是抽象的。

To continue, let’s create a concrete error response:
为了继续,让我们创建一个具体的错误响应:

public sealed class CompanyNotFoundResponse : ApiNotFoundResponse { public CompanyNotFoundResponse(Guid id) : base($"Company with id: {id} is not found in db.") { } }

The class inherits from the ApiNotFoundResponse abstract class, which again inherits from the ApiBaseResponse class. It accepts an id parameter and creates a message that sends to the base class.
该类继承自 ApiNotFoundResponse 抽象类,而 ApiNotFoundResponse 抽象类又继承自 ApiBaseResponse 类。它接受 id 参数并创建发送到基类的消息。

We are not going to create the CompanyBadRequestResponse class because we are not going to need it in our example. But the principle is the same.
我们不打算创建 CompanyBadRequestResponse 类,因为在我们的示例中不需要它。但原理是一样的。

32.2 Service Layer Modification

32.2 服务层修改

Now that we have the response model classes, we can start with the service layer modification.‌
现在我们有了响应模型类,我们可以从服务层修改开始。

Let’s start with the ICompanyService interface:
让我们从 ICompanyService 接口开始:

public interface ICompanyService { ApiBaseResponse GetAllCompanies(bool trackChanges); ApiBaseResponse GetCompany(Guid companyId, bool trackChanges); }

We don’t return concrete types in our methods anymore. Instead of the IEnumerable or CompanyDto return types, we return the ApiBaseResponse type. This will enable us to return either the success result or to return any of the error response results.
我们不再在方法中返回具体类型。 我们返回 ApiBaseResponse 类型,而不是 IEnumerable 或 CompanyDto 返回类型。这将使我们能够返回成功结果或返回任何错误响应结果。

After the interface modification, we can modify the CompanyService class:
修改接口后,我们可以修改 CompanyService 类:

public ApiBaseResponse GetAllCompanies(bool trackChanges) { var companies = _repository.Company.GetAllCompanies(trackChanges); var companiesDto = _mapper.Map<IEnumerable<CompanyDto>>(companies); return new ApiOkResponse<IEnumerable<CompanyDto>>(companiesDto); } public ApiBaseResponse GetCompany(Guid id, bool trackChanges) { var company = _repository.Company.GetCompany(id, trackChanges); if (company is null) return new CompanyNotFoundResponse(id); var companyDto = _mapper.Map<CompanyDto>(company); return new ApiOkResponse<CompanyDto>(companyDto); }

Both method signatures are modified to use APIBaseResponse, and also the return types are modified accordingly. Additionally, in the GetCompany method, we are not using an exception class to return an error result but the CompanyNotFoundResponse class. With the ApiBaseResponse abstraction, we are safe to return multiple types from our method as long as they inherit from the ApiBaseResponse abstract class. Here you could also log some messages with _logger.
两个方法签名都被修改为使用 APIBaseResponse,并且返回类型也相应地被修改。此外,在 GetCompany 方法中,我们没有使用异常类来返回错误结果,而是使用 CompanyNotFoundResponse 类。使用 ApiBaseResponse 抽象,我们可以安全地从方法中返回多个类型,只要它们继承自 ApiBaseResponse 抽象类。在这里,您还可以使用 _logger 记录一些消息。

One more thing to notice here.
这里还有一点需要注意。

In the GetAllCompanies method, we don’t have an error response just a successful one. That means we didn’t have to implement our Api response flow, and we could’ve left the method unchanged (in the interface and this class). If you want that kind of implementation it is perfectly fine. We just like consistency in our projects, and due to that fact, we’ve changed both methods.
在 GetAllCompanies 方法中,我们没有错误响应,只有一个成功的响应。这意味着我们不必实现 Api 响应流,并且可以保持方法不变(在接口和这个类中)。如果你想要这种实现,那完全没问题。我们就像我们项目中的一致性一样,因此,我们改变了这两种方法。

32.3 Controller Modification

32.3 控制器修改

Before we start changing the actions in the CompaniesController, we have to create a way to handle error responses and return them to the client – similar to what we have with the global error handler middleware.‌
在我们开始更改 CompaniesController 中的作之前,我们必须创建一种方法来处理错误响应并将其返回给客户端 – 类似于我们使用全局错误处理程序中间件的方法。

We are not going to create any additional middleware but another controller base class inside the Presentation/Controllers folder:
我们不打算创建任何其他中间件,而是在 Presentation/Controllers 文件夹中创建另一个控制器基类:

public class ApiControllerBase : ControllerBase { public IActionResult ProcessError(ApiBaseResponse baseResponse) { return baseResponse switch { ApiNotFoundResponse => NotFound(new ErrorDetails { Message = ((ApiNotFoundResponse)baseResponse).Message, StatusCode = StatusCodes.Status404NotFound }), ApiBadRequestResponse => BadRequest(new ErrorDetails { Message = ((ApiBadRequestResponse)baseResponse).Message, StatusCode = StatusCodes.Status400BadRequest }), _ => throw new NotImplementedException() }; } }

This class inherits from the ControllerBase class and implements a single ProcessError action accepting an ApiBaseResponse parameter. Inside the action, we are inspecting the type of the sent parameter, and based on that type we return an appropriate message to the client. A similar thing we did in the exception middleware class.
此类继承自 ControllerBase 类,并实现接受 ApiBaseResponse 参数的单个 ProcessError作。在作中,我们将检查已发送参数的类型,并根据该类型向客户端返回适当的消息。我们在异常中间件类中做了类似的事情。

If you add additional error response classes to the Response folder, you only have to add them here to process the response for the client.
如果向 Response 文件夹添加其他错误响应类,则只需在此处添加它们即可处理客户端的响应。

Additionally, this is where we can see the advantage of our abstraction approach.
此外,这就是我们可以看到抽象方法的优势的地方。

Now, we can modify our CompaniesController:
现在,我们可以修改我们的 CompaniesController:

[Route("api/companies")] [ApiController] public class CompaniesController : ApiControllerBase { private readonly IServiceManager _service; public CompaniesController(IServiceManager service) => _service = service; [HttpGet] public IActionResult GetCompanies() { var baseResult = _service.CompanyService.GetAllCompanies(trackChanges: false); var companies = ((ApiOkResponse<IEnumerable<CompanyDto>>)baseResult).Result; return Ok(companies); } [HttpGet("{id:guid}")] public IActionResult GetCompany(Guid id) { var baseResult = _service.CompanyService.GetCompany(id, trackChanges: false); if (!baseResult.Success) return ProcessError(baseResult); var company = ((ApiOkResponse<CompanyDto>)baseResult).Result; return Ok(company); } }

Now our controller inherits from the ApiControllerBase, which inherits from the ControllerBase class. In the GetCompanies action, we extract the result from the service layer and cast the baseResult variable to the concrete ApiOkResponse type, and use the Result property to extract our required result of type IEnumerable.
现在我们的控制器继承自 ApiControllerBase,而 ApiControllerBase 继承自 ControllerBase 类。在 GetCompanies作中,我们从服务层提取结果,并将 baseResult 变量转换为具体的 ApiOkResponse 类型,并使用 Result 属性提取所需的 IEnumerable 类型结果。

We do a similar thing for the GetCompany action. Of course, here we check if our result is successful and if it’s not, we return the result of the ProcessError method.
我们对 GetCompany作执行类似的作。当然,这里我们检查结果是否成功,如果不是,我们返回 ProcessError 方法的结果。

And that’s it.
就是这样。

We can leave the solution as is, but we mind having these castings inside our actions – they can be moved somewhere else making them reusable and our actions cleaner. So, let’s do that.
我们可以保持解决方案不变,但我们介意在我们的 action 中放置这些铸件——它们可以移动到其他地方,使它们可重用,我们的 action 更干净。所以,让我们开始吧。

In the same project, we are going to create a new Extensions folder and a new ApiBaseResponseExtensions class:
在同一个项目中,我们将创建一个新的 Extensions 文件夹和一个新的 ApiBaseResponseExtensions 类:

public static class ApiBaseResponseExtensions { public static TResultType GetResult<TResultType>(this ApiBaseResponse apiBaseResponse) => ((ApiOkResponse<TResultType>)apiBaseResponse).Result; }

The GetResult method will extend the ApiBaseResponse type and return the result of the required type.
GetResult 方法将扩展 ApiBaseResponse 类型并返回所需类型的结果。

Now, we can modify actions inside the controller:
现在,我们可以修改控制器内部的 action:

[HttpGet] public IActionResult GetCompanies() { var baseResult = _service.CompanyService.GetAllCompanies(trackChanges: false); var companies = baseResult.GetResult<IEnumerable<CompanyDto>>(); return Ok(companies); } [HttpGet("{id:guid}")] public IActionResult GetCompany(Guid id) { var baseResult = _service.CompanyService.GetCompany(id, trackChanges: false); if (!baseResult.Success) return ProcessError(baseResult); var company = baseResult.GetResult<CompanyDto>(); return Ok(company); }

This is much cleaner and easier to read and understand.
这更简洁,更容易阅读和理解。

32.4 Testing the API Response Flow

32.4 测试 API 响应流

Now we can start our application, open Postman, and send some requests.‌
现在我们可以启动应用程序,打开 Postman,并发送一些请求。

Let’s try to get all the companies:
让我们尝试获取所有公司:

https://localhost:5001/api/companies

alt text

Then, we can try to get a single company:
然后,我们可以尝试获得一家公司:

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

alt text

And finally, let’s try to get a company that does not exist:
最后,让我们尝试获得一家不存在的公司:

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

alt text

And we have our response with a proper status code and response body. Excellent.
我们的响应具有适当的状态代码和响应正文。非常好。

We have a solution that is easy to implement, fast, and extendable.
我们有一个易于实施、快速且可扩展的解决方案。

Our suggestion is to go with custom exceptions since they are easier to implement and fast as well. But if you have an app flow where you have to return error responses at a much higher rate and thus maybe impact the app’s performance, the APi Response flow is the way to go.
我们建议使用自定义异常,因为它们更容易实现且速度更快。但是,如果您的应用程序流必须以更高的速率返回错误响应,从而可能会影响应用程序的性能,那么 APi 响应流就是您的不二之选。

Leave a Reply

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