{"id":1134,"date":"2025-05-27T14:47:12","date_gmt":"2025-05-27T06:47:12","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1134"},"modified":"2025-05-27T14:47:12","modified_gmt":"2025-05-27T06:47:12","slug":"ultimate-asp-net-core-web-api-18-searching","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1134","title":{"rendered":"Ultimate ASP.NET Core Web API 18 SEARCHING"},"content":{"rendered":"<p>18 SEARCHING<br \/>\n18 \u641c\u7d22<\/p>\n<p>In this chapter, we\u2019re going to tackle the topic of searching in ASP.NET Core Web API. Searching is one of those functionalities that can make or break your API, and the level of difficulty when implementing it can vary greatly depending on your specifications.\u200c<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba ASP.NET Core Web API \u4e2d\u7684\u641c\u7d22\u4e3b\u9898\u3002\u641c\u7d22\u662f\u53ef\u4ee5\u6210\u5c31\u6216\u7834\u574f API \u7684\u529f\u80fd\u4e4b\u4e00\uff0c\u5b9e\u73b0\u5b83\u7684\u96be\u5ea6\u53ef\u80fd\u4f1a\u56e0\u60a8\u7684\u89c4\u8303\u800c\u6709\u5f88\u5927\u5dee\u5f02\u3002<\/p>\n<p>If you need to implement a basic searching feature where you are just trying to search one field in the database, you can easily implement it. On the other hand, if it\u2019s a multi-column, multi-term search, you would probably be better off with some of the great search libraries out there like Lucene.NET which are already optimized and proven.<br \/>\n\u5982\u679c\u60a8\u9700\u8981\u5b9e\u73b0\u4e00\u4e2a\u57fa\u672c\u7684\u641c\u7d22\u529f\u80fd\uff0c\u5373\u60a8\u53ea\u662f\u5c1d\u8bd5\u641c\u7d22\u6570\u636e\u5e93\u4e2d\u7684\u4e00\u4e2a\u5b57\u6bb5\uff0c\u5219\u53ef\u4ee5\u8f7b\u677e\u5b9e\u73b0\u5b83\u3002\u53e6\u4e00\u65b9\u9762\uff0c\u5982\u679c\u5b83\u662f\u4e00\u4e2a\u591a\u5217\u3001\u591a\u8bcd\u7684\u641c\u7d22\uff0c\u4f60\u53ef\u80fd\u4f1a\u6700\u597d\u4f7f\u7528\u4e00\u4e9b\u5f88\u68d2\u7684\u641c\u7d22\u5e93\uff0c\u6bd4\u5982 Lucene.NET \u5df2\u7ecf\u7ecf\u8fc7\u4f18\u5316\u548c\u9a8c\u8bc1\u7684\u641c\u7d22\u5e93\u3002<\/p>\n<h2>18.1 What is Searching?<\/h2>\n<p>18.1 \u4ec0\u4e48\u662f\u641c\u7d22\uff1f<\/p>\n<p>There is no doubt in our minds that you\u2019ve seen a search field on almost every website on the internet. It\u2019s easy to find something when we are familiar with the website structure or when a website is not that large.\u200c<br \/>\n\u6beb\u65e0\u7591\u95ee\uff0c\u5728\u6211\u4eec\u770b\u6765\uff0c\u60a8\u51e0\u4e4e\u5728\u4e92\u8054\u7f51\u4e0a\u7684\u6bcf\u4e2a\u7f51\u7ad9\u4e0a\u90fd\u770b\u5230\u4e86\u641c\u7d22\u5b57\u6bb5\u3002\u5f53\u6211\u4eec\u719f\u6089\u7f51\u7ad9\u7ed3\u6784\u6216\u7f51\u7ad9\u4e0d\u662f\u90a3\u4e48\u5927\u65f6\uff0c\u5f88\u5bb9\u6613\u627e\u5230\u4e00\u4e9b\u4e1c\u897f\u3002<\/p>\n<p>But if we want to find the most relevant topic for us, we don\u2019t know what we\u2019re going to find, or maybe we\u2019re first-time visitors to a large website, we\u2019re probably going to use a search field.<br \/>\n\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u60f3\u627e\u5230\u4e0e\u6211\u4eec\u6700\u76f8\u5173\u7684\u4e3b\u9898\uff0c\u6211\u4eec\u4e0d\u77e5\u9053\u4f1a\u627e\u5230\u4ec0\u4e48\uff0c\u6216\u8005\u4e5f\u8bb8\u6211\u4eec\u662f\u7b2c\u4e00\u6b21\u8bbf\u95ee\u5927\u578b\u7f51\u7ad9\uff0c\u6211\u4eec\u53ef\u80fd\u4f1a\u4f7f\u7528\u641c\u7d22\u5b57\u6bb5\u3002<\/p>\n<p>In our simple project, one use case of a search would be to find an employee by name.<br \/>\n\u5728\u6211\u4eec\u7684\u7b80\u5355\u9879\u76ee\u4e2d\uff0c\u641c\u7d22\u7684\u4e00\u4e2a\u7528\u4f8b\u662f\u6309\u59d3\u540d\u67e5\u627e\u5458\u5de5\u3002<\/p>\n<p>Let\u2019s see how we can achieve that.<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u5b9e\u73b0\u8fd9\u4e00\u76ee\u6807\u3002<\/p>\n<h2>18.2 Implementing Searching in Our Application<\/h2>\n<p>18.2 \u5728\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5b9e\u73b0\u641c\u7d22<\/p>\n<p>Since we\u2019re going to implement the most basic search in our project, the implementation won\u2019t be complex at all. We have all we need infrastructure-wise since we already covered paging and filtering. We\u2019ll just extend our implementation a bit.\u200c<br \/>\n\u7531\u4e8e\u6211\u4eec\u5c06\u5728\u9879\u76ee\u4e2d\u5b9e\u73b0\u6700\u57fa\u672c\u7684\u641c\u7d22\uff0c\u56e0\u6b64\u5b9e\u73b0\u8d77\u6765\u4e00\u70b9\u4e5f\u4e0d\u590d\u6742\u3002\u6211\u4eec\u5df2\u7ecf\u5728\u57fa\u7840\u8bbe\u65bd\u65b9\u9762\u62e5\u6709\u4e86\u6240\u9700\u7684\u4e00\u5207\uff0c\u56e0\u4e3a\u6211\u4eec\u5df2\u7ecf\u4ecb\u7ecd\u4e86\u5206\u9875\u548c\u8fc7\u6ee4\u3002\u6211\u4eec\u53ea\u662f\u7a0d\u5fae\u6269\u5c55\u4e00\u4e0b\u6211\u4eec\u7684\u5b9e\u73b0\u3002<\/p>\n<p>What we want to achieve is something like this:<br \/>\n\u6211\u4eec\u60f3\u8981\u5b9e\u73b0\u7684\u662f\u8fd9\u6837\u7684\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\/companyId\/employees?searchTerm=MihaelFins\">https:\/\/localhost:5001\/api\/companies\/companyId\/employees?searchTerm=MihaelFins<\/a><\/p>\n<p>This should return just one result: Mihael Fins. Of course, the search needs to work together with filtering and paging, so that\u2019s one of the things we\u2019ll need to keep in mind too.<br \/>\n\u8fd9\u5e94\u8be5\u53ea\u8fd4\u56de\u4e00\u4e2a\u7ed3\u679c\uff1aMihael Fins\u3002\u5f53\u7136\uff0c\u641c\u7d22\u9700\u8981\u4e0e\u8fc7\u6ee4\u548c\u5206\u9875\u4e00\u8d77\u5de5\u4f5c\uff0c\u6240\u4ee5\u8fd9\u4e5f\u662f\u6211\u4eec\u9700\u8981\u8bb0\u4f4f\u7684\u4e8b\u60c5\u4e4b\u4e00\u3002<\/p>\n<p>Like we did with filtering, we\u2019re going to extend our EmployeeParameters class first since we\u2019re going to send our search query as a query parameter:<br \/>\n\u5c31\u50cf\u6211\u4eec\u5bf9\u7b5b\u9009\u6240\u505a\u7684\u90a3\u6837\uff0c\u6211\u4eec\u5c06\u9996\u5148\u6269\u5c55\u6211\u4eec\u7684 EmployeeParameters \u7c7b\uff0c\u56e0\u4e3a\u6211\u4eec\u8981\u5c06\u641c\u7d22\u67e5\u8be2\u4f5c\u4e3a\u67e5\u8be2\u53c2\u6570\u53d1\u9001\uff1a<\/p>\n<pre><code>namespace Shared.RequestFeatures;\n\npublic class EmployeeParameters : RequestParameters\n{\n    public uint MinAge { get; set; }\n    public uint MaxAge { get; set; } = int.MaxValue;\n    public bool ValidAgeRange =&gt; MaxAge &gt; MinAge;\n    public string? SearchTerm { get; set; }\n}<\/code><\/pre>\n<p>Simple as that.<br \/>\n\u5c31\u8fd9\u4e48\u7b80\u5355\u3002<\/p>\n<p>Now we can write queries with searchTerm=\u201dname\u201d in them.<br \/>\n\u73b0\u5728\u6211\u4eec\u53ef\u4ee5\u7f16\u5199\u5305\u542b searchTerm=\u201cname\u201d \u7684\u67e5\u8be2\u3002<\/p>\n<p>The next thing we need to do is actually implement the search functionality in our EmployeeRepository class:<br \/>\n\u63a5\u4e0b\u6765\u6211\u4eec\u9700\u8981\u505a\u7684\u662f\u5728\u6211\u4eec\u7684 EmployeeRepository \u7c7b\u4e2d\u5b9e\u9645\u5b9e\u73b0\u641c\u7d22\u529f\u80fd\uff1a<\/p>\n<pre><code>public async Task&lt;PagedList&lt;Employee&gt;&gt; GetEmployeesAsync(Guid companyId,\n    EmployeeParameters employeeParameters, bool trackChanges)\n{\n    var employees = await FindByCondition(e =&gt; e.CompanyId.Equals(companyId), trackChanges)\n        .FilterEmployees(employeeParameters.MinAge, employeeParameters.MaxAge)\n        .Search(employeeParameters.SearchTerm)\n        .OrderBy(e =&gt; e.Name)\n        .ToListAsync();\n\n    return PagedList&lt;Employee&gt;\n        .ToPagedList(employees, employeeParameters.PageNumber, employeeParameters.PageSize);\n}<\/code><\/pre>\n<p>We have made two changes here. The first is modifying the filter logic and the second is adding the Search method for the searching functionality.<br \/>\n\u6211\u4eec\u5728\u6b64\u5904\u8fdb\u884c\u4e86\u4e24\u9879\u66f4\u6539\u3002\u7b2c\u4e00\u4e2a\u662f\u4fee\u6539\u7b5b\u9009\u903b\u8f91\uff0c\u7b2c\u4e8c\u4e2a\u662f\u6dfb\u52a0 Search \u641c\u7d22\u529f\u80fd\u7684\u65b9\u6cd5\u3002<\/p>\n<p>But these methods (FilterEmployees and Search) are not created yet, so let\u2019s create them.<br \/>\n\u4f46\u662f\u8fd9\u4e9b\u65b9\u6cd5\uff08FilterEmployees \u548c Search\uff09\u5c1a\u672a\u521b\u5efa\uff0c\u56e0\u6b64\u8ba9\u6211\u4eec\u521b\u5efa\u5b83\u4eec\u3002<\/p>\n<p>In the Repository project, we are going to create the new folder Extensions and inside of that folder the new class RepositoryEmployeeExtensions:<br \/>\n\u5728 Repository \u9879\u76ee\u4e2d\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u65b0\u6587\u4ef6\u5939 Extensions\uff0c\u5e76\u5728\u8be5\u6587\u4ef6\u5939\u5185\u521b\u5efa\u65b0\u7c7b RepositoryEmployeeExtensions\uff1a<\/p>\n<pre><code>using Entities.Models;\n\nnamespace Repository.Extensions;\n\npublic static class RepositoryEmployeeExtensions\n{\n    public static IQueryable&lt;Employee&gt; FilterEmployees(this IQueryable&lt;Employee&gt; employees, uint minAge, uint maxAge) =&gt;\n        employees.Where(e =&gt; (e.Age &gt;= minAge &amp;&amp; e.Age &lt;= maxAge));\n\n    public static IQueryable&lt;Employee&gt; Search(this IQueryable&lt;Employee&gt; employees, string searchTerm)\n    {\n        if (string.IsNullOrWhiteSpace(searchTerm))\n            return employees;\n\n        var lowerCaseTerm = searchTerm.Trim().ToLower();\n\n        return employees.Where(e =&gt; e.Name.ToLower().Contains(lowerCaseTerm));\n    }\n}<\/code><\/pre>\n<p>So, we are just creating our extension methods to update our query until it is executed in the repository. Now, all we have to do is add a using directive to the EmployeeRepository class:<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u53ea\u662f\u5728\u521b\u5efa\u6269\u5c55\u65b9\u6cd5\u6765\u66f4\u65b0\u6211\u4eec\u7684\u67e5\u8be2\uff0c\u76f4\u5230\u5b83\u5728\u5b58\u50a8\u5e93\u4e2d\u6267\u884c\u3002\u73b0\u5728\uff0c\u6211\u4eec\u6240\u8981\u505a\u7684\u5c31\u662f\u5411 EmployeeRepository \u7c7b\u6dfb\u52a0\u4e00\u4e2a using \u6307\u4ee4\uff1a<\/p>\n<pre><code>using Repository.Extensions;<\/code><\/pre>\n<p>That\u2019s it for our implementation. As you can see, it isn\u2019t that hard since it is the most basic search and we already had an infrastructure set.<br \/>\n\u8fd9\u5c31\u662f\u6211\u4eec\u7684\u5b9e\u65bd\u3002\u5982\u60a8\u6240\u89c1\uff0c\u8fd9\u5e76\u4e0d\u96be\uff0c\u56e0\u4e3a\u5b83\u662f\u6700\u57fa\u672c\u7684\u641c\u7d22\uff0c\u800c\u4e14\u6211\u4eec\u5df2\u7ecf\u8bbe\u7f6e\u4e86\u57fa\u7840\u8bbe\u65bd\u3002<\/p>\n<h2>18.3 Testing Our Implementation<\/h2>\n<p>18.3 \u6d4b\u8bd5\u6211\u4eec\u7684\u5b9e\u73b0<\/p>\n<p>Let\u2019s send a first request with the value Mihael Fins for the search term:\u200c<br \/>\n\u8ba9\u6211\u4eec\u53d1\u9001\u7b2c\u4e00\u4e2a\u8bf7\u6c42\uff0c\u641c\u7d22\u8bcd\u7684\u503c\u4e3a Mihael Fins\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/c9d4c053-49b6-410c-bc78-2d54a9991870\/employees?searchTerm=MihaelFins\">https:\/\/localhost:5001\/api\/companies\/c9d4c053-49b6-410c-bc78-2d54a9991870\/employees?searchTerm=MihaelFins<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/1801.jpg\" alt=\"alt text\" \/><\/p>\n<p>This is working great.<br \/>\n\u8fd9\u6548\u679c\u5f88\u597d\u3002<\/p>\n<p>Now, let\u2019s find all employees that contain the letters \u201cae\u201d:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u67e5\u627e\u5305\u542b\u5b57\u6bcd \u201cae\u201d \u7684\u6240\u6709\u5458\u5de5\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/c9d4c053-49b6-410c-bc78-2d54a9991870\/employees?searchTerm=ae\">https:\/\/localhost:5001\/api\/companies\/c9d4c053-49b6-410c-bc78-2d54a9991870\/employees?searchTerm=ae<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/1802.jpg\" alt=\"alt text\" \/><\/p>\n<p>Great. One more request with the paging and filtering:<br \/>\n\u4f1f\u5927\u3002\u53e6\u4e00\u4e2a\u5e26\u6709\u5206\u9875\u548c\u8fc7\u6ee4\u7684\u8bf7\u6c42\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees?pageNumber=1&amp;pageSize=4&amp;minAge=32&amp;maxAge=35&amp;searchTerm=MA\">https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees?pageNumber=1&pageSize=4&minAge=32&maxAge=35&searchTerm=MA<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/1803.jpg\" alt=\"alt text\" \/><\/p>\n<p>And this works as well.<br \/>\n\u8fd9\u4e5f\u6709\u6548\u3002<\/p>\n<p>That\u2019s it! We\u2019ve successfully implemented and tested our search functionality.<br \/>\n\u5c31\u662f\u8fd9\u6837\uff01\u6211\u4eec\u5df2\u7ecf\u6210\u529f\u5b9e\u65bd\u5e76\u6d4b\u8bd5\u4e86\u6211\u4eec\u7684\u641c\u7d22\u529f\u80fd\u3002<\/p>\n<p>If we check the Headers tab for each request, we will find valid x- pagination as well.<br \/>\n\u5982\u679c\u6211\u4eec\u68c0\u67e5\u6bcf\u4e2a\u8bf7\u6c42\u7684 Headers \u9009\u9879\u5361\uff0c\u6211\u4eec\u4e5f\u4f1a\u53d1\u73b0\u6709\u6548\u7684 x \u5206\u9875\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>18 SEARCHING 18 \u641c\u7d22 In this chapter, we\u2019re going to tackle the topic of searching in ASP.NET Core Web API. Searching is one of those functionalities that can make or break your API, and the level of difficulty when implementing it can vary greatly depending on your specifications.\u200c \u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba ASP.NET Core Web API \u4e2d\u7684\u641c\u7d22\u4e3b\u9898\u3002\u641c\u7d22\u662f\u53ef\u4ee5\u6210\u5c31\u6216\u7834\u574f API [&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-1134","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1134","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=1134"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1134\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1134"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1134"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1134"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}