{"id":1106,"date":"2025-05-27T14:46:12","date_gmt":"2025-05-27T06:46:12","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1106"},"modified":"2025-05-27T14:46:12","modified_gmt":"2025-05-27T06:46:12","slug":"ultimate-asp-net-core-web-api-4-handling-get-requests","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1106","title":{"rendered":"Ultimate ASP.NET Core Web API  4 HANDLING GET REQUESTS"},"content":{"rendered":"<h1>4 HANDLING GET REQUESTS<\/h1>\n<p>4 \u5904\u7406 GET \u8bf7\u6c42<\/p>\n<p>We\u2019re all set to add some business logic to our application. But before we do that, let\u2019s talk a bit about controller classes and routing because they play an important part while working with HTTP requests.\u200c<br \/>\n\u6211\u4eec\u5df2\u51c6\u5907\u597d\u5411\u5e94\u7528\u7a0b\u5e8f\u6dfb\u52a0\u4e00\u4e9b\u4e1a\u52a1\u903b\u8f91\u3002\u4f46\u5728\u6211\u4eec\u8fd9\u6837\u505a\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u5148\u8c08\u8c08\u63a7\u5236\u5668\u7c7b\u548c\u8def\u7531\uff0c\u56e0\u4e3a\u5b83\u4eec\u5728\u5904\u7406 HTTP \u8bf7\u6c42\u65f6\u8d77\u7740\u91cd\u8981\u7684\u4f5c\u7528\u3002<\/p>\n<h2>4.1 Controllers and Routing in WEB API<\/h2>\n<p>4.1 WEB API \u4e2d\u7684\u63a7\u5236\u5668\u548c\u8def\u7531<\/p>\n<p>Controllers should only be responsible for handling requests, model validation, and returning responses to the frontend or some HTTP client. Keeping business logic away from controllers is a good way to keep them lightweight, and our code more readable and maintainable.\u200c<br \/>\n\u63a7\u5236\u5668\u5e94\u8be5\u53ea\u8d1f\u8d23\u5904\u7406\u8bf7\u6c42\u3001\u6a21\u578b\u9a8c\u8bc1\u4ee5\u53ca\u5c06\u54cd\u5e94\u8fd4\u56de\u7ed9\u524d\u7aef\u6216\u67d0\u4e9b HTTP \u5ba2\u6237\u7aef\u3002\u8ba9\u4e1a\u52a1\u903b\u8f91\u8fdc\u79bb\u63a7\u5236\u5668\u662f\u4fdd\u6301\u5b83\u4eec\u8f7b\u91cf\u7ea7\u7684\u597d\u65b9\u6cd5\uff0c\u5e76\u4e14\u6211\u4eec\u7684\u4ee3\u7801\u66f4\u5177\u53ef\u8bfb\u6027\u548c\u53ef\u7ef4\u62a4\u6027\u3002<\/p>\n<p>If you want to create the controller in the main project, you would right- click on the Controllers folder and then Add=&gt;Controller. Then from the menu, you would choose API Controller Class and give it a name:<br \/>\n\u5982\u679c\u8981\u5728\u4e3b\u9879\u76ee\u4e2d\u521b\u5efa\u63a7\u5236\u5668\uff0c\u8bf7\u53f3\u952e\u5355\u51fb Controllers \u6587\u4ef6\u5939\uff0c\u7136\u540e\u5355\u51fb Add=&gt;Controller\u3002\u7136\u540e\u4ece\u83dc\u5355\u4e2d\u9009\u62e9 API Controller Class \u5e76\u4e3a\u5176\u547d\u540d\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0401.jpg\" alt=\"alt text\" \/><\/p>\n<p>But, that\u2019s not the thing we are going to do. We don\u2019t want to create our controllers in the main project.<br \/>\n\u4f46\u662f\uff0c\u8fd9\u4e0d\u662f\u6211\u4eec\u8981\u505a\u7684\u4e8b\u60c5\u3002\u6211\u4eec\u4e0d\u60f3\u5728\u4e3b\u9879\u76ee\u4e2d\u521b\u5efa\u63a7\u5236\u5668\u3002<\/p>\n<p>What we are going to do instead is create a presentation layer in our application.<br \/>\n\u76f8\u53cd\uff0c\u6211\u4eec\u8981\u505a\u7684\u662f\u5728\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u521b\u5efa\u4e00\u4e2a\u8868\u793a\u5c42\u3002<\/p>\n<p>The purpose of the presentation layer is to provide the entry point to our system so that consumers can interact with the data. We can implement this layer in many ways, for example creating a REST API, gRPC, etc.<br \/>\n\u8868\u793a\u5c42\u7684\u76ee\u7684\u662f\u63d0\u4f9b\u6211\u4eec\u7cfb\u7edf\u7684\u5165\u53e3\u70b9\uff0c\u4ee5\u4fbf\u6d88\u8d39\u8005\u53ef\u4ee5\u4e0e\u6570\u636e\u8fdb\u884c\u4ea4\u4e92\u3002\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u591a\u79cd\u65b9\u5f0f\u5b9e\u73b0\u6b64\u5c42\uff0c\u4f8b\u5982\u521b\u5efa REST API\u3001gRPC \u7b49\u3002<\/p>\n<p>However, we are going to do something different from what you are normally used to when creating Web APIs. By convention, controllers are defined in the Controllers folder inside the main project.<br \/>\n\u4f46\u662f\uff0c\u6211\u4eec\u5c06\u505a\u4e00\u4e9b\u4e0e\u60a8\u901a\u5e38\u4e60\u60ef\u7684\u521b\u5efa Web API \u4e0d\u540c\u7684\u4e8b\u60c5\u3002\u6309\u7167\u7ea6\u5b9a\uff0c\u63a7\u5236\u5668\u5728\u4e3b\u9879\u76ee\u5185\u7684 Controllers \u6587\u4ef6\u5939\u4e2d\u5b9a\u4e49\u3002<\/p>\n<p>Why is this a problem?<br \/>\n\u4e3a\u4ec0\u4e48\u8fd9\u662f\u4e00\u4e2a\u95ee\u9898\uff1f<\/p>\n<p>Because ASP.NET Core uses Dependency Injection everywhere, we need to have a reference to all of the projects in the solution from the main project. This allows us to configure our services inside the Program class.<br \/>\n\u7531\u4e8e ASP.NET Core \u5728\u4efb\u4f55\u5730\u65b9\u90fd\u4f7f\u7528\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165\uff0c\u56e0\u6b64\u6211\u4eec\u9700\u8981\u4ece\u4e3b\u9879\u76ee\u5f15\u7528\u89e3\u51b3\u65b9\u6848\u4e2d\u7684\u6240\u6709\u9879\u76ee\u3002\u8fd9\u5141\u8bb8\u6211\u4eec\u5728 Program \u7c7b\u4e2d\u914d\u7f6e\u6211\u4eec\u7684\u670d\u52a1\u3002<\/p>\n<p>While this is exactly what we want to do, it introduces a big design flaw. What\u2019s preventing our controllers from injecting anything they want inside the constructor?<br \/>\n\u867d\u7136\u8fd9\u6b63\u662f\u6211\u4eec\u60f3\u8981\u505a\u7684\uff0c\u4f46\u5b83\u5f15\u5165\u4e86\u4e00\u4e2a\u5f88\u5927\u7684\u8bbe\u8ba1\u7f3a\u9677\u3002\u662f\u4ec0\u4e48\u963b\u6b62\u4e86\u6211\u4eec\u7684\u63a7\u5236\u5668\u5728\u6784\u9020\u51fd\u6570\u4e2d\u6ce8\u5165\u4ed6\u4eec\u60f3\u8981\u7684\u4efb\u4f55\u4e1c\u897f\uff1f<\/p>\n<p>So how can we impose some more strict rules about what controllers can do?<br \/>\n\u90a3\u4e48\uff0c\u6211\u4eec\u5982\u4f55\u5bf9\u63a7\u5236\u5668\u53ef\u4ee5\u505a\u4ec0\u4e48\u65bd\u52a0\u4e00\u4e9b\u66f4\u4e25\u683c\u7684\u89c4\u5219\u5462\uff1f<\/p>\n<p>Do you remember how we split the Service layer into the Service.Contracts and Service projects? That was one piece of the puzzle.<br \/>\n\u4f60\u8fd8\u8bb0\u5f97\u6211\u4eec\u662f\u5982\u4f55\u5c06 Service \u5c42\u62c6\u5206\u4e3a Service.Contracts \u548c Service \u9879\u76ee\u7684\u5417\uff1f\u8fd9\u662f\u62fc\u56fe\u7684\u4e00\u90e8\u5206\u3002<\/p>\n<p>Another part of the puzzle is the creation of a new class library project,CompanyEmployees.Presentation.<br \/>\n\u96be\u9898\u7684\u53e6\u4e00\u90e8\u5206\u662f\u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u7c7b\u5e93\u9879\u76ee CompanyEmployees.Presentation\u3002<\/p>\n<p>Inside that new project, we are going to install Microsoft.AspNetCore.Mvc.Core package so it has access to the ControllerBase class for our future controllers. Additionally, let\u2019s create a single class inside the Presentation project:<br \/>\n\u5728\u8be5\u65b0\u9879\u76ee\u4e2d\uff0c\u6211\u4eec\u5c06\u5b89\u88c5 Microsoft.AspNetCore.Mvc.Core \u5305\uff0c\u4ee5\u4fbf\u5b83\u53ef\u4ee5\u8bbf\u95ee\u6211\u4eec\u672a\u6765\u63a7\u5236\u5668\u7684 ControllerBase \u7c7b\u3002\u6b64\u5916\uff0c\u8ba9\u6211\u4eec\u5728 Presentation \u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u7c7b\uff1a<\/p>\n<pre><code>namespace CompanyEmployees.Presentation\n{\n    public static class AssemblyReference { }\n}<\/code><\/pre>\n<p>It's an empty static class that we are going to use for the assembly reference inside the main project, you will see that in a minute.<br \/>\n\u8fd9\u662f\u4e00\u4e2a\u7a7a\u7684\u9759\u6001\u7c7b\uff0c\u6211\u4eec\u5c06\u7528\u4e8e\u4e3b\u9879\u76ee\u4e2d\u7684\u7a0b\u5e8f\u96c6\u5f15\u7528\uff0c\u60a8\u7a0d\u540e\u4f1a\u770b\u5230\u5b83\u3002<\/p>\n<p>The one more thing, we have to do is to reference the Service.Contracts project inside the Presentation project.<br \/>\n\u6211\u4eec\u8fd8\u8981\u505a\u7684\u53e6\u4e00\u4ef6\u4e8b\u662f\u5f15\u7528 Presentation \u9879\u76ee\u4e2d\u7684 Service.Contracts \u9879\u76ee\u3002<\/p>\n<p>Now, we are going to delete the Controllers folder and the WeatherForecast.cs file from the main project because we are not going to need them anymore.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5c06\u4ece\u4e3b\u9879\u76ee\u4e2d\u5220\u9664 Controllers \u6587\u4ef6\u5939\u548c WeatherForecast.cs \u6587\u4ef6\uff0c\u56e0\u4e3a\u6211\u4eec\u4e0d\u518d\u9700\u8981\u5b83\u4eec\u3002<\/p>\n<p>Next, we have to reference the Presentation project inside the main one. As you can see, our presentation layer depends only on the service contracts, thus imposing more strict rules on our controllers.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u5fc5\u987b\u5728\u4e3b\u9879\u76ee\u4e2d\u5f15\u7528 Presentation \u9879\u76ee\u3002\u6b63\u5982\u4f60\u6240\u770b\u5230\u7684\uff0c\u6211\u4eec\u7684\u8868\u793a\u5c42\u53ea\u4f9d\u8d56\u4e8e\u670d\u52a1\u5951\u7ea6\uff0c\u56e0\u6b64\u5bf9\u6211\u4eec\u7684\u63a7\u5236\u5668\u65bd\u52a0\u4e86\u66f4\u4e25\u683c\u7684\u89c4\u5219\u3002<\/p>\n<p>Then, we have to modify the Program.cs file:<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u4fee\u6539 Program.cs \u6587\u4ef6\uff1a<\/p>\n<pre><code>\/\/builder.Services.AddControllers();\nbuilder.Services.AddControllers().AddApplicationPart(typeof(CompanyEmployees.Presentation.AssemblyReference).Assembly);<\/code><\/pre>\n<p>Without this code, our API wouldn\u2019t work, and wouldn\u2019t know where to route incoming requests. But now, our app will find all of the controllers inside of the Presentation project and configure them with the framework. They are going to be treated the same as if they were defined conventionally.<br \/>\n\u5982\u679c\u6ca1\u6709\u8fd9\u4e9b\u4ee3\u7801\uff0c\u6211\u4eec\u7684 API \u5c06\u65e0\u6cd5\u5de5\u4f5c\uff0c\u5e76\u4e14\u4e0d\u77e5\u9053\u5c06\u4f20\u5165\u8bf7\u6c42\u8def\u7531\u5230\u4f55\u5904\u3002\u4f46\u73b0\u5728\uff0c\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u5c06\u5728 Presentation \u9879\u76ee\u4e2d\u627e\u5230\u6240\u6709\u63a7\u5236\u5668\uff0c\u5e76\u4f7f\u7528\u6846\u67b6\u5bf9\u5176\u8fdb\u884c\u914d\u7f6e\u3002\u5b83\u4eec\u5c06\u88ab\u5f53\u4f5c\u6309\u7ea6\u5b9a\u5b9a\u4e49\u7684\u4e00\u6837\u5bf9\u5f85\u3002<\/p>\n<p>But, we don\u2019t have our controllers yet. So, let\u2019s navigate to the Presentation project, create a new folder named Controllers, and then a new class named CompaniesController. Since this is a class library project, we don\u2019t have an option to create a controller as we had in the main project. Therefore, we have to create a regular class and then modify it:<br \/>\n\u4f46\u662f\uff0c\u6211\u4eec\u8fd8\u6ca1\u6709\u63a7\u5236\u5668\u3002\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u5bfc\u822a\u5230 Presentation \u9879\u76ee\uff0c\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a Controllers \u7684\u65b0\u6587\u4ef6\u5939\uff0c\u7136\u540e\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a CompaniesController \u7684\u65b0\u7c7b\u3002\u7531\u4e8e\u8fd9\u662f\u4e00\u4e2a\u7c7b\u5e93\u9879\u76ee\uff0c\u56e0\u6b64\u6211\u4eec\u65e0\u6cd5\u50cf\u5728\u4e3b\u9879\u76ee\u4e2d\u90a3\u6837\u521b\u5efa\u63a7\u5236\u5668\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u5fc5\u987b\u521b\u5efa\u4e00\u4e2a\u5e38\u89c4\u7c7b\uff0c\u7136\u540e\u5bf9\u5176\u8fdb\u884c\u4fee\u6539\uff1a<\/p>\n<pre><code>using Microsoft.AspNetCore.Mvc;\n\nnamespace CompanyEmployees.Presentation.Controllers\n{\n    [Route(&quot;api\/[controller]&quot;)]\n    [ApiController]\n    public class CompaniesController : ControllerBase\n    { }\n}<\/code><\/pre>\n<p>We\u2019ve created this controller in the same way the main project would.<br \/>\n\u6211\u4eec\u4ee5\u4e0e\u4e3b\u9879\u76ee\u76f8\u540c\u7684\u65b9\u5f0f\u521b\u5efa\u6b64\u63a7\u5236\u5668\u3002<\/p>\n<p>Every web API controller class inherits from the ControllerBase abstract class, which provides all necessary behavior for the derived class.<br \/>\n\u6bcf\u4e2a Web API \u63a7\u5236\u5668\u7c7b\u90fd\u7ee7\u627f\u81ea ControllerBase \u62bd\u8c61\u7c7b\uff0c\u8be5\u62bd\u8c61\u7c7b\u4e3a\u6d3e\u751f\u7c7b\u63d0\u4f9b\u6240\u6709\u5fc5\u8981\u7684\u884c\u4e3a\u3002<\/p>\n<p>Also, above the controller class we can see this part of the code:<br \/>\n\u6b64\u5916\uff0c\u5728 controller \u7c7b\u7684\u4e0a\u65b9\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u8fd9\u90e8\u5206\u4ee3\u7801\uff1a<\/p>\n<pre><code>[Route(&quot;api\/[controller]&quot;)]<\/code><\/pre>\n<p>This attribute represents routing and we are going to talk more about routing inside Web APIs.<br \/>\n\u8fd9\u4e2a\u5c5e\u6027\u8868\u793a\u8def\u7531\uff0c\u6211\u4eec\u5c06\u66f4\u591a\u5730\u8ba8\u8bba Web API \u4e2d\u7684\u8def\u7531\u3002<\/p>\n<p>Web API routing routes incoming HTTP requests to the particular action method inside the Web API controller. As soon as we send our HTTP request, the MVC framework parses that request and tries to match it to an action in the controller.<br \/>\nWeb API \u8def\u7531\u5c06\u4f20\u5165\u7684 HTTP \u8bf7\u6c42\u8def\u7531\u5230 Web API \u63a7\u5236\u5668\u5185\u7684\u7279\u5b9a\u4f5c\u65b9\u6cd5\u3002\u4e00\u65e6\u6211\u4eec\u53d1\u9001 HTTP \u8bf7\u6c42\uff0cMVC \u6846\u67b6\u5c31\u4f1a\u89e3\u6790\u8be5\u8bf7\u6c42\u5e76\u5c1d\u8bd5\u5c06\u5176\u4e0e\u63a7\u5236\u5668\u4e2d\u7684\u4f5c\u5339\u914d\u3002<\/p>\n<p>There are two ways to implement routing in the project:<br \/>\n\u6709\u4e24\u79cd\u65b9\u6cd5\u53ef\u4ee5\u5728\u9879\u76ee\u4e2d\u5b9e\u73b0\u8def\u7531\uff1a<\/p>\n<p>\u2022 Convention-based routing and<br \/>\n\u2022 \u57fa\u4e8e\u7ea6\u5b9a\u7684\u8def\u7531\u548c<\/p>\n<p>\u2022 Attribute routing<br \/>\n\u2022 \u5c5e\u6027\u8def\u7531<\/p>\n<p>Convention-based routing is called such because it establishes a convention for the URL paths. The first part creates the mapping for the controller name, the second part creates the mapping for the action method, and the third part is used for the optional parameter. We can configure this type of routing in the Program class:<br \/>\n\u4e4b\u6240\u4ee5\u79f0\u4e3a\u57fa\u4e8e\u7ea6\u5b9a\u7684\u8def\u7531\uff0c\u662f\u56e0\u4e3a\u5b83\u4e3a URL \u8def\u5f84\u5efa\u7acb\u4e86\u7ea6\u5b9a\u3002\u7b2c\u4e00\u90e8\u5206\u4e3a\u63a7\u5236\u5668\u540d\u79f0\u521b\u5efa\u6620\u5c04\uff0c\u7b2c\u4e8c\u90e8\u5206\u4e3a\u4f5c\u65b9\u6cd5\u521b\u5efa\u6620\u5c04\uff0c\u7b2c\u4e09\u90e8\u5206\u7528\u4e8e\u53ef\u9009\u53c2\u6570\u3002\u6211\u4eec\u53ef\u4ee5\u5728 Program \u7c7b\u4e2d\u914d\u7f6e\u8fd9\u79cd\u7c7b\u578b\u7684\u8def\u7531\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0402.jpg\" alt=\"alt text\" \/><\/p>\n<p>Our Web API project doesn\u2019t configure routes this way, but if you create an MVC project this will be the default route configuration. Of course, if you are using this type of route configuration, you have to use the app.UseRouting method to add the routing middleware in the application\u2019s pipeline.<br \/>\n\u6211\u4eec\u7684 Web API \u9879\u76ee\u4e0d\u4f1a\u4ee5\u8fd9\u79cd\u65b9\u5f0f\u914d\u7f6e\u8def\u7531\uff0c\u4f46\u5982\u679c\u60a8\u521b\u5efa\u4e00\u4e2a MVC \u9879\u76ee\uff0c\u8fd9\u5c06\u662f\u9ed8\u8ba4\u8def\u7531\u914d\u7f6e\u3002\u5f53\u7136\uff0c\u5982\u679c\u60a8\u4f7f\u7528\u7684\u662f\u8fd9\u79cd\u7c7b\u578b\u7684\u8def\u7531\u914d\u7f6e\uff0c\u5219\u5fc5\u987b\u4f7f\u7528\u8be5\u5e94\u7528\u7a0b\u5e8f\u3002UseRouting \u65b9\u6cd5\u5728\u5e94\u7528\u7a0b\u5e8f\u7684\u7ba1\u9053\u4e2d\u6dfb\u52a0\u8def\u7531\u4e2d\u95f4\u4ef6\u3002<\/p>\n<p>If you inspect the Program class in our main project, you won\u2019t find the UseRouting method because the routes are configured with the app.MapControllers method, which adds endpoints for controller actions without specifying any routes.<br \/>\n\u5982\u679c\u4f60\u68c0\u67e5\u4e3b\u9879\u76ee\u4e2d\u7684 Program \u7c7b\uff0c\u5219\u4e0d\u4f1a\u627e\u5230 UseRouting \u65b9\u6cd5\uff0c\u56e0\u4e3a\u8def\u7531\u662f\u4f7f\u7528\u5e94\u7528\u7a0b\u5e8f\u914d\u7f6e\u7684app.MapControllers \u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u4e3a\u63a7\u5236\u5668\u4f5c\u6dfb\u52a0\u7aef\u70b9\uff0c\u800c\u65e0\u9700\u6307\u5b9a\u4efb\u4f55\u8def\u7531\u3002<\/p>\n<p>Attribute routing uses the attributes to map the routes directly to the action methods inside the controller. Usually, we place the base route above the controller class, as you can see in our Web API controller class. Similarly, for the specific action methods, we create their routes right above them.<br \/>\n\u5c5e\u6027\u8def\u7531 \uff08Attribute routing\uff09 \u4f7f\u7528\u5c5e\u6027\u5c06\u8def\u7531\u76f4\u63a5\u6620\u5c04\u5230\u63a7\u5236\u5668\u5185\u7684\u4f5c\u65b9\u6cd5\u3002\u901a\u5e38\uff0c\u6211\u4eec\u5c06\u57fa\u8def\u7531\u653e\u5728 controller \u7c7b\u4e4b\u4e0a\uff0c\u6b63\u5982\u60a8\u5728 Web API controller \u7c7b\u4e2d\u770b\u5230\u7684\u90a3\u6837\u3002\u540c\u6837\uff0c\u5bf9\u4e8e\u7279\u5b9a\u7684 action methods\uff0c\u6211\u4eec\u5728\u5b83\u4eec\u7684\u6b63\u4e0a\u65b9\u521b\u5efa\u5b83\u4eec\u7684 route\u3002<\/p>\n<p>While working with the Web API project, the ASP.NET Core team suggests that we shouldn\u2019t use Convention-based Routing, but Attribute routing instead.<br \/>\n\u5728\u4f7f\u7528 Web API \u9879\u76ee\u65f6\uff0cASP.NET Core \u56e2\u961f\u5efa\u8bae\u6211\u4eec\u4e0d\u5e94\u4f7f\u7528\u57fa\u4e8e\u7ea6\u5b9a\u7684\u8def\u7531\uff0c\u800c\u5e94\u4f7f\u7528\u5c5e\u6027\u8def\u7531\u3002<\/p>\n<p>Different actions can be executed on the resource with the same URI, but with different HTTP Methods. In the same manner for different actions, we can use the same HTTP Method, but different URIs. Let\u2019s explain this quickly.<br \/>\n\u53ef\u4ee5\u5bf9\u5177\u6709\u76f8\u540c URI \u4f46\u4f7f\u7528\u4e0d\u540c\u7684 HTTP \u65b9\u6cd5\u7684\u8d44\u6e90\u6267\u884c\u4e0d\u540c\u7684\u4f5c\u3002\u4ee5\u76f8\u540c\u7684\u65b9\u5f0f\u7528\u4e8e\u4e0d\u540c\u7684\u4f5c\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u76f8\u540c\u7684 HTTP \u65b9\u6cd5\uff0c\u4f46\u4f7f\u7528\u4e0d\u540c\u7684 URI\u3002\u8ba9\u6211\u4eec\u5feb\u901f\u89e3\u91ca\u4e00\u4e0b\u3002<\/p>\n<p>For Get request, Post, or Delete, we use the same URI \/api\/companies but we use different HTTP Methods like GET, POST, or DELETE. But if we send a request for all companies or just one company, we are going to use the same GET method but different URIs (\/api\/companies for all companies and \/api\/companies\/{companyId} for a single company).<br \/>\n\u5bf9\u4e8e Get \u8bf7\u6c42\u3001Post \u6216 Delete\uff0c\u6211\u4eec\u4f7f\u7528\u76f8\u540c\u7684 URI \/api\/companies\uff0c\u4f46\u6211\u4eec\u4f7f\u7528\u4e0d\u540c\u7684 HTTP \u65b9\u6cd5\uff0c\u5982 GET\u3001POST \u6216 DELETE\u3002\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u5411\u6240\u6709\u516c\u53f8\u6216\u4ec5\u5411\u4e00\u5bb6\u516c\u53f8\u53d1\u9001\u8bf7\u6c42\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u76f8\u540c\u7684 GET \u65b9\u6cd5\uff0c\u4f46\u4f7f\u7528\u4e0d\u540c\u7684 URI\uff08\u6240\u6709\u516c\u53f8\u7684 \/api\/companies\uff0c\u5355\u4e2a\u516c\u53f8\u7684 \/api\/companies\/{companyId}\uff09\u3002<\/p>\n<p>We are going to understand this even more once we start implementing different actions in our controller.<br \/>\n\u4e00\u65e6\u6211\u4eec\u5f00\u59cb\u5728\u63a7\u5236\u5668\u4e2d\u5b9e\u73b0\u4e0d\u540c\u7684\u4f5c\uff0c\u6211\u4eec\u5c06\u66f4\u52a0\u4e86\u89e3\u8fd9\u4e00\u70b9\u3002<\/p>\n<h2>4.2 Naming Our Resources<\/h2>\n<p>4.2 \u547d\u540d\u6211\u4eec\u7684\u8d44\u6e90<\/p>\n<p>The resource name in the URI should always be a noun and not an action. That means if we want to create a route to get all companies, we should create this route: api\/companies and not this one:\u200c\/api\/getCompanies.<br \/>\nURI \u4e2d\u7684\u8d44\u6e90\u540d\u79f0\u5e94\u59cb\u7ec8\u662f\u540d\u8bcd\uff0c\u800c\u4e0d\u662f\u4f5c\u3002\u8fd9\u610f\u5473\u7740\uff0c\u5982\u679c\u6211\u4eec\u60f3\u521b\u5efa\u4e00\u4e2a\u8def\u7531\u6765\u83b7\u53d6\u6240\u6709\u516c\u53f8\uff0c\u6211\u4eec\u5e94\u8be5\u521b\u5efa\u8fd9\u4e2a\u8def\u7531\uff1aapi\/companies\uff0c\u800c\u4e0d\u662f\u8fd9\u4e2a\uff1a\/api\/getCompanies\u3002<\/p>\n<p>The noun used in URI represents the resource and helps the consumer to understand what type of resource we are working with. So, we shouldn\u2019t choose the noun products or orders when we work with the companies resource; the noun should always be companies. Therefore, by following this convention if our resource is employees (and we are going to work with this type of resource), the noun should be employees.<br \/>\nURI \u4e2d\u4f7f\u7528\u7684\u540d\u8bcd\u4ee3\u8868\u8d44\u6e90\uff0c\u5e76\u5e2e\u52a9\u4f7f\u7528\u8005\u4e86\u89e3\u6211\u4eec\u6b63\u5728\u4f7f\u7528\u7684\u8d44\u6e90\u7c7b\u578b\u3002\u56e0\u6b64\uff0c\u5f53\u6211\u4eec\u4f7f\u7528 companies \u8d44\u6e90\u65f6\uff0c\u6211\u4eec\u4e0d\u5e94\u8be5\u9009\u62e9\u540d\u8bcd products \u6216 orders;\u540d\u8bcd\u5e94\u59cb\u7ec8\u4e3a companies\u3002\u56e0\u6b64\uff0c\u5982\u679c\u6211\u4eec\u7684\u8d44\u6e90\u662f employees\uff08\u5e76\u4e14\u6211\u4eec\u5c06\u4f7f\u7528\u8fd9\u79cd\u7c7b\u578b\u7684\u8d44\u6e90\uff09\uff0c\u5219\u540d\u8bcd\u5e94\u8be5\u662f employees\uff0c\u8bf7\u9075\u5faa\u6b64\u7ea6\u5b9a\u3002<\/p>\n<p>Another important part we need to pay attention to is the hierarchy between our resources. In our example, we have a Company as a principal entity and an Employee as a dependent entity. When we create a route for a dependent entity, we should follow a slightly different convention:\/api\/principalResource\/{principalId}\/dependentResource.<br \/>\n\u6211\u4eec\u9700\u8981\u6ce8\u610f\u7684\u53e6\u4e00\u4e2a\u91cd\u8981\u90e8\u5206\u662f\u6211\u4eec\u8d44\u6e90\u4e4b\u95f4\u7684\u5c42\u6b21\u7ed3\u6784\u3002\u5728\u6211\u4eec\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5c06 Company \u4f5c\u4e3a\u59d4\u6258\u4eba\u5b9e\u4f53\uff0c\u5c06 Employee \u4f5c\u4e3a\u4f9d\u8d56\u5b9e\u4f53\u3002\u5f53\u6211\u4eec\u4e3a\u4f9d\u8d56\u5b9e\u4f53\u521b\u5efa\u8def\u7531\u65f6\uff0c\u6211\u4eec\u5e94\u8be5\u9075\u5faa\u7565\u6709\u4e0d\u540c\u7684\u7ea6\u5b9a\uff1a\/api\/principalResource\/{principalId}\/dependentResource\u3002<\/p>\n<p>Because our employees can\u2019t exist without a company, the route for the employee's resource should be \/api\/companies\/{companyId}\/employees.<br \/>\n\u56e0\u4e3a\u6211\u4eec\u7684\u5458\u5de5\u6ca1\u6709\u516c\u53f8\u5c31\u65e0\u6cd5\u5b58\u5728\uff0c\u6240\u4ee5\u5458\u5de5\u8d44\u6e90\u7684\u8def\u7531\u5e94\u8be5\u662f \/api\/companies\/{companyId}\/employees\u3002<\/p>\n<p>With all of this in mind, we can start with the Get requests.<br \/>\n\u8003\u8651\u5230\u6240\u6709\u8fd9\u4e9b\uff0c\u6211\u4eec\u53ef\u4ee5\u4ece Get \u8bf7\u6c42\u5f00\u59cb\u3002<\/p>\n<h2>4.3 Getting All Companies From the Database<\/h2>\n<p>4.3 \u4ece\u6570\u636e\u5e93\u4e2d\u83b7\u53d6\u6240\u6709\u516c\u53f8<\/p>\n<p>So let\u2019s start.\u200c<br \/>\n\u90a3\u4e48\u8ba9\u6211\u4eec\u5f00\u59cb\u5427\u3002<\/p>\n<p>The first thing we are going to do is to change the base route from [Route(&quot;api\/[controller]&quot;)] to [Route(&quot;api\/companies&quot;)]. Even though the first route will work just fine, with the second example we are more specific to show that this routing should point to the CompaniesController class.<br \/>\n\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u66f4\u6539 base \u8def\u7531\u4ece [Route\uff08\u201capi\/[controller]\u201d\uff09] \u5230 [Route\uff08\u201capi\/companies\u201d\uff09]\u3002\u5c3d\u7ba1\u7b2c\u4e00\u4e2a\u8def\u7531\u53ef\u4ee5\u6b63\u5e38\u5de5\u4f5c\uff0c\u4f46\u5728\u7b2c\u4e8c\u4e2a\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u66f4\u5177\u4f53\u5730\u8868\u660e\u6b64\u8def\u7531\u5e94\u6307\u5411 CompaniesController \u7c7b\u3002<\/p>\n<p>Now it is time to create the first action method to return all the companies from the database. Let\u2019s create a definition for the GetAllCompanies method in the ICompanyRepository interface:<br \/>\n\u73b0\u5728\uff0c\u662f\u65f6\u5019\u521b\u5efa\u7b2c\u4e00\u4e2a\u4f5c\u65b9\u6cd5\u4ee5\u4ece\u6570\u636e\u5e93\u4e2d\u8fd4\u56de\u6240\u6709\u516c\u53f8\u4e86\u3002\u8ba9\u6211\u4eec\u5728 ICompanyRepository \u63a5\u53e3\u4e2d\u4e3a GetAllCompanies \u65b9\u6cd5\u521b\u5efa\u4e00\u4e2a\u5b9a\u4e49\uff1a<\/p>\n<pre><code>using Entities.Models;\n\nnamespace Contract\n{\n    public interface ICompanyRepository\n    {\n\n            IEnumerable&lt;Company&gt; GetAllCompanies(bool trackChanges);\n\n    }\n}<\/code><\/pre>\n<p>For this to work, we need to add a reference from the Entities project to the Contracts project.<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u9700\u8981\u6dfb\u52a0\u4ece Entities \u9879\u76ee\u5230 Contracts \u9879\u76ee\u7684\u5f15\u7528\u3002<\/p>\n<p>Now, we can continue with the interface implementation in the CompanyRepository class:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u7ee7\u7eed\u5728 CompanyRepository \u7c7b\u4e2d\u5b9e\u73b0\u63a5\u53e3\uff1a<\/p>\n<pre><code>using Contract;\nusing Entities.Models;\n\nnamespace Repository\n{\n    \/\/public class CompanyRepository : RepositoryBase&lt;Company&gt;, ICompanyRepository\n    \/\/{\n    \/\/    public CompanyRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }\n    \/\/}\n\n    internal sealed class CompanyRepository : RepositoryBase&lt;Company&gt;, ICompanyRepository\n    {\n        public CompanyRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }\n        public IEnumerable&lt;Company&gt; GetAllCompanies(bool trackChanges) =&gt; FindAll(trackChanges).OrderBy(c =&gt; c.Name).ToList();\n    }\n}<\/code><\/pre>\n<p>As you can see, we are calling the FindAll method from the RepositoryBase class, ordering the result with the OrderBy method, and then executing the query with the ToList method.<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u6211\u4eec\u4ece RepositoryBase \u7c7b\u8c03\u7528 FindAll \u65b9\u6cd5\uff0c\u4f7f\u7528 OrderBy \u65b9\u6cd5\u5bf9\u7ed3\u679c\u8fdb\u884c\u6392\u5e8f\uff0c\u7136\u540e\u4f7f\u7528 ToList \u65b9\u6cd5\u6267\u884c\u67e5\u8be2\u3002<\/p>\n<p>After the repository implementation, we have to implement a service layer.<br \/>\n\u5728\u4ed3\u5e93\u5b9e\u73b0\u4e4b\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u5b9e\u73b0\u4e00\u4e2a\u670d\u52a1\u5c42\u3002<\/p>\n<p>Let\u2019s start with the ICompanyService interface modification:<br \/>\n\u8ba9\u6211\u4eec\u4ece ICompanyService \u63a5\u53e3\u4fee\u6539\u5f00\u59cb\uff1a<\/p>\n<pre><code>using Entities.Models;\n\nnamespace Service.Contracts\n{\n    public interface ICompanyService\n    {\n        IEnumerable&lt;Company&gt; GetAllCompanies(bool trackChanges);\n    }\n}<\/code><\/pre>\n<p>Since the Company model resides in the Entities project, we have to add the Entities reference to the Service.Contracts project. At least, we have for now.<br \/>\n\u7531\u4e8e Company \u6a21\u578b\u9a7b\u7559\u5728 Entities \u9879\u76ee\u4e2d\uff0c\u56e0\u6b64\u6211\u4eec\u5fc5\u987b\u5c06 Entities \u5f15\u7528\u6dfb\u52a0\u5230 Service.Contracts \u9879\u76ee\u4e2d\u3002\u81f3\u5c11\uff0c\u6211\u4eec\u73b0\u5728\u6709\u3002<\/p>\n<p>Let\u2019s be clear right away before we proceed. Getting all the entities from the database is a bad idea. We\u2019re going to start with the simplest method and change it later on.<br \/>\n\u5728\u6211\u4eec\u7ee7\u7eed\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u9a6c\u4e0a\u5f04\u6e05\u695a\u3002\u4ece\u6570\u636e\u5e93\u4e2d\u83b7\u53d6\u6240\u6709\u5b9e\u4f53\u662f\u4e00\u4e2a\u574f\u4e3b\u610f\u3002\u6211\u4eec\u5c06\u4ece\u6700\u7b80\u5355\u7684\u65b9\u6cd5\u5f00\u59cb\uff0c\u7a0d\u540e\u518d\u8fdb\u884c\u66f4\u6539\u3002<\/p>\n<p>Then, let\u2019s continue with the CompanyService modification:<br \/>\n\u7136\u540e\uff0c\u8ba9\u6211\u4eec\u7ee7\u7eed\u8fdb\u884c CompanyService \u4fee\u6539\uff1a<\/p>\n<pre><code>internal sealed class CompanyService : ICompanyService { private readonly IRepositoryManager _repository; private readonly ILoggerManager _logger; public CompanyService(IRepositoryManager repository, ILoggerManager logger) { _repository = repository; _logger = logger; } public IEnumerable&lt;Company&gt; GetAllCompanies(bool trackChanges) { try { var companies = _repository.Company.GetAllCompanies(trackChanges); return companies; } catch (Exception ex) { _logger.LogError($&quot;Something went wrong in the {nameof(GetAllCompanies)} service method {ex}&quot;); throw; } } }<\/code><\/pre>\n<p>We are using our repository manager to call the GetAllCompanies method from the CompanyRepository class and return all the companies from the database.<br \/>\n\u6211\u4eec\u4f7f\u7528\u5b58\u50a8\u5e93\u7ba1\u7406\u5668\u4ece CompanyRepository \u7c7b\u8c03\u7528 GetAllCompanies \u65b9\u6cd5\uff0c\u5e76\u8fd4\u56de\u6570\u636e\u5e93\u4e2d\u7684\u6240\u6709\u516c\u53f8\u3002<\/p>\n<p>Finally, we have to return companies by using the GetAllCompanies method inside the Web API controller.<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u5728 Web API \u63a7\u5236\u5668\u4e2d\u4f7f\u7528 GetAllCompanies \u65b9\u6cd5\u8fd4\u56de\u516c\u53f8\u3002<\/p>\n<p>The purpose of the action methods inside the Web API controllers is not only to return results. It is the main purpose, but not the only one. We need to pay attention to the status codes of our Web API responses as well. Additionally, we are going to decorate our actions with the HTTP attributes which will mark the type of the HTTP request to that action.<br \/>\nWeb API \u63a7\u5236\u5668\u4e2d\u7684\u4f5c\u65b9\u6cd5\u7684\u76ee\u7684\u4e0d\u4ec5\u4ec5\u662f\u8fd4\u56de\u7ed3\u679c\u3002\u8fd9\u662f\u4e3b\u8981\u76ee\u7684\uff0c\u4f46\u4e0d\u662f\u552f\u4e00\u76ee\u7684\u3002\u6211\u4eec\u8fd8\u9700\u8981\u6ce8\u610f Web API \u54cd\u5e94\u7684\u72b6\u6001\u4ee3\u7801\u3002\u6b64\u5916\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 HTTP \u5c5e\u6027\u6765\u88c5\u9970\u6211\u4eec\u7684\u4f5c\uff0c\u8fd9\u4e9b\u5c5e\u6027\u5c06\u6807\u8bb0\u8be5\u4f5c\u7684 HTTP \u8bf7\u6c42\u7684\u7c7b\u578b\u3002<\/p>\n<p>So, let\u2019s modify the CompaniesController:<br \/>\n\u90a3\u4e48\uff0c\u8ba9\u6211\u4eec\u4fee\u6539 CompaniesController\uff1a<\/p>\n<pre><code>using Microsoft.AspNetCore.Mvc;\nusing Service.Contracts;\n\nnamespace CompanyEmployees.Presentation.Controllers\n{\n    [Route(&quot;api\/companies&quot;)]\n    [ApiController]\n    public class CompaniesController : ControllerBase\n    {\n        private readonly IServiceManager _service;\n        public CompaniesController(IServiceManager service) =&gt; _service = service;\n        [HttpGet]\n        public IActionResult GetCompanies()\n        {\n            try\n            {\n                var companies = _service.CompanyService.GetAllCompanies(trackChanges: false);\n                return Ok(companies);\n            }\n            catch\n            {\n                return StatusCode(500, &quot;Internal server error&quot;);\n            }\n        }\n    }\n}<\/code><\/pre>\n<p>Let\u2019s explain this code a bit.<br \/>\n\u8ba9\u6211\u4eec\u7a0d\u5fae\u89e3\u91ca\u4e00\u4e0b\u8fd9\u6bb5\u4ee3\u7801\u3002<\/p>\n<p>First of all, we inject the IServiceManager interface inside the constructor. Then by decorating the GetCompanies action with the [HttpGet] attribute, we are mapping this action to the GET request. Then, we use an injected service to call the service method that gets the data from the repository class.<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u5728\u6784\u9020\u51fd\u6570\u4e2d\u6ce8\u5165 IServiceManager \u63a5\u53e3\u3002\u7136\u540e\uff0c\u901a\u8fc7\u4f7f\u7528 GetCompanies\u7684[HttpGet] \u5c5e\u6027\uff0c\u6211\u4eec\u5c06\u6b64\u4f5c\u6620\u5c04\u5230 GET \u8bf7\u6c42\u3002\u7136\u540e\uff0c\u6211\u4eec\u4f7f\u7528\u6ce8\u5165\u7684\u670d\u52a1\u6765\u8c03\u7528\u4ece repository \u7c7b\u83b7\u53d6\u6570\u636e\u7684 service \u65b9\u6cd5\u3002<\/p>\n<p>The IActionResult interface supports using a variety of methods, which return not only the result but also the status codes. In this situation,the OK method returns all the companies and also the status code 200 \u2014 which stands for OK. If an exception occurs, we are going to return the internal server error with the status code 500.<br \/>\nIActionResult \u63a5\u53e3\u652f\u6301\u4f7f\u7528\u591a\u79cd\u65b9\u6cd5\uff0c\u8fd9\u4e9b\u65b9\u6cd5\u4e0d\u4ec5\u8fd4\u56de\u7ed3\u679c\uff0c\u8fd8\u8fd4\u56de\u72b6\u6001\u4ee3\u7801\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0cOK \u65b9\u6cd5\u8fd4\u56de\u6240\u6709\u516c\u53f8\u4ee5\u53ca\u72b6\u6001\u4ee3\u7801 200 \u2014 \u4ee3\u8868 OK\u3002\u5982\u679c\u53d1\u751f\u5f02\u5e38\uff0c\u6211\u4eec\u5c06\u8fd4\u56de\u72b6\u6001\u4ee3\u7801\u4e3a 500 \u7684\u5185\u90e8\u670d\u52a1\u5668\u9519\u8bef\u3002<\/p>\n<p>Because there is no route attribute right above the action, the route for the GetCompanies action will be api\/companies which is the route placed on top of our controller.<br \/>\n\u7531\u4e8e\u4f5c\u6b63\u4e0a\u65b9\u6ca1\u6709 route \u5c5e\u6027\uff0c\u56e0\u6b64 GetCompanies\u4f5c\u7684\u8def\u7531\u5c06\u662f api\/companies\uff0c\u8fd9\u662f\u653e\u7f6e\u5728\u63a7\u5236\u5668\u9876\u90e8\u7684\u8def\u7531\u3002<\/p>\n<h2>4.4 Testing the Result with Postman<\/h2>\n<p>4.4 \u4f7f\u7528 Postman \u6d4b\u8bd5\u7ed3\u679c<\/p>\n<p>To check the result, we are going to use a great tool named Postman, which helps a lot with sending requests and displaying responses. If you download our exercise files, you will find the file Bonus 2- CompanyEmployeesRequests.postman_collection.json, which contains a request collection divided for each chapter of this book. You can import them in Postman to save yourself the time of manually typing them:\u200c<br \/>\n\u4e3a\u4e86\u68c0\u67e5\u7ed3\u679c\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u4e00\u4e2a\u540d\u4e3a Postman \u7684\u51fa\u8272\u5de5\u5177\uff0c\u5b83\u5bf9\u53d1\u9001\u8bf7\u6c42\u548c\u663e\u793a\u54cd\u5e94\u6709\u5f88\u5927\u5e2e\u52a9\u3002\u5982\u679c\u60a8\u4e0b\u8f7d\u6211\u4eec\u7684\u7ec3\u4e60\u6587\u4ef6\uff0c\u60a8\u5c06\u627e\u5230\u6587\u4ef6 Bonus 2- CompanyEmployeesRequests.postman_collection.json\uff0c\u5176\u4e2d\u5305\u542b\u4e3a\u672c\u4e66\u7684\u6bcf\u4e00\u7ae0\u5212\u5206\u7684\u8bf7\u6c42\u96c6\u5408\u3002\u60a8\u53ef\u4ee5\u5728 Postman \u4e2d\u5bfc\u5165\u5b83\u4eec\uff0c\u4ee5\u8282\u7701\u624b\u52a8\u8f93\u5165\u5b83\u4eec\u7684\u65f6\u95f4\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0403.jpg\" alt=\"alt text\" \/><\/p>\n<p><b>NOTE:<\/b> Please note that some GUID values will be different for your project, so you have to change them according to those values.<br \/>\n\u6ce8\u610f\uff1a\u8bf7\u6ce8\u610f\uff0c\u67d0\u4e9b GUID \u503c\u5bf9\u4e8e\u60a8\u7684\u9879\u76ee\u4f1a\u6709\u6240\u4e0d\u540c\uff0c\u56e0\u6b64\u60a8\u5fc5\u987b\u6839\u636e\u8fd9\u4e9b\u503c\u66f4\u6539\u5b83\u4eec\u3002<\/p>\n<p>So let\u2019s start the application by pressing the F5 button and check that it is now listening on the <a href=\"https:\/\/localhost:5001\">https:\/\/localhost:5001<\/a> address:<br \/>\n\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u6309 F5 \u6309\u94ae\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\uff0c\u5e76\u68c0\u67e5\u5b83\u73b0\u5728\u662f\u5426\u6b63\u5728\u4fa6\u542c <a href=\"https:\/\/localhost:5001\">https:\/\/localhost:5001<\/a> \u5730\u5740\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0404.jpg\" alt=\"alt text\" \/><\/p>\n<p>If this is not the case, you probably ran it in the IIS mode; so turn the application off and start it again, but in the CompanyEmployees mode:<br \/>\n\u5982\u679c\u4e0d\u662f\u8fd9\u79cd\u60c5\u51b5\uff0c\u60a8\u53ef\u80fd\u5728 IIS \u6a21\u5f0f\u4e0b\u8fd0\u884c\u4e86\u5b83;\u56e0\u6b64\uff0c\u8bf7\u5173\u95ed\u5e94\u7528\u7a0b\u5e8f\u5e76\u518d\u6b21\u542f\u52a8\u5b83\uff0c\u4f46\u5728 CompanyEmployees \u6a21\u5f0f\u4e0b\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0405.jpg\" alt=\"alt text\" \/><\/p>\n<p>Now, we can use Postman to test the result:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 Postman \u6765\u6d4b\u8bd5\u7ed3\u679c\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0406.jpg\" alt=\"alt text\" \/><\/p>\n<p>Excellent, everything is working as planned. But we are missing something. We are using the Company entity to map our requests to the database and then returning it as a result to the client, and this is not a good practice. So, in the next part, we are going to learn how to improve our code with DTO classes.<br \/>\n\u592a\u597d\u4e86\uff0c\u4e00\u5207\u90fd\u6309\u8ba1\u5212\u8fdb\u884c\u3002\u4f46\u6211\u4eec\u7f3a\u5c11\u4e00\u4e9b\u4e1c\u897f\u3002\u6211\u4eec\u4f7f\u7528 Company \u5b9e\u4f53\u5c06\u8bf7\u6c42\u6620\u5c04\u5230\u6570\u636e\u5e93\uff0c\u7136\u540e\u5c06\u5176\u4f5c\u4e3a\u7ed3\u679c\u8fd4\u56de\u7ed9\u5ba2\u6237\u7aef\uff0c\u8fd9\u4e0d\u662f\u4e00\u4e2a\u597d\u7684\u505a\u6cd5\u3002\u56e0\u6b64\uff0c\u5728\u4e0b\u4e00\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528 DTO \u7c7b\u6539\u8fdb\u6211\u4eec\u7684\u4ee3\u7801\u3002<\/p>\n<h2>4.5 DTO Classes vs. Entity Model Classes<\/h2>\n<p>4.5 DTO \u7c7b\u4e0e\u5b9e\u4f53\u6a21\u578b\u7c7b<\/p>\n<p>A data transfer object (DTO) is an object that we use to transport data between the client and server applications.\u200c<br \/>\n\u6570\u636e\u4f20\u8f93\u5bf9\u8c61 \uff08DTO\uff09 \u662f\u6211\u4eec\u7528\u6765\u5728\u5ba2\u6237\u7aef\u548c\u670d\u52a1\u5668\u5e94\u7528\u7a0b\u5e8f\u4e4b\u95f4\u4f20\u8f93\u6570\u636e\u7684\u5bf9\u8c61\u3002<\/p>\n<p>So, as we said in a previous section of this book, it is not a good practice to return entities in the Web API response; we should instead use data transfer objects. But why is that?<br \/>\n\u56e0\u6b64\uff0c\u6b63\u5982\u6211\u4eec\u5728\u672c\u4e66\u7684\u4e0a\u4e00\u8282\u4e2d\u6240\u8bf4\uff0c\u5728 Web API \u54cd\u5e94\u4e2d\u8fd4\u56de\u5b9e\u4f53\u4e0d\u662f\u4e00\u4e2a\u597d\u7684\u505a\u6cd5;\u6211\u4eec\u5e94\u8be5\u6539\u7528 Data Transfer \u5bf9\u8c61\u3002\u4f46\u8fd9\u662f\u4e3a\u4ec0\u4e48\u5462\uff1f<\/p>\n<p>Well, EF Core uses model classes to map them to the tables in the database and that is the main purpose of a model class. But as we saw, our models have navigational properties and sometimes we don\u2019t want to map them in an API response. So, we can use DTO to remove any property or concatenate properties into a single property.<br \/>\nEF Core \u4f7f\u7528\u6a21\u578b\u7c7b\u5c06\u5b83\u4eec\u6620\u5c04\u5230\u6570\u636e\u5e93\u4e2d\u7684\u8868\uff0c\u8fd9\u5c31\u662f\u6a21\u578b\u7c7b\u7684\u4e3b\u8981\u7528\u9014\u3002\u4f46\u6b63\u5982\u6211\u4eec\u6240\u770b\u5230\u7684\uff0c\u6211\u4eec\u7684\u6a21\u578b\u5177\u6709\u5bfc\u822a\u5c5e\u6027\uff0c\u6709\u65f6\u6211\u4eec\u4e0d\u60f3\u5728 API \u54cd\u5e94\u4e2d\u6620\u5c04\u5b83\u4eec\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 DTO \u5220\u9664\u4efb\u4f55\u5c5e\u6027\u6216\u5c06\u5c5e\u6027\u8fde\u63a5\u6210\u5355\u4e2a\u5c5e\u6027\u3002<\/p>\n<p>Moreover, there are situations where we want to map all the properties from a model class to the result \u2014 but still, we want to use DTO instead. The reason is if we change the database, we also have to change the properties in a model \u2014 but that doesn\u2019t mean our clients want the result changed. So, by using DTO, the result will stay as it was before the model changes.<br \/>\n\u6b64\u5916\uff0c\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u5e0c\u671b\u5c06\u6a21\u578b\u7c7b\u4e2d\u7684\u6240\u6709\u5c5e\u6027\u6620\u5c04\u5230\u7ed3\u679c\u2014\u2014\u4f46\u6211\u4eec\u4ecd\u7136\u5e0c\u671b\u4f7f\u7528 DTO \u6765\u4ee3\u66ff\u3002\u539f\u56e0\u662f\u5982\u679c\u6211\u4eec\u66f4\u6539\u6570\u636e\u5e93\uff0c\u6211\u4eec\u4e5f\u5fc5\u987b\u66f4\u6539\u6a21\u578b\u4e2d\u7684\u5c5e\u6027 \u2014 \u4f46\u8fd9\u5e76\u4e0d\u610f\u5473\u7740\u6211\u4eec\u7684\u5ba2\u6237\u5e0c\u671b\u66f4\u6539\u7ed3\u679c\u3002\u56e0\u6b64\uff0c\u901a\u8fc7\u4f7f\u7528 DTO\uff0c\u7ed3\u679c\u5c06\u4fdd\u6301\u4e0e\u6a21\u578b\u66f4\u6539\u4e4b\u524d\u76f8\u540c\u3002<\/p>\n<p>As we can see, keeping these objects separate (the DTO and model classes) leads to a more robust and maintainable code in our application.<br \/>\n\u6b63\u5982\u6211\u4eec\u6240\u770b\u5230\u7684\uff0c\u5c06\u8fd9\u4e9b\u5bf9\u8c61\u5206\u5f00\uff08DTO \u548c\u6a21\u578b\u7c7b\uff09\u4f1a\u5bfc\u81f4\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u4ee3\u7801\u66f4\u52a0\u5065\u58ee\u548c\u53ef\u7ef4\u62a4\u3002<\/p>\n<p>Now, when we know why should we separate DTO from a model class in our code, let\u2019s create a new project named Shared and then a new folder DataTransferObjects with the CompanyDto record inside:<br \/>\n\u73b0\u5728\uff0c\u5f53\u6211\u4eec\u77e5\u9053\u4e3a\u4ec0\u4e48\u5e94\u8be5\u5728\u4ee3\u7801\u4e2d\u5c06 DTO \u4e0e\u6a21\u578b\u7c7b\u5206\u5f00\u65f6\uff0c\u8ba9\u6211\u4eec\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a Shared \u7684\u65b0\u9879\u76ee\uff0c\u7136\u540e\u521b\u5efa\u4e00\u4e2a\u65b0\u6587\u4ef6\u5939 DataTransferObjects\uff0c\u521b\u5efaCompanyDto \u7c7b\uff1a<\/p>\n<pre><code>namespace Shared.DataTransferObjects\n{\n    public record CompanyDto(Guid Id, string Name, string FullAddress);\n}<\/code><\/pre>\n<p>Instead of a regular class, we are using a record for DTO. This specific record type is known as a Positional record.<br \/>\n\u6211\u4eec\u4f7f\u7528\u7684\u4e0d\u662f\u5e38\u89c4\u7c7b\uff0c\u800c\u662f DTO \u7684\u8bb0\u5f55\u3002\u6b64\u7279\u5b9a\u8bb0\u5f55\u7c7b\u578b\u79f0\u4e3a Positional record\u3002<\/p>\n<p>A Record type provides us an easier way to create an immutable reference type in .NET. This means that the Record\u2019s instance property values cannot change after its initialization. The data are passed by value and the equality between two Records is verified by comparing the value of their properties.<br \/>\nRecord \u7c7b\u578b\u4e3a\u6211\u4eec\u63d0\u4f9b\u4e86\u4e00\u79cd\u5728 .NET \u4e2d\u521b\u5efa\u4e0d\u53ef\u53d8\u5f15\u7528\u7c7b\u578b\u7684\u66f4\u7b80\u5355\u65b9\u6cd5\u3002\u8fd9\u610f\u5473\u7740 Record \u7684\u5b9e\u4f8b\u5c5e\u6027\u503c\u5728\u521d\u59cb\u5316\u540e\u65e0\u6cd5\u66f4\u6539\u3002\u6570\u636e\u6309\u503c\u4f20\u9012\uff0c\u5e76\u901a\u8fc7\u6bd4\u8f83\u4e24\u4e2a Record \u7684\u5c5e\u6027\u503c\u6765\u9a8c\u8bc1\u5b83\u4eec\u4e4b\u95f4\u7684\u76f8\u7b49\u6027\u3002<\/p>\n<p>Records can be a valid alternative to classes when we have to send or receive data. The very purpose of a DTO is to transfer data from one part of the code to another, and immutability in many cases is useful. We use them to return data from a Web API or to represent events in our application.<br \/>\n\u5f53\u6211\u4eec\u5fc5\u987b\u53d1\u9001\u6216\u63a5\u6536\u6570\u636e\u65f6\uff0c\u8bb0\u5f55\u53ef\u4ee5\u662f\u7c7b\u7684\u6709\u6548\u66ff\u4ee3\u65b9\u6848\u3002DTO \u7684\u771f\u6b63\u76ee\u7684\u662f\u5c06\u6570\u636e\u4ece\u4ee3\u7801\u7684\u4e00\u90e8\u5206\u4f20\u8f93\u5230\u53e6\u4e00\u90e8\u5206\uff0c\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b\uff0c\u4e0d\u53d8\u6027\u5f88\u6709\u7528\u3002\u6211\u4eec\u4f7f\u7528\u5b83\u4eec\u4ece Web API \u8fd4\u56de\u6570\u636e\u6216\u8868\u793a\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u4e8b\u4ef6\u3002<\/p>\n<p>This is the exact reason why we are using records for our DTOs.<br \/>\n\u8fd9\u5c31\u662f\u6211\u4eec\u4e3a DTO \u4f7f\u7528\u8bb0\u5f55\u7684\u786e\u5207\u539f\u56e0\u3002<\/p>\n<p>In our DTO, we have removed the Employees property and we are going to use the FullAddress property to concatenate the Address and Country properties from the Company class. Furthermore, we are not using validation attributes in this record, because we are going to use this record only to return a response to the client. Therefore, validation attributes are not required.<br \/>\n\u5728\u6211\u4eec\u7684 DTO \u4e2d\uff0c\u6211\u4eec\u5220\u9664\u4e86 Employees \u5c5e\u6027\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 FullAddress \u5c5e\u6027\u6765\u8fde\u63a5 Company \u7c7b\u4e2d\u7684 Address \u548c Country \u5c5e\u6027\u3002\u6b64\u5916\uff0c\u6211\u4eec\u6ca1\u6709\u5728\u6b64\u8bb0\u5f55\u4e2d\u4f7f\u7528\u9a8c\u8bc1\u5c5e\u6027\uff0c\u56e0\u4e3a\u6211\u4eec\u4ec5\u5c06\u4f7f\u7528\u6b64\u8bb0\u5f55\u5c06\u54cd\u5e94\u8fd4\u56de\u7ed9\u5ba2\u6237\u7aef\u3002\u56e0\u6b64\uff0c\u9a8c\u8bc1\u5c5e\u6027\u4e0d\u662f\u5fc5\u9700\u7684\u3002<\/p>\n<p>So, the first thing we have to do is to add the reference from the Shared project to the Service.Contracts project, and remove the Entities reference. At this moment the Service.Contracts project is only referencing the Shared project.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u5c06 Shared \u9879\u76ee\u4e2d\u7684\u5f15\u7528\u6dfb\u52a0\u5230 Service.Contracts \u9879\u76ee\u4e2d\uff0c\u5e76\u5220\u9664 Entities \u5f15\u7528\u3002\u6b64\u65f6\uff0cService.Contracts \u9879\u76ee\u4ec5\u5f15\u7528 Shared \u9879\u76ee\u3002<\/p>\n<p>Then, we have to modify the ICompanyService interface:<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u4fee\u6539 ICompanyService \u63a5\u53e3\uff1a<\/p>\n<pre><code>using Shared.DataTransferObjects;\n\nnamespace Service.Contracts\n{\n    public interface ICompanyService\n    {\n        IEnumerable&lt;CompanyDto&gt; GetAllCompanies(bool trackChanges);\n    }\n}<\/code><\/pre>\n<p>And the CompanyService class:<br \/>\n\u4ee5\u53ca CompanyService \u7c7b\uff1a<\/p>\n<pre><code>using Contract;\nusing Service.Contracts;\nusing Shared.DataTransferObjects;\n\nnamespace Service\n{\n    internal sealed class CompanyService : ICompanyService\n    {\n        private readonly IRepositoryManager _repository;\n        private readonly ILoggerManager _logger;\n        public CompanyService(IRepositoryManager repository, ILoggerManager logger)\n        {\n            _repository = repository;\n            _logger = logger;\n        }\n        public IEnumerable&lt;CompanyDto&gt; GetAllCompanies(bool trackChanges)\n        {\n            try\n            {\n                var companies = _repository.Company.GetAllCompanies(trackChanges);\n                var companiesDto = companies.Select(c =&gt; new CompanyDto(c.Id, c.Name ?? &quot;&quot;, string.Join(&#039; &#039;, c.Address, c.Country))).ToList();\n                return companiesDto;\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError($&quot;Something went wrong in the {nameof(GetAllCompanies)} service method {ex}&quot;);\n                throw;\n            }\n        }\n    }\n}<\/code><\/pre>\n<p>Let\u2019s start our application and test it with the same request from Postman:<a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><br \/>\n\u8ba9\u6211\u4eec\u542f\u52a8\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\uff0c\u5e76\u4f7f\u7528\u6765\u81ea Postman\uff1a<a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a> \u7684\u76f8\u540c\u8bf7\u6c42\u5bf9\u5176\u8fdb\u884c\u6d4b\u8bd5<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0407.jpg\" alt=\"alt text\" \/><\/p>\n<p>This time we get our CompanyDto result, which is a more preferred way. But this can be improved as well. If we take a look at our mapping code in the GetCompanies action, we can see that we manually map all the properties. Sure, it is okay for a few fields \u2014 but what if we have a lot more? There is a better and cleaner way to map our classes and that is by using the Automapper.<br \/>\n\u8fd9\u6b21\u6211\u4eec\u5f97\u5230 CompanyDto \u7ed3\u679c\uff0c\u8fd9\u662f\u4e00\u79cd\u66f4\u53ef\u53d6\u7684\u65b9\u6cd5\u3002\u4f46\u8fd9\u4e5f\u53ef\u4ee5\u6539\u8fdb\u3002\u5982\u679c\u6211\u4eec\u67e5\u770b GetCompanies\u4f5c\u4e2d\u7684\u6620\u5c04\u4ee3\u7801\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u6211\u4eec\u624b\u52a8\u6620\u5c04\u4e86\u6240\u6709\u5c5e\u6027\u3002\u5f53\u7136\uff0c\u5bf9\u4e8e\u4e00\u4e9b\u9886\u57df\u6765\u8bf4\uff0c\u8fd9\u662f\u53ef\u4ee5\u7684\u2014\u2014\u4f46\u5982\u679c\u6211\u4eec\u6709\u66f4\u591a\u7684\u9886\u57df\u5462\uff1f\u6709\u4e00\u79cd\u66f4\u597d\u3001\u66f4\u7b80\u6d01\u7684\u65b9\u6cd5\u6765\u6620\u5c04\u6211\u4eec\u7684\u7c7b\uff0c\u90a3\u5c31\u662f\u4f7f\u7528 Automapper\u3002<\/p>\n<h2>4.6  Using AutoMapper in ASP.NET Core<\/h2>\n<p>4.6 \u5728 ASP.NET Core \u4e2d\u4f7f\u7528 AutoMapper<\/p>\n<p>AutoMapper is a library that helps us with mapping objects in our applications. By using this library, we are going to remove the code for manual mapping \u2014 thus making the action readable and maintainable.\u200c<br \/>\nAutoMapper \u662f\u4e00\u4e2a\u5e93\uff0c\u53ef\u5e2e\u52a9\u6211\u4eec\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u6620\u5c04\u5bf9\u8c61\u3002\u901a\u8fc7\u4f7f\u7528\u8fd9\u4e2a\u5e93\uff0c\u6211\u4eec\u5c06\u5220\u9664\u7528\u4e8e\u624b\u52a8\u6620\u5c04\u7684\u4ee3\u7801 \u2014 \u4ece\u800c\u4f7f\u4f5c\u53ef\u8bfb\u4e14\u53ef\u7ef4\u62a4\u3002<\/p>\n<p>So, to install AutoMapper, let\u2019s open a Package Manager Console window, choose the Service project as a default project from the drop-down list, and run the following command:<br \/>\n\u56e0\u6b64\uff0c\u8981\u5b89\u88c5 AutoMapper\uff0c\u8ba9\u6211\u4eec\u6253\u5f00\u4e00\u4e2a Package Manager Console \u7a97\u53e3\uff0c\u4ece\u4e0b\u62c9\u5217\u8868\u4e2d\u9009\u62e9 Service \u9879\u76ee\u4f5c\u4e3a\u9ed8\u8ba4\u9879\u76ee\uff0c\u7136\u540e\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a<\/p>\n<pre><code>PM&gt; Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection<\/code><\/pre>\n<p>After installation, we are going to register this library in the Program class:<br \/>\n\u5b89\u88c5\u540e\uff0c\u6211\u4eec\u5c06\u5728 Program \u7c7b\u4e2d\u6ce8\u518c\u6b64\u5e93\uff1a<\/p>\n<pre><code>builder.Services.AddAutoMapper(typeof(Program));<\/code><\/pre>\n<p>As soon as our library is registered, we are going to create a profile class, also in the main project, where we specify the source and destination objects for mapping:<br \/>\n\u6ce8\u518c\u5e93\u540e\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a profile \u7c7b\uff0c\u4e5f\u662f\u5728\u4e3b\u9879\u76ee\u4e2d\uff0c\u6211\u4eec\u5728\u5176\u4e2d\u6307\u5b9a\u8981\u6620\u5c04\u7684\u6e90\u548c\u76ee\u6807\u5bf9\u8c61\uff1a<\/p>\n<pre><code>using AutoMapper;\nusing Entities.Models;\nusing Shared.DataTransferObjects;\n\nnamespace CompanyEmployees\n{\n    public class MappingProfile : Profile\n    {\n        public MappingProfile()\n        {\n            CreateMap&lt;Company, CompanyDto&gt;().ForMember(c =&gt; c.FullAddress, opt =&gt; opt.MapFrom(x =&gt; string.Join(&#039; &#039;, x.Address, x.Country)));\n        }\n    }\n}<\/code><\/pre>\n<p>The MappingProfile class must inherit from the AutoMapper\u2019s Profile class. In the constructor, we are using the CreateMap method where we specify the source object and the destination object to map to. Because we have the FullAddress property in our DTO record, which contains both the Address and the Country from the model class, we have to specify additional mapping rules with the ForMember method.<br \/>\nMappingProfile \u7c7b\u5fc5\u987b\u7ee7\u627f\u81ea AutoMapper \u7684 Profile \u7c7b\u3002\u5728\u6784\u9020\u51fd\u6570\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528 CreateMap \u65b9\u6cd5\uff0c\u5728\u5176\u4e2d\u6307\u5b9a\u8981\u6620\u5c04\u5230\u7684\u6e90\u5bf9\u8c61\u548c\u76ee\u6807\u5bf9\u8c61\u3002\u7531\u4e8e\u6211\u4eec\u7684 DTO \u8bb0\u5f55\u4e2d\u6709 FullAddress \u5c5e\u6027\uff0c\u5176\u4e2d\u5305\u542b\u6a21\u578b\u7c7b\u4e2d\u7684 Address \u548c Country\uff0c\u56e0\u6b64\u6211\u4eec\u5fc5\u987b\u4f7f\u7528 ForMember \u65b9\u6cd5\u6307\u5b9a\u5176\u4ed6\u6620\u5c04\u89c4\u5219\u3002<\/p>\n<p>Now, we have to modify the ServiceManager class to enable DI in our service classes:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5fc5\u987b\u4fee\u6539 ServiceManager \u7c7b\u4ee5\u5728\u6211\u4eec\u7684\u670d\u52a1\u7c7b\u4e2d\u542f\u7528 DI\uff1a<\/p>\n<pre><code>using Contract;\nusing Service.Contracts;\nusing Service;\nusing AutoMapper;\n\n\/\/public sealed class ServiceManager : IServiceManager\n\/\/{\n\/\/    private readonly Lazy&lt;ICompanyService&gt; _companyService; private readonly Lazy&lt;IEmployeeService&gt; _employeeService;\n\/\/    public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger)\n\/\/    {\n\/\/        _companyService = new Lazy&lt;ICompanyService&gt;(() =&gt; new CompanyService(repositoryManager, logger));\n\/\/        _employeeService = new Lazy&lt;IEmployeeService&gt;(() =&gt; new EmployeeService(repositoryManager, logger));\n\/\/    }\n\/\/    public ICompanyService CompanyService =&gt; _companyService.Value;\n\/\/    public IEmployeeService EmployeeService =&gt; _employeeService.Value;\n\/\/}\n\npublic sealed class ServiceManager : IServiceManager\n{\n    private readonly Lazy&lt;ICompanyService&gt; _companyService;\n    private readonly Lazy&lt;IEmployeeService&gt; _employeeService;\n    public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger, IMapper mapper)\n    {\n        _companyService = new Lazy&lt;ICompanyService&gt;(() =&gt; new CompanyService(repositoryManager, logger, mapper));\n        _employeeService = new Lazy&lt;IEmployeeService&gt;(() =&gt; new EmployeeService(repositoryManager, logger, mapper));\n    }\n    public ICompanyService CompanyService =&gt; _companyService.Value;\n    public IEmployeeService EmployeeService =&gt; _employeeService.Value;\n}<\/code><\/pre>\n<p>Of course, now we have two errors regarding our service constructors. So we need to fix that in both CompanyService and EmployeeService classes:<br \/>\n\u5f53\u7136\uff0c\u73b0\u5728\u6211\u4eec\u6709\u4e24\u4e2a\u5173\u4e8e\u670d\u52a1\u6784\u9020\u51fd\u6570\u7684\u9519\u8bef\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u9700\u8981\u5728 CompanyService \u548c EmployeeService \u7c7b\u4e2d\u4fee\u590d\u8be5\u95ee\u9898\uff1a<\/p>\n<pre><code>using AutoMapper;\nusing Contract;\nusing Service.Contracts;\nusing Shared.DataTransferObjects;\n\nnamespace Service\n{\n    \/\/internal sealed class CompanyService : ICompanyService\n    \/\/{\n    \/\/    private readonly IRepositoryManager _repository;\n    \/\/    private readonly ILoggerManager _logger;\n    \/\/    public CompanyService(IRepositoryManager repository, ILoggerManager logger)\n    \/\/    {\n    \/\/        _repository = repository;\n    \/\/        _logger = logger;\n    \/\/    }\n    \/\/}\n\n    \/\/internal sealed class CompanyService : ICompanyService\n    \/\/{\n    \/\/    private readonly IRepositoryManager _repository;\n    \/\/    private readonly ILoggerManager _logger;\n    \/\/    public CompanyService(IRepositoryManager repository, ILoggerManager logger)\n    \/\/    {\n    \/\/        _repository = repository; \n    \/\/        _logger = logger;\n    \/\/    }\n    \/\/    public IEnumerable&lt;Company&gt; GetAllCompanies(bool trackChanges)\n    \/\/    {\n    \/\/        try\n    \/\/        {\n    \/\/            var companies = _repository.Company.GetAllCompanies(trackChanges);\n    \/\/            return companies;\n    \/\/        }\n    \/\/        catch (Exception ex)\n    \/\/        {\n    \/\/            _logger.LogError($&quot;Something went wrong in the {nameof(GetAllCompanies)} service method {ex}&quot;);\n    \/\/            throw;\n    \/\/        }\n    \/\/    }\n    \/\/}\n\n    internal sealed class CompanyService : ICompanyService\n    {\n        private readonly IRepositoryManager _repository;\n        private readonly ILoggerManager _logger;\n        private readonly IMapper _mapper;\n        public CompanyService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)\n        {\n            _repository = repository;\n            _logger = logger;\n            _mapper = mapper;\n        }\n        public IEnumerable&lt;CompanyDto&gt; GetAllCompanies(bool trackChanges)\n        {\n            try\n            {\n                var companies = _repository.Company.GetAllCompanies(trackChanges);\n                var companiesDto = companies.Select(c =&gt; new CompanyDto(c.Id, c.Name ?? &quot;&quot;, string.Join(&#039; &#039;, c.Address, c.Country))).ToList();\n                return companiesDto;\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError($&quot;Something went wrong in the {nameof(GetAllCompanies)} service method {ex}&quot;);\n                throw;\n            }\n        }\n    }\n}<\/code><\/pre>\n<p>We should do the same in the EmployeeService class:<br \/>\n\u6211\u4eec\u5e94\u8be5\u5728 EmployeeService \u7c7b\u4e2d\u505a\u540c\u6837\u7684\u4e8b\u60c5\uff1a<\/p>\n<pre><code>using AutoMapper;\nusing Contract;\nusing Service.Contracts;\n\nnamespace Service\n{\n    internal sealed class EmployeeService : IEmployeeService\n    {\n        private readonly IRepositoryManager _repository; \n        private readonly ILoggerManager _logger;\n        private readonly IMapper _mapper;\n        public EmployeeService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)\n        {\n            _repository = repository; \n            _logger = logger;\n            _mapper = mapper;\n        }\n    }\n}<\/code><\/pre>\n<p>Finally, we can modify the GetAllCompanies method in the CompanyService class:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u4fee\u6539 CompanyService \u7c7b\u4e2d\u7684 GetAllCompanies \u65b9\u6cd5\uff1a<\/p>\n<pre><code>using AutoMapper;\nusing Contract;\nusing Service.Contracts;\nusing Shared.DataTransferObjects;\n\nnamespace Service\n{\n    internal sealed class CompanyService : ICompanyService\n    {\n        private readonly IRepositoryManager _repository;\n        private readonly ILoggerManager _logger;\n        private readonly IMapper _mapper;\n        public CompanyService(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)\n        {\n            _repository = repository;\n            _logger = logger;\n            _mapper = mapper;\n        }\n\n        public IEnumerable&lt;CompanyDto&gt; GetAllCompanies(bool trackChanges)\n        {\n            try\n            {\n                var companies = _repository.Company.GetAllCompanies(trackChanges);\n                var companiesDto = _mapper.Map&lt;IEnumerable&lt;CompanyDto&gt;&gt;(companies);\n                return companiesDto;\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError($&quot;Something went wrong in the {nameof(GetAllCompanies)} service method {ex}&quot;);\n                throw;\n            }\n        }\n    }\n}<\/code><\/pre>\n<p>We are using the Map method and specify the destination and then the source object.<br \/>\n\u6211\u4eec\u4f7f\u7528\u7684\u662f Map \u65b9\u6cd5\uff0c\u5e76\u6307\u5b9a\u76ee\u6807\u5bf9\u8c61\uff0c\u7136\u540e\u6307\u5b9a\u6e90\u5bf9\u8c61\u3002<\/p>\n<p>Excellent.<br \/>\n\u975e\u5e38\u597d\u3002<\/p>\n<p>Now if we start our app and send the same request from Postman, we are going to get an error message:<br \/>\n\u73b0\u5728\uff0c\u5982\u679c\u6211\u4eec\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\u5e76\u4ece Postman \u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\uff0c\u6211\u4eec\u5c06\u6536\u5230\u4e00\u6761\u9519\u8bef\u6d88\u606f\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0408.jpg\" alt=\"alt text\" \/><\/p>\n<p>This happens because AutoMapper is not able to find the specific FullAddress property as we specified in the MappingProfile class. We are intentionally showing this error for you to know what to do if it happens to you in your projects.<br \/>\n\u53d1\u751f\u8fd9\u79cd\u60c5\u51b5\u662f\u56e0\u4e3a AutoMapper \u65e0\u6cd5\u627e\u5230\u6211\u4eec\u5728 MappingProfile \u7c7b\u4e2d\u6307\u5b9a\u7684\u7279\u5b9a FullAddress \u5c5e\u6027\u3002\u6211\u4eec\u6709\u610f\u663e\u793a\u6b64\u9519\u8bef\uff0c\u4ee5\u4fbf\u60a8\u77e5\u9053\u5982\u679c\u5728\u60a8\u7684\u9879\u76ee\u4e2d\u53d1\u751f\u8be5\u600e\u4e48\u505a\u3002<\/p>\n<p>So to solve this, all we have to do is to modify the MappingProfile class:<br \/>\n\u56e0\u6b64\uff0c\u8981\u89e3\u51b3\u6b64\u95ee\u9898\uff0c\u6211\u4eec\u6240\u8981\u505a\u7684\u5c31\u662f\u4fee\u6539 MappingProfile \u7c7b\uff1a<\/p>\n<pre><code>using AutoMapper;\nusing Entities.Models;\nusing Shared.DataTransferObjects;\n\nnamespace CompanyEmployees\n{\n    public class MappingProfile : Profile\n    {\n        \/\/public MappingProfile()\n        \/\/{\n        \/\/    CreateMap&lt;Company, CompanyDto&gt;().ForMember(c =&gt; c.FullAddress, opt =&gt; opt.MapFrom(x =&gt; string.Join(&#039; &#039;, x.Address, x.Country)));\n        \/\/}\n\n        public MappingProfile() { CreateMap&lt;Company, CompanyDto&gt;().ForCtorParam(&quot;FullAddress&quot;, opt =&gt; opt.MapFrom(x =&gt; string.Join(&#039; &#039;, x.Address, x.Country))); }\n    }\n}<\/code><\/pre>\n<p>This time, we are not using the ForMember method but the ForCtorParam method to specify the name of the parameter in the constructor that AutoMapper needs to map to.<br \/>\n\u8fd9\u4e00\u6b21\uff0c\u6211\u4eec\u4e0d\u4f7f\u7528 ForMember \u65b9\u6cd5\uff0c\u800c\u662f\u4f7f\u7528 ForCtorParam \u65b9\u6cd5\u6765\u6307\u5b9a AutoMapper \u9700\u8981\u6620\u5c04\u5230\u7684\u6784\u9020\u51fd\u6570\u4e2d\u7684\u53c2\u6570\u540d\u79f0\u3002<\/p>\n<p>Now, let\u2019s use Postman again to send the request to test our app:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u518d\u6b21\u4f7f\u7528 Postman \u53d1\u9001\u8bf7\u6c42\u6765\u6d4b\u8bd5\u6211\u4eec\u7684 app\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0409.jpg\" alt=\"alt text\" \/><\/p>\n<p>We can see that everything is working as it is supposed to, but now with much better code.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u4e00\u5207\u90fd\u6309\u9884\u671f\u5de5\u4f5c\uff0c\u4f46\u73b0\u5728\u4ee3\u7801\u8981\u597d\u5f97\u591a\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>4 HANDLING GET REQUESTS 4 \u5904\u7406 GET \u8bf7\u6c42 We\u2019re all set to add some business logic to our application. But before we do that, let\u2019s talk a bit about controller classes and routing because they play an important part while working with HTTP requests.\u200c \u6211\u4eec\u5df2\u51c6\u5907\u597d\u5411\u5e94\u7528\u7a0b\u5e8f\u6dfb\u52a0\u4e00\u4e9b\u4e1a\u52a1\u903b\u8f91\u3002\u4f46\u5728\u6211\u4eec\u8fd9\u6837\u505a\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u5148\u8c08\u8c08\u63a7\u5236\u5668\u7c7b\u548c\u8def\u7531\uff0c\u56e0\u4e3a\u5b83\u4eec\u5728\u5904\u7406 HTTP \u8bf7\u6c42\u65f6\u8d77\u7740\u91cd\u8981\u7684\u4f5c\u7528\u3002 4.1 Controllers and Routing in WEB API 4.1 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1106","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1106","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1106"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1106\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1106"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1106"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1106"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}