{"id":1044,"date":"2025-05-19T00:58:01","date_gmt":"2025-05-18T16:58:01","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1044"},"modified":"2025-05-19T00:58:01","modified_gmt":"2025-05-18T16:58:01","slug":"mastering-minimal-apis-in-asp-net-core","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1044","title":{"rendered":"Mastering Minimal APIs in ASP.NET Core"},"content":{"rendered":"<p>Mastering Minimal APIs in ASP.NET Core<br \/>\nCopyright \u00a9 2022 Packt Publishing<\/p>\n<p>In memory of my mother and father, Giovanna and Francesco, for their sacrifices and for supporting me in studying and facing new challenges every day.<br \/>\n\u4e3a\u4e86\u7eaa\u5ff5\u6211\u7684\u7236\u6bcd Giovanna \u548c Francesco\uff0c\u611f\u8c22\u4ed6\u4eec\u7684\u727a\u7272\uff0c\u4ee5\u53ca\u652f\u6301\u6211\u5b66\u4e60\u548c\u6bcf\u5929\u9762\u5bf9\u65b0\u7684\u6311\u6218\u3002<br \/>\n\u2013 \u5b89\u5fb7\u91cc\u4e9a\u00b7\u571f\u91cc<br \/>\n\u2013 Andrea Tosato<\/p>\n<p>To my family, friends, and colleagues, who have always believed in me during this journey.<br \/>\n\u2013 Marco Minerva<br \/>\n\u611f\u8c22\u6211\u7684\u5bb6\u4eba\u3001\u670b\u53cb\u548c\u540c\u4e8b\uff0c\u4ed6\u4eec\u5728\u8fd9\u6bb5\u65c5\u7a0b\u4e2d\u4e00\u76f4\u76f8\u4fe1\u6211\u3002<br \/>\n\u2013 \u9a6c\u53ef\u00b7\u5bc6\u6d85\u74e6<\/p>\n<p>In memory of my beloved mom, and to my wife, Francesca, for her sacrifices and understanding.<br \/>\nLast but not least, to my son, Leonardo. The greatest success in my life.<br \/>\n\u2013 Emanuele Bartolesi<br \/>\n\u4e3a\u4e86\u7eaa\u5ff5\u6211\u656c\u7231\u7684\u5988\u5988\uff0c\u4ee5\u53ca\u6211\u7684\u59bb\u5b50\u5f17\u6717\u897f\u65af\u5361\uff0c\u611f\u8c22\u5979\u7684\u727a\u7272\u548c\u7406\u89e3\u3002<br \/>\n\u6700\u540e\u4f46\u5e76\u975e\u6700\u4e0d\u91cd\u8981\u7684\u4e00\u70b9\u662f\uff0c\u611f\u8c22\u6211\u7684\u513f\u5b50\u83b1\u6602\u7eb3\u591a\u3002\u6211\u4e00\u751f\u4e2d\u6700\u5927\u7684\u6210\u529f\u3002<br \/>\n\u2013 \u57c3\u9a6c\u52aa\u57c3\u83b1\u00b7\u5df4\u6258\u83b1\u897f<\/p>\n<p>Contributors<br \/>\n\u8d21\u732e<\/p>\n<p>About the authors<br \/>\n\u4f5c\u8005\u7b80\u4ecb<\/p>\n<p>Andrea Tosato is a full stack software engineer and architect of .NET applications. Andrea has successfully developed .NET applications in various industries, sometimes facing complex technological challenges. He deals with desktop, web, and mobile development but with the arrival of the cloud, Azure has become his passion. In 2017, he co-founded Cloudgen Verona (a .NET community based in Verona, Italy) with his friend, Marco Zamana. In 2019, he was named Microsoft MVP for the first time in the Azure category. Andrea graduated from the University of Pavia with a degree in computer engineering in 2008 and successfully completed his master\u2019s degree, also in computer engineering, in Modena in 2011. Andrea was born in 1986 in Verona, Italy, where he currently works as a remote worker. You can find Andrea on Twitter.<br \/>\nAndrea Tosato \u662f\u4e00\u540d\u5168\u6808\u8f6f\u4ef6\u5de5\u7a0b\u5e08\u548c .NET \u5e94\u7528\u7a0b\u5e8f\u67b6\u6784\u5e08\u3002Andrea \u5728\u5404\u4e2a\u884c\u4e1a\u6210\u529f\u5f00\u53d1\u4e86 .NET \u5e94\u7528\u7a0b\u5e8f\uff0c\u6709\u65f6\u9762\u4e34\u590d\u6742\u7684\u6280\u672f\u6311\u6218\u3002\u4ed6\u5904\u7406\u684c\u9762\u3001Web \u548c\u79fb\u52a8\u5f00\u53d1\uff0c\u4f46\u968f\u7740\u4e91\u7684\u5230\u6765\uff0cAzure \u5df2\u6210\u4e3a\u4ed6\u7684\u70ed\u60c5\u6240\u5728\u30022017 \u5e74\uff0c\u4ed6\u4e0e\u670b\u53cb Marco Zamana \u5171\u540c\u521b\u7acb\u4e86 Cloudgen Verona\uff08\u4e00\u4e2a\u4f4d\u4e8e\u610f\u5927\u5229\u7ef4\u7f57\u7eb3\u7684 .NET \u793e\u533a\uff09\u30022019 \u5e74\uff0c\u4ed6\u9996\u6b21\u88ab\u8bc4\u4e3a Azure \u7c7b\u522b\u7684 Microsoft MVP\u3002Andrea \u4e8e 2008 \u5e74\u6bd5\u4e1a\u4e8e\u5e15\u7ef4\u4e9a\u5927\u5b66\uff0c\u83b7\u5f97\u8ba1\u7b97\u673a\u5de5\u7a0b\u5b66\u4f4d\uff0c\u5e76\u4e8e 2011 \u5e74\u5728\u6469\u5fb7\u7eb3\u6210\u529f\u5b8c\u6210\u4e86\u8ba1\u7b97\u673a\u5de5\u7a0b\u7855\u58eb\u5b66\u4f4d\u3002Andrea \u4e8e 1986 \u5e74\u51fa\u751f\u4e8e\u610f\u5927\u5229\u7ef4\u7f57\u7eb3\uff0c\u76ee\u524d\u5728\u90a3\u91cc\u62c5\u4efb\u8fdc\u7a0b\u5de5\u4f5c\u8005\u3002\u4f60\u53ef\u4ee5\u5728 Twitter \u4e0a\u627e\u5230 Andrea\u3002<\/p>\n<p>Marco Minerva has been a computer enthusiast since elementary school when he received an old Commodore VIC-20 as a gift. He began developing with GW-BASIC. After some experience with Visual Basic, he has been using .NET since its first introduction. He got his master\u2019s degree in information technology in 2006. Today, he lives in Taggia, Italy, where he works as a freelance consultant and is involved in designing and developing solutions for the Microsoft ecosystem, building applications for desktop, mobile, and web. His expertise is in backend development as a software architect. He runs training courses, is a speaker at technical events, writes articles for magazines, and regularly makes live streams about coding on Twitch. He has been a Microsoft MVP since 2013. You can find Marco on Twitter.<br \/>\nMarco Minerva \u4ece\u5c0f\u5b66\u5f00\u59cb\u5c31\u662f\u4e00\u4e2a\u8ba1\u7b97\u673a\u7231\u597d\u8005\uff0c\u5f53\u65f6\u4ed6\u6536\u5230\u4e86\u4e00\u53f0\u65e7\u7684 Commodore VIC-20 \u4f5c\u4e3a\u793c\u7269\u3002\u4ed6\u5f00\u59cb\u4f7f\u7528 GW-BASIC \u8fdb\u884c\u5f00\u53d1\u3002\u5728\u5177\u5907\u4e00\u4e9b Visual Basic \u7ecf\u9a8c\u540e\uff0c\u4ed6\u81ea\u9996\u6b21\u5f15\u5165 .NET \u4ee5\u6765\u5c31\u4e00\u76f4\u5728\u4f7f\u7528 .NET\u3002\u4ed6\u4e8e 2006 \u5e74\u83b7\u5f97\u4fe1\u606f\u6280\u672f\u7855\u58eb\u5b66\u4f4d\u3002\u5982\u4eca\uff0c\u4ed6\u4f4f\u5728\u610f\u5927\u5229\u5854\u5409\u4e9a\uff0c\u5728\u90a3\u91cc\u4ed6\u662f\u4e00\u540d\u81ea\u7531\u987e\u95ee\uff0c\u53c2\u4e0e\u4e3a Microsoft \u751f\u6001\u7cfb\u7edf\u8bbe\u8ba1\u548c\u5f00\u53d1\u89e3\u51b3\u65b9\u6848\uff0c\u6784\u5efa\u684c\u9762\u3001\u79fb\u52a8\u548c Web \u5e94\u7528\u7a0b\u5e8f\u3002\u4ed6\u7684\u4e13\u957f\u662f\u4f5c\u4e3a\u8f6f\u4ef6\u67b6\u6784\u5e08\u8fdb\u884c\u540e\u7aef\u5f00\u53d1\u3002\u4ed6\u4e3e\u529e\u57f9\u8bad\u8bfe\u7a0b\uff0c\u5728\u6280\u672f\u6d3b\u52a8\u4e2d\u53d1\u8868\u6f14\u8bb2\uff0c\u4e3a\u6742\u5fd7\u64b0\u5199\u6587\u7ae0\uff0c\u5e76\u5b9a\u671f\u5728 Twitch \u4e0a\u5236\u4f5c\u6709\u5173\u7f16\u7801\u7684\u76f4\u64ad\u3002\u81ea 2013 \u5e74\u4ee5\u6765\uff0c\u4ed6\u4e00\u76f4\u662f Microsoft MVP\u3002\u60a8\u53ef\u4ee5\u5728 Twitter \u4e0a\u627e\u5230 Marco\u3002<\/p>\n<p>Emanuele Bartolesi is a Microsoft 365 architect who is passionate about frontend technologies and everything related to the cloud, especially Microsoft Azure. He currently lives in Zurich and actively participates in local and international community activities and events. Emanuele shares his love of technology through his blog. He has also become a Twitch affiliate as a live coder, and you can find him as kasuken on Twitch to write some code with him. Emanuele has been a Microsoft MVP in the developer technologies category since 2014, and a GitHub Star since 2022. You can find Emanuele on Twitter.<br \/>\nEmanuele Bartolesi \u662f\u4e00\u540d Microsoft 365 \u67b6\u6784\u5e08\uff0c\u4ed6\u5bf9\u524d\u7aef\u6280\u672f\u4ee5\u53ca\u4e0e\u4e91\u76f8\u5173\u7684\u4e00\u5207\uff08\u5c24\u5176\u662f Microsoft Azure\uff09\u5145\u6ee1\u70ed\u60c5\u3002\u4ed6\u76ee\u524d\u5c45\u4f4f\u5728\u82cf\u9ece\u4e16\uff0c\u79ef\u6781\u53c2\u4e0e\u5f53\u5730\u548c\u56fd\u9645\u793e\u533a\u6d3b\u52a8\u3002Emanuele \u901a\u8fc7\u4ed6\u7684\u535a\u5ba2\u5206\u4eab\u4e86\u4ed6\u5bf9\u6280\u672f\u7684\u70ed\u7231\u3002\u4ed6\u8fd8\u4f5c\u4e3a\u5b9e\u65f6\u7f16\u7801\u5458\u6210\u4e3a Twitch \u7684\u9644\u5c5e\u673a\u6784\uff0c\u60a8\u53ef\u4ee5\u5728 Twitch \u4e0a\u627e\u5230\u4ed6\u4f5c\u4e3a kasuken \u4e0e\u4ed6\u4e00\u8d77\u7f16\u5199\u4e00\u4e9b\u4ee3\u7801\u3002Emanuele \u81ea 2014 \u5e74\u4ee5\u6765\u4e00\u76f4\u662f\u5f00\u53d1\u4eba\u5458\u6280\u672f\u7c7b\u522b\u7684 Microsoft MVP\uff0c\u81ea 2022 \u5e74\u4ee5\u6765\u4e00\u76f4\u662f GitHub Star\u3002\u60a8\u53ef\u4ee5\u5728 Twitter \u4e0a\u627e\u5230 Emanuele\u3002<\/p>\n<p>About the reviewers<br \/>\n\u5173\u4e8e\u5ba1\u7a3f\u4eba<\/p>\n<p>Marco Parenzan is a senior solution architect for Smart Factory, IoT, and Azure-based solutions at beanTech, a tech company in Italy. He has been a Microsoft Azure MVP since 2014 and has been playing with the cloud since 2010. He speaks about Azure and .NET development at major community events in Italy. He is a community lead for 1nn0va, a recognized Microsoft-oriented community in Pordenone, Italy, where he organizes local community events. He wrote a book on Azure for Packt Publishing in 2016. He loves playing with his Commodore 64 and trying to write small retro games in .NET or JavaScript.<br \/>\nMarco Parenzan \u662f\u610f\u5927\u5229\u79d1\u6280\u516c\u53f8 beanTech \u7684\u667a\u80fd\u5de5\u5382\u3001IoT \u548c\u57fa\u4e8e Azure \u7684\u89e3\u51b3\u65b9\u6848\u7684\u9ad8\u7ea7\u89e3\u51b3\u65b9\u6848\u67b6\u6784\u5e08\u3002\u81ea 2014 \u5e74\u4ee5\u6765\uff0c\u4ed6\u4e00\u76f4\u662f Microsoft Azure MVP\uff0c\u81ea 2010 \u5e74\u4ee5\u6765\u4e00\u76f4\u5728\u73a9\u4e91\u3002\u4ed6\u5728\u610f\u5927\u5229\u7684\u4e3b\u8981\u793e\u533a\u6d3b\u52a8\u4e2d\u8c08\u8bba Azure \u548c .NET \u5f00\u53d1\u3002\u4ed6\u662f 1nn0va \u7684\u793e\u533a\u8d1f\u8d23\u4eba\uff0c\u8fd9\u662f\u610f\u5927\u5229\u6ce2\u4ee3\u8bfa\u5185\u4e00\u4e2a\u516c\u8ba4\u7684\u9762\u5411 Microsoft \u7684\u793e\u533a\uff0c\u4ed6\u5728\u90a3\u91cc\u7ec4\u7ec7\u5f53\u5730\u793e\u533a\u6d3b\u52a8\u3002\u4ed6\u5728 2016 \u5e74\u4e3a Packt Publishing \u64b0\u5199\u4e86\u4e00\u672c\u5173\u4e8e Azure \u7684\u4e66\u3002\u4ed6\u559c\u6b22\u73a9\u4ed6\u7684 Commodore 64\uff0c\u5e76\u5c1d\u8bd5\u7528 .NET \u6216 JavaScript \u7f16\u5199\u5c0f\u578b\u590d\u53e4\u6e38\u620f\u3002<\/p>\n<p>Marco Zamana lives in Verona in the magnificent hills of Valpolicella. He has a background as a software developer and architect. He was Microsoft\u2019s Most Valuable Professional for 3 years in the artificial intelligence category. He currently works as a cloud solution architect in engineering at Microsoft. He is the co-founder of Cloudgen Verona, a Veronese association that discusses topics related to the cloud and, above all, Azure.<br \/>\nMarco Zamana \u4f4f\u5728\u7ef4\u7f57\u7eb3 Valpolicella \u58ee\u4e3d\u7684\u5c71\u4e18\u4e0a\u3002\u4ed6\u62e5\u6709\u8f6f\u4ef6\u5f00\u53d1\u4eba\u5458\u548c\u67b6\u6784\u5e08\u7684\u80cc\u666f\u3002\u4ed6\u5728\u4eba\u5de5\u667a\u80fd\u7c7b\u522b\u4e2d\u8fde\u7eed 3 \u5e74\u88ab\u8bc4\u4e3a Microsoft \u6700\u6709\u4ef7\u503c\u4e13\u5bb6\u3002\u4ed6\u76ee\u524d\u5728 Microsoft \u62c5\u4efb\u5de5\u7a0b\u90e8\u95e8\u7684\u4e91\u89e3\u51b3\u65b9\u6848\u67b6\u6784\u5e08\u3002\u4ed6\u662f Cloudgen Verona \u7684\u8054\u5408\u521b\u59cb\u4eba\uff0c\u8fd9\u662f\u4e00\u4e2a Veronese \u534f\u4f1a\uff0c\u8ba8\u8bba\u4e0e\u4e91\u76f8\u5173\u7684\u4e3b\u9898\uff0c\u5c24\u5176\u662f Azure\u3002<\/p>\n<p>Ashirwad Satapathi works as an associate consultant at Microsoft and has expertise in building scalable applications with ASP.NET Core and Microsoft Azure. He is a published author and an active blogger in the C# Corner developer community. He was awarded the title of C# Corner Most Valuable Professional (MVP) in September 2020 and September 2021 for his contributions to the developer community. He is also a member of the Outreach Committee of the .NET Foundation.<br \/>\nAshirwad Satapathi \u662f Microsoft \u7684\u52a9\u7406\u987e\u95ee\uff0c\u62e5\u6709\u4f7f\u7528 ASP.NET Core \u548c Microsoft Azure \u6784\u5efa\u53ef\u7f29\u653e\u5e94\u7528\u7a0b\u5e8f\u7684\u4e13\u4e1a\u77e5\u8bc6\u3002\u4ed6\u662f C# Corner \u5f00\u53d1\u4eba\u5458\u793e\u533a\u7684\u51fa\u7248\u4f5c\u8005\u548c\u6d3b\u8dc3\u7684\u535a\u5ba2\u4f5c\u8005\u3002\u4ed6\u4e8e 2020 \u5e74 9 \u6708\u548c 2021 \u5e74 9 \u6708\u88ab\u6388\u4e88 C# Corner \u6700\u6709\u4ef7\u503c\u4e13\u5bb6 \uff08MVP\uff09 \u79f0\u53f7\uff0c\u4ee5\u8868\u5f70\u4ed6\u5bf9\u5f00\u53d1\u8005\u793e\u533a\u7684\u8d21\u732e\u3002\u4ed6\u8fd8\u662f .NET Foundation \u5916\u5c55\u59d4\u5458\u4f1a\u7684\u6210\u5458\u3002<\/p>\n<p>Table of Contents<br \/>\n\u76ee\u5f55<\/p>\n<p>Preface<br \/>\n\u524d\u8a00<\/p>\n<p>Part 1: Introduction<br \/>\n\u7b2c 1 \u90e8\u5206\uff1a\u7b80\u4ecb<\/p>\n<p>1 Introduction to Minimal APIs<br \/>\n\u6700\u5c0f API \u7b80\u4ecb<\/p>\n<p>2 Exploring Minimal APIs and Their Advantages<br \/>\n\u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf<\/p>\n<p>3 Working with Minimal APIs<br \/>\n\u4f7f\u7528\u6700\u5c11\u7684 API<\/p>\n<p>Part 2: What\u2019s New in .NET 6?<br \/>\n\u7b2c 2 \u90e8\u5206\uff1a.NET 6 \u4e2d\u7684\u65b0\u589e\u529f\u80fd<\/p>\n<p>4 Dependency Injection in a Minimal API Project<br \/>\n\u6700\u5c0f API \u9879\u76ee\u4e2d\u7684\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165<\/p>\n<p>5 Using Logging to Identify Errors<br \/>\n\u4f7f\u7528\u65e5\u5fd7\u8bb0\u5f55\u8bc6\u522b\u9519\u8bef<\/p>\n<p>6 Exploring Validation and Mapping<br \/>\n\u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04<\/p>\n<p>7 Integration with the Data Access Layer<br \/>\n\u4e0e Data Access Layer \u96c6\u6210<\/p>\n<p>Part 3: Advanced Development and Microservices Concepts<br \/>\n\u7b2c 3 \u90e8\u5206\uff1a\u9ad8\u7ea7\u5f00\u53d1\u548c\u5fae\u670d\u52a1\u6982\u5ff5<\/p>\n<p>8 Adding Authentication and Authorization<br \/>\n\u6dfb\u52a0\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743<\/p>\n<p>9 Leveraging Globalization and Localization<br \/>\n\u5229\u7528\u5168\u7403\u5316\u548c\u672c\u5730\u5316<\/p>\n<p>10 Evaluating and Benchmarking the Performance of Minimal APIs<br \/>\n\u8bc4\u4f30\u6700\u5c0f API \u7684\u6027\u80fd\u5e76\u5bf9\u5176\u8fdb\u884c\u57fa\u51c6\u6d4b\u8bd5<\/p>\n<p>Index<br \/>\n\u7d22\u5f15 <\/p>\n<p>Other Books You May Enjoy<br \/>\n\u60a8\u53ef\u80fd\u559c\u6b22\u7684\u5176\u4ed6\u4e66\u7c4d<\/p>\n<h1>Preface<\/h1>\n<p>\u524d\u8a00<\/p>\n<p>The simplification of code is every developer\u2019s dream. Minimal APIs are a new feature in .NET 6 that aims to simplify code. They are used for building APIs with minimal dependencies in ASP.NET Core. Minimal APIs simplify API development through the use of more compact code syntax.<br \/>\n\u7b80\u5316\u4ee3\u7801\u662f\u6bcf\u4e2a\u5f00\u53d1\u4eba\u5458\u7684\u68a6\u60f3\u3002\u6700\u5c0f API \u662f .NET 6 \u4e2d\u7684\u4e00\u9879\u65b0\u529f\u80fd\uff0c\u65e8\u5728\u7b80\u5316\u4ee3\u7801\u3002\u5b83\u4eec\u7528\u4e8e\u5728 ASP.NET Core \u4e2d\u6784\u5efa\u5177\u6709\u6700\u5c0f\u4f9d\u8d56\u9879\u7684 API\u3002\u6700\u5c11\u7684 API \u901a\u8fc7\u4f7f\u7528\u66f4\u7d27\u51d1\u7684\u4ee3\u7801\u8bed\u6cd5\u7b80\u5316\u4e86 API \u5f00\u53d1\u3002<\/p>\n<p>Developers using minimal APIs will be able to take advantage of this syntax on some occasions to work more quickly with less code and fewer files to maintain. Here, you will be introduced to the main new features of .NET 6 and understand the basic themes of minimal APIs, which weren\u2019t available in .NET 5 and previous versions. You\u2019ll see how to enable Swagger for API documentation, along with CORS, and how to handle application errors. You will learn to structure your code better with Microsoft\u2019s new .NET framework called Dependency Injection. Finally, you will see the performance and benchmarking improvements in .NET 6 that are introduced with minimal APIs.<br \/>\n\u4f7f\u7528\u6700\u5c11 API \u7684\u5f00\u53d1\u4eba\u5458\u5c06\u80fd\u591f\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u5229\u7528\u6b64\u8bed\u6cd5\uff0c\u4ee5\u66f4\u5c11\u7684\u4ee3\u7801\u548c\u66f4\u5c11\u7684\u6587\u4ef6\u66f4\u5feb\u5730\u5de5\u4f5c\u3002\u5728\u8fd9\u91cc\uff0c\u5c06\u5411\u60a8\u4ecb\u7ecd .NET 6 \u7684\u4e3b\u8981\u65b0\u529f\u80fd\uff0c\u5e76\u4e86\u89e3\u6700\u5c0f API \u7684\u57fa\u672c\u4e3b\u9898\uff0c\u8fd9\u4e9b\u4e3b\u9898\u5728 .NET 5 \u548c\u4ee5\u524d\u7684\u7248\u672c\u4e2d\u4e0d\u53ef\u7528\u3002\u60a8\u5c06\u4e86\u89e3\u5982\u4f55\u4e3a API \u6587\u6863\u4ee5\u53ca CORS \u542f\u7528 Swagger\uff0c\u4ee5\u53ca\u5982\u4f55\u5904\u7406\u5e94\u7528\u7a0b\u5e8f\u9519\u8bef\u3002\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528 Microsoft \u7684\u65b0 .NET \u6846\u67b6\uff08\u79f0\u4e3a Dependency Injection\uff09\u66f4\u597d\u5730\u6784\u5efa\u4ee3\u7801\u3002\u6700\u540e\uff0c\u60a8\u5c06\u770b\u5230 .NET 6 \u4e2d\u7684\u6027\u80fd\u548c\u57fa\u51c6\u6d4b\u8bd5\u6539\u8fdb\uff0c\u8fd9\u4e9b\u6539\u8fdb\u662f\u901a\u8fc7\u6700\u5c11\u7684 API \u5f15\u5165\u7684\u3002<\/p>\n<p>By the end of this book, you will be able to leverage minimal APIs and understand in what way they are related to the classic development of web APIs.<br \/>\n\u5728\u672c\u4e66\u7ed3\u675f\u65f6\uff0c\u60a8\u5c06\u80fd\u591f\u5229\u7528\u6700\u5c11\u7684 API\uff0c\u5e76\u4e86\u89e3\u5b83\u4eec\u4e0e Web API \u7684\u7ecf\u5178\u5f00\u53d1\u6709\u4f55\u5173\u7cfb\u3002<\/p>\n<p>Who this book is for<br \/>\n\u8fd9\u672c\u4e66\u662f\u7ed9\u8c01\u7684<\/p>\n<p>This book is for .NET developers who want to build .NET and .NET Core APIs and want to study the new features of .NET 6. Basic knowledge of C#, .NET, Visual Studio, and REST APIs is assumed.<br \/>\n\u672c\u4e66\u9002\u7528\u4e8e\u60f3\u8981\u6784\u5efa .NET \u548c .NET Core API \u5e76\u5e0c\u671b\u5b66\u4e60 .NET 6 \u65b0\u529f\u80fd\u7684 .NET \u5f00\u53d1\u4eba\u5458\u3002\u5047\u5b9a\u60a8\u5177\u5907 C#\u3001.NET\u3001Visual Studio \u548c REST API \u7684\u57fa\u672c\u77e5\u8bc6\u3002<\/p>\n<p>What this book covers<br \/>\n\u672c\u4e66\u6db5\u76d6\u7684\u5185\u5bb9<\/p>\n<p>Chapter 1, Introduction to Minimal APIs, introduces you to the motivations behind introducing minimal APIs within .NET 6. We will explain the main new features of .NET 6 and the work that the .NET team is doing with this latest version. You will come to understand the reasons why we decided to write the book.<br \/>\n\u7b2c 1 \u7ae0 \u6700\u5c0f API \u7b80\u4ecb\uff0c\u4ecb\u7ecd\u4e86\u5728 .NET 6 \u4e2d\u5f15\u5165\u6700\u5c0f API \u7684\u52a8\u673a\u3002\u6211\u4eec\u5c06\u89e3\u91ca .NET 6 \u7684\u4e3b\u8981\u65b0\u529f\u80fd\u4ee5\u53ca .NET \u56e2\u961f\u6b63\u5728\u4f7f\u7528\u6b64\u6700\u65b0\u7248\u672c\u6240\u505a\u7684\u5de5\u4f5c\u3002\u60a8\u5c06\u4e86\u89e3\u6211\u4eec\u51b3\u5b9a\u5199\u8fd9\u672c\u4e66\u7684\u539f\u56e0\u3002<\/p>\n<p>Chapter 2, Exploring Minimal APIs and Their Advantages, introduces you to the basic ways in which minimal APIs differ from .NET 5 and all previous versions. We will explore in detail routing and serialization with System.Text.JSON. Finally, we will end with some concepts related to writing our first REST API.<br \/>\n\u7b2c 2 \u7ae0\u201c\u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf\u201d\u4ecb\u7ecd\u4e86\u6700\u5c0f API \u4e0e .NET 5 \u548c\u6240\u6709\u4ee5\u524d\u7248\u672c\u7684\u57fa\u672c\u533a\u522b\u3002\u6211\u4eec\u5c06\u8be6\u7ec6\u63a2\u8ba8 System.Text.JSON \u7684\u8def\u7531\u548c\u5e8f\u5217\u5316\u3002\u6700\u540e\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4e0e\u7f16\u5199\u7b2c\u4e00\u4e2a REST API \u76f8\u5173\u7684\u4e00\u4e9b\u6982\u5ff5\u3002<\/p>\n<p>Chapter 3, Working with Minimal APIs, introduces you to the advanced ways in which minimal APIs differ from .NET 5 and all previous versions. We will explore in detail how to enable Swagger for API documentation. We will see how to enable CORS and how to handle application errors.<br \/>\n\u7b2c 3 \u7ae0 \u4f7f\u7528\u6700\u5c0f API \u4ecb\u7ecd\u4e86\u6700\u5c0f API \u4e0e .NET 5 \u548c\u6240\u6709\u4ee5\u524d\u7248\u672c\u7684\u4e0d\u540c\u4e4b\u5904\u3002\u6211\u4eec\u5c06\u8be6\u7ec6\u63a2\u8ba8\u5982\u4f55\u4e3a API \u6587\u6863\u542f\u7528 Swagger\u3002\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u542f\u7528 CORS \u4ee5\u53ca\u5982\u4f55\u5904\u7406\u5e94\u7528\u7a0b\u5e8f\u9519\u8bef\u3002<\/p>\n<p>Chapter 4, Dependency Injection in a Minimal API Project, introduces you to Dependency Injection and goes over how to use it with a minimal API.<br \/>\n\u7b2c 4 \u7ae0 \u6700\u5c0f API \u9879\u76ee\u4e2d\u7684\u4f9d\u8d56\u6ce8\u5165 \u4ecb\u7ecd\u4e86\u4f9d\u8d56\u6ce8\u5165\uff0c\u5e76\u4ecb\u7ecd\u4e86\u5982\u4f55\u5c06\u5176\u4e0e\u6700\u5c0f API \u4e00\u8d77\u4f7f\u7528\u3002<\/p>\n<p>Chapter 5, Using Logging to Identify Errors, teaches you about the logging tools that .NET provides. A logger is one of the tools that developers have to use to debug an application or understand its failure in production. The logging library has been built into ASP.NET with several features enabled by design.<br \/>\n\u7b2c 5 \u7ae0 \u4f7f\u7528\u65e5\u5fd7\u8bb0\u5f55\u8bc6\u522b\u9519\u8bef\uff0c\u4ecb\u7ecd .NET \u63d0\u4f9b\u7684\u65e5\u5fd7\u8bb0\u5f55\u5de5\u5177\u3002\u8bb0\u5f55\u5668\u662f\u5f00\u53d1\u4eba\u5458\u7528\u6765\u8c03\u8bd5\u5e94\u7528\u7a0b\u5e8f\u6216\u4e86\u89e3\u5176\u5728\u751f\u4ea7\u4e2d\u7684\u6545\u969c\u7684\u5de5\u5177\u4e4b\u4e00\u3002\u65e5\u5fd7\u8bb0\u5f55\u5e93\u5df2\u5185\u7f6e\u4e8e ASP.NET \u4e2d\uff0c\u5e76\u901a\u8fc7\u8bbe\u8ba1\u542f\u7528\u4e86\u591a\u9879\u529f\u80fd\u3002<\/p>\n<p>Chapter 6, Exploring Validation and Mapping, will teach you how to validate incoming data to an API and how to return any errors or messages. Once the data is validated, it can be mapped to a model that will then be used to process the request.<br \/>\n\u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04 \u5c06\u6559\u60a8\u5982\u4f55\u9a8c\u8bc1 API \u7684\u4f20\u5165\u6570\u636e\u4ee5\u53ca\u5982\u4f55\u8fd4\u56de\u4efb\u4f55\u9519\u8bef\u6216\u6d88\u606f\u3002\u9a8c\u8bc1\u6570\u636e\u540e\uff0c\u53ef\u4ee5\u5c06\u5176\u6620\u5c04\u5230\u6a21\u578b\uff0c\u7136\u540e\u8be5\u6a21\u578b\u5c06\u7528\u4e8e\u5904\u7406\u8bf7\u6c42\u3002<\/p>\n<p>Chapter 7, Integration with the Data Access Layer, helps you understand the best practices for accessing and using data in minimal APIs.<br \/>\n\u7b2c 7 \u7ae0 \u4e0e\u6570\u636e\u8bbf\u95ee\u5c42\u96c6\u6210 \u53ef\u5e2e\u52a9\u60a8\u4e86\u89e3\u5728\u6700\u5c0f API \u4e2d\u8bbf\u95ee\u548c\u4f7f\u7528\u6570\u636e\u7684\u6700\u4f73\u5b9e\u8df5\u3002<\/p>\n<p>Chapter 8, Adding Authentication and Authorization, looks at how to write an authentication and authorization system by leveraging our own database or a cloud service such as Azure Active Directory.<br \/>\n\u7b2c 8 \u7ae0 \u6dfb\u52a0\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\uff0c\u4ecb\u7ecd\u5982\u4f55\u5229\u7528\u6211\u4eec\u81ea\u5df1\u7684\u6570\u636e\u5e93\u6216\u4e91\u670d\u52a1\uff08\u5982 Azure Active Directory\uff09\u7f16\u5199\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u7cfb\u7edf\u3002<\/p>\n<p>Chapter 9, Leveraging Globalization and Localization, shows you how to leverage the translation system in a minimal API project and provide errors in the same language of the client.<br \/>\n\u7b2c 9 \u7ae0 \u5229\u7528\u5168\u7403\u5316\u548c\u672c\u5730\u5316 \u5411\u60a8\u5c55\u793a\u5982\u4f55\u5728\u6700\u5c0f\u7684 API \u9879\u76ee\u4e2d\u5229\u7528\u7ffb\u8bd1\u7cfb\u7edf\uff0c\u5e76\u4ee5\u5ba2\u6237\u7aef\u7684\u76f8\u540c\u8bed\u8a00\u63d0\u4f9b\u9519\u8bef\u3002<\/p>\n<p>Chapter 10, Evaluating and Benchmarking the Performance of Minimal APIs, shows the improvements in .NET 6 and those that will be introduced with the minimal APIs.<br \/>\n\u7b2c 10 \u7ae0 \u8bc4\u4f30\u6700\u5c0f API \u7684\u6027\u80fd\u5e76\u5bf9\u5176\u8fdb\u884c\u57fa\u51c6\u6d4b\u8bd5\uff0c\u4ecb\u7ecd\u4e86 .NET 6 \u4e2d\u7684\u6539\u8fdb\u4ee5\u53ca\u6700\u5c0f API \u5c06\u5f15\u5165\u7684\u6539\u8fdb\u3002<\/p>\n<p>To get the most out of this book<br \/>\n\u5145\u5206\u5229\u7528\u672c\u4e66<\/p>\n<p>You will need Visual Studio 2022 with ASP.NET and a web development workload or Visual Studio Code and K6 installed on your computer.<br \/>\n\u60a8\u7684\u8ba1\u7b97\u673a\u4e0a\u9700\u8981\u5e26\u6709 ASP.NET \u548c Web \u5f00\u53d1\u5de5\u4f5c\u8d1f\u8f7d\u7684 Visual Studio 2022 \u6216 Visual Studio Code \u548c K6\u3002<\/p>\n<p>All code examples have been tested using Visual Studio 2022 and Visual Studio Code on the Windows OS.<br \/>\n\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u5747\u5df2\u5728 Windows\u4f5c\u7cfb\u7edf\u4e0a\u4f7f\u7528 Visual Studio 2022 \u548c Visual Studio Code \u8fdb\u884c\u4e86\u6d4b\u8bd5\u3002<\/p>\n<p>If you are using the digital version of this book, we advise you to type the code yourself or access the code from the book\u2019s GitHub repository (a link is available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.<br \/>\n\u5982\u679c\u60a8\u4f7f\u7528\u7684\u662f\u672c\u4e66\u7684\u6570\u5b57\u7248\u672c\uff0c\u6211\u4eec\u5efa\u8bae\u60a8\u81ea\u5df1\u8f93\u5165\u4ee3\u7801\u6216\u4ece\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u8bbf\u95ee\u4ee3\u7801\uff08\u4e0b\u4e00\u8282\u4e2d\u63d0\u4f9b\u4e86\u94fe\u63a5\uff09\u3002\u8fd9\u6837\u505a\u5c06\u5e2e\u52a9\u60a8\u907f\u514d\u4e0e\u590d\u5236\u548c\u7c98\u8d34\u4ee3\u7801\u76f8\u5173\u7684\u4efb\u4f55\u6f5c\u5728\u9519\u8bef\u3002<\/p>\n<p>Basic development skills for Microsoft web technology are required to fully understand this book.<br \/>\n\u8981\u5b8c\u5168\u7406\u89e3\u672c\u4e66\uff0c\u9700\u8981\u5177\u5907 Microsoft Web \u6280\u672f\u7684\u57fa\u672c\u5f00\u53d1\u6280\u80fd\u3002<\/p>\n<p>Download the example code files<br \/>\n\u4e0b\u8f7d\u793a\u4f8b\u4ee3\u7801\u6587\u4ef6<\/p>\n<p>You can download the example code files for this book from GitHub at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6<\/a>. If there\u2019s an update to the code, it will be updated in the GitHub repository.<br \/>\n\u60a8\u53ef\u4ee5\u4ece GitHub \u4e0b\u8f7d\u672c\u4e66\u7684\u793a\u4f8b\u4ee3\u7801\u6587\u4ef6\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\u3002\u5982\u679c\u4ee3\u7801\u6709\u66f4\u65b0\uff0c\u5b83\u5c06\u5728\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\u3002\u5982\u679c\u4ee3\u7801\u6709\u66f4\u65b0\uff0c\u5b83\u5c06\u5728<\/a> GitHub \u5b58\u50a8\u5e93\u4e2d\u66f4\u65b0\u3002<\/p>\n<p>We also have other code bundles from our rich catalog of books and videos available at <a href=\"https:\/\/github.com\/PacktPublishing\/\">https:\/\/github.com\/PacktPublishing\/<\/a>. Check them out!<br \/>\n\u6211\u4eec\u8fd8\u5728 <a href=\"https:\/\/github.com\/PacktPublishing\/\">https:\/\/github.com\/PacktPublishing\/<\/a> \u4e0a\u63d0\u4f9b\u4e86\u4e30\u5bcc\u7684\u4e66\u7c4d\u548c\u89c6\u9891\u76ee\u5f55\u4e2d\u7684\u5176\u4ed6\u4ee3\u7801\u5305\u3002\u770b\u770b\u4ed6\u4eec\u5427\uff01<\/p>\n<p>Download the color images<br \/>\n\u4e0b\u8f7d\u5f69\u8272\u56fe\u50cf<\/p>\n<p>We also provide a PDF file that has color images of the screenshots and diagrams used in this book.You can download it here: <a href=\"https:\/\/packt.link\/GmUNL\">https:\/\/packt.link\/GmUNL<\/a><br \/>\n\u6211\u4eec\u8fd8\u63d0\u4f9b\u4e86\u4e00\u4e2a PDF \u6587\u4ef6\uff0c\u5176\u4e2d\u5305\u542b\u672c\u4e66\u4e2d\u4f7f\u7528\u7684\u5c4f\u5e55\u622a\u56fe\u548c\u56fe\u8868\u7684\u5f69\u8272\u56fe\u50cf\u3002\u60a8\u53ef\u4ee5\u5728\u6b64\u5904\u4e0b\u8f7d\uff1a<a href=\"https:\/\/packt.link\/GmUNL\">https:\/\/packt.link\/GmUNL<\/a><\/p>\n<p>Conventions used<br \/>\n\u4f7f\u7528\u7684\u7ea6\u5b9a<\/p>\n<p>There are a number of text conventions used throughout this book.<br \/>\n\u672c\u4e66\u4e2d\u4f7f\u7528\u4e86\u8bb8\u591a\u6587\u672c\u7ea6\u5b9a\u3002<\/p>\n<p>Code in text: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: \u201cIn minimal APIs, we define the route patterns using the Map<em> methods of the WebApplication object.\u201d<br \/>\n\u6587\u672c\u4e2d\u7684\u4ee3\u7801\uff1a\u6307\u793a\u6587\u672c\u4e2d\u7684\u4ee3\u7801\u8bcd\u3001\u6570\u636e\u5e93\u8868\u540d\u79f0\u3001\u6587\u4ef6\u5939\u540d\u79f0\u3001\u6587\u4ef6\u540d\u3001\u6587\u4ef6\u6269\u5c55\u540d\u3001\u8def\u5f84\u540d\u3001\u865a\u62df URL\u3001\u7528\u6237\u8f93\u5165\u548c Twitter \u53e5\u67c4\u3002\u4e0b\u9762\u662f\u4e00\u4e2a\u793a\u4f8b\uff1a\u201c\u5728\u6700\u5c0f\u7684 API \u4e2d\uff0c\u6211\u4eec\u4f7f\u7528 WebApplication \u5bf9\u8c61\u7684 Map<\/em> \u65b9\u6cd5\u5b9a\u4e49\u8def\u7531\u6a21\u5f0f\u3002<\/p>\n<p>A block of code is set as follows:<br \/>\n\u4ee3\u7801\u5757\u8bbe\u7f6e\u5982\u4e0b\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/hello-get&quot;, () =&gt; &quot;[GET] Hello World!&quot;); \napp.MapPost(&quot;\/hello-post&quot;, () =&gt; &quot;[POST] Hello World!&quot;); \napp.MapPut(&quot;\/hello-put&quot;, () =&gt; &quot;[PUT] Hello World!&quot;); \napp.MapDelete(&quot;\/hello-delete&quot;, () =&gt; &quot;[DELETE] Hello World!&quot;);<\/code><\/pre>\n<p>When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:<br \/>\n\u5f53\u6211\u4eec\u5e0c\u671b\u60a8\u6ce8\u610f\u5230\u4ee3\u7801\u5757\u7684\u7279\u5b9a\u90e8\u5206\u65f6\uff0c\u76f8\u5173\u884c\u6216\u9879\u76ee\u4ee5\u7c97\u4f53\u8bbe\u7f6e\uff1a<\/p>\n<pre><code>if (app.Environment.IsDevelopment()) \n{\n    app.UseSwagger(); \n    app.UseSwaggerUI(); \n}<\/code><\/pre>\n<p>Any command-line input or output is written as follows:<br \/>\n\u4efb\u4f55\u547d\u4ee4\u884c\u8f93\u5165\u6216\u8f93\u51fa\u7684\u7f16\u5199\u65b9\u5f0f\u5982\u4e0b\uff1a<\/p>\n<pre><code>dotnet new webapi -minimal -o Chapter01<\/code><\/pre>\n<p>Bold: Indicates a new term, an important word, or words that you see onscreen. For instance, words in menus or dialog boxes appear in bold. Here is an example: \u201cOpen Visual Studio 2022 and from the main screen, click on Create a new project.\u201d<br \/>\n\u7c97\u4f53\uff1a\u8868\u793a\u65b0\u8bcd\u3001\u91cd\u8981\u5b57\u8bcd\u6216\u60a8\u5728\u5c4f\u5e55\u4e0a\u770b\u5230\u7684\u5b57\u8bcd\u3002\u4f8b\u5982\uff0c\u83dc\u5355\u6216\u5bf9\u8bdd\u6846\u4e2d\u7684\u5355\u8bcd\u4ee5\u7c97\u4f53\u663e\u793a\u3002\u8fd9\u662f\u4e00\u4e2a\u4f8b\u5b50\uff1a\u201c\u6253\u5f00 Visual Studio 2022\uff0c\u7136\u540e\u5728\u4e3b\u5c4f\u5e55\u4e0a\u5355\u51fb\u521b\u5efa\u65b0\u9879\u76ee\u3002<\/p>\n<pre><code>Tips or important notes\n\u63d0\u793a\u6216\u91cd\u8981\u8bf4\u660e\nAppear like this.\n\u5982\u4e0b\u6240\u793a\u3002<\/code><\/pre>\n<p>Get in touch<br \/>\n\u8054\u7cfb\u6211\u4eec<\/p>\n<p>Feedback from our readers is always welcome.<br \/>\n\u6211\u4eec\u59cb\u7ec8\u6b22\u8fce\u8bfb\u8005\u7684\u53cd\u9988\u3002<\/p>\n<p>General feedback: If you have questions about any aspect of this book, email us at customercare@packtpub.com and mention the book title in the subject of your message.<br \/>\n\u4e00\u822c\u53cd\u9988\uff1a\u5982\u679c\u60a8\u5bf9\u672c\u4e66\u7684\u4efb\u4f55\u65b9\u9762\u6709\u4efb\u4f55\u7591\u95ee\uff0c\u8bf7\u53d1\u9001\u7535\u5b50\u90ae\u4ef6\u81f3 customercare@packtpub.com \u5e76\u5728\u90ae\u4ef6\u4e3b\u9898\u4e2d\u63d0\u53ca\u4e66\u540d\u3002<\/p>\n<p>Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com\/support\/errata and fill in the form.<br \/>\n\u52d8\u8bef\u8868\uff1a \u5c3d\u7ba1\u6211\u4eec\u5df2\u5c3d\u4e00\u5207\u52aa\u529b\u786e\u4fdd\u5185\u5bb9\u7684\u51c6\u786e\u6027\uff0c\u4f46\u9519\u8bef\u8fd8\u662f\u4f1a\u53d1\u751f\u3002\u5982\u679c\u60a8\u53d1\u73b0\u672c\u4e66\u4e2d\u6709\u9519\u8bef\uff0c\u5982\u679c\u60a8\u80fd\u5411\u6211\u4eec\u62a5\u544a\uff0c\u6211\u4eec\u5c06\u4e0d\u80dc\u611f\u6fc0\u3002\u8bf7\u8bbf\u95ee www.packtpub.com\/support\/errata \u5e76\u586b\u5199\u8868\u683c\u3002<\/p>\n<p>Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at copyright@packt.com with a link to the material.<br \/>\n\u76d7\u7248\uff1a\u5982\u679c\u60a8\u5728\u4e92\u8054\u7f51\u4e0a\u53d1\u73b0\u4efb\u4f55\u5f62\u5f0f\u7684\u975e\u6cd5\u590d\u5236\u6211\u4eec\u7684\u4f5c\u54c1\uff0c\u5982\u679c\u60a8\u80fd\u5411\u6211\u4eec\u63d0\u4f9b\u4f4d\u7f6e\u5730\u5740\u6216\u7f51\u7ad9\u540d\u79f0\uff0c\u6211\u4eec\u5c06\u4e0d\u80dc\u611f\u6fc0\u3002\u8bf7\u901a\u8fc7 copyright@packt.com \u4e0e\u6211\u4eec\u8054\u7cfb\uff0c\u5e76\u63d0\u4f9b\u6750\u6599\u94fe\u63a5\u3002<\/p>\n<p>If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.<br \/>\n\u5982\u679c\u60a8\u6709\u5174\u8da3\u6210\u4e3a\u4f5c\u8005\uff1a\u5982\u679c\u60a8\u64c5\u957f\u67d0\u4e2a\u4e3b\u9898\uff0c\u5e76\u4e14\u60a8\u5bf9\u5199\u4f5c\u6216\u4e3a\u4e00\u672c\u4e66\u505a\u51fa\u8d21\u732e\u611f\u5174\u8da3\uff0c\u8bf7\u8bbf\u95ee authors.packtpub.com\u3002<\/p>\n<p>Share Your Thoughts<br \/>\n\u5206\u4eab\u60a8\u7684\u60f3\u6cd5<\/p>\n<p>Once you\u2019ve read Mastering Minimal APIs in ASP.NET Core, we\u2019d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.<br \/>\n\u9605\u8bfb\u4e86\u638c\u63e1 ASP.NET Core \u4e2d\u7684\u6700\u5c0f API \u540e\uff0c\u6211\u4eec\u5f88\u60f3\u542c\u542c\u4f60\u7684\u60f3\u6cd5\uff01\u8bf7\u5355\u51fb\u6b64\u5904\u76f4\u63a5\u8fdb\u5165\u672c\u4e66\u7684\u4e9a\u9a6c\u900a\u8bc4\u8bba\u9875\u9762\u5e76\u5206\u4eab\u60a8\u7684\u53cd\u9988\u3002<\/p>\n<p>Your review is important to us and the tech community and will help us make sure we\u2019re delivering excellent quality content.<br \/>\n\u60a8\u7684\u8bc4\u8bba\u5bf9\u6211\u4eec\u548c\u6280\u672f\u793e\u533a\u90fd\u5f88\u91cd\u8981\uff0c\u8fd9\u5c06\u6709\u52a9\u4e8e\u6211\u4eec\u786e\u4fdd\u6211\u4eec\u63d0\u4f9b\u5353\u8d8a\u7684\u5185\u5bb9\u8d28\u91cf\u3002<\/p>\n<h1>Part 1: Introduction<\/h1>\n<p>\u7b2c 1 \u90e8\u5206\uff1a\u7b80\u4ecb<\/p>\n<p>In the first part of the book, we want to introduce you to the context of the book. We will explain the basics of minimal APIs and how they work. We want to add, brick by brick, the knowledge needed to take advantage of all the power that minimal APIs can grant us.<br \/>\n\u5728\u672c\u4e66\u7684\u7b2c\u4e00\u90e8\u5206\uff0c\u6211\u4eec\u60f3\u5411\u60a8\u4ecb\u7ecd\u8fd9\u672c\u4e66\u7684\u80cc\u666f\u3002\u6211\u4eec\u5c06\u89e3\u91ca\u6700\u5c0f API \u7684\u57fa\u7840\u77e5\u8bc6\u53ca\u5176\u5de5\u4f5c\u539f\u7406\u3002\u6211\u4eec\u5e0c\u671b\u4e00\u7816\u4e00\u74e6\u5730\u6dfb\u52a0\u6240\u9700\u7684\u77e5\u8bc6\uff0c\u4ee5\u5229\u7528\u6700\u5c0f API \u53ef\u4ee5\u8d4b\u4e88\u6211\u4eec\u7684\u6240\u6709\u529f\u80fd\u3002<\/p>\n<p>We will cover the following chapters in this part:<br \/>\n\u6211\u4eec\u5c06\u5728\u8fd9\u90e8\u5206\u4ecb\u7ecd\u4ee5\u4e0b\u7ae0\u8282\uff1a<\/p>\n<p>Chapter 1, Introduction to Minimal APIs<br \/>\n\u7b2c 1 \u7ae0 \u6700\u5c0f API \u7b80\u4ecb<\/p>\n<p>Chapter 2, Exploring Minimal APIs and Their Advantages<br \/>\n\u7b2c 2 \u7ae0 \u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u70b9<\/p>\n<p>Chapter 3, Working with Minimal APIs<br \/>\n\u7b2c 3 \u7ae0 \u4f7f\u7528\u6700\u5c11\u7684 API<\/p>\n<h1>1 Introduction to Minimal APIs<\/h1>\n<p>1 \u6700\u5c0f API \u7b80\u4ecb<\/p>\n<p>In this chapter of the book, we will introduce some basic themes related to minimal APIs in .NET 6.0, showing how to set up a development environment for .NET 6 and more specifically for developing minimal APIs with ASP.NET Core.<br \/>\n\u5728\u672c\u4e66\u7684\u8fd9\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4e00\u4e9b\u4e0e .NET 6.0 \u4e2d\u7684\u6700\u5c0f API \u76f8\u5173\u7684\u57fa\u672c\u4e3b\u9898\uff0c\u5c55\u793a\u5982\u4f55\u4e3a .NET 6 \u8bbe\u7f6e\u5f00\u53d1\u73af\u5883\uff0c\u66f4\u5177\u4f53\u5730\u8bf4\uff0c\u5982\u4f55\u4e3a ASP.NET Core \u5f00\u53d1\u6700\u5c0f API\u3002<\/p>\n<p>We will first begin with a brief history of minimal APIs. Then, we will create a new minimal API project with Visual Studio 2022 and Visual Code Studio. At the end, we will take a look at the structure of our project.<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u5c06\u4ece\u6700\u5c0f API \u7684\u7b80\u8981\u5386\u53f2\u5f00\u59cb\u3002\u7136\u540e\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 Visual Studio 2022 \u548c Visual Code Studio \u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u6700\u5c0f API \u9879\u76ee\u3002\u6700\u540e\uff0c\u6211\u4eec\u5c06\u770b\u770b\u6211\u4eec\u9879\u76ee\u7684\u7ed3\u6784\u3002<\/p>\n<p>By the end of this chapter, you will be able to create a new minimal API project and start to work with this new template for a REST API.<br \/>\n\u5728\u672c\u7ae0\u7ed3\u675f\u65f6\uff0c\u60a8\u5c06\u80fd\u591f\u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u6700\u5c0f API \u9879\u76ee\uff0c\u5e76\u5f00\u59cb\u4e3a REST API \u4f7f\u7528\u8fd9\u4e2a\u65b0\u6a21\u677f\u3002<\/p>\n<p>In this chapter, we will be covering the following topics:<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u4e3b\u9898\uff1a<\/p>\n<p>\u2022  A brief history of the Microsoft Web API<br \/>\n\u2022  Creating a new minimal API project<br \/>\n\u2022  Looking at the structure of the project<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>To work with the ASP.NET Core 6 minimal APIs you need to install, first of all, .NET 6 on your development environment.<br \/>\n\u8981\u4f7f\u7528 ASP.NET Core 6 \u6700\u5c0f API\uff0c\u60a8\u9700\u8981\u9996\u5148\u5728\u5f00\u53d1\u73af\u5883\u4e2d\u5b89\u88c5 .NET 6\u3002<\/p>\n<p>If you have not already installed it, let\u2019s do that now:<br \/>\n\u5982\u679c\u60a8\u8fd8\u6ca1\u6709\u5b89\u88c5\u5b83\uff0c\u6211\u4eec\u73b0\u5728\u5c31\u5b89\u88c5\u5b83\uff1a<\/p>\n<ol>\n<li>\n<p>Navigate to the following link: <a href=\"https:\/\/dotnet.microsoft.com\">https:\/\/dotnet.microsoft.com<\/a>.<br \/>\n\u5bfc\u822a\u5230\u4ee5\u4e0b\u94fe\u63a5\uff1a<a href=\"https:\/\/dotnet.microsoft.com\">https:\/\/dotnet.microsoft.com<\/a>\u3002<\/p>\n<\/li>\n<li>\n<p>Click on the Download button.<br \/>\n\u70b9\u51fb \u4e0b\u8f7d \u6309\u94ae\u3002<\/p>\n<\/li>\n<li>\n<p>By default, the browser chooses the right operating system for you, but if not, select your operating system at the top of the page.<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u6d4f\u89c8\u5668\u4f1a\u4e3a\u60a8\u9009\u62e9\u5408\u9002\u7684\u4f5c\u7cfb\u7edf\uff0c\u5982\u679c\u6ca1\u6709\uff0c\u8bf7\u5728\u9875\u9762\u9876\u90e8\u9009\u62e9\u60a8\u7684\u4f5c\u7cfb\u7edf\u3002<\/p>\n<\/li>\n<li>\n<p>Download the LTS version of the .NET 6.0 SDK.<br \/>\n\u4e0b\u8f7d .NET 6.0 SDK \u7684 LTS \u7248\u672c\u3002<\/p>\n<\/li>\n<li>\n<p>Start the installer.<br \/>\n\u542f\u52a8\u5b89\u88c5\u7a0b\u5e8f\u3002<\/p>\n<\/li>\n<li>\n<p>Reboot the machine (this is not mandatory).<br \/>\n\u91cd\u65b0\u542f\u52a8\u8ba1\u7b97\u673a\uff08\u8fd9\u4e0d\u662f\u5f3a\u5236\u6027\u7684\uff09\u3002<\/p>\n<\/li>\n<\/ol>\n<p>You can see which SDKs are installed on your development machine using the following command in a terminal:<br \/>\n\u60a8\u53ef\u4ee5\u5728\u7ec8\u7aef\u4e2d\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u67e5\u770b\u5f00\u53d1\u8ba1\u7b97\u673a\u4e0a\u5b89\u88c5\u4e86\u54ea\u4e9b SDK\uff1a<\/p>\n<pre><code>dotnet \u2013list-sdks<\/code><\/pre>\n<p>Before you start coding, you will need a code editor or an Integrated Development Environment (IDE). You can choose your favorite from the following list:<br \/>\n\u5728\u5f00\u59cb\u7f16\u7801\u4e4b\u524d\uff0c\u60a8\u9700\u8981\u4e00\u4e2a\u4ee3\u7801\u7f16\u8f91\u5668\u6216\u96c6\u6210\u5f00\u53d1\u73af\u5883 \uff08IDE\uff09\u3002\u60a8\u53ef\u4ee5\u4ece\u4ee5\u4e0b\u5217\u8868\u4e2d\u9009\u62e9\u60a8\u6700\u559c\u6b22\u7684\uff1a<\/p>\n<p>\u2022  Visual Studio Code for Windows, Mac, or Linux<br \/>\n\u2022  Visual Studio 2022<br \/>\n\u2022  Visual Studio 2022 for Mac<\/p>\n<p>In the last few years, Visual Studio Code has become very popular not only in the developer community but also in the Microsoft community. Even if you use Visual Studio 2022 for your day-to-day work, we recommend downloading and installing Visual Studio Code and giving it a try.<br \/>\n\u5728\u8fc7\u53bb\u7684\u51e0\u5e74\u91cc\uff0cVisual Studio Code \u4e0d\u4ec5\u5728\u5f00\u53d1\u4eba\u5458\u793e\u533a\u4e2d\u975e\u5e38\u6d41\u884c\uff0c\u800c\u4e14\u5728 Microsoft \u793e\u533a\u4e2d\u4e5f\u975e\u5e38\u6d41\u884c\u3002\u5373\u4f7f\u60a8\u5c06 Visual Studio 2022 \u7528\u4e8e\u65e5\u5e38\u5de5\u4f5c\uff0c\u6211\u4eec\u4e5f\u5efa\u8bae\u60a8\u4e0b\u8f7d\u5e76\u5b89\u88c5 Visual Studio Code \u5e76\u8bd5\u4e00\u8bd5\u3002<\/p>\n<p>Let\u2019s download and install Visual Studio Code and some extensions:<br \/>\n\u8ba9\u6211\u4eec\u4e0b\u8f7d\u5e76\u5b89\u88c5 Visual Studio Code \u548c\u4e00\u4e9b\u6269\u5c55\uff1a<\/p>\n<ol>\n<li>\n<p>Navigate to <a href=\"https:\/\/code.visualstudio.com\">https:\/\/code.visualstudio.com<\/a>.<br \/>\n\u5bfc\u822a\u5230 <a href=\"https:\/\/code.visualstudio.com\">https:\/\/code.visualstudio.com<\/a>\u3002<\/p>\n<\/li>\n<li>\n<p>Download the Stable or the Insiders edition.<br \/>\n\u4e0b\u8f7d Stable \u6216 Insiders \u7248\u672c\u3002<\/p>\n<\/li>\n<li>\n<p>Start the installer.<br \/>\n\u542f\u52a8\u5b89\u88c5\u7a0b\u5e8f\u3002<\/p>\n<\/li>\n<li>\n<p>Launch Visual Studio Code.<br \/>\n\u542f\u52a8 Visual Studio Code\u3002<\/p>\n<\/li>\n<li>\n<p>Click on the Extensions icon.<br \/>\n\u5355\u51fb Extensions \u56fe\u6807\u3002<\/p>\n<\/li>\n<\/ol>\n<p>You will see the C# extension at the top of the list.<br \/>\n\u60a8\u5c06\u5728\u5217\u8868\u9876\u90e8\u770b\u5230 C# \u6269\u5c55\u3002<\/p>\n<ol start=\"6\">\n<li>Click on the Install button and wait.<br \/>\n\u70b9\u51fb Install \u5b89\u88c5 \u6309\u94ae\u5e76\u7b49\u5f85\u3002<\/li>\n<\/ol>\n<p>You can install other recommended extensions for developing with C# and ASP.NET Core. If you want to install them, you see our recommendations in the following table:<br \/>\n\u60a8\u53ef\u4ee5\u5b89\u88c5\u5176\u4ed6\u63a8\u8350\u7684\u6269\u5c55\uff0c\u4ee5\u4fbf\u4f7f\u7528 C# \u548c ASP.NET Core \u8fdb\u884c\u5f00\u53d1\u3002\u5982\u679c\u60a8\u60f3\u5b89\u88c5\u5b83\u4eec\uff0c\u60a8\u53ef\u4ee5\u5728\u4e0b\u8868\u4e2d\u770b\u5230\u6211\u4eec\u7684\u5efa\u8bae\uff1a<\/p>\n<p>Additionally, if you want to proceed with the IDE that\u2019s most widely used by .NET developers, you can download and install Visual Studio 2022.<br \/>\n\u6b64\u5916\uff0c\u5982\u679c\u60a8\u60f3\u7ee7\u7eed\u4f7f\u7528 .NET \u5f00\u53d1\u4eba\u5458\u4f7f\u7528\u6700\u5e7f\u6cdb\u7684 IDE\uff0c\u60a8\u53ef\u4ee5\u4e0b\u8f7d\u5e76\u5b89\u88c5 Visual Studio 2022\u3002<\/p>\n<p>If you don\u2019t have a license, check if you can use the Community Edition. There are a few restrictions on getting a license, but you can use it if you are a student, have open source projects, or want to use it as an individual. Here\u2019s how to download and install Visual Studio 2022:<br \/>\n\u5982\u679c\u60a8\u6ca1\u6709\u8bb8\u53ef\u8bc1\uff0c\u8bf7\u68c0\u67e5\u662f\u5426\u53ef\u4ee5\u4f7f\u7528 Community Edition\u3002\u83b7\u5f97\u8bb8\u53ef\u8bc1\u6709\u4e00\u4e9b\u9650\u5236\uff0c\u4f46\u5982\u679c\u60a8\u662f\u5b66\u751f\u3001\u62e5\u6709\u5f00\u6e90\u9879\u76ee\u6216\u60f3\u4ee5\u4e2a\u4eba\u8eab\u4efd\u4f7f\u7528\u5b83\uff0c\u5219\u53ef\u4ee5\u4f7f\u7528\u5b83\u3002\u4ee5\u4e0b\u662f\u4e0b\u8f7d\u548c\u5b89\u88c5 Visual Studio 2022 \u7684\u65b9\u6cd5\uff1a<\/p>\n<ol>\n<li>\n<p>Navigate to <a href=\"https:\/\/visualstudio.microsoft.com\/downloads\/\">https:\/\/visualstudio.microsoft.com\/downloads\/<\/a>.<br \/>\n\u5bfc\u822a\u5230 <a href=\"https:\/\/visualstudio.microsoft.com\/downloads\/\">https:\/\/visualstudio.microsoft.com\/downloads\/<\/a>\u3002<\/p>\n<\/li>\n<li>\n<p>Select Visual Studio 2022 version 17.0 or later and download it.<br \/>\n\u9009\u62e9 Visual Studio 2022 \u7248\u672c 17.0 \u6216\u66f4\u9ad8\u7248\u672c\u5e76\u4e0b\u8f7d\u5b83\u3002<\/p>\n<\/li>\n<li>\n<p>Start the installer.<br \/>\n\u542f\u52a8\u5b89\u88c5\u7a0b\u5e8f\u3002<\/p>\n<\/li>\n<li>\n<p>On the Workloads tab, select the following:<br \/>\n\u5728 Workloads \uff08\u5de5\u4f5c\u8d1f\u8f7d\uff09 \u9009\u9879\u5361\u4e0a\uff0c\u9009\u62e9\u4ee5\u4e0b\u9009\u9879\uff1a<\/p>\n<\/li>\n<\/ol>\n<p>\u2022  ASP.NET and web development<br \/>\n\u2022  Azure Development<\/p>\n<ol start=\"5\">\n<li>On the Individual Components tab, select the following:<br \/>\n\u5728 Individual Components \u9009\u9879\u5361\u4e0a\uff0c\u9009\u62e9\u4ee5\u4e0b\u9009\u9879\uff1a<\/li>\n<\/ol>\n<p>\u2022  Git for Windows<\/p>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter01\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter01<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter01\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter01<\/a>\u3002<\/p>\n<p>Now, you have an environment in which you can follow and try the code used in this book.<br \/>\n\u73b0\u5728\uff0c\u60a8\u6709\u4e00\u4e2a\u73af\u5883\uff0c\u53ef\u4ee5\u5728\u5176\u4e2d\u9075\u5faa\u548c\u5c1d\u8bd5\u672c\u4e66\u4e2d\u4f7f\u7528\u7684\u4ee3\u7801\u3002<\/p>\n<p>A brief history of the Microsoft Web API<br \/>\nMicrosoft Web API \u7b80\u53f2<\/p>\n<p>A few years ago in 2007, .NET web applications went through an evolution with the introduction of ASP.NET MVC. Since then, .NET has provided native support for the Model-View-Controller pattern that was common in other languages.<br \/>\n\u51e0\u5e74\u524d\u7684 2007 \u5e74\uff0c\u968f\u7740 ASP.NET MVC \u7684\u63a8\u51fa\uff0c.NET Web \u5e94\u7528\u7a0b\u5e8f\u7ecf\u5386\u4e86\u4e00\u573a\u6f14\u53d8\u3002\u4ece\u90a3\u65f6\u8d77\uff0c.NET \u5c31\u4e3a\u5176\u4ed6\u8bed\u8a00\u4e2d\u5e38\u89c1\u7684 Model-View-Controller \u6a21\u5f0f\u63d0\u4f9b\u4e86\u672c\u673a\u652f\u6301\u3002<\/p>\n<p>Five years later, in 2012, RESTful APIs were the new trend on the internet and .NET responded to this with a new approach for developing APIs, called ASP.NET Web API. It was a significant improvement over Windows Communication Foundation (WCF) because it was easier to develop services for the web. Later, in ASP.NET Core these frameworks were unified under the name ASP.NET Core MVC: one single framework with which to develop web applications and APIs.<br \/>\n\u4e94\u5e74\u540e\uff0c\u5373 2012 \u5e74\uff0cRESTful API \u6210\u4e3a Internet \u4e0a\u7684\u65b0\u8d8b\u52bf\uff0c.NET \u4ee5\u4e00\u79cd\u79f0\u4e3a ASP.NET Web API \u7684 API \u5f00\u53d1\u65b0\u65b9\u6cd5\u5bf9\u6b64\u505a\u51fa\u4e86\u56de\u5e94\u3002\u4e0e Windows Communication Foundation \uff08WCF\uff09 \u76f8\u6bd4\uff0c\u8fd9\u662f\u4e00\u4e2a\u91cd\u5927\u6539\u8fdb\uff0c\u56e0\u4e3a\u5b83\u66f4\u5bb9\u6613\u5f00\u53d1 Web \u670d\u52a1\u3002\u540e\u6765\uff0c\u5728 ASP.NET Core \u4e2d\uff0c\u8fd9\u4e9b\u6846\u67b6\u7edf\u4e00\u4e3a ASP.NET Core MVC\uff1a\u4e00\u4e2a\u7528\u4e8e\u5f00\u53d1 Web \u5e94\u7528\u7a0b\u5e8f\u548c API \u7684\u5355\u4e00\u6846\u67b6\u3002<\/p>\n<p>In ASP.NET Core MVC applications, the controller is responsible for accepting inputs, orchestrating operations, and at the end, returning a response. A developer can extend the entire pipeline with filters, binding, validation, and much more. It\u2019s a fully featured framework for building modern web applications.<br \/>\n\u5728 ASP.NET Core MVC \u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u63a7\u5236\u5668\u8d1f\u8d23\u63a5\u53d7\u8f93\u5165\u3001\u7f16\u6392\u4f5c\uff0c\u5e76\u5728\u6700\u540e\u8fd4\u56de\u54cd\u5e94\u3002\u5f00\u53d1\u4eba\u5458\u53ef\u4ee5\u4f7f\u7528\u8fc7\u6ee4\u5668\u3001\u7ed1\u5b9a\u3001\u9a8c\u8bc1\u7b49\u6765\u6269\u5c55\u6574\u4e2a\u7ba1\u9053\u3002\u5b83\u662f\u4e00\u4e2a\u529f\u80fd\u9f50\u5168\u7684\u6846\u67b6\uff0c\u7528\u4e8e\u6784\u5efa\u73b0\u4ee3 Web \u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>But in the real world, there are also scenarios and use cases where you don\u2019t need all the features of the MVC framework or you have to factor in a constraint on performance. ASP.NET Core implements a lot of middleware that you can remove from or add to your applications at will, but there are a lot of common features that you would need to implement by yourself in this scenario.<br \/>\n\u4f46\u5728\u73b0\u5b9e\u4e16\u754c\u4e2d\uff0c\u4e5f\u6709\u4e00\u4e9b\u573a\u666f\u548c\u7528\u4f8b\u4e0d\u9700\u8981 MVC \u6846\u67b6\u7684\u6240\u6709\u529f\u80fd\uff0c\u6216\u8005\u5fc5\u987b\u8003\u8651\u6027\u80fd\u7ea6\u675f\u3002ASP.NET Core \u5b9e\u73b0\u4e86\u8bb8\u591a\u4e2d\u95f4\u4ef6\uff0c\u4f60\u53ef\u4ee5\u968f\u610f\u4ece\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5220\u9664\u6216\u6dfb\u52a0\u5230\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u4f46\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6709\u8bb8\u591a\u5e38\u89c1\u529f\u80fd\u9700\u8981\u4f60\u81ea\u5df1\u5b9e\u73b0\u3002<\/p>\n<p>At last, ASP.NET Core 6.0 has filled these gaps with minimal APIs.<br \/>\n\u6700\u540e\uff0cASP.NET Core 6.0 \u7528\u6700\u5c11\u7684 API \u586b\u8865\u4e86\u8fd9\u4e9b\u7a7a\u767d\u3002<\/p>\n<p>Now that we have covered a brief history of minimal APIs, we will start creating a new minimal API project in the next section.<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u7b80\u8981\u4ecb\u7ecd\u4e86\u6700\u5c0f API \u7684\u5386\u53f2\uff0c\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u8282\u4e2d\u5f00\u59cb\u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u6700\u5c0f API \u9879\u76ee\u3002<\/p>\n<p>Creating a new minimal API project<br \/>\n\u521b\u5efa\u65b0\u7684\u6700\u5c0f API \u9879\u76ee<\/p>\n<p>Let\u2019s start with our first project and try to analyze the new template for the minimal API approach when writing a RESTful API.<br \/>\n\u8ba9\u6211\u4eec\u4ece\u7b2c\u4e00\u4e2a\u9879\u76ee\u5f00\u59cb\uff0c\u5c1d\u8bd5\u5728\u7f16\u5199 RESTful API \u65f6\u5206\u6790\u6700\u5c0f API \u65b9\u6cd5\u7684\u65b0\u6a21\u677f\u3002<\/p>\n<p>In this section, we will create our first minimal API project. We will start by using Visual Studio 2022 and then we will show how you can also create the project with Visual Studio Code and the .NET CLI.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u6211\u4eec\u7684\u7b2c\u4e00\u4e2a\u6700\u5c0f API \u9879\u76ee\u3002\u6211\u4eec\u5c06\u4ece\u4f7f\u7528 Visual Studio 2022 \u5f00\u59cb\uff0c\u7136\u540e\u6211\u4eec\u5c06\u5c55\u793a\u5982\u4f55\u4f7f\u7528 Visual Studio Code \u548c .NET CLI \u521b\u5efa\u9879\u76ee\u3002<\/p>\n<p>Creating the project with Visual Studio 2022<br \/>\n\u4f7f\u7528 Visual Studio 2022 \u521b\u5efa\u9879\u76ee<\/p>\n<p>Follow these steps to create a new project in Visual Studio 2022:<br \/>\n\u6309\u7167\u4ee5\u4e0b\u6b65\u9aa4\u5728 Visual Studio 2022 \u4e2d\u521b\u5efa\u65b0\u9879\u76ee\uff1a<\/p>\n<ol>\n<li>Open Visual Studio 2022 and on the main screen, click on Create a new project:<br \/>\n\u6253\u5f00 Visual Studio 2022 \u5e76\u5728\u4e3b\u5c4f\u5e55\u4e0a\u5355\u51fb Create a new project\uff1a<\/li>\n<\/ol>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0101.jpg\"><\/p>\n<p>Figure 1.1 \u2013 Visual Studio 2022 splash screen<br \/>\n\u56fe 1.1 \u2013 Visual Studio 2022 \u521d\u59cb\u5c4f\u5e55<\/p>\n<ol start=\"2\">\n<li>\n<p>On the next screen, write API in the textbox at the top of the window and select the template called ASP.NET Core Web API:<br \/>\n\u5728\u4e0b\u4e00\u4e2a\u5c4f\u5e55\u4e0a\uff0c\u5728\u7a97\u53e3\u9876\u90e8\u7684\u6587\u672c\u6846\u4e2d\u7f16\u5199 API\uff0c\u7136\u540e\u9009\u62e9\u540d\u4e3a ASP.NET Core Web API \u7684\u6a21\u677f\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0102.jpg\"><\/p>\n<p>Figure 1.2 \u2013 Create a new project screen<br \/>\n\u56fe 1.2 \u2013 Create a new project \u5c4f\u5e55<\/p>\n<\/li>\n<li>\n<p>Next, on the Configure your new project screen, insert a name for the new project and select the root folder for your new solution:<br \/>\n\u63a5\u4e0b\u6765\uff0c\u5728 Configure your new project \u5c4f\u5e55\u4e0a\uff0c\u63d2\u5165\u65b0\u9879\u76ee\u7684\u540d\u79f0\uff0c\u7136\u540e\u9009\u62e9\u65b0\u89e3\u51b3\u65b9\u6848\u7684\u6839\u6587\u4ef6\u5939\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0103.jpg\"><\/p>\n<p>Figure 1.3 \u2013 Configure your new project screen<br \/>\n\u56fe 1.3 \u2013 \u914d\u7f6e\u60a8\u7684\u65b0\u9879\u76ee\u5c4f\u5e55<\/p>\n<\/li>\n<\/ol>\n<p>For this example we will use the name Chapter01, but you can choose any name that appeals to you.<br \/>\n\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u540d\u79f0 Chapter01\uff0c\u4f46\u60a8\u53ef\u4ee5\u9009\u62e9\u4efb\u4f55\u5438\u5f15\u60a8\u7684\u540d\u79f0\u3002<\/p>\n<ol start=\"4\">\n<li>On the following Additional information screen, make sure to select .NET 6.0 (Long-term-support) from the Framework dropdown. And most important of all, uncheck the Use controllers (uncheck to use minimal APIs) option.<br \/>\n\u5728\u4e0b\u9762\u7684 Additional information \u5c4f\u5e55\u4e0a\uff0c\u786e\u4fdd\u4ece Framework \u4e0b\u62c9\u5217\u8868\u4e2d\u9009\u62e9 .NET 6.0 \uff08Long-term-support\uff09\u3002\u6700\u91cd\u8981\u7684\u662f\uff0c\u53d6\u6d88\u9009\u4e2d Use controllers \uff08\u53d6\u6d88\u9009\u4e2d\u4ee5\u4f7f\u7528\u6700\u5c11\u7684 API\uff09 \u9009\u9879\u3002<\/li>\n<\/ol>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0104.jpg\"><\/p>\n<p>Figure 1.4 \u2013 Additional information screen<\/p>\n<ol start=\"5\">\n<li>Click Create and, after a few seconds, you will see the code of your new minimal API project.<br \/>\n\u5355\u51fb Create\uff08\u521b\u5efa\uff09\uff0c\u51e0\u79d2\u949f\u540e\uff0c\u60a8\u5c06\u770b\u5230\u65b0\u7684\u6700\u5c0f API \u9879\u76ee\u7684\u4ee3\u7801\u3002<\/li>\n<\/ol>\n<p>Now we are going to show how to create the same project using Visual Studio Code and the .NET CLI.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5c06\u5c55\u793a\u5982\u4f55\u4f7f\u7528 Visual Studio Code \u548c .NET CLI \u521b\u5efa\u76f8\u540c\u7684\u9879\u76ee\u3002<\/p>\n<p>Creating the project with Visual Studio Code<br \/>\n\u4f7f\u7528 Visual Studio Code \u521b\u5efa\u9879\u76ee<\/p>\n<p>Creating a project with Visual Studio Code is easier and faster than with Visual Studio 2022 because you don\u2019t have to use a UI or wizard, rather just a terminal and the .NET CLI.<br \/>\n\u4f7f\u7528 Visual Studio Code \u521b\u5efa\u9879\u76ee\u6bd4\u4f7f\u7528 Visual Studio 2022 \u66f4\u5bb9\u6613\u3001\u66f4\u5feb\u6377\uff0c\u56e0\u4e3a\u60a8\u4e0d\u5fc5\u4f7f\u7528 UI \u6216\u5411\u5bfc\uff0c\u800c\u53ea\u9700\u4f7f\u7528\u7ec8\u7aef\u548c .NET CLI\u3002<\/p>\n<p>You don\u2019t need to install anything new for this because the .NET CLI is included with the .NET 6 installation (as in the previous versions of the .NET SDKs). Follow these steps to create a project using Visual Studio Code:<br \/>\n\u60a8\u65e0\u9700\u4e3a\u6b64\u5b89\u88c5\u4efb\u4f55\u65b0\u5185\u5bb9\uff0c\u56e0\u4e3a .NET CLI \u5305\u542b\u5728 .NET 6 \u5b89\u88c5\u4e2d\uff08\u4e0e\u4ee5\u524d\u7248\u672c\u7684 .NET SDK \u4e00\u6837\uff09\u3002\u6309\u7167\u4ee5\u4e0b\u6b65\u9aa4\u4f7f\u7528 Visual Studio Code \u521b\u5efa\u9879\u76ee\uff1a<\/p>\n<ol>\n<li>\n<p>Open your console, shell, or Bash terminal, and switch to your working directory.<br \/>\n\u6253\u5f00\u60a8\u7684\u63a7\u5236\u53f0\u3001shell \u6216 Bash \u7ec8\u7aef\uff0c\u7136\u540e\u5207\u6362\u5230\u60a8\u7684\u5de5\u4f5c\u76ee\u5f55\u3002<\/p>\n<\/li>\n<li>\n<p>Use the following command to create a new Web API application:<br \/>\n\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u521b\u5efa\u65b0\u7684 Web API \u5e94\u7528\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>dotnet new webapi -minimal -o Chapter01<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>As you can see, we have inserted the -minimal parameter in the preceding command to use the minimal API project template instead of the ASP.NET Core template with the controllers.<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u6211\u4eec\u5728\u524d\u9762\u7684\u547d\u4ee4\u4e2d\u63d2\u5165\u4e86 -minimal \u53c2\u6570\uff0c\u4ee5\u4f7f\u7528\u6700\u5c0f API \u9879\u76ee\u6a21\u677f\uff0c\u800c\u4e0d\u662f\u63a7\u5236\u5668\u7684 ASP.NET Core \u6a21\u677f\u3002<\/p>\n<ol start=\"3\">\n<li>Now open the new project with Visual Studio Code using the following commands:<br \/>\n\u73b0\u5728\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u4f7f\u7528 Visual Studio Code \u6253\u5f00\u65b0\u9879\u76ee\uff1a<\/p>\n<pre><code>cd Chapter01\ncode.<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>Now that we know how to create a new minimal API project, we are going to have a quick look at the structure of this new template.<br \/>\n\u73b0\u5728\u6211\u4eec\u77e5\u9053\u5982\u4f55\u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u6700\u5c0f API \u9879\u76ee\uff0c\u6211\u4eec\u5c06\u5feb\u901f\u4e86\u89e3\u4e00\u4e0b\u8fd9\u4e2a\u65b0\u6a21\u677f\u7684\u7ed3\u6784\u3002<\/p>\n<p>Looking at the structure of the project<br \/>\n\u67e5\u770b\u9879\u76ee\u7ed3\u6784<\/p>\n<p>Whether you are using Visual Studio or Visual Studio Code, you should see the following code in the Program.cs file:<br \/>\n\u65e0\u8bba\u60a8\u4f7f\u7528\u7684\u662f Visual Studio \u8fd8\u662f Visual Studio Code\uff0c\u60a8\u90fd\u5e94\u8be5\u5728 Program.cs \u6587\u4ef6\u4e2d\u770b\u5230\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/ Add services to the container.\n\/\/ Learn more about configuring Swagger\/OpenAPI at https:\/\/aka.\nms\/aspnetcore\/swashbuckle\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen();\nvar app = builder.Build();\n\/\/ Configure the HTTP request pipeline.\nif (app.Environment.IsDevelopment())\n{\napp.UseSwagger();\napp.UseSwaggerUI();\n}\napp.UseHttpsRedirection();\nvar summaries = new[]\n{\n&quot;Freezing&quot;, &quot;Bracing&quot;, &quot;Chilly&quot;, &quot;Cool&quot;, &quot;Mild&quot;, &quot;Warm&quot;,\n&quot;Balmy&quot;, &quot;Hot&quot;, &quot;Sweltering&quot;, &quot;Scorching&quot;\n};\napp.MapGet(&quot;\/weatherforecast&quot;, () =&gt;\n{\nvar forecast = Enumerable.Range(1, 5).Select(index =&gt;\nnew WeatherForecast\n(\nDateTime.Now.AddDays(index),\nRandom.Shared.Next(-20, 55),\nsummaries[Random.Shared.Next(summaries.Length)]\n))\n.ToArray();\nreturn forecast;\n})\n.WithName(&quot;GetWeatherForecast&quot;);\napp.Run();\ninternal record WeatherForecast(DateTime Date, int\nTemperatureC, string? Summary)\n{\npublic int TemperatureF =&gt; 32 + (int)(TemperatureC \/\n0.5556);\n}<\/code><\/pre>\n<p>First of all, with the minimal API approach, all of your code will be inside the Program.cs file. If you are a seasoned .NET developer, it\u2019s easy to understand the preceding code, and you\u2019ll find it similar to some of the things you\u2019ve always used with the controller approach.<br \/>\n\u9996\u5148\uff0c\u4f7f\u7528\u6700\u5c0f API \u65b9\u6cd5\uff0c\u60a8\u7684\u6240\u6709\u4ee3\u7801\u90fd\u5c06\u4f4d\u4e8e Program.cs \u6587\u4ef6\u4e2d\u3002\u5982\u679c\u60a8\u662f\u4e00\u4f4d\u7ecf\u9a8c\u4e30\u5bcc\u7684 .NET \u5f00\u53d1\u4eba\u5458\uff0c\u5219\u5f88\u5bb9\u6613\u7406\u89e3\u524d\u9762\u7684\u4ee3\u7801\uff0c\u5e76\u4e14\u60a8\u4f1a\u53d1\u73b0\u5b83\u7c7b\u4f3c\u4e8e\u60a8\u4e00\u76f4\u4f7f\u7528\u63a7\u5236\u5668\u65b9\u6cd5\u7684\u4e00\u4e9b\u5185\u5bb9\u3002<\/p>\n<p>At the end of the day, it\u2019s another way to write an API, but it\u2019s based on ASP.NET Core.<br \/>\n\u5f52\u6839\u7ed3\u5e95\uff0c\u8fd9\u662f\u7f16\u5199 API \u7684\u53e6\u4e00\u79cd\u65b9\u5f0f\uff0c\u4f46\u5b83\u57fa\u4e8e ASP.NET Core\u3002<\/p>\n<p>However, if you are new to ASP.NET, this single file approach is easy to understand. It\u2019s easy to understand how to extend the code in the template and add more features to this API.<br \/>\n\u4f46\u662f\uff0c\u5982\u679c\u60a8\u4e0d\u719f\u6089 ASP.NET\uff0c\u8fd9\u79cd\u5355\u6587\u4ef6\u65b9\u6cd5\u5f88\u5bb9\u6613\u7406\u89e3\u3002\u5f88\u5bb9\u6613\u7406\u89e3\u5982\u4f55\u6269\u5c55\u6a21\u677f\u4e2d\u7684\u4ee3\u7801\u5e76\u5411\u6b64 API \u6dfb\u52a0\u66f4\u591a\u529f\u80fd\u3002<\/p>\n<p>Don\u2019t forget that minimal means that it contains the minimum set of components needed to build an HTTP API but it doesn\u2019t mean that the application you are going to build will be simple. It will require a good design like any other .NET application.<br \/>\n\u4e0d\u8981\u5fd8\u8bb0\uff0cminimal \u610f\u5473\u7740\u5b83\u5305\u542b\u6784\u5efa HTTP API \u6240\u9700\u7684\u6700\u5c11\u7ec4\u4ef6\u96c6\uff0c\u4f46\u8fd9\u5e76\u4e0d\u610f\u5473\u7740\u60a8\u8981\u6784\u5efa\u7684\u5e94\u7528\u7a0b\u5e8f\u4f1a\u5f88\u7b80\u5355\u3002\u4e0e\u4efb\u4f55\u5176\u4ed6 .NET \u5e94\u7528\u7a0b\u5e8f\u4e00\u6837\uff0c\u5b83\u9700\u8981\u826f\u597d\u7684\u8bbe\u8ba1\u3002<\/p>\n<p>As a final point, the minimal API approach is not a replacement for the MVC approach. It\u2019s just another way to write the same thing.<br \/>\n\u6700\u540e\u4e00\u70b9\uff0c\u6700\u5c0f API \u65b9\u6cd5\u4e0d\u80fd\u66ff\u4ee3 MVC \u65b9\u6cd5\u3002\u8fd9\u53ea\u662f\u53e6\u4e00\u79cd\u5199\u540c\u6837\u4e1c\u897f\u7684\u65b9\u6cd5\u3002<\/p>\n<p>Let\u2019s go back to the code.<br \/>\n\u8ba9\u6211\u4eec\u56de\u5230\u4ee3\u7801\u3002<\/p>\n<p>Even the template of the minimal API uses the new approach of .NET 6 web applications: a top-level statement.<br \/>\n\u5373\u4f7f\u662f\u6700\u5c0f API \u7684\u6a21\u677f\u4e5f\u4f7f\u7528 .NET 6 Web \u5e94\u7528\u7a0b\u5e8f\u7684\u65b0\u65b9\u6cd5\uff1a\u9876\u7ea7\u8bed\u53e5\u3002<\/p>\n<p>It means that the project has a Program.cs file only instead of using two files to configure an application.<br \/>\n\u8fd9\u610f\u5473\u7740\u9879\u76ee\u53ea\u6709\u4e00\u4e2a Program.cs \u6587\u4ef6\uff0c\u800c\u4e0d\u662f\u4f7f\u7528\u4e24\u4e2a\u6587\u4ef6\u6765\u914d\u7f6e\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>If you don\u2019t like this style of coding, you can convert your application to the old template for ASP.NET Core 3.x\/5. This approach still continues to work in .NET as well.<br \/>\n\u5982\u679c\u60a8\u4e0d\u559c\u6b22\u8fd9\u79cd\u7f16\u7801\u6837\u5f0f\uff0c\u53ef\u4ee5\u5c06\u5e94\u7528\u7a0b\u5e8f\u8f6c\u6362\u4e3a ASP.NET Core 3.x\/5 \u7684\u65e7\u6a21\u677f\u3002\u6b64\u65b9\u6cd5\u5728 .NET \u4e2d\u4e5f\u5c06\u7ee7\u7eed\u6709\u6548\u3002<\/p>\n<p>Important note : We can find more information about the .NET 6 top-level statements template at <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/tutorials\/top-level-templates\">https:\/\/docs.microsoft.com\/dotnet\/core\/tutorials\/top-level-templates<\/a>.<br \/>\n\u91cd\u8981\u63d0\u793a : \u6211\u4eec\u53ef\u4ee5\u5728 <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/tutorials\/top-level-templates\">https:\/\/docs.microsoft.com\/dotnet\/core\/tutorials\/top-level-templates<\/a> \u4e2d\u627e\u5230\u6709\u5173 .NET 6 \u9876\u7ea7\u8bed\u53e5\u6a21\u677f\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>By default, the new template includes support for the OpenAPI Specification and more specifically, Swagger.<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u65b0\u6a21\u677f\u5305\u62ec\u5bf9 OpenAPI \u89c4\u8303\u7684\u652f\u6301\uff0c\u66f4\u5177\u4f53\u5730\u8bf4\uff0c\u5305\u62ec\u5bf9 Swagger \u7684\u652f\u6301\u3002<\/p>\n<p>Let\u2019s say that we have our documentation and playground for the endpoints working out of the box without any additional configuration needed.<br \/>\n\u5047\u8bbe\u6211\u4eec\u6709\u73b0\u6210\u7684\u7aef\u70b9\u6587\u6863\u548c Playground\uff0c\u65e0\u9700\u4efb\u4f55\u989d\u5916\u7684\u914d\u7f6e\u3002<\/p>\n<p>You can see the default configuration for Swagger in the following two lines of codes:<br \/>\n\u60a8\u53ef\u4ee5\u5728\u4ee5\u4e0b\u4e24\u884c\u4ee3\u7801\u4e2d\u770b\u5230 Swagger \u7684\u9ed8\u8ba4\u914d\u7f6e\uff1a<\/p>\n<pre><code>builder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen();<\/code><\/pre>\n<p>Very often, you don\u2019t want to expose Swagger and all the endpoints to the production or staging environments. The default template enables Swagger out of the box only in the development environment with the following lines of code:<br \/>\n\u901a\u5e38\uff0c\u60a8\u4e0d\u5e0c\u671b\u5c06 Swagger \u548c\u6240\u6709\u7ec8\u7aef\u8282\u70b9\u516c\u5f00\u7ed9\u751f\u4ea7\u6216\u6682\u5b58\u73af\u5883\u3002\u9ed8\u8ba4\u6a21\u677f\u4ec5\u5728\u5f00\u53d1\u73af\u5883\u4e2d\u542f\u7528\u5f00\u7bb1\u5373\u7528\u7684 Swagger\uff0c\u4ee3\u7801\u884c\u5982\u4e0b\uff1a<\/p>\n<pre><code>if (app.Environment.IsDevelopment())\n{\n         app.UseSwagger();\n         app.UseSwaggerUI();\n}<\/code><\/pre>\n<p>If the application is running on the dev elopment environment, you must also include the Swagger documentation, but otherwise not.<br \/>\n\u5982\u679c\u5e94\u7528\u7a0b\u5e8f\u5728 dev elopment \u73af\u5883\u4e2d\u8fd0\u884c\uff0c\u5219\u8fd8\u5fc5\u987b\u5305\u542b Swagger \u6587\u6863\uff0c\u5426\u5219\u4e0d\u5f97\u5305\u542b\u3002<\/p>\n<p>Note : We\u2019ll talk in detail about Swagger in Chapter 3, Working with Minimal APIs.<br \/>\n\u6ce8\u610f:\u6211\u4eec\u5c06\u5728\u7b2c 3 \u7ae0 \u4f7f\u7528\u6700\u5c0f API \u4e2d\u8be6\u7ec6\u8ba8\u8bba Swagger\u3002<\/p>\n<p>In these last few lines of code in the template, we are introducing another generic concept for .NET 6 web applications: environments.<br \/>\n\u5728\u6a21\u677f\u7684\u6700\u540e\u51e0\u884c\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u5f15\u5165\u4e86 .NET 6 Web \u5e94\u7528\u7a0b\u5e8f\u7684\u53e6\u4e00\u4e2a\u901a\u7528\u6982\u5ff5\uff1a\u73af\u5883\u3002<\/p>\n<p>Typically, when we develop a professional application, there are a lot of phases through which an application is developed, tested, and finally published to the end users.<br \/>\n\u901a\u5e38\uff0c\u5f53\u6211\u4eec\u5f00\u53d1\u4e13\u4e1a\u5e94\u7528\u7a0b\u5e8f\u65f6\uff0c\u5e94\u7528\u7a0b\u5e8f\u4f1a\u7ecf\u5386\u8bb8\u591a\u5f00\u53d1\u3001\u6d4b\u8bd5\u5e76\u6700\u7ec8\u53d1\u5e03\u7ed9\u6700\u7ec8\u7528\u6237\u7684\u9636\u6bb5\u3002<\/p>\n<p>By convention, these phases are regulated and called development, staging, and production. As developers, we might like to change the behavior of the application based on the current environment.<br \/>\n\u6309\u7167\u60ef\u4f8b\uff0c\u8fd9\u4e9b\u9636\u6bb5\u53d7\u5230\u76d1\u7ba1\uff0c\u79f0\u4e3a\u5f00\u53d1\u3001\u6682\u5b58\u548c\u751f\u4ea7\u3002\u4f5c\u4e3a\u5f00\u53d1\u4eba\u5458\uff0c\u6211\u4eec\u53ef\u80fd\u5e0c\u671b\u6839\u636e\u5f53\u524d\u73af\u5883\u66f4\u6539\u5e94\u7528\u7a0b\u5e8f\u7684\u884c\u4e3a\u3002<\/p>\n<p>There are several ways to access this information but the typical way to retrieve the actual environment in modern .NET 6 applications is to use environment variables. You can access the environment variables directly from the app variable in the Program.cs file.<br \/>\n\u6709\u591a\u79cd\u65b9\u6cd5\u53ef\u4ee5\u8bbf\u95ee\u6b64\u4fe1\u606f\uff0c\u4f46\u5728\u73b0\u4ee3 .NET 6 \u5e94\u7528\u7a0b\u5e8f\u4e2d\u68c0\u7d22\u5b9e\u9645\u73af\u5883\u7684\u5178\u578b\u65b9\u6cd5\u662f\u4f7f\u7528\u73af\u5883\u53d8\u91cf\u3002\u60a8\u53ef\u4ee5\u76f4\u63a5\u4ece Program.cs \u6587\u4ef6\u4e2d\u7684 app \u53d8\u91cf\u8bbf\u95ee\u73af\u5883\u53d8\u91cf\u3002<\/p>\n<p>The following code block shows how to retrieve all the information about the environments directly from the startup point of the application:<br \/>\n\u4ee5\u4e0b\u4ee3\u7801\u5757\u6f14\u793a\u5982\u4f55\u76f4\u63a5\u4ece\u5e94\u7528\u7a0b\u5e8f\u7684\u542f\u52a8\u70b9\u68c0\u7d22\u6709\u5173\u73af\u5883\u7684\u6240\u6709\u4fe1\u606f\uff1a<\/p>\n<pre><code>if (app.Environment.IsDevelopment())\n{\n           \/\/ your code here\n}\nif (app.Environment.IsStaging())\n{\n           \/\/ your code here\n}\nif (app.Environment.IsProduction())\n{\n           \/\/ your code here\n}<\/code><\/pre>\n<p>In many cases, you can define additional environments, and you can check your custom environment with the following code:<br \/>\n\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b\uff0c\u60a8\u53ef\u4ee5\u5b9a\u4e49\u5176\u4ed6\u73af\u5883\uff0c\u5e76\u4e14\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u4ee3\u7801\u68c0\u67e5\u60a8\u7684\u81ea\u5b9a\u4e49\u73af\u5883\uff1a<\/p>\n<pre><code>if (app.Environment.IsEnvironment(&quot;TestEnvironment&quot;))\n{\n           \/\/ your code here\n}<\/code><\/pre>\n<p>To define routes and handlers in minimal APIs, we use the MapGet, MapPost, MapPut, and MapDelete methods. If you are used to using HTTP verbs, you will have noticed that the verb Patch is not present, but you can define any set of verbs using MapMethods.<br \/>\n\u8981\u5728\u6700\u5c0f\u7684 API \u4e2d\u5b9a\u4e49\u8def\u7531\u548c\u5904\u7406\u7a0b\u5e8f\uff0c\u6211\u4eec\u4f7f\u7528 MapGet\u3001MapPost\u3001MapPut \u548c MapDelete \u65b9\u6cd5\u3002\u5982\u679c\u60a8\u4e60\u60ef\u4f7f\u7528 HTTP \u52a8\u8bcd\uff0c\u60a8\u4f1a\u6ce8\u610f\u5230\u52a8\u8bcd Patch \u4e0d\u5b58\u5728\uff0c\u4f46\u60a8\u53ef\u4ee5\u4f7f\u7528 MapMethods \u5b9a\u4e49\u4efb\u4f55\u52a8\u8bcd\u96c6\u3002<\/p>\n<p>For instance, if you want to create a new endpoint to post some data to the API, you can write the following code:<\/p>\n<p>\u4f8b\u5982\uff0c\u5982\u679c\u8981\u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u7ec8\u7aef\u8282\u70b9\u4ee5\u5c06\u4e00\u4e9b\u6570\u636e\u53d1\u5e03\u5230 API\uff0c\u5219\u53ef\u4ee5\u7f16\u5199\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/weatherforecast&quot;, async (WeatherForecast \n    model, IWeatherService repo) =&gt;\n{\n         \/\/ ...\n});<\/code><\/pre>\n<p>As you can see in the short preceding code, it\u2019s very easy to add a new endpoint with the new minimal API template.<br \/>\n\u6b63\u5982\u60a8\u5728\u524d\u9762\u7684\u7b80\u77ed\u4ee3\u7801\u4e2d\u6240\u770b\u5230\u7684\uff0c\u4f7f\u7528\u65b0\u7684\u6700\u5c0f API \u6a21\u677f\u6dfb\u52a0\u65b0\u7ec8\u7aef\u8282\u70b9\u975e\u5e38\u5bb9\u6613\u3002<\/p>\n<p>It was more difficult previously, especially for a new developer, to code a new endpoint with binding parameters and use dependency injection.<br \/>\n\u4ee5\u524d\uff0c\u4f7f\u7528\u7ed1\u5b9a\u53c2\u6570\u7f16\u5199\u65b0\u7ec8\u7aef\u8282\u70b9\u5e76\u4f7f\u7528\u4f9d\u8d56\u9879\u6ce8\u5165\u66f4\u52a0\u56f0\u96be\uff0c\u5c24\u5176\u662f\u5bf9\u4e8e\u65b0\u5f00\u53d1\u4eba\u5458\u800c\u8a00\u3002<\/p>\n<p>Important note : We\u2019ll talk in detail about routing in Chapter 2, Exploring Minimal APIs and Their Advantages, and about dependency injection in Chapter 4, Dependency Injection in a Minimal API Project.<br \/>\n\u91cd\u8981\u63d0\u793a : \u6211\u4eec\u5c06\u5728\u7b2c 2 \u7ae0 \u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf\u4e2d\u8be6\u7ec6\u8ba8\u8bba\u8def\u7531\uff0c\u5e76\u5728\u7b2c 4 \u7ae0 \u6700\u5c0f API \u9879\u76ee\u4e2d\u7684\u4f9d\u8d56\u6ce8\u5165\u3002<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>In this chapter, we first started with a brief history of minimal APIs. Next, we saw how to create a project with Visual Studio 2022 as well as Visual Studio Code and the .NET CLI. After that, we examined the structure of the new template, how to access different environments, and how to start interacting with REST endpoints.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u9996\u5148\u4ece\u6700\u5c0f API \u7684\u7b80\u8981\u5386\u53f2\u5f00\u59cb\u3002\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u5982\u4f55\u4f7f\u7528 Visual Studio 2022 \u4ee5\u53ca Visual Studio Code \u548c .NET CLI \u521b\u5efa\u9879\u76ee\u3002\u4e4b\u540e\uff0c\u6211\u4eec\u68c0\u67e5\u4e86\u65b0\u6a21\u677f\u7684\u7ed3\u6784\u3001\u5982\u4f55\u8bbf\u95ee\u4e0d\u540c\u7684\u73af\u5883\u4ee5\u53ca\u5982\u4f55\u5f00\u59cb\u4e0e REST \u7aef\u70b9\u4ea4\u4e92\u3002<\/p>\n<p>In the next chapter, we will see how to bind parameters, the new routing configuration, and how to customize a response.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u7ed1\u5b9a\u53c2\u6570\u3001\u65b0\u7684\u8def\u7531\u914d\u7f6e\u4ee5\u53ca\u5982\u4f55\u81ea\u5b9a\u4e49\u54cd\u5e94\u3002<\/p>\n<h1>2 Exploring Minimal APIs and Their Advantages<\/h1>\n<p>\u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf<\/p>\n<p>In this chapter of the book, we will introduce some of the basic themes related to minimal APIs in .NET 6.0, showing how they differ from the controller-based web APIs that we have written in the previous version of .NET. We will also try to underline both the pros and the cons of this new approach of writing APIs.<br \/>\n\u5728\u672c\u4e66\u7684\u8fd9\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4e0e .NET 6.0 \u4e2d\u7684\u6700\u5c0f API \u76f8\u5173\u7684\u4e00\u4e9b\u57fa\u672c\u4e3b\u9898\uff0c\u5c55\u793a\u5b83\u4eec\u4e0e\u6211\u4eec\u5728\u65e9\u671f\u7248\u672c\u7684 .NET \u4e2d\u7f16\u5199\u7684\u57fa\u4e8e\u63a7\u5236\u5668\u7684 Web API \u6709\u4f55\u4e0d\u540c\u3002\u6211\u4eec\u8fd8\u5c06\u5c1d\u8bd5\u5f3a\u8c03\u8fd9\u79cd\u7f16\u5199 API \u7684\u65b0\u65b9\u6cd5\u7684\u4f18\u7f3a\u70b9\u3002<\/p>\n<p>In this chapter, we will be covering the following topics:<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u4e3b\u9898\uff1a<\/p>\n<p>\u2022  Routing<br \/>\n\u2022  Parameter binding<br \/>\n\u2022  Exploring responses<br \/>\n\u2022  Controlling serialization<br \/>\n\u2022  Architecting a minimal API project<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>To follow the descriptions in this chapter, you will need to create an ASP.NET Core 6.0 Web API application. You can either use one of the following options:<br \/>\n\u8981\u6309\u7167\u672c\u7ae0\u4e2d\u7684\u63cf\u8ff0\u8fdb\u884c\u4f5c\uff0c\u60a8\u9700\u8981\u521b\u5efa\u4e00\u4e2a ASP.NET Core 6.0 Web API \u5e94\u7528\u7a0b\u5e8f\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u9009\u9879\u4e4b\u4e00\uff1a<\/p>\n<p>\u2022  Option 1: Click on the New | Project command in the File menu of Visual Studio 2022 \u2013 then, choose the ASP.NET Core Web API template. Select a name and the working directory in the wizard and be sure to uncheck the Use controllers (uncheck to use minimal APIs) option in the next step.<br \/>\n\u9009\u9879 1\uff1a\u70b9\u51fb\u65b0\u5efa |Visual Studio 2022 \u7684 File \uff08\u6587\u4ef6\uff09 \u83dc\u5355\u4e2d\u7684 Project \uff08\u9879\u76ee\uff09 \u547d\u4ee4\uff0c\u7136\u540e\u9009\u62e9 ASP.NET Core Web API \u6a21\u677f\u3002\u5728\u5411\u5bfc\u4e2d\u9009\u62e9\u4e00\u4e2a\u540d\u79f0\u548c\u5de5\u4f5c\u76ee\u5f55\uff0c\u5e76\u786e\u4fdd\u5728\u4e0b\u4e00\u6b65\u4e2d\u53d6\u6d88\u9009\u4e2d Use controllers \uff08\u4e0d\u9009\u4e2d\u4f7f\u7528\u6700\u5c11\u7684 API\uff09 \u9009\u9879\u3002<\/p>\n<p>\u2022  Option 2: Open your console, shell, or Bash terminal, and change to your working directory. Use the following command to create a new Web API application:<br \/>\n\u9009\u9879 2\uff1a\u6253\u5f00\u60a8\u7684\u63a7\u5236\u53f0\u3001shell \u6216 Bash \u7ec8\u7aef\uff0c\u7136\u540e\u5207\u6362\u5230\u60a8\u7684\u5de5\u4f5c\u76ee\u5f55\u3002\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u521b\u5efa\u65b0\u7684 Web API \u5e94\u7528\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>dotnet new webapi -minimal -o Chapter02<\/code><\/pre>\n<p>Now, open the project in Visual Studio by double-clicking the project file, or in Visual Studio Code, by typing the following command in the already open console:<br \/>\n\u73b0\u5728\uff0c\u901a\u8fc7\u5728 Visual Studio \u4e2d\u53cc\u51fb\u9879\u76ee\u6587\u4ef6\u6216\u5728 Visual Studio Code \u4e2d\u901a\u8fc7\u5728\u5df2\u6253\u5f00\u7684\u63a7\u5236\u53f0\u4e2d\u952e\u5165\u4ee5\u4e0b\u547d\u4ee4\u6765\u6253\u5f00\u9879\u76ee\uff1a<\/p>\n<pre><code>cd Chapter02\ncode.<\/code><\/pre>\n<p>Finally, you can safely remove all the code related to the WeatherForecast sample, as we don\u2019t need it for this chapter.<br \/>\n\u6700\u540e\uff0c\u60a8\u53ef\u4ee5\u5b89\u5168\u5730\u5220\u9664\u4e0e WeatherForecast \u793a\u4f8b\u76f8\u5173\u7684\u6240\u6709\u4ee3\u7801\uff0c\u56e0\u4e3a\u672c\u7ae0\u4e0d\u9700\u8981\u5b83\u3002<\/p>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter02\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter02<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter02\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter02<\/a>\u3002<\/p>\n<p>Routing<br \/>\n\u8def\u7531<\/p>\n<p>According to the official Microsoft documentation available at <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing<\/a>, the following definition is given for routing:<br \/>\n\u6839\u636e <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing<\/a> \u4e0a\u63d0\u4f9b\u7684\u5b98\u65b9 Microsoft \u6587\u6863\uff0c\u8def\u7531\u7ed9\u51fa\u4e86\u4ee5\u4e0b\u5b9a\u4e49\uff1a<\/p>\n<p>Routing is responsible for matching incoming HTTP requests and dispatching those requests to the app\u2019s executable endpoints. Endpoints are the app\u2019s units of executable request-handling code. Endpoints are defined in the app and configured when the app starts. The endpoint matching process can extract values from the request\u2019s URL and provide those values for request processing. Using endpoint information from the app, routing is also able to generate URLs that map to endpoints.<br \/>\n\u8def\u7531\u8d1f\u8d23\u5339\u914d\u4f20\u5165\u7684 HTTP \u8bf7\u6c42\u5e76\u5c06\u8fd9\u4e9b\u8bf7\u6c42\u5206\u6d3e\u5230\u5e94\u7528\u7a0b\u5e8f\u7684\u53ef\u6267\u884c\u7aef\u70b9\u3002\u7aef\u70b9\u662f\u5e94\u7528\u7a0b\u5e8f\u7684\u53ef\u6267\u884c\u8bf7\u6c42\u5904\u7406\u4ee3\u7801\u5355\u5143\u3002\u7ec8\u7aef\u8282\u70b9\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5b9a\u4e49\uff0c\u5e76\u5728\u5e94\u7528\u7a0b\u5e8f\u542f\u52a8\u65f6\u8fdb\u884c\u914d\u7f6e\u3002\u7ec8\u7aef\u8282\u70b9\u5339\u914d\u8fc7\u7a0b\u53ef\u4ee5\u4ece\u8bf7\u6c42\u7684 URL \u4e2d\u63d0\u53d6\u503c\uff0c\u5e76\u63d0\u4f9b\u8fd9\u4e9b\u503c\u4ee5\u4f9b\u8bf7\u6c42\u5904\u7406\u3002\u4f7f\u7528\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u7ec8\u7aef\u8282\u70b9\u4fe1\u606f\uff0c\u8def\u7531\u8fd8\u80fd\u591f\u751f\u6210\u6620\u5c04\u5230\u7ec8\u7aef\u8282\u70b9\u7684 URL\u3002<\/p>\n<p>In controller-based web APIs, routing is defined via the UseEndpoints() method in Startup.cs or using data annotations such as Route, HttpGet, HttpPost, HttpPut, HttpPatch, and HttpDelete right over the action methods.<br \/>\n\u5728\u57fa\u4e8e\u63a7\u5236\u5668\u7684 Web API \u4e2d\uff0c\u8def\u7531\u662f\u901a\u8fc7 Startup.cs \u4e2d\u7684 UseEndpoints\uff08\uff09 \u65b9\u6cd5\u5b9a\u4e49\u7684\uff0c\u6216\u8005\u4f7f\u7528\u4f5c\u65b9\u6cd5\u4e0a\u7684\u6570\u636e\u6ce8\u91ca\uff08\u5982 Route\u3001HttpGet\u3001HttpPost\u3001HttpPut\u3001HttpPatch \u548c HttpDelete\uff09\u6765\u5b9a\u4e49\u3002<\/p>\n<p>As mentioned in Chapter 1, Introduction to Minimal APIs in minimal APIs, we define the route patterns using the Map<em> methods of the WebApplication object. Here\u2019s an example:<br \/>\n\u5982\u7b2c 1 \u7ae0 \u6700\u5c0f API \u7b80\u4ecb\u4e2d\u6240\u8ff0\uff0c\u5728\u6700\u5c0f API \u4e2d\uff0c\u6211\u4eec\u4f7f\u7528 WebApplication \u5bf9\u8c61\u7684 Map<\/em> \u65b9\u6cd5\u5b9a\u4e49\u8def\u7531\u6a21\u5f0f\u3002\u4e0b\u9762\u662f\u4e00\u4e2a\u793a\u4f8b\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/hello-get&quot;, () =&gt; &quot;[GET] Hello World!&quot;);\napp.MapPost(&quot;\/hello-post&quot;, () =&gt; &quot;[POST] Hello World!&quot;);\napp.MapPut(&quot;\/hello-put&quot;, () =&gt; &quot;[PUT] Hello World!&quot;);\napp.MapDelete(&quot;\/hello-delete&quot;, () =&gt; &quot;[DELETE] Hello\n                World!&quot;);<\/code><\/pre>\n<p>In this code, we have defined four endpoints, each with a different routing and method. Of course, we can use the same route pattern with different HTTP verbs.<br \/>\n\u5728\u6b64\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u5b9a\u4e49\u4e86\u56db\u4e2a\u7ec8\u7aef\u8282\u70b9\uff0c\u6bcf\u4e2a\u7ec8\u7aef\u8282\u70b9\u90fd\u6709\u4e0d\u540c\u7684\u8def\u7531\u548c\u65b9\u6cd5\u3002\u5f53\u7136\uff0c\u6211\u4eec\u53ef\u4ee5\u5bf9\u4e0d\u540c\u7684 HTTP \u52a8\u8bcd\u4f7f\u7528\u76f8\u540c\u7684\u8def\u7531\u6a21\u5f0f\u3002<\/p>\n<p>Note : As soon as we add an endpoint to our application (for example, using MapGet()), UseRouting() is automatically added at the start of the middleware pipeline and UseEndpoints() at the end of the pipeline.<br \/>\n\u6ce8\u610f : \u4e00\u65e6\u6211\u4eec\u5c06\u7aef\u70b9\u6dfb\u52a0\u5230\u5e94\u7528\u7a0b\u5e8f\uff08\u4f8b\u5982\uff0c\u4f7f\u7528 MapGet\uff08\uff09\uff09\uff0cUseRouting\uff08\uff09 \u5c31\u4f1a\u81ea\u52a8\u6dfb\u52a0\u5230\u4e2d\u95f4\u4ef6\u7ba1\u9053\u7684\u5f00\u5934\uff0cUseEndpoints\uff08\uff09 \u4f1a\u81ea\u52a8\u6dfb\u52a0\u5230\u7ba1\u9053\u7684\u672b\u5c3e\u3002<\/p>\n<p>As shown here, ASP.NET Core 6.0 provides Map<em> methods for the most common HTTP verbs. If we need to use other verbs, we can use the generic MapMethods:<br \/>\n\u5982\u6b64\u5904\u6240\u793a\uff0cASP.NET Core 6.0 \u4e3a\u6700\u5e38\u89c1\u7684 HTTP \u52a8\u8bcd\u63d0\u4f9b\u4e86 Map<\/em> \u65b9\u6cd5\u3002\u5982\u679c\u6211\u4eec\u9700\u8981\u4f7f\u7528\u5176\u4ed6\u52a8\u8bcd\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u901a\u7528\u7684 MapMethods\uff1a<\/p>\n<pre><code>app.MapMethods(&quot;\/hello-patch&quot;, new[] { HttpMethods.Patch }, \n    () =&gt; &quot;[PATCH] Hello World!&quot;);\napp.MapMethods(&quot;\/hello-head&quot;, new[] { HttpMethods.Head }, \n    () =&gt; &quot;[HEAD] Hello World!&quot;);\napp.MapMethods(&quot;\/hello-options&quot;, new[] { \n    HttpMethods.Options }, () =&gt; &quot;[OPTIONS] Hello World!&quot;);<\/code><\/pre>\n<p>In the following sections, we will show in detail how routing works effectively and how we can control its behavior.<br \/>\n\u5728\u4ee5\u4e0b\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06\u8be6\u7ec6\u5c55\u793a\u8def\u7531\u5982\u4f55\u6709\u6548\u5de5\u4f5c\u4ee5\u53ca\u5982\u4f55\u63a7\u5236\u5176\u884c\u4e3a\u3002<\/p>\n<p>Route handlers<br \/>\n\u8def\u7531\u5904\u7406\u7a0b\u5e8f<\/p>\n<p>Methods that execute when a route URL matches (according to parameters and constraints, as described in the following sections) are called route handlers. Route handlers can be a lambda expression, a local function, an instance method, or a static method, whether synchronous or asynchronous:<br \/>\n\u5f53\u8def\u7531 URL \u5339\u914d\u65f6\u6267\u884c\u7684\u65b9\u6cd5\uff08\u6839\u636e\u53c2\u6570\u548c\u7ea6\u675f\uff0c\u5982\u4ee5\u4e0b\u90e8\u5206\u6240\u8ff0\uff09\u79f0\u4e3a\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u3002\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u53ef\u4ee5\u662f lambda \u8868\u8fbe\u5f0f\u3001\u672c\u5730\u51fd\u6570\u3001\u5b9e\u4f8b\u65b9\u6cd5\u6216\u9759\u6001\u65b9\u6cd5\uff0c\u65e0\u8bba\u662f\u540c\u6b65\u65b9\u6cd5\u8fd8\u662f\u5f02\u6b65\u65b9\u6cd5\uff1a<\/p>\n<p>\u2022  Here\u2019s an example of a lambda expression (inline or using a variable):<br \/>\n\u4ee5\u4e0b\u662f lambda \u8868\u8fbe\u5f0f\u7684\u793a\u4f8b\uff08\u5185\u8054\u6216\u4f7f\u7528\u53d8\u91cf\uff09\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/hello-inline&quot;, () =&gt; &quot;[INLINE LAMBDA]\n\n             Hello World!&quot;);\n\nvar handler = () =&gt; &quot;[LAMBDA VARIABLE] Hello World!&quot;;\n\napp.MapGet(&quot;\/hello&quot;, handler);<\/code><\/pre>\n<p>\u2022  Here\u2019s an example of a local function:<br \/>\n\u4e0b\u9762\u662f\u4e00\u4e2a\u672c\u5730\u51fd\u6570\u7684\u793a\u4f8b\uff1a<\/p>\n<pre><code>string Hello() =&gt; &quot;[LOCAL FUNCTION] Hello World!&quot;;\n\napp.MapGet(&quot;\/hello&quot;, Hello);<\/code><\/pre>\n<p>\u2022  The following is an example of an instance method:<br \/>\n\u4ee5\u4e0b\u662f\u5b9e\u4f8b\u65b9\u6cd5\u7684\u793a\u4f8b\uff1a<\/p>\n<pre><code>var handler = new HelloHandler();\n\napp.MapGet(&quot;\/hello&quot;, handler.Hello);\n\nclass HelloHandler\n\n{\n\n    public string Hello()\n\n      =&gt; &quot;[INSTANCE METHOD] Hello\n\n           World!&quot;;\n\n}<\/code><\/pre>\n<p>\u2022  Here, we can see an example of a static method:<br \/>\n\u5728\u8fd9\u91cc\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u4e00\u4e2a\u9759\u6001\u65b9\u6cd5\u7684\u793a\u4f8b\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/hello&quot;, HelloHandler.Hello);\n\nclass HelloHandler\n\n{\n\n    public static string Hello()\n\n      =&gt; &quot;[STATIC METHOD] Hello World!&quot;;\n\n}<\/code><\/pre>\n<p>Route parameters<br \/>\n\u8def\u7531\u53c2\u6570<\/p>\n<p>As with the previous versions of .NET, we can create route patterns with parameters that will be automatically captured by the handler:<br \/>\n\u4e0e\u4ee5\u524d\u7248\u672c\u7684 .NET \u4e00\u6837\uff0c\u6211\u4eec\u53ef\u4ee5\u521b\u5efa\u8def\u7531\u6a21\u5f0f\uff0c\u5176\u4e2d\u5305\u542b\u5904\u7406\u7a0b\u5e8f\u5c06\u81ea\u52a8\u6355\u83b7\u7684\u53c2\u6570\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/users\/{username}\/products\/{productId}&quot;, \n          (string username, int productId) \n         =&gt; $&quot;The Username is {username} and the product Id \n              is {productId}&quot;);<\/code><\/pre>\n<p>A route can contain an arbitrary number of parameters. When a request is made to this route, the parameters will be captured, parsed, and passed as arguments to the corresponding handler. In this way, the handler will always receive typed arguments (in the preceding sample, we are sure that the username is string and the product ID is int).<br \/>\n\u8def\u7531\u53ef\u4ee5\u5305\u542b\u4efb\u610f\u6570\u91cf\u7684\u53c2\u6570\u3002\u5f53\u5411\u6b64\u8def\u7531\u53d1\u51fa\u8bf7\u6c42\u65f6\uff0c\u53c2\u6570\u5c06\u88ab\u6355\u83b7\u3001\u89e3\u6790\u5e76\u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u7ed9\u76f8\u5e94\u7684\u5904\u7406\u7a0b\u5e8f\u3002\u8fd9\u6837\uff0c\u5904\u7406\u7a0b\u5e8f\u5c06\u59cb\u7ec8\u63a5\u6536\u7c7b\u578b\u5316\u53c2\u6570\uff08\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u786e\u4fdd username \u662f string\uff0c\u4ea7\u54c1 ID \u662f int\uff09\u3002<\/p>\n<p>If the route values cannot be casted to the specified types, then an exception of the BadHttpRequestException type will be thrown, and the API will respond with a 400 Bad Request message.<br \/>\n\u5982\u679c\u65e0\u6cd5\u5c06\u8def\u7531\u503c\u5f3a\u5236\u8f6c\u6362\u4e3a\u6307\u5b9a\u7c7b\u578b\uff0c\u5219\u5c06\u5f15\u53d1 BadHttpRequestException \u7c7b\u578b\u7684\u5f02\u5e38\uff0c\u5e76\u4e14 API \u5c06\u4ee5 400 Bad Request \u6d88\u606f\u8fdb\u884c\u54cd\u5e94\u3002<\/p>\n<p>Route constraints<br \/>\n\u8def\u7531\u7ea6\u675f<\/p>\n<p>Route constraints are used to restrict valid types for route parameters. Typical constraints allow us to specify that a parameter must be a number, a string, or a GUID. To specify a route constraint, we simply need to add a colon after the parameter name, then specify the constraint name:<br \/>\n\u8def\u7531\u7ea6\u675f\u7528\u4e8e\u9650\u5236\u8def\u7531\u53c2\u6570\u7684\u6709\u6548\u7c7b\u578b\u3002\u5178\u578b\u7ea6\u675f\u5141\u8bb8\u6211\u4eec\u6307\u5b9a\u53c2\u6570\u5fc5\u987b\u662f\u6570\u5b57\u3001\u5b57\u7b26\u4e32\u6216 GUID\u3002\u8981\u6307\u5b9a\u8def\u7531\u7ea6\u675f\uff0c\u6211\u4eec\u53ea\u9700\u8981\u5728\u53c2\u6570\u540d\u79f0\u540e\u6dfb\u52a0\u4e00\u4e2a\u5192\u53f7\uff0c\u7136\u540e\u6307\u5b9a\u7ea6\u675f\u540d\u79f0\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/users\/{id:int}&quot;, (int id) =&gt; $&quot;The user Id is \n                                            {id}&quot;);\napp.MapGet(&quot;\/users\/{id:guid}&quot;, (Guid id) =&gt; $&quot;The user Guid \n                                              is {id}&quot;);<\/code><\/pre>\n<p>Minimal APIs support all the route constraints that were already available in the previous versions of ASP.NET Core. You can find the full list of route constraints at the following link: <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing#route-constraint-reference\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing#route-constraint-reference<\/a>.<br \/>\n\u6700\u5c0f API \u652f\u6301\u4ee5\u524d\u7248\u672c\u7684 ASP.NET Core \u4e2d\u5df2\u7ecf\u63d0\u4f9b\u7684\u6240\u6709\u8def\u7531\u7ea6\u675f\u3002\u60a8\u53ef\u4ee5\u5728\u4ee5\u4e0b\u94fe\u63a5\u4e2d\u627e\u5230\u8def\u7531\u7ea6\u675f\u7684\u5b8c\u6574\u5217\u8868\uff1a<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing#route-constraint-reference\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/routing#route-constraint-reference<\/a>\u3002<\/p>\n<p>If, according to the constraints, no route matches the specified path, we don\u2019t get an exception. Instead we obtain a 404 Not Found message, because, in fact, if the constraints do not fit, the route itself isn\u2019t reachable. So, for example, in the following cases we get 404 responses:<br \/>\n\u5982\u679c\u6839\u636e\u7ea6\u675f\uff0c\u6ca1\u6709\u8def\u7531\u4e0e\u6307\u5b9a\u7684\u8def\u5f84\u5339\u914d\uff0c\u5219\u4e0d\u4f1a\u6536\u5230\u5f02\u5e38\u3002\u76f8\u53cd\uff0c\u6211\u4eec\u4f1a\u6536\u5230 404 Not Found \u6d88\u606f\uff0c\u56e0\u4e3a\u4e8b\u5b9e\u4e0a\uff0c\u5982\u679c\u7ea6\u675f\u4e0d\u5408\u9002\uff0c\u5219\u8def\u7531\u672c\u8eab\u65e0\u6cd5\u8bbf\u95ee\u3002\u56e0\u6b64\uff0c\u4f8b\u5982\uff0c\u5728\u4ee5\u4e0b\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u4f1a\u6536\u5230 404 \u4e2a\u54cd\u5e94\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0201.jpg\"><\/p>\n<p>Table 2.1 \u2013 Examples of an invalid path according to the route constraints<br \/>\n\u8868 2.1 \u2013 \u6839\u636e\u8def\u7531\u7ea6\u675f\u7684\u65e0\u6548\u8def\u5f84\u793a\u4f8b<\/p>\n<p>Every other argument in the handler that is not declared as a route constraint is expected, by default, in the query string. For example, see the following:<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5904\u7406\u7a0b\u5e8f\u4e2d\u672a\u58f0\u660e\u4e3a\u8def\u7531\u7ea6\u675f\u7684\u6240\u6709\u5176\u4ed6\u53c2\u6570\u90fd\u5e94\u5728\u67e5\u8be2\u5b57\u7b26\u4e32\u4e2d\u3002\u4f8b\u5982\uff0c\u8bf7\u53c2\u9605\u4ee5\u4e0b\u5185\u5bb9\uff1a<\/p>\n<pre><code>\/\/ Matches hello?name=Marco\napp.MapGet(&quot;\/hello&quot;, (string name) =&gt; $&quot;Hello, {name}!&quot;); <\/code><\/pre>\n<p>In the next section, Parameter binding, we\u2019ll go deeper into how to use binding to further customize routing by specifying, for example, where to search for routing arguments, how to change their names, and how to have optional route parameters.<br \/>\n\u5728\u4e0b\u4e00\u8282 \u53c2\u6570\u7ed1\u5b9a \u4e2d\uff0c\u6211\u4eec\u5c06\u66f4\u6df1\u5165\u5730\u4ecb\u7ecd\u5982\u4f55\u4f7f\u7528 binding \u8fdb\u4e00\u6b65\u81ea\u5b9a\u4e49\u8def\u7531\uff0c\u4f8b\u5982\uff0c\u6307\u5b9a\u5728\u4f55\u5904\u641c\u7d22\u8def\u7531\u53c2\u6570\u3001\u5982\u4f55\u66f4\u6539\u5176\u540d\u79f0\u4ee5\u53ca\u5982\u4f55\u62e5\u6709\u53ef\u9009\u7684\u8def\u7531\u53c2\u6570\u3002<\/p>\n<p>Parameter binding<br \/>\n\u53c2\u6570\u7ed1\u5b9a<\/p>\n<p>Parameter binding is the process that converts request data (i.e., URL paths, query strings, or the body) into strongly typed parameters that can be consumed by route handlers. ASP.NET Core minimal APIs support the following binding sources:<br \/>\n\u53c2\u6570\u7ed1\u5b9a\u662f\u5c06\u8bf7\u6c42\u6570\u636e\uff08\u5373 URL \u8def\u5f84\u3001\u67e5\u8be2\u5b57\u7b26\u4e32\u6216\u6b63\u6587\uff09\u8f6c\u6362\u4e3a\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u53ef\u4ee5\u4f7f\u7528\u7684\u5f3a\u7c7b\u578b\u53c2\u6570\u7684\u8fc7\u7a0b\u3002ASP.NET Core \u6700\u5c0f API \u652f\u6301\u4ee5\u4e0b\u7ed1\u5b9a\u6e90\uff1a<\/p>\n<p>\u2022  Route values<br \/>\n\u2022  Query strings<br \/>\n\u2022  Headers<br \/>\n\u2022  The body (as JSON, the only format supported by default)<br \/>\n\u2022  A service provider (dependency injection)<\/p>\n<p>We\u2019ll talk in detail about dependency injection in Chapter 4, Implementing Dependency Injection.<br \/>\n\u6211\u4eec\u5c06\u5728 \u7b2c 4 \u7ae0 \u5b9e\u73b0\u4f9d\u8d56\u6ce8\u5165 \u4e2d\u8be6\u7ec6\u8ba8\u8bba\u4f9d\u8d56\u6ce8\u5165\u3002<\/p>\n<p>As we\u2019ll see later in this chapter, if necessary, we can customize the way in which binding is performed for a particular input. Unfortunately, in the current version, binding from Form is not natively supported in minimal APIs. This means that, for example, IFormFile is not supported either.<br \/>\n\u6b63\u5982\u6211\u4eec\u5728\u672c\u7ae0\u540e\u9762\u770b\u5230\u7684\u90a3\u6837\uff0c\u5982\u6709\u5fc5\u8981\uff0c\u6211\u4eec\u53ef\u4ee5\u81ea\u5b9a\u4e49\u5bf9\u7279\u5b9a input \u6267\u884c\u7ed1\u5b9a\u7684\u65b9\u5f0f\u3002\u9057\u61be\u7684\u662f\uff0c\u5728\u5f53\u524d\u7248\u672c\u4e2d\uff0c\u6700\u5c0f\u7684 API \u672c\u8eab\u5e76\u4e0d\u652f\u6301\u4ece Form \u8fdb\u884c\u7ed1\u5b9a\u3002\u8fd9\u610f\u5473\u7740\uff0c\u4f8b\u5982\uff0cIFormFile \u4e5f\u4e0d\u53d7\u652f\u6301\u3002<\/p>\n<p>To better understand how parameter binding works, let\u2019s take a look at the following API:<br \/>\n\u4e3a\u4e86\u66f4\u597d\u5730\u7406\u89e3\u53c2\u6570\u7ed1\u5b9a\u7684\u5de5\u4f5c\u539f\u7406\uff0c\u6211\u4eec\u6765\u770b\u4e00\u4e0b\u4ee5\u4e0b API\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddScoped&lt;PeopleService&gt;();\nvar app = builder.Build();\napp.MapPut(&quot;\/people\/{id:int}&quot;, (int id, bool notify, Person \n             person, PeopleService peopleService) =&gt; { });\napp.Run();\npublic class PeopleService { }\npublic record class Person(string FirstName, string \n                           LastName);<\/code><\/pre>\n<p>Parameters that are passed to the handler are resolved in the following ways:<br \/>\n\u4f20\u9012\u7ed9\u5904\u7406\u7a0b\u5e8f\u7684\u53c2\u6570\u901a\u8fc7\u4ee5\u4e0b\u65b9\u5f0f\u89e3\u6790\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0202.jpg\"><\/p>\n<p>Table 2.2 \u2013 Parameter binding sources<br \/>\n\u8868 2.2 \u2013 \u53c2\u6570\u7ed1\u5b9a\u6e90<\/p>\n<p>As we can see, ASP.NET Core is able to automatically understand where to search for parameters for binding, based on the route pattern and the types of the parameters themselves. For example, a complex type such as the Person class is expected in the request body.<br \/>\n\u6b63\u5982\u6211\u4eec\u6240\u770b\u5230\u7684\uff0cASP.NET Core \u80fd\u591f\u6839\u636e\u8def\u7531\u6a21\u5f0f\u548c\u53c2\u6570\u672c\u8eab\u7684\u7c7b\u578b\uff0c\u81ea\u52a8\u7406\u89e3\u5728\u4f55\u5904\u641c\u7d22\u8981\u7ed1\u5b9a\u7684\u53c2\u6570\u3002\u4f8b\u5982\uff0c\u8bf7\u6c42\u6b63\u6587\u4e2d\u5e94\u5305\u542b\u590d\u6742\u7c7b\u578b\uff08\u5982 Person \u7c7b\uff09\u3002<\/p>\n<p>If needed, as in the previous versions of ASP.NET Core, we can use attributes to explicitly specify where parameters are bound from and, optionally, use different names for them. See the following endpoint:<br \/>\n\u5982\u679c\u9700\u8981\uff0c\u5c31\u50cf\u5728\u65e9\u671f\u7248\u672c\u7684 ASP.NET Core \u4e2d\u4e00\u6837\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u5c5e\u6027\u6765\u663e\u5f0f\u6307\u5b9a\u53c2\u6570\u7684\u7ed1\u5b9a\u4f4d\u7f6e\uff0c\u5e76\u53ef\u9009\u62e9\u4e3a\u5b83\u4eec\u4f7f\u7528\u4e0d\u540c\u7684\u540d\u79f0\u3002\u8bf7\u53c2\u9605\u4ee5\u4e0b\u7ec8\u7aef\u8282\u70b9\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/search&quot;, string q) =&gt; { });<\/code><\/pre>\n<p>The API can be invoked with \/search?q=text. However, using q as the name of the argument isn\u2019t a good idea, because its meaning is not self-explanatory. So, we can modify the handler using FromQueryAttribute:<br \/>\n\u53ef\u4ee5\u4f7f\u7528 \/search\uff1fq=text \u8c03\u7528 API\u3002\u4f46\u662f\uff0c\u4f7f\u7528 q \u4f5c\u4e3a\u53c2\u6570\u7684\u540d\u79f0\u5e76\u4e0d\u662f\u4e00\u4e2a\u597d\u4e3b\u610f\uff0c\u56e0\u4e3a\u5b83\u7684\u542b\u4e49\u4e0d\u8a00\u81ea\u660e\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 FromQueryAttribute \u4fee\u6539\u5904\u7406\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/search&quot;, ([FromQuery(Name = &quot;q&quot;)] string \n             searchText) =&gt; { });<\/code><\/pre>\n<p>In this way, the API still expects a query string parameter named q, but in the handler its value is now bound to the searchText argument.<br \/>\n\u8fd9\u6837\uff0cAPI \u4ecd\u7136\u9700\u8981\u540d\u4e3a q \u7684\u67e5\u8be2\u5b57\u7b26\u4e32\u53c2\u6570\uff0c\u4f46\u5728\u5904\u7406\u7a0b\u5e8f\u4e2d\uff0c\u5176\u503c\u73b0\u5728\u7ed1\u5b9a\u5230 searchText \u53c2\u6570\u3002<\/p>\n<p>Note : According to the standard, the GET, DELETE, HEAD, and OPTIONS HTTP options should never have a body. If, nevertheless, you want to use it, you need to explicitly add the [FromBody] attribute to the handler argument; otherwise, you\u2019ll get an InvalidOperationException error. However, keep in mind that this is a bad practice.<br \/>\n\u6ce8\u610f : \u6839\u636e\u8be5\u6807\u51c6\uff0cGET\u3001DELETE\u3001HEAD \u548c OPTIONS HTTP \u9009\u9879\u4e0d\u5e94\u6709\u6b63\u6587\u3002\u4f46\u662f\uff0c\u5982\u679c\u8981\u4f7f\u7528\u5b83\uff0c\u5219\u9700\u8981\u5c06 [FromBody] \u5c5e\u6027\u663e\u5f0f\u6dfb\u52a0\u5230 handler \u53c2\u6570;\u5426\u5219\uff0c\u60a8\u5c06\u6536\u5230 InvalidOperationException \u9519\u8bef\u3002\u4f46\u662f\uff0c\u8bf7\u8bb0\u4f4f\uff0c\u8fd9\u662f\u4e00\u79cd\u4e0d\u597d\u7684\u505a\u6cd5\u3002<\/p>\n<p>By default, all the parameters in route handlers are required. So, if, according to routing, ASP.NET Core finds a valid route, but not all the required parameters are provided, we will get an error. For example, let\u2019s look at the following method:<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u6240\u6709\u53c2\u6570\u90fd\u662f\u5fc5\u9700\u7684\u3002\u56e0\u6b64\uff0c\u5982\u679c\u6839\u636e\u8def\u7531\uff0cASP.NET Core \u627e\u5230\u4e86\u4e00\u4e2a\u6709\u6548\u7684\u8def\u7531\uff0c\u4f46\u672a\u63d0\u4f9b\u6240\u6709\u5fc5\u9700\u7684\u53c2\u6570\uff0c\u6211\u4eec\u5c06\u6536\u5230\u9519\u8bef\u3002\u4f8b\u5982\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u4e0b\u9762\u7684\u65b9\u6cd5\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/people&quot;, (int pageIndex, int itemsPerPage) =&gt; { });<\/code><\/pre>\n<p>If we call the endpoint without the pageIndex or itemsPerPage query string values, we will obtain a BadHttpRequestException error, and the response will be 400 Bad Request.<br \/>\n\u5982\u679c\u6211\u4eec\u5728\u6ca1\u6709 pageIndex \u6216 itemsPerPage \u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u7684\u60c5\u51b5\u4e0b\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\uff0c\u6211\u4eec\u5c06\u83b7\u5f97 BadHttpRequestException \u9519\u8bef\uff0c\u5e76\u4e14\u54cd\u5e94\u5c06\u4e3a 400 Bad Request\u3002<\/p>\n<p>To make the parameters optional, we just need to declare them as nullable or provide a default value. The latter case is the most common. However, if we adopt this solution, we cannot use a lambda expression for the handler. We need another approach, for example, a local function:<br \/>\n\u8981\u4f7f\u53c2\u6570\u6210\u4e3a\u53ef\u9009\u7684\uff0c\u6211\u4eec\u53ea\u9700\u8981\u5c06\u5b83\u4eec\u58f0\u660e\u4e3a nullable \u6216\u63d0\u4f9b\u9ed8\u8ba4\u503c\u3002\u540e\u4e00\u79cd\u60c5\u51b5\u662f\u6700\u5e38\u89c1\u7684\u3002\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u91c7\u7528\u6b64\u89e3\u51b3\u65b9\u6848\uff0c\u5219\u4e0d\u80fd\u5bf9\u5904\u7406\u7a0b\u5e8f\u4f7f\u7528 lambda \u8868\u8fbe\u5f0f\u3002\u6211\u4eec\u9700\u8981\u53e6\u4e00\u79cd\u65b9\u6cd5\uff0c\u4f8b\u5982\u672c\u5730\u51fd\u6570\uff1a<\/p>\n<pre><code>\/\/ This won&#039;t compile\n\/\/app.MapGet(&quot;\/people&quot;, (int pageIndex = 0, int \n                         itemsPerPage = 50) =&gt; { });\nstring SearchMethod(int pageIndex = 0, \n                    int itemsPerPage = 50) =&gt; $&quot;Sample \n                    result for page {pageIndex} getting \n                    {itemsPerPage} elements&quot;;\napp.MapGet(&quot;\/people&quot;, SearchMethod);<\/code><\/pre>\n<p>In this case, we are dealing with a query string, but the same rules apply to all the binding sources.<br \/>\n\u5728\u672c\u4f8b\u4e2d\uff0c\u6211\u4eec\u6b63\u5728\u5904\u7406\u67e5\u8be2\u5b57\u7b26\u4e32\uff0c\u4f46\u76f8\u540c\u7684\u89c4\u5219\u9002\u7528\u4e8e\u6240\u6709\u7ed1\u5b9a\u6e90\u3002<\/p>\n<p>Keep in mind that if we use nullable reference types (which are enabled by default in .NET 6.0 projects) and we have, for example, a string parameter that could be null, we need to declare it as nullable \u2013 otherwise, we\u2019ll get a BadHttpRequestException error again. The following example correctly defines the orderBy query string parameter as optional:<br \/>\n\u8bf7\u8bb0\u4f4f\uff0c\u5982\u679c\u6211\u4eec\u4f7f\u7528\u53ef\u4e3a null \u7684\u5f15\u7528\u7c7b\u578b\uff08\u5728 .NET 6.0 \u9879\u76ee\u4e2d\u9ed8\u8ba4\u542f\u7528\uff09\uff0c\u5e76\u4e14\u6211\u4eec\u6709\u4e00\u4e2a\u53ef\u80fd\u4e3a null \u7684\u5b57\u7b26\u4e32\u53c2\u6570\uff0c\u5219\u9700\u8981\u5c06\u5176\u58f0\u660e\u4e3a\u53ef\u4e3a null\uff0c\u5426\u5219\uff0c\u6211\u4eec\u5c06\u518d\u6b21\u6536\u5230 BadHttpRequestException \u9519\u8bef\u3002\u4ee5\u4e0b\u793a\u4f8b\u6b63\u786e\u5730\u5c06 orderBy \u67e5\u8be2\u5b57\u7b26\u4e32\u53c2\u6570\u5b9a\u4e49\u4e3a\u53ef\u9009\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/people&quot;, (string? orderBy) =&gt; $&quot;Results ordered by {orderBy}&quot;);<\/code><\/pre>\n<p>Special bindings<br \/>\n\u7279\u6b8a\u7ed1\u5b9a<\/p>\n<p>In controller-based web APIs, a controller that inherits from Microsoft.AspNetCore.Mvc.ControllerBase has access to some properties that allows it to get the context of the request and response: HttpContext, Request, Response, and User. In minimal APIs, we don\u2019t have a base class, but we can still access this information because it is treated as a special binding that is always available to any handler:<br \/>\n\u5728\u57fa\u4e8e\u63a7\u5236\u5668\u7684 Web API \u4e2d\uff0c\u4ece Microsoft.AspNetCore.Mvc.ControllerBase \u7ee7\u627f\u7684\u63a7\u5236\u5668\u6709\u6743\u8bbf\u95ee\u4e00\u4e9b\u5c5e\u6027\uff0c\u8fd9\u4e9b\u5c5e\u6027\u5141\u8bb8\u5b83\u83b7\u53d6\u8bf7\u6c42\u548c\u54cd\u5e94\u7684\u4e0a\u4e0b\u6587\uff1aHttpContext\u3001Request\u3001Response \u548c User\u3002\u5728\u6700\u5c0f\u7684 API \u4e2d\uff0c\u6211\u4eec\u6ca1\u6709\u57fa\u7c7b\uff0c\u4f46\u6211\u4eec\u4ecd\u7136\u53ef\u4ee5\u8bbf\u95ee\u6b64\u4fe1\u606f\uff0c\u56e0\u4e3a\u5b83\u88ab\u89c6\u4e3a\u4efb\u4f55\u5904\u7406\u7a0b\u5e8f\u59cb\u7ec8\u53ef\u7528\u7684\u7279\u6b8a\u7ed1\u5b9a\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/products&quot;, (HttpContext context, HttpRequest req, HttpResponse res, ClaimsPrincipal user) =&gt; { });<\/code><\/pre>\n<p>Tip : We can also access all these objects using the IHttpContextAccessor interface, as we did in the previous ASP.NET Core versions.<br \/>\n\u63d0\u793a : \u6211\u4eec\u8fd8\u53ef\u4ee5\u4f7f\u7528 IHttpContextAccessor \u63a5\u53e3\u8bbf\u95ee\u6240\u6709\u8fd9\u4e9b\u5bf9\u8c61\uff0c\u5c31\u50cf\u6211\u4eec\u5728\u4ee5\u524d\u7684 ASP.NET Core \u7248\u672c\u4e2d\u6240\u505a\u7684\u90a3\u6837\u3002<\/p>\n<p>Custom binding<br \/>\n\u81ea\u5b9a\u4e49\u7ed1\u5b9a<\/p>\n<p>In some cases, the default way in which parameter binding works isn\u2019t enough for our purpose. In minimal APIs, we don\u2019t have support for the IModelBinderProvider and IModelBinder interfaces, but we have two alternatives to implement custom model binding.<br \/>\n\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u53c2\u6570\u7ed1\u5b9a\u7684\u9ed8\u8ba4\u5de5\u4f5c\u65b9\u5f0f\u4e0d\u8db3\u4ee5\u6ee1\u8db3\u6211\u4eec\u7684\u76ee\u7684\u3002\u5728\u6700\u5c0f\u7684 API \u4e2d\uff0c\u6211\u4eec\u4e0d\u652f\u6301 IModelBinderProvider \u548c IModelBinder \u63a5\u53e3\uff0c\u4f46\u6211\u4eec\u6709\u4e24\u79cd\u5b9e\u73b0\u81ea\u5b9a\u4e49\u6a21\u578b\u7ed1\u5b9a\u7684\u65b9\u6cd5\u3002<\/p>\n<p>Important note : The IModelBinderProvider and IModelBinder interfaces in controller-based projects allow us to define the mapping between the request data and the application model. The default model binder provided by ASP.NET Core supports most of the common data types, but, if necessary, we can extend the system by creating our own providers. We can find more information at the following link: <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/mvc\/advanced\/custom-model-binding\">https:\/\/docs.microsoft.com\/aspnet\/core\/mvc\/advanced\/custom-model-binding<\/a>.<br \/>\n\u91cd\u8981\u63d0\u793a  : \u57fa\u4e8e\u63a7\u5236\u5668\u7684\u9879\u76ee\u4e2d\u7684 IModelBinderProvider \u548c IModelBinder \u63a5\u53e3\u5141\u8bb8\u6211\u4eec\u5b9a\u4e49\u8bf7\u6c42\u6570\u636e\u548c\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u4e4b\u95f4\u7684\u6620\u5c04\u3002ASP.NET Core \u63d0\u4f9b\u7684\u9ed8\u8ba4\u6a21\u578b Binder \u652f\u6301\u5927\u591a\u6570\u5e38\u89c1\u6570\u636e\u7c7b\u578b\uff0c\u4f46\u5982\u6709\u5fc5\u8981\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u521b\u5efa\u81ea\u5df1\u7684\u63d0\u4f9b\u7a0b\u5e8f\u6765\u6269\u5c55\u7cfb\u7edf\u3002\u6211\u4eec\u53ef\u4ee5\u5728\u4ee5\u4e0b\u94fe\u63a5\u4e2d\u627e\u5230\u66f4\u591a\u4fe1\u606f\uff1a<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/mvc\/advanced\/custom-model-binding\">https:\/\/docs.microsoft.com\/aspnet\/core\/mvc\/advanced\/custom-model-binding<\/a>\u3002<\/p>\n<p>If we want to bind a parameter that comes from a route, query string, or header to a custom type, we can add a static TryParse method to the type:<br \/>\n\u5982\u679c\u6211\u4eec\u60f3\u5c06\u6765\u81ea\u8def\u7531\u3001\u67e5\u8be2\u5b57\u7b26\u4e32\u6216\u6807\u5934\u7684\u53c2\u6570\u7ed1\u5b9a\u5230\u81ea\u5b9a\u4e49\u7c7b\u578b\uff0c\u6211\u4eec\u53ef\u4ee5\u5411\u8be5\u7c7b\u578b\u6dfb\u52a0\u9759\u6001 TryParse \u65b9\u6cd5\uff1a<\/p>\n<pre><code>\/\/ GET \/navigate?location=43.8427,7.8527\napp.MapGet(&quot;\/navigate&quot;, (Location location) =&gt; $&quot;Location: \n            {location.Latitude}, {location.Longitude}&quot;);\npublic class Location\n{\n    public double Latitude { get; set; }\n    public double Longitude { get; set; }\n    public static bool TryParse(string? value, \n      IFormatProvider? provider, out Location? location)\n    {\n          if (!string.IsNullOrWhiteSpace(value))\n          {\n               var values = value.Split(&#039;,&#039;, \n               StringSplitOptions.RemoveEmptyEntries);\n               if (values.Length == 2 &amp;&amp; double.\n                   TryParse(values[0],\n                   NumberStyles.AllowDecimalPoint, \n                   CultureInfo.InvariantCulture, \n                   out var latitude) &amp;&amp; double.\n                   TryParse(values[1], NumberStyles.\n                   AllowDecimalPoint, CultureInfo.\n                   InvariantCulture, out var longitude))\n               {\n                       location = new Location \n                       { Latitude = latitude, \n                       Longitude = longitude };\n                       return true;\n               }\n          }\n          location = null;\n          return false;\n    }\n}<\/code><\/pre>\n<p>In the TryParse method, we can try to split the input parameter and check whether it contains two decimal values: in this case, we parse the numbers to build the Location object and we return true. Otherwise, we return false because the Location object cannot be initialized.<br \/>\n\u5728 TryParse \u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u5c1d\u8bd5\u62c6\u5206\u8f93\u5165\u53c2\u6570\u5e76\u68c0\u67e5\u5b83\u662f\u5426\u5305\u542b\u4e24\u4e2a\u5341\u8fdb\u5236\u503c\uff1a\u5728\u672c\u4f8b\u4e2d\uff0c\u6211\u4eec\u89e3\u6790\u6570\u5b57\u4ee5\u6784\u5efa Location \u5bf9\u8c61\u5e76\u8fd4\u56de true\u3002\u5426\u5219\uff0c\u6211\u4eec\u5c06\u8fd4\u56de false\uff0c\u56e0\u4e3a\u65e0\u6cd5\u521d\u59cb\u5316 Location \u5bf9\u8c61\u3002<\/p>\n<p>Important note : When the minimal API finds that a type contains a static TryParse method, even if it is a complex type, it assumes that it is passed in the route or the query string, based on the routing template. We can use the [FromHeader] attributes to change the binding source. In any case, TryParse will never be invoked for the body of the request.<br \/>\n\u91cd\u8981\u63d0\u793a : \u5f53\u6700\u5c0f API \u53d1\u73b0\u67d0\u4e2a\u7c7b\u578b\u5305\u542b\u9759\u6001 TryParse \u65b9\u6cd5\u65f6\uff0c\u5373\u4f7f\u5b83\u662f\u4e00\u4e2a\u590d\u6742\u7c7b\u578b\uff0c\u5b83\u4e5f\u4f1a\u6839\u636e\u8def\u7531\u6a21\u677f\u5047\u5b9a\u5b83\u662f\u5728\u8def\u7531\u6216\u67e5\u8be2\u5b57\u7b26\u4e32\u4e2d\u4f20\u9012\u7684\u3002\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 [FromHeader] \u5c5e\u6027\u6765\u66f4\u6539\u7ed1\u5b9a\u6e90\u3002\u5728\u4efb\u4f55\u60c5\u51b5\u4e0b\uff0c\u90fd\u4e0d\u4f1a\u4e3a\u8bf7\u6c42\u6b63\u6587\u8c03\u7528 TryParse\u3002<\/p>\n<p>If we need to completely control how binding is performed, we can implement a static BindAsync method on the type. This isn\u2019t a very common solution, but in some cases, it can be useful:<br \/>\n\u5982\u679c\u6211\u4eec\u9700\u8981\u5b8c\u5168\u63a7\u5236\u7ed1\u5b9a\u7684\u6267\u884c\u65b9\u5f0f\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u7c7b\u578b\u4e0a\u5b9e\u73b0\u9759\u6001 BindAsync \u65b9\u6cd5\u3002\u8fd9\u4e0d\u662f\u4e00\u4e2a\u975e\u5e38\u5e38\u89c1\u7684\u89e3\u51b3\u65b9\u6848\uff0c\u4f46\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u5b83\u53ef\u80fd\u5f88\u6709\u7528\uff1a<\/p>\n<pre><code>\/\/ POST \/navigate?lat=43.8427&amp;lon=7.8527\napp.MapPost(&quot;\/navigate&quot;, (Location location) =&gt; \n   $&quot;Location: {location.Latitude}, {location.Longitude}&quot;);\npublic class Location\n{\n    \/\/ ...\n    public static ValueTask&lt;Location?&gt; BindAsync(HttpContext \n    context, ParameterInfo parameter)\n    {\n        if (double.TryParse(context.Request.Query[&quot;lat&quot;], \n            NumberStyles.AllowDecimalPoint, CultureInfo.\n            InvariantCulture, out var latitude)&amp;&amp; double.\n            TryParse(context.Request.Query[&quot;lon&quot;], \n            NumberStyles.AllowDecimalPoint, CultureInfo.\n            InvariantCulture, out var longitude))\n        {\n                var location = new Location \n                { Latitude = latitude, Longitude = longitude };\n                return ValueTask.\n                  FromResult&lt;Location?&gt;(location);\n        }\n        return ValueTask.FromResult&lt;Location?&gt;(null);\n    }\n}<\/code><\/pre>\n<p>As we can see, the BindAsync method takes the whole HttpContext as an argument, so we can read all the information we need to create the actual Location object that is passed to the route handler. In this example, we read two query string parameters (lat and lon), but (in the case of POST, PUT, or PATCH methods) we can also read the entire body of the request and manually parse its content. This can be useful, for instance, if we need to handle requests that have a format other than JSON (which, as said before, is the only one supported by default).<br \/>\n\u6b63\u5982\u6211\u4eec\u6240\u770b\u5230\u7684\uff0cBindAsync \u65b9\u6cd5\u5c06\u6574\u4e2a HttpContext \u4f5c\u4e3a\u53c2\u6570\uff0c\u56e0\u6b64\u6211\u4eec\u53ef\u4ee5\u8bfb\u53d6\u521b\u5efa\u4f20\u9012\u7ed9\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u7684\u5b9e\u9645 Location \u5bf9\u8c61\u6240\u9700\u7684\u6240\u6709\u4fe1\u606f\u3002\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u8bfb\u53d6\u4e24\u4e2a\u67e5\u8be2\u5b57\u7b26\u4e32\u53c2\u6570\uff08lat \u548c lon\uff09\uff0c\u4f46\uff08\u5728 POST\u3001PUT \u6216 PATCH \u65b9\u6cd5\u7684\u60c5\u51b5\u4e0b\uff09\u6211\u4eec\u8fd8\u53ef\u4ee5\u8bfb\u53d6\u8bf7\u6c42\u7684\u6574\u4e2a\u6b63\u6587\u5e76\u624b\u52a8\u89e3\u6790\u5176\u5185\u5bb9\u3002\u4f8b\u5982\uff0c\u5982\u679c\u6211\u4eec\u9700\u8981\u5904\u7406\u683c\u5f0f\u4e0d\u662f JSON \u7684\u8bf7\u6c42\uff08\u5982\u524d\u6240\u8ff0\uff0cJSON \u662f\u9ed8\u8ba4\u652f\u6301\u7684\u552f\u4e00\u683c\u5f0f\uff09\uff0c\u8fd9\u53ef\u80fd\u5f88\u6709\u7528\u3002<\/p>\n<p>If the BindAsync method returns null, while the corresponding route handler parameter cannot assume this value (as in the previous example), we will get an HttpBadRequestException error, which. as usual, will be wrapped in a 400 Bad Request response.<br \/>\n\u5982\u679c BindAsync \u65b9\u6cd5\u8fd4\u56de null\uff0c\u800c\u76f8\u5e94\u7684\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u4e0d\u80fd\u91c7\u7528\u6b64\u503c\uff08\u5982\u524d\u9762\u7684\u793a\u4f8b\u6240\u793a\uff09\uff0c\u6211\u4eec\u5c06\u6536\u5230 HttpBadRequestException \u9519\u8bef\u3002\u50cf\u5f80\u5e38\u4e00\u6837\uff0c\u5c06\u5305\u88c5\u5728 400 Bad Request \u54cd\u5e94\u4e2d\u3002<\/p>\n<p>Important note : We shouldn\u2019t define both the TryParse and BindAsync methods using a type; if both are present, BindAsync always has precedence (that is, TryParse will never be invoked).<br \/>\n\u91cd\u8981\u63d0\u793a : \u6211\u4eec\u4e0d\u5e94\u8be5\u4f7f\u7528\u7c7b\u578b\u540c\u65f6\u5b9a\u4e49 TryParse \u548c BindAsync \u65b9\u6cd5;\u5982\u679c\u4e24\u8005\u90fd\u5b58\u5728\uff0c\u5219 BindAsync \u59cb\u7ec8\u5177\u6709\u4f18\u5148\u6743\uff08\u5373\uff0c\u6c38\u8fdc\u4e0d\u4f1a\u8c03\u7528 TryParse\uff09\u3002<\/p>\n<p>Now that we have looked at parameter binding and understood how to use it and customize its behavior, let\u2019s see how to work with responses in minimal APIs.<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u4e86\u89e3\u4e86\u53c2\u6570\u7ed1\u5b9a\u5e76\u4e86\u89e3\u4e86\u5982\u4f55\u4f7f\u7528\u5b83\u5e76\u81ea\u5b9a\u4e49\u5176\u884c\u4e3a\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u5728\u6700\u5c0f\u7684 API \u4e2d\u4f7f\u7528\u54cd\u5e94\u3002<\/p>\n<p>Exploring responses<br \/>\n\u63a2\u7d22\u54cd\u5e94<\/p>\n<p>As with controller-based projects, with route handlers of minimal APIs as well, we can directly return a string or a class (either synchronously or asynchronously):<br \/>\n\u4e0e\u57fa\u4e8e\u63a7\u5236\u5668\u7684\u9879\u76ee\u4e00\u6837\uff0c\u4f7f\u7528\u6700\u5c0f API \u7684\u8def\u7531\u5904\u7406\u7a0b\u5e8f\uff0c\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u8fd4\u56de\u5b57\u7b26\u4e32\u6216\u7c7b\uff08\u540c\u6b65\u6216\u5f02\u6b65\uff09\uff1a<\/p>\n<p>\u2022  If we return a string (as in the examples of the previous section), the framework writes the string directly to the response, setting its content type to text\/plain and the status code to 200 OK<br \/>\n\u5982\u679c\u6211\u4eec\u8fd4\u56de\u4e00\u4e2a\u5b57\u7b26\u4e32\uff08\u5982\u4e0a\u4e00\u8282\u7684\u793a\u4f8b\u6240\u793a\uff09\uff0c\u6846\u67b6\u4f1a\u5c06\u8be5\u5b57\u7b26\u4e32\u76f4\u63a5\u5199\u5165\u54cd\u5e94\uff0c\u5c06\u5176\u5185\u5bb9\u7c7b\u578b\u8bbe\u7f6e\u4e3a text\/plain\uff0c\u5e76\u5c06\u72b6\u6001\u4ee3\u7801\u8bbe\u7f6e\u4e3a 200 OK<\/p>\n<p>\u2022  If we use a class, the object is serialized into the JSON format and sent to the response with the application\/json content type and a 200 OK status code<br \/>\n\u5982\u679c\u6211\u4eec\u4f7f\u7528\u7c7b\uff0c\u5219\u5bf9\u8c61\u5c06\u5e8f\u5217\u5316\u4e3a JSON \u683c\u5f0f\uff0c\u5e76\u4f7f\u7528 application\/json \u5185\u5bb9\u7c7b\u578b\u548c 200 OK \u72b6\u6001\u4ee3\u7801\u53d1\u9001\u5230\u54cd\u5e94<\/p>\n<p>However, in a real application, we typically need to control the response type and the status code. In this case, we can use the static Results class, which allows us to return an instance of the IResult interface, which in minimal APIs acts how IActionResult does for controllers. For instance, we can use it to return a 201 Created response rather than a 400 Bad Request or a 404 Not Found message. L et\u2019s look at some examples:<br \/>\n\u4f46\u662f\uff0c\u5728\u5b9e\u9645\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u6211\u4eec\u901a\u5e38\u9700\u8981\u63a7\u5236\u54cd\u5e94\u7c7b\u578b\u548c\u72b6\u6001\u4ee3\u7801\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u9759\u6001 Results \u7c7b\uff0c\u8be5\u7c7b\u5141\u8bb8\u6211\u4eec\u8fd4\u56de IResult \u63a5\u53e3\u7684\u5b9e\u4f8b\uff0c\u8be5\u5b9e\u4f8b\u5728\u6700\u5c0f\u7684 API \u4e2d\u7684\u4f5c\u7528\u7c7b\u4f3c\u4e8e IActionResult \u5bf9\u63a7\u5236\u5668\u7684\u4f5c\u7528\u3002\u4f8b\u5982\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u5b83\u6765\u8fd4\u56de 201 Created \u54cd\u5e94\uff0c\u800c\u4e0d\u662f 400 Bad Request \u6216 404 Not Found \u6d88\u606f\u3002\u6211\u4eec\u6765\u770b\u770b\u4e00\u4e9b\u4f8b\u5b50\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/ok&quot;, () =&gt; Results.Ok(new Person(&quot;Donald&quot;, \n                                              &quot;Duck&quot;)));\napp.MapGet(&quot;\/notfound&quot;, () =&gt; Results.NotFound());\napp.MapPost(&quot;\/badrequest&quot;, () =&gt;\n{\n    \/\/ Creates a 400 response with a JSON body.\n    return Results.BadRequest(new { ErrorMessage = &quot;Unable to\n                                    complete the request&quot; });\n});\napp.MapGet(&quot;\/download&quot;, (string fileName) =&gt; \n             Results.File(fileName));\nrecord class Person(string FirstName, string LastName);<\/code><\/pre>\n<p>Each method of the Results class is responsible for setting the response type and status code that correspond to the meaning of the method itself (e.g., the Results.NotFound() method returns a 404 Not Found response). Note that even if we typically need to return an object in the case of a 200 OK response (with Results.Ok()), it isn\u2019t the only method that allows this. Many other methods allow us to include a custom response; in all these cases, the response type will be set to application\/json and the object will automatically be JSON-serialized.<br \/>\nResults \u7c7b\u7684\u6bcf\u4e2a\u65b9\u6cd5\u90fd\u8d1f\u8d23\u8bbe\u7f6e\u4e0e\u65b9\u6cd5\u672c\u8eab\u7684\u542b\u4e49\u76f8\u5bf9\u5e94\u7684\u54cd\u5e94\u7c7b\u578b\u548c\u72b6\u6001\u4ee3\u7801\uff08\u4f8b\u5982\uff0cResults.NotFound\uff08\uff09 \u65b9\u6cd5\u8fd4\u56de 404 Not Found \u54cd\u5e94\uff09\u3002\u8bf7\u6ce8\u610f\uff0c\u5373\u4f7f\u6211\u4eec\u901a\u5e38\u9700\u8981\u5728 200 OK \u54cd\u5e94\u7684\u60c5\u51b5\u4e0b\u8fd4\u56de\u4e00\u4e2a\u5bf9\u8c61\uff08\u4f7f\u7528 Results.Ok\uff08\uff09\uff09\uff0c\u5b83\u4e5f\u4e0d\u662f\u552f\u4e00\u5141\u8bb8\u8fd9\u6837\u505a\u7684\u65b9\u6cd5\u3002\u8bb8\u591a\u5176\u4ed6\u65b9\u6cd5\u5141\u8bb8\u6211\u4eec\u5305\u542b\u81ea\u5b9a\u4e49\u54cd\u5e94;\u5728\u6240\u6709\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c\u54cd\u5e94\u7c7b\u578b\u90fd\u5c06\u8bbe\u7f6e\u4e3a application\/json\uff0c\u5e76\u4e14\u5bf9\u8c61\u5c06\u81ea\u52a8\u8fdb\u884c JSON \u5e8f\u5217\u5316\u3002<\/p>\n<p>The current version of minimal APIs does not support content negotiation. We only have a few methods that allow us to explicitly set the content type, when getting a file with Results.Bytes(), Results.Stream(), and Results.File(), or when using Results.Text() and Results.Content(). In all other cases, when we\u2019re dealing with complex objects, the response will be in JSON format. This is a precise design choice since most developers rarely need to support other media types. By supporting only JSON without performing content negotiation, minimal APIs can be very efficient.<br \/>\n\u5f53\u524d\u7248\u672c\u7684 minimal API \u4e0d\u652f\u6301\u5185\u5bb9\u534f\u5546\u3002\u53ea\u6709\u5c11\u6570\u65b9\u6cd5\u5141\u8bb8\u6211\u4eec\u663e\u5f0f\u8bbe\u7f6e\u5185\u5bb9\u7c7b\u578b\uff0c\u5f53\u4f7f\u7528 Results.Bytes\uff08\uff09\u3001Results.Stream\uff08\uff09 \u548c Results.File\uff08\uff09 \u83b7\u53d6\u6587\u4ef6\u65f6\uff0c\u6216\u8005\u4f7f\u7528 Results.Text\uff08\uff09 \u548c Results.Content\uff08\uff09 \u65f6\u3002\u5728\u6240\u6709\u5176\u4ed6\u60c5\u51b5\u4e0b\uff0c\u5f53\u6211\u4eec\u5904\u7406\u590d\u6742\u5bf9\u8c61\u65f6\uff0c\u54cd\u5e94\u5c06\u91c7\u7528 JSON \u683c\u5f0f\u3002\u8fd9\u662f\u4e00\u4e2a\u7cbe\u786e\u7684\u8bbe\u8ba1\u9009\u62e9\uff0c\u56e0\u4e3a\u5927\u591a\u6570\u5f00\u53d1\u4eba\u5458\u5f88\u5c11\u9700\u8981\u652f\u6301\u5176\u4ed6\u5a92\u4f53\u7c7b\u578b\u3002\u901a\u8fc7\u4ec5\u652f\u6301 JSON \u800c\u4e0d\u6267\u884c\u5185\u5bb9\u534f\u5546\uff0c\u6700\u5c11\u7684 API \u53ef\u4ee5\u975e\u5e38\u9ad8\u6548\u3002<\/p>\n<p>However, this approach isn\u2019t enough in all scenarios. In some cases, we may need to create a custom response type, for example, if we want to return an HTML or XML response instead of the standard JSON. We can manually use the Results.Content() method (which allows us to specify the content as a simple string with a particular content type), but, if we have this requirement, it is better to implement a custom IResult type, so that the solution can be reused.<br \/>\n\u4f46\u662f\uff0c\u8fd9\u79cd\u65b9\u6cd5\u5e76\u975e\u5728\u6240\u6709\u60c5\u51b5\u4e0b\u90fd\u8db3\u591f\u3002\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u53ef\u80fd\u9700\u8981\u521b\u5efa\u81ea\u5b9a\u4e49\u54cd\u5e94\u7c7b\u578b\uff0c\u4f8b\u5982\uff0c\u5982\u679c\u6211\u4eec\u8981\u8fd4\u56de HTML \u6216 XML \u54cd\u5e94\u800c\u4e0d\u662f\u6807\u51c6 JSON\u3002\u6211\u4eec\u53ef\u4ee5\u624b\u52a8\u4f7f\u7528 Results.Content\uff08\uff09 \u65b9\u6cd5\uff08\u5b83\u5141\u8bb8\u6211\u4eec\u5c06\u5185\u5bb9\u6307\u5b9a\u4e3a\u5177\u6709\u7279\u5b9a\u5185\u5bb9\u7c7b\u578b\u7684\u7b80\u5355\u5b57\u7b26\u4e32\uff09\uff0c\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u6709\u6b64\u8981\u6c42\uff0c\u6700\u597d\u5b9e\u73b0\u81ea\u5b9a\u4e49 IResult \u7c7b\u578b\uff0c\u4ee5\u4fbf\u53ef\u4ee5\u91cd\u7528\u89e3\u51b3\u65b9\u6848\u3002<\/p>\n<p>For example, let\u2019s suppose that we want to serialize objects in XML instead of JSON. We can then define an XmlResult class that implements the IResult interface:<br \/>\n\u4f8b\u5982\uff0c\u5047\u8bbe\u6211\u4eec\u60f3\u7528 XML \u800c\u4e0d\u662f JSON \u6765\u5e8f\u5217\u5316\u5bf9\u8c61\u3002\u7136\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5b9a\u4e49\u4e00\u4e2a\u5b9e\u73b0 IResult \u63a5\u53e3\u7684 XmlResult \u7c7b\uff1a<\/p>\n<pre><code>public class XmlResult : IResult\n{\n   private readonly object value;\n   public XmlResult(object value)\n   {\n       this.value = value;\n   }\n   public Task ExecuteAsync(HttpContext httpContext)\n   {\n       using var writer = new StringWriter();\n\n       var serializer = new XmlSerializer(value.GetType());\n       serializer.Serialize(writer, value);\n       var xml = writer.ToString();\n       httpContext.Response.ContentType = MediaTypeNames.\n       Application.Xml;\n       httpContext.Response.ContentLength = Encoding.UTF8\n      .GetByteCount(xml);\n       return httpContext.Response.WriteAsync(xml);\n   }\n}<\/code><\/pre>\n<p>The IResult interface requires us to implement the ExecuteAsync method, which receives the current HttpContext as an argument. We serialize the value using the XmlSerializer class and then write it to the response, specifying the correct response type.<br \/>\nIResult \u63a5\u53e3\u8981\u6c42\u6211\u4eec\u5b9e\u73b0 ExecuteAsync \u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u63a5\u6536\u5f53\u524d HttpContext \u4f5c\u4e3a\u53c2\u6570\u3002\u6211\u4eec\u4f7f\u7528 XmlSerializer \u7c7b\u5e8f\u5217\u5316\u8be5\u503c\uff0c\u7136\u540e\u5c06\u5176\u5199\u5165\u54cd\u5e94\uff0c\u5e76\u6307\u5b9a\u6b63\u786e\u7684\u54cd\u5e94\u7c7b\u578b\u3002<\/p>\n<p>Now, we can directly use the new XmlResult type in our route handlers. However, best practices suggest that we create an extension method for the IResultExtensions interface, as with the following one:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u5728\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u65b0\u7684 XmlResult \u7c7b\u578b\u3002\u4f46\u662f\uff0c\u6700\u4f73\u5b9e\u8df5\u5efa\u8bae\u6211\u4eec\u4e3a IResultExtensions \u63a5\u53e3\u521b\u5efa\u4e00\u4e2a\u6269\u5c55\u65b9\u6cd5\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>public static class ResultExtensions\n{\n    public static IResult Xml(this IResultExtensions \n    resultExtensions, object value) =&gt; new XmlResult(value);\n}<\/code><\/pre>\n<p>In this way, we have a new Xml method available on the Results.Extensions property:<br \/>\n\u8fd9\u6837\uff0c\u6211\u4eec\u5728 Results.Extensions \u5c5e\u6027\u4e0a\u5c31\u6709\u4e86\u4e00\u4e2a\u65b0\u7684 Xml \u65b9\u6cd5\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/xml&quot;, () =&gt; Results.Extensions.Xml(new City { Name = &quot;Taggia&quot; }));\npublic record class City\n{\n    public string? Name { get; init; }\n}<\/code><\/pre>\n<p>The benefit of this approach is that we can reuse it everywhere we need to deal with XML without having to manually handle the serialization and the response type (as we should have done using the Result.Content() method instead).<br \/>\n\u8fd9\u79cd\u65b9\u6cd5\u7684\u597d\u5904\u662f\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u9700\u8981\u5904\u7406 XML \u7684\u4efb\u4f55\u5730\u65b9\u91cd\u7528\u5b83\uff0c\u800c\u4e0d\u5fc5\u624b\u52a8\u5904\u7406\u5e8f\u5217\u5316\u548c\u54cd\u5e94\u7c7b\u578b\uff08\u5c31\u50cf\u6211\u4eec\u5e94\u8be5\u4f7f\u7528 Result.Content\uff08\uff09 \u65b9\u6cd5\u6240\u505a\u7684\u90a3\u6837\uff09\u3002<\/p>\n<p>Tip : If we want to perform content validation, we need to manually check the Accept header of the HttpRequest object, which we can pass to our handlers, and then create the correct response accordingly.<br \/>\n\u63d0\u793a : \u5982\u679c\u6211\u4eec\u60f3\u6267\u884c\u5185\u5bb9\u9a8c\u8bc1\uff0c\u6211\u4eec\u9700\u8981\u624b\u52a8\u68c0\u67e5 HttpRequest \u5bf9\u8c61\u7684 Accept \u6807\u5934\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u5176\u4f20\u9012\u7ed9\u6211\u4eec\u7684\u5904\u7406\u7a0b\u5e8f\uff0c\u7136\u540e\u76f8\u5e94\u5730\u521b\u5efa\u6b63\u786e\u7684\u54cd\u5e94\u3002<\/p>\n<p>After analyzing how to properly handle responses in minimal APIs, we\u2019ll see how to control the way our data is serialized and deserialized in the next section.<br \/>\n\u5728\u5206\u6790\u4e86\u5982\u4f55\u5728\u6700\u5c0f API \u4e2d\u6b63\u786e\u5904\u7406\u54cd\u5e94\u4e4b\u540e\uff0c\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u8282\u4e2d\u4e86\u89e3\u5982\u4f55\u63a7\u5236\u6570\u636e\u7684\u5e8f\u5217\u5316\u548c\u53cd\u5e8f\u5217\u5316\u65b9\u5f0f\u3002<\/p>\n<p>Controlling serialization<br \/>\n\u63a7\u5236\u5e8f\u5217\u5316<\/p>\n<p>As described in the previous sections, minimal APIs only provide built-in support for the JSON format. In particular, the framework uses System.Text.Json for serialization and deserialization. In controller-based APIs, we can change this default and use JSON.NET instead. This is not possible when working with minimal APIs: we can\u2019t replace the serializer at all.<br \/>\n\u5982\u524d\u51e0\u8282\u6240\u8ff0\uff0c\u6700\u5c0f API \u4ec5\u63d0\u4f9b\u5bf9 JSON \u683c\u5f0f\u7684\u5185\u7f6e\u652f\u6301\u3002\u5177\u4f53\u800c\u8a00\uff0c\u6846\u67b6\u4f7f\u7528 System.Text.Json \u8fdb\u884c\u5e8f\u5217\u5316\u548c\u53cd\u5e8f\u5217\u5316\u3002\u5728\u57fa\u4e8e\u63a7\u5236\u5668\u7684 API \u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u66f4\u6539\u6b64\u9ed8\u8ba4\u503c\u5e76\u6539\u7528 JSON.NET\u3002\u5f53\u4f7f\u7528\u6700\u5c11\u7684 API \u65f6\uff0c\u8fd9\u662f\u4e0d\u53ef\u80fd\u7684\uff1a\u6211\u4eec\u6839\u672c\u65e0\u6cd5\u66ff\u6362\u5e8f\u5217\u5316\u5668\u3002<\/p>\n<p>The built-in serializer uses the following options:<br \/>\n\u5185\u7f6e\u5e8f\u5217\u5316\u7a0b\u5e8f\u4f7f\u7528\u4ee5\u4e0b\u9009\u9879\uff1a<\/p>\n<p>\u2022  Case-insensitive property names during serialization<br \/>\n\u5e8f\u5217\u5316\u671f\u95f4\u4e0d\u533a\u5206\u5927\u5c0f\u5199\u7684\u5c5e\u6027\u540d\u79f0<\/p>\n<p>\u2022  Camel case property naming policy<br \/>\n\u9a7c\u5cf0\u5f0f\u5927\u5c0f\u5199\u5c5e\u6027\u547d\u540d\u7b56\u7565<\/p>\n<p>\u2022  Support for quoted numbers (JSON strings for number properties)<br \/>\n\u652f\u6301\u5e26\u5f15\u53f7\u7684\u6570\u5b57\uff08\u6570\u5b57\u5c5e\u6027\u7684 JSON \u5b57\u7b26\u4e32\uff09<\/p>\n<p>Note : We can find more information about the System.Text.Json namespace and all the APIs it provides at the following link: <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.text.json\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.text.json<\/a>.<br \/>\n\u6ce8\u610f : \u6211\u4eec\u53ef\u4ee5\u5728\u4ee5\u4e0b\u94fe\u63a5\u4e2d\u627e\u5230\u6709\u5173 System.Text.Json \u547d\u540d\u7a7a\u95f4\u53ca\u5176\u63d0\u4f9b\u7684\u6240\u6709 API \u7684\u66f4\u591a\u4fe1\u606f\uff1a<a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.text.json\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.text.json<\/a>\u3002<\/p>\n<p>In controller-based APIs, we can customize these settings by calling AddJsonOptions() fluently after AddControllers(). In minimal APIs, we can\u2019t use this approach since we don\u2019t have controllers at all, so we need to explicitly call the Configure method for JsonOptions. So, let\u2019s consider this handler:<br \/>\n\u5728\u57fa\u4e8e\u63a7\u5236\u5668\u7684 API \u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u5728 AddControllers\uff08\uff09 \u4e4b\u540e\u6d41\u7545\u5730\u8c03\u7528 AddJsonOptions\uff08\uff09 \u6765\u81ea\u5b9a\u4e49\u8fd9\u4e9b\u8bbe\u7f6e\u3002\u5728\u6700\u5c0f\u7684 API \u4e2d\uff0c\u6211\u4eec\u4e0d\u80fd\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff0c\u56e0\u4e3a\u6211\u4eec\u6839\u672c\u6ca1\u6709\u63a7\u5236\u5668\uff0c\u56e0\u6b64\u6211\u4eec\u9700\u8981\u663e\u5f0f\u8c03\u7528 JsonOptions \u7684 Configure \u65b9\u6cd5\u3002\u90a3\u4e48\uff0c\u8ba9\u6211\u4eec\u8003\u8651\u4e00\u4e0b\u8fd9\u4e2a\u5904\u7406\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/product&quot;, () =&gt;\n{\n    var product = new Product(&quot;Apple&quot;, null, 0.42, 6);\n    return Results.Ok(product); \n});\npublic record class Product(string Name, string? Description, double UnitPrice, int Quantity)\n{\n    public double TotalPrice =&gt; UnitPrice * Quantity;\n}<\/code><\/pre>\n<p>Using the default JSON options, we get this result:<br \/>\n\u4f7f\u7528\u9ed8\u8ba4\u7684 JSON \u9009\u9879\uff0c\u6211\u4eec\u5f97\u5230\u4ee5\u4e0b\u7ed3\u679c\uff1a<\/p>\n<pre><code>{\n    &quot;name&quot;: &quot;Apple&quot;,\n    &quot;description&quot;: null,\n    &quot;unitPrice&quot;: 0.42,\n    &quot;quantity&quot;: 6,\n    &quot;totalPrice&quot;: 2.52\n}<\/code><\/pre>\n<p>Now, let\u2019s configure JsonOptions:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u914d\u7f6e JsonOptions\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nbuilder.Services.Configure&lt;Microsoft.AspNetCore.Http.Json.\nJsonOptions&gt;(options =&gt;\n{\n    options.SerializerOptions.DefaultIgnoreCondition = \n    JsonIgnoreCondition.WhenWritingNull;\n    options.SerializerOptions.IgnoreReadOnlyProperties \n    = true;\n});<\/code><\/pre>\n<p>Calling the \/product endpoint again, we\u2019ll now get the following:<br \/>\n\u518d\u6b21\u8c03\u7528 \/product \u7aef\u70b9\uff0c\u6211\u4eec\u73b0\u5728\u5c06\u83b7\u5f97\u4ee5\u4e0b\u5185\u5bb9\uff1a<\/p>\n<pre><code>{\n    &quot;name&quot;: &quot;Apple&quot;,\n    &quot;unitPrice&quot;: 0.42,\n    &quot;quantity&quot;: 6\n}<\/code><\/pre>\n<p>As expected, the Description property hasn\u2019t been serialized because it is null, as well as TotalPrice, which isn\u2019t included in the response because it is read-only.<br \/>\n\u6b63\u5982\u9884\u671f\u7684\u90a3\u6837\uff0cDescription \u5c5e\u6027\u5c1a\u672a\u5e8f\u5217\u5316\uff0c\u56e0\u4e3a\u5b83\u4e3a null\uff0c\u4ee5\u53ca TotalPrice\uff0c\u7531\u4e8e\u5b83\u662f\u53ea\u8bfb\u7684\uff0c\u56e0\u6b64\u672a\u5305\u542b\u5728\u54cd\u5e94\u4e2d\u3002<\/p>\n<p>Another typical use case for JsonOptions is when we want to add converters that will be automatically applied for each serialization or deserialization, for example, JsonStrinEnumConverter to convert enumeration values into or from strings.<br \/>\nJsonOptions \u7684\u53e6\u4e00\u4e2a\u5178\u578b\u7528\u4f8b\u662f\u5f53\u6211\u4eec\u60f3\u8981\u6dfb\u52a0\u5c06\u81ea\u52a8\u5e94\u7528\u4e8e\u6bcf\u4e2a\u5e8f\u5217\u5316\u6216\u53cd\u5e8f\u5217\u5316\u7684\u8f6c\u6362\u5668\u65f6\uff0c\u4f8b\u5982\uff0cJsonStrinEnumConverter \u7528\u4e8e\u5c06\u679a\u4e3e\u503c\u8f6c\u6362\u4e3a\u5b57\u7b26\u4e32\u6216\u4ece\u5b57\u7b26\u4e32\u8f6c\u6362\u3002<\/p>\n<p>Important note : Be aware that the JsonOptions class used by minimal APIs is the one available in the Microsoft.AspNetCore.Http.Json namespace. Do not confuse it with the one that is defined in the Microsoft.AspNetCore.Mvc namespace; the name of the object is the same, but the latter is valid only for controllers, so it has no effect if set in a minimal API project.<br \/>\n\u91cd\u8981\u63d0\u793a : \u8bf7\u6ce8\u610f\uff0c\u6700\u5c0f API \u4f7f\u7528\u7684 JsonOptions \u7c7b\u662f Microsoft.AspNetCore.Http.Json \u547d\u540d\u7a7a\u95f4\u4e2d\u53ef\u7528\u7684\u7c7b\u3002\u4e0d\u8981\u5c06\u5176\u4e0e Microsoft.AspNetCore.Mvc \u547d\u540d\u7a7a\u95f4\u4e2d\u5b9a\u4e49\u7684\u540d\u79f0\u6df7\u6dc6;\u5bf9\u8c61\u7684\u540d\u79f0\u76f8\u540c\uff0c\u4f46\u540e\u8005\u4ec5\u5bf9\u63a7\u5236\u5668\u6709\u6548\uff0c\u56e0\u6b64\u5982\u679c\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u8bbe\u7f6e\uff0c\u5219\u65e0\u6548\u3002<\/p>\n<p>Because of the JSON-only support, if we do not explicitly add support for other formats, as described in the previous sections (using, for example, the BindAsync method on a custom type), minimal APIs will automatically perform some validations on the body binding source and handle the following scenarios:<br \/>\n\u7531\u4e8e\u4ec5\u652f\u6301 JSON\uff0c\u5982\u679c\u6211\u4eec\u6ca1\u6709\u663e\u5f0f\u6dfb\u52a0\u5bf9\u5176\u4ed6\u683c\u5f0f\u7684\u652f\u6301\uff0c\u5982\u524d\u9762\u90e8\u5206\u6240\u8ff0\uff08\u4f8b\u5982\uff0c\u5728\u81ea\u5b9a\u4e49\u7c7b\u578b\u4e0a\u4f7f\u7528 BindAsync \u65b9\u6cd5\uff09\uff0c\u5219\u6700\u5c0f API \u5c06\u5728\u6b63\u6587\u7ed1\u5b9a\u6e90\u4e0a\u81ea\u52a8\u6267\u884c\u4e00\u4e9b\u9a8c\u8bc1\u5e76\u5904\u7406\u4ee5\u4e0b\u60c5\u51b5\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0202.jpg\"><\/p>\n<p>Table 2.3 \u2013 The response status codes for body binding problems<br \/>\n\u8868 2.3 \u2013 \u6b63\u6587\u7ed1\u5b9a\u95ee\u9898\u7684\u54cd\u5e94\u72b6\u6001\u4ee3\u7801<\/p>\n<p>In these cases, because body validation fails, our route handlers will never be invoked, and we will get the response status codes shown in the preceding table directly.<br \/>\n\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c\u7531\u4e8e\u4e3b\u4f53\u9a8c\u8bc1\u5931\u8d25\uff0c\u6211\u4eec\u7684\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u5c06\u6c38\u8fdc\u4e0d\u4f1a\u88ab\u8c03\u7528\uff0c\u6211\u4eec\u5c06\u76f4\u63a5\u83b7\u53d6\u4e0a\u8868\u4e2d\u663e\u793a\u7684\u54cd\u5e94\u72b6\u6001\u4ee3\u7801\u3002<\/p>\n<p>Now, we have covered all the pillars that we need to start developing minimal APIs. However, there is another important thing to talk about: the correct way to design a real project to avoid common mistakes within the architecture.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5df2\u7ecf\u6db5\u76d6\u4e86\u5f00\u59cb\u5f00\u53d1\u6700\u5c0f API \u6240\u9700\u7684\u6240\u6709\u652f\u67f1\u3002\u4f46\u662f\uff0c\u8fd8\u6709\u4e00\u4ef6\u91cd\u8981\u7684\u4e8b\u60c5\u8981\u8c08\uff1a\u8bbe\u8ba1\u771f\u5b9e\u9879\u76ee\u7684\u6b63\u786e\u65b9\u6cd5\uff0c\u4ee5\u907f\u514d\u67b6\u6784\u4e2d\u7684\u5e38\u89c1\u9519\u8bef\u3002<\/p>\n<p>Architecting a minimal API project<br \/>\n\u6784\u5efa\u4e00\u4e2a\u6700\u5c0f\u7684 API \u9879\u76ee<\/p>\n<p>Up to now, we have written route handlers directly in the Program.cs file. This is a perfectly supported scenario: with minimal APIs, we can write all our code inside this single file. In fact, almost all the samples show this solution. However, while this is allowed, we can easily imagine how this approach can lead to unstructured and therefore unmaintainable projects. If we have fewer endpoints, it is fine \u2013 otherwise, it is better to organize our handlers in separate files.<br \/>\n\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u6211\u4eec\u5df2\u7ecf\u76f4\u63a5\u5728 Program.cs \u6587\u4ef6\u4e2d\u7f16\u5199\u4e86\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u3002\u8fd9\u662f\u4e00\u4e2a\u5b8c\u5168\u652f\u6301\u7684\u573a\u666f\uff1a\u4f7f\u7528\u6700\u5c11\u7684 API\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u8fd9\u4e2a\u6587\u4ef6\u4e2d\u7f16\u5199\u6240\u6709\u4ee3\u7801\u3002\u4e8b\u5b9e\u4e0a\uff0c\u51e0\u4e4e\u6240\u6709\u6837\u672c\u90fd\u663e\u793a\u4e86\u8fd9\u79cd\u89e3\u51b3\u65b9\u6848\u3002\u7136\u800c\uff0c\u867d\u7136\u8fd9\u662f\u5141\u8bb8\u7684\uff0c\u4f46\u6211\u4eec\u53ef\u4ee5\u5f88\u5bb9\u6613\u5730\u60f3\u8c61\u8fd9\u79cd\u65b9\u6cd5\u5982\u4f55\u5bfc\u81f4\u975e\u7ed3\u6784\u5316\u7684\u3001\u56e0\u6b64\u65e0\u6cd5\u7ef4\u62a4\u7684\u9879\u76ee\u3002\u5982\u679c\u7aef\u70b9\u8f83\u5c11\uff0c\u90a3\u5f88\u597d \u2014\u2014 \u5426\u5219\uff0c\u6700\u597d\u5c06\u6211\u4eec\u7684\u5904\u7406\u7a0b\u5e8f\u7ec4\u7ec7\u5728\u5355\u72ec\u7684\u6587\u4ef6\u4e2d\u3002<\/p>\n<p>Let\u2019s suppose that we have the following code right in the Program.cs file because we have to handle CRUD operations:<br \/>\n\u5047\u8bbe Program.cs \u6587\u4ef6\u4e2d\u6709\u4ee5\u4e0b\u4ee3\u7801\uff0c\u56e0\u4e3a\u6211\u4eec\u5fc5\u987b\u5904\u7406 CRUD\u4f5c\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/people&quot;, (PeopleService peopleService) =&gt; \n            { });\napp.MapGet(&quot;\/api\/people\/{id:guid}&quot;, (Guid id, PeopleService \n             peopleService) =&gt; { });\napp.MapPost(&quot;\/api\/people&quot;, (Person Person, PeopleService \n              people) =&gt; { });\napp.MapPut(&quot;\/api\/people\/{id:guid}&quot;, (Guid id, Person \n             Person, PeopleService people) =&gt; { });\napp.MapDelete(&quot;\/api\/people\/{id:guid}&quot;, (Guid id, \n                PeopleService people) =&gt; { });<\/code><\/pre>\n<p>It\u2019s easy to imagine that, if we have all the implementation here (even if we\u2019re using PeopleService to extract the business logic), this file can easily explode. So, in real scenarios, the inline lambda approach isn\u2019t the best practice. We should use the other methods that we have covered in the Routing section to define the handlers instead. So, it is a good idea to create an external class to hold all the route handlers:<br \/>\n\u5f88\u5bb9\u6613\u60f3\u8c61\uff0c\u5982\u679c\u6211\u4eec\u5728\u8fd9\u91cc\u62e5\u6709\u6240\u6709\u5b9e\u73b0\uff08\u5373\u4f7f\u6211\u4eec\u4f7f\u7528 PeopleService \u6765\u63d0\u53d6\u4e1a\u52a1\u903b\u8f91\uff09\uff0c\u6b64\u6587\u4ef6\u5f88\u5bb9\u6613\u7206\u70b8\u3002\u56e0\u6b64\uff0c\u5728\u5b9e\u9645\u573a\u666f\u4e2d\uff0c\u5185\u8054 lambda \u65b9\u6cd5\u5e76\u4e0d\u662f\u6700\u4f73\u5b9e\u8df5\u3002\u6211\u4eec\u5e94\u8be5\u4f7f\u7528 \u8def\u7531 \u90e8\u5206\u4ecb\u7ecd\u7684\u5176\u4ed6\u65b9\u6cd5\u6765\u5b9a\u4e49\u5904\u7406\u7a0b\u5e8f\u3002\u56e0\u6b64\uff0c\u521b\u5efa\u4e00\u4e2a\u5916\u90e8\u7c7b\u6765\u4fdd\u5b58\u6240\u6709\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u662f\u4e00\u4e2a\u597d\u4e3b\u610f\uff1a<\/p>\n<pre><code>public class PeopleHandler\n{\n   public static void MapEndpoints(IEndpointRouteBuilder \n   app)\n   {\n       app.MapGet(&quot;\/api\/people&quot;, GetList);\n       app.MapGet(&quot;\/api\/people\/{id:guid}&quot;, Get);\n       app.MapPost(&quot;\/api\/people&quot;, Insert);\n       app.MapPut(&quot;\/api\/people\/{id:guid}&quot;, Update);\n       app.MapDelete(&quot;\/api\/people\/{id:guid}&quot;, Delete);\n   }\n\n   private static IResult GetList(PeopleService    \n   peopleService) { \/* ... *\/ }\n   private static IResult Get(Guid id, PeopleService \n   peopleService) { \/* ... *\/ }\n   private static IResult Insert(Person person, \n   PeopleService people) { \/* ... *\/ }\n   private static IResult Update(Guid id, Person \n   person, PeopleService people) { \/* ... *\/ }\n   private static IResult Delete(Guid id) { \/* ... *\/ }\n}<\/code><\/pre>\n<p>We have grouped all the endpoint definitions inside the PeopleHandler.MapEndpoints static method, which takes the IEndpointRouteBuilder interface as an argument, which in turn is implemented by the WebApplication class. Then, instead of using lambda expressions, we have created separate methods for each handler, so that the code is much cleaner. In this way, to register all these handlers in our minimal API, we just need the following code in Program.cs:<br \/>\n\u6211\u4eec\u5df2\u5c06\u6240\u6709\u7aef\u70b9\u5b9a\u4e49\u5206\u7ec4\u5230 PeopleHandler.MapEndpoints \u9759\u6001\u65b9\u6cd5\u4e2d\uff0c\u8be5\u65b9\u6cd5\u5c06 IEndpointRouteBuilder \u63a5\u53e3\u4f5c\u4e3a\u53c2\u6570\uff0c\u800c\u8be5\u63a5\u53e3\u53c8\u7531 WebApplication \u7c7b\u5b9e\u73b0\u3002\u7136\u540e\uff0c\u6211\u4eec\u6ca1\u6709\u4f7f\u7528 lambda \u8868\u8fbe\u5f0f\uff0c\u800c\u662f\u4e3a\u6bcf\u4e2a\u5904\u7406\u7a0b\u5e8f\u521b\u5efa\u4e86\u5355\u72ec\u7684\u65b9\u6cd5\uff0c\u4ee5\u4fbf\u4ee3\u7801\u66f4\u52a0\u7b80\u6d01\u3002\u8fd9\u6837\uff0c\u8981\u5728\u6211\u4eec\u7684\u6700\u5c0f API \u4e2d\u6ce8\u518c\u6240\u6709\u8fd9\u4e9b\u5904\u7406\u7a0b\u5e8f\uff0c\u6211\u4eec\u53ea\u9700\u8981\u5728 Program.cs \u4e2d\u7f16\u5199\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/ ..\nvar app = builder.Build();\n\/\/ ..\nPeopleHandler.MapEndpoints(app);\napp.Run();<\/code><\/pre>\n<p>Going forward<br \/>\n\u5c55\u671b\u672a\u6765<\/p>\n<p>The approach just shown allows us to better organize a minimal API project, but still requires that we explicitly add a line to Program.cs for every handler we want to define. Using an interface and a bit of reflection, we can create a straightforward and reusable solution to simplify our work with minimal APIs.<br \/>\n\u521a\u624d\u5c55\u793a\u7684\u65b9\u6cd5\u4f7f\u6211\u4eec\u80fd\u591f\u66f4\u597d\u5730\u7ec4\u7ec7\u4e00\u4e2a\u6700\u5c0f\u7684 API \u9879\u76ee\uff0c\u4f46\u4ecd\u7136\u9700\u8981\u6211\u4eec\u4e3a\u8981\u5b9a\u4e49\u7684\u6bcf\u4e2a\u5904\u7406\u7a0b\u5e8f\u663e\u5f0f\u6dfb\u52a0\u4e00\u884c to Program.cs\u3002\u4f7f\u7528\u63a5\u53e3\u548c\u4e00\u4e9b\u53cd\u5c04\uff0c\u6211\u4eec\u53ef\u4ee5\u521b\u5efa\u4e00\u4e2a\u7b80\u5355\u4e14\u53ef\u91cd\u7528\u7684\u89e3\u51b3\u65b9\u6848\uff0c\u4ee5\u6700\u5c11\u7684 API \u7b80\u5316\u6211\u4eec\u7684\u5de5\u4f5c\u3002<\/p>\n<p>So, let\u2019s start by defining the following interface:<br \/>\n\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u4ece\u5b9a\u4e49\u4ee5\u4e0b\u63a5\u53e3\u5f00\u59cb\uff1a<\/p>\n<pre><code>public interface IEndpointRouteHandler\n{\n   public void MapEndpoints(IEndpointRouteBuilder app);\n}<\/code><\/pre>\n<p>As the name implies, we need to make all our handlers (as with PeopleHandler previously) implement it:<br \/>\n\u987e\u540d\u601d\u4e49\uff0c\u6211\u4eec\u9700\u8981\u8ba9\u6240\u6709\u7684\u5904\u7406\u7a0b\u5e8f\uff08\u5c31\u50cf\u4e4b\u524d\u7684 PeopleHandler \u4e00\u6837\uff09\u5b9e\u73b0\u5b83\uff1a<\/p>\n<pre><code>public class PeopleHandler : IEndpointRouteHandler\n{\n       public void MapEndpoints(IEndpointRouteBuilder app)\n         {\n                \/\/ ...\n         }\n         \/\/ ...\n}<\/code><\/pre>\n<p>Note : The MapEndpoints method isn\u2019t static anymore, because now it is the implementation of the IEndpointRouteHandler interface.<br \/>\n\u6ce8\u610f : MapEndpoints \u65b9\u6cd5\u4e0d\u518d\u662f\u9759\u6001\u7684\uff0c\u56e0\u4e3a\u5b83\u73b0\u5728\u662f IEndpointRouteHandler \u63a5\u53e3\u7684\u5b9e\u73b0\u3002<\/p>\n<p>Now we need a new extension method that, using reflection, scans an assembly for all the classes that implement this interface and automatically calls their MapEndpoints methods:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u9700\u8981\u4e00\u4e2a\u65b0\u7684\u6269\u5c55\u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u4f7f\u7528\u53cd\u5c04\u626b\u63cf\u7a0b\u5e8f\u96c6\u4e2d\u5b9e\u73b0\u6b64\u63a5\u53e3\u7684\u6240\u6709\u7c7b\uff0c\u5e76\u81ea\u52a8\u8c03\u7528\u5176 MapEndpoints \u65b9\u6cd5\uff1a<\/p>\n<pre><code>public static class IEndpointRouteBuilderExtensions\n{\n    public static void MapEndpoints(this\n    IEndpointRouteBuilder app, Assembly assembly)\n    {\n        var endpointRouteHandlerInterfaceType = \n          typeof(IEndpointRouteHandler);\n        var endpointRouteHandlerTypes = \n        assembly.GetTypes().Where(t =&gt;\n        t.IsClass &amp;&amp; !t.IsAbstract &amp;&amp; !t.IsGenericType\n        &amp;&amp; t.GetConstructor(Type.EmptyTypes) != null\n        &amp;&amp; endpointRouteHandlerInterfaceType\n        .IsAssignableFrom(t));\n        foreach (var endpointRouteHandlerType in \n        endpointRouteHandlerTypes)\n        {\n            var instantiatedType = (IEndpointRouteHandler)\n              Activator.CreateInstance\n                (endpointRouteHandlerType)!;\n            instantiatedType.MapEndpoints(app);\n        }\n    }\n}<\/code><\/pre>\n<p>Tip : If you want to go into further detail about reflection and how it works in .NET, you can start by browsing the following page: <a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/programming-guide\/concepts\/reflection\">https:\/\/docs.microsoft.com\/dotnet\/csharp\/programming-guide\/concepts\/reflection<\/a>.<br \/>\n\u63d0\u793a : \u5982\u679c\u60a8\u60f3\u66f4\u8be6\u7ec6\u5730\u4e86\u89e3\u53cd\u5c04\u53ca\u5176\u5728 .NET \u4e2d\u7684\u5de5\u4f5c\u539f\u7406\uff0c\u53ef\u4ee5\u5148\u6d4f\u89c8\u4ee5\u4e0b\u9875\u9762\uff1a<a href=\"https:\/\/docs.microsoft.com\/dotnet\/csharp\/programming-guide\/concepts\/reflection\">https:\/\/docs.microsoft.com\/dotnet\/csharp\/programming-guide\/concepts\/reflection<\/a>\u3002<\/p>\n<p>With all these pieces in place, the last thing to do is to call the extension method in the Program.cs file, before the Run() method:<br \/>\n\u5b8c\u6210\u6240\u6709\u8fd9\u4e9b\u90e8\u5206\u540e\uff0c\u6700\u540e\u8981\u505a\u7684\u662f\u5728 Run\uff08\uff09 \u65b9\u6cd5\u4e4b\u524d\u8c03\u7528 Program.cs \u6587\u4ef6\u4e2d\u7684\u6269\u5c55\u65b9\u6cd5\uff1a<\/p>\n<pre><code>app.MapEndpoints(Assembly.GetExecutingAssembly());\napp.Run();<\/code><\/pre>\n<p>In this way, when we add new handlers, we should only need to create a new class that implements the IEndpointRouteHandler interface. No other changes will be required in Program.cs to add the new endpoints to the routing engine.<br \/>\n\u8fd9\u6837\uff0c\u5f53\u6211\u4eec\u6dfb\u52a0\u65b0\u7684\u5904\u7406\u7a0b\u5e8f\u65f6\uff0c\u6211\u4eec\u5e94\u8be5\u53ea\u9700\u8981\u521b\u5efa\u4e00\u4e2a\u5b9e\u73b0 IEndpointRouteHandler \u63a5\u53e3\u7684\u65b0\u7c7b\u3002Program.cs \u4e2d\u65e0\u9700\u8fdb\u884c\u5176\u4ed6\u66f4\u6539\u5373\u53ef\u5c06\u65b0\u7ec8\u7aef\u8282\u70b9\u6dfb\u52a0\u5230\u8def\u7531\u5f15\u64ce\u3002<\/p>\n<p>Writing route handlers in external files and thinking about a way to automate endpoint registrations so that Program.cs won\u2019t grow for each feature addition is the right way to architect a minimal API project.<br \/>\n\u5728\u5916\u90e8\u6587\u4ef6\u4e2d\u7f16\u5199\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u5e76\u8003\u8651\u4e00\u79cd\u81ea\u52a8\u5316\u7ec8\u7aef\u8282\u70b9\u6ce8\u518c\u7684\u65b9\u6cd5\uff0c\u4ee5\u4fbfProgram.cs\u4e0d\u4f1a\u56e0\u6bcf\u4e2a\u529f\u80fd\u6dfb\u52a0\u800c\u589e\u957f\uff0c\u8fd9\u662f\u6784\u5efa\u6700\u5c0f API \u9879\u76ee\u7684\u6b63\u786e\u65b9\u6cd5\u3002<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>ASP.NET Core minimal APIs represent a new way of writing HTTP APIs in the .NET world. In this chapter, we covered all the pillars that we need to start developing minimal APIs, how to effectively approach them, and the best practices to take into consideration when deciding to follow this architecture.<br \/>\nASP.NET Core \u6700\u5c0f API \u4ee3\u8868\u4e86\u5728 .NET \u73af\u5883\u4e2d\u7f16\u5199 HTTP API \u7684\u4e00\u79cd\u65b0\u65b9\u6cd5\u3002\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u4ecb\u7ecd\u4e86\u5f00\u59cb\u5f00\u53d1\u6700\u5c0f API \u6240\u9700\u7684\u6240\u6709\u652f\u67f1\u3001\u5982\u4f55\u6709\u6548\u5730\u5904\u7406\u5b83\u4eec\uff0c\u4ee5\u53ca\u5728\u51b3\u5b9a\u9075\u5faa\u6b64\u67b6\u6784\u65f6\u8981\u8003\u8651\u7684\u6700\u4f73\u5b9e\u8df5\u3002<\/p>\n<p>In the next chapter, we\u2019ll focus on some advanced concepts such as documenting APIs with Swagger, defining a correct error handling system, and integrating a minimal API with a single-page application.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u91cd\u70b9\u4ecb\u7ecd\u4e00\u4e9b\u9ad8\u7ea7\u6982\u5ff5\uff0c\u4f8b\u5982\u4f7f\u7528 Swagger \u8bb0\u5f55 API\u3001\u5b9a\u4e49\u6b63\u786e\u7684\u9519\u8bef\u5904\u7406\u7cfb\u7edf\u4ee5\u53ca\u5c06\u6700\u5c0f API \u4e0e\u5355\u9875\u5e94\u7528\u7a0b\u5e8f\u96c6\u6210\u3002<\/p>\n<h1>3 Working with Minimal APIs<\/h1>\n<p>\u4f7f\u7528\u6700\u5c11\u7684 API<\/p>\n<p>In this chapter, we will try to apply some advanced development techniques available in earlier versions of .NET. We will touch on four common topics that are disjointed from each other.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u5c1d\u8bd5\u5e94\u7528\u65e9\u671f\u7248\u672c\u7684 .NET \u4e2d\u63d0\u4f9b\u7684\u4e00\u4e9b\u9ad8\u7ea7\u5f00\u53d1\u6280\u672f\u3002\u6211\u4eec\u5c06\u8ba8\u8bba\u56db\u4e2a\u5f7c\u6b64\u8131\u8282\u7684\u5e38\u89c1\u4e3b\u9898\u3002<\/p>\n<p>We\u2019ll cover productivity topics and best practices for frontend interfacing and configuration management.<br \/>\n\u6211\u4eec\u5c06\u4ecb\u7ecd\u524d\u7aef\u63a5\u53e3\u548c\u914d\u7f6e\u7ba1\u7406\u7684\u751f\u4ea7\u529b\u4e3b\u9898\u548c\u6700\u4f73\u5b9e\u8df5\u3002<\/p>\n<p>Every developer, sooner or later, will encounter the issues that we describe in this chapter. A programmer will have to write documentation for APIs, will have to make the API talk to a JavaScript frontend, will have to handle errors and try to fix them, and will have to configure the application according to parameters.<br \/>\n\u6bcf\u4e2a\u5f00\u53d1\u4eba\u5458\u8fdf\u65e9\u90fd\u4f1a\u9047\u5230\u6211\u4eec\u5728\u672c\u7ae0\u4e2d\u63cf\u8ff0\u7684\u95ee\u9898\u3002\u7a0b\u5e8f\u5458\u5fc5\u987b\u4e3a API \u7f16\u5199\u6587\u6863\uff0c\u5fc5\u987b\u4f7f API \u4e0e JavaScript \u524d\u7aef\u901a\u4fe1\uff0c\u5fc5\u987b\u5904\u7406\u9519\u8bef\u5e76\u5c1d\u8bd5\u4fee\u590d\u5b83\u4eec\uff0c\u5e76\u4e14\u5fc5\u987b\u6839\u636e\u53c2\u6570\u914d\u7f6e\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>The themes we will touch on in this chapter are as follows:<br \/>\n\u6211\u4eec\u5c06\u5728\u672c\u7ae0\u4e2d\u8ba8\u8bba\u7684\u4e3b\u9898\u5982\u4e0b\uff1a<\/p>\n<p>\u2022  Exploring Swagger<br \/>\n\u2022  Supporting CORS<br \/>\n\u2022  Working with global API settings<br \/>\n\u2022  Error handling<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>As reported in the previous chapters, it will be necessary to have the .NET 6 development framework available; you will also need to use .NET tools to run an in-memory web server.<br \/>\n\u5982\u524d\u51e0\u7ae0\u6240\u8ff0\uff0c\u6709\u5fc5\u8981\u63d0\u4f9b .NET 6 \u5f00\u53d1\u6846\u67b6;\u60a8\u8fd8\u9700\u8981\u4f7f\u7528 .NET \u5de5\u5177\u6765\u8fd0\u884c\u5185\u5b58\u4e2d\u7684 Web \u670d\u52a1\u5668\u3002<\/p>\n<p>To validate the functionality of cross-origin resource sharing (CORS), we should exploit a frontend application residing on a different HTTP address from the one where we will host the API.<br \/>\n\u4e3a\u4e86\u9a8c\u8bc1\u8de8\u57df\u8d44\u6e90\u5171\u4eab \uff08CORS\uff09 \u7684\u529f\u80fd\uff0c\u6211\u4eec\u5e94\u8be5\u5229\u7528\u9a7b\u7559\u5728\u4e0e\u6211\u4eec\u5c06\u6258\u7ba1 API \u7684 HTTP \u5730\u5740\u4e0d\u540c\u7684 HTTP \u5730\u5740\u4e0a\u7684\u524d\u7aef\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>To test the CORS example that we will propose within the chapter, we will take advantage of a web server in memory, which will allow us to host a simple static HTML page.<br \/>\n\u4e3a\u4e86\u6d4b\u8bd5\u6211\u4eec\u5c06\u5728\u672c\u7ae0\u4e2d\u63d0\u51fa\u7684 CORS \u793a\u4f8b\uff0c\u6211\u4eec\u5c06\u5229\u7528\u5185\u5b58\u4e2d\u7684 Web \u670d\u52a1\u5668\uff0c\u8fd9\u5c06\u5141\u8bb8\u6211\u4eec\u6258\u7ba1\u4e00\u4e2a\u7b80\u5355\u7684\u9759\u6001 HTML \u9875\u9762\u3002<\/p>\n<p>To host the web page (HTML and JavaScript), we will therefore use LiveReloadServer, which you can install as a .NET tool with the following command:<br \/>\n\u56e0\u6b64\uff0c\u4e3a\u4e86\u6258\u7ba1\u7f51\u9875\uff08HTML \u548c JavaScript\uff09\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 LiveReloadServer\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u5c06\u5176\u4f5c\u4e3a .NET \u5de5\u5177\u5b89\u88c5\uff1a<\/p>\n<pre><code>dotnet tool install -g LiveReloadServer<\/code><\/pre>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter03\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter03<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter03\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter03<\/a>\u3002<\/p>\n<p>Exploring Swagger<br \/>\n\u63a2\u7d22 Swagger<\/p>\n<p>Swagger has entered the life of .NET developers in a big way; it\u2019s been present on the project shelves for several versions of Visual Studio.<br \/>\nSwagger \u5df2\u7ecf\u5728\u5f88\u5927\u7a0b\u5ea6\u4e0a\u8fdb\u5165\u4e86 .NET \u5f00\u53d1\u4eba\u5458\u7684\u751f\u6d3b;\u5b83\u5df2\u51fa\u73b0\u5728\u591a\u4e2a\u7248\u672c\u7684 Visual Studio \u7684\u9879\u76ee\u67b6\u4e0a\u3002<\/p>\n<p>Swagger is a tool based on the OpenAPI specification and allows you to document APIs with a web application. According to the official documentation available at <a href=\"https:\/\/oai.github.io\/Documentation\/introduction.xhtml\">https:\/\/oai.github.io\/Documentation\/introduction.xhtml<\/a>:<br \/>\nSwagger \u662f\u57fa\u4e8e OpenAPI \u89c4\u8303\u7684\u5de5\u5177\uff0c\u5141\u8bb8\u60a8\u4f7f\u7528 Web \u5e94\u7528\u7a0b\u5e8f\u8bb0\u5f55 API\u3002\u6839\u636e <a href=\"https:\/\/oai.github.io\/Documentation\/introduction.xhtml\">https:\/\/oai.github.io\/Documentation\/introduction.xhtml<\/a> \u4e0a\u63d0\u4f9b\u7684\u5b98\u65b9\u6587\u6863\uff1a<\/p>\n<p>\u201cThe OpenAPI Specification allows the description of a remote API accessible through HTTP or HTTP-like protocols.<\/p>\n<p>An API defines the allowed interactions between two pieces of software, just like a user interface defines the ways in which a user can interact with a program.<br \/>\n\u201cOpenAPI \u89c4\u8303\u5141\u8bb8\u63cf\u8ff0\u53ef\u901a\u8fc7 HTTP \u6216\u7c7b\u4f3c HTTP \u7684\u534f\u8bae\u8bbf\u95ee\u7684\u8fdc\u7a0b API\u3002API \u5b9a\u4e49\u4e24\u4e2a\u8f6f\u4ef6\u4e4b\u95f4\u5141\u8bb8\u7684\u4ea4\u4e92\uff0c\u5c31\u50cf\u7528\u6237\u754c\u9762\u5b9a\u4e49\u7528\u6237\u4e0e\u7a0b\u5e8f\u4ea4\u4e92\u7684\u65b9\u5f0f\u4e00\u6837\u3002<\/p>\n<p>An API is composed of the list of possible methods to call (requests to make), their parameters, return values and any data format they require (among other things). This is equivalent to how a user\u2019s interactions with a mobile phone app are limited to the buttons, sliders and text boxes in the app\u2019s user interface.\u201d<br \/>\nAPI \u7531\u53ef\u80fd\u8c03\u7528\u7684\u65b9\u6cd5\u5217\u8868 \uff08\u53d1\u51fa\u7684\u8bf7\u6c42\uff09 \u3001\u5b83\u4eec\u7684\u53c2\u6570\u3001\u8fd4\u56de\u503c\u548c\u5b83\u4eec\u9700\u8981\u7684\u4efb\u4f55\u6570\u636e\u683c\u5f0f \uff08\u4ee5\u53ca\u5176\u4ed6\u5185\u5bb9\uff09 \u7ec4\u6210\u3002\u8fd9\u76f8\u5f53\u4e8e\u7528\u6237\u4e0e\u624b\u673a\u5e94\u7528\u7a0b\u5e8f\u7684\u4ea4\u4e92\u4ec5\u9650\u4e8e\u5e94\u7528\u7a0b\u5e8f\u7528\u6237\u754c\u9762\u4e2d\u7684\u6309\u94ae\u3001\u6ed1\u5757\u548c\u6587\u672c\u6846\u3002<\/p>\n<p>Swagger in the Visual Studio scaffold<br \/>\nVisual Studio \u57fa\u67b6\u4e2d\u7684 Swagger<\/p>\n<p>We understand then that Swagger, as we know it in the .NET world, is nothing but a set of specifications defined for all applications that expose web-based APIs:<br \/>\n\u7136\u540e\u6211\u4eec\u660e\u767d\uff0c\u6b63\u5982\u6211\u4eec\u5728 .NET \u4e16\u754c\u4e2d\u6240\u77e5\u9053\u7684\u90a3\u6837\uff0cSwagger \u53ea\u4e0d\u8fc7\u662f\u4e3a\u516c\u5f00\u57fa\u4e8e Web \u7684 API \u7684\u6240\u6709\u5e94\u7528\u7a0b\u5e8f\u5b9a\u4e49\u7684\u4e00\u7ec4\u89c4\u8303\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0301.jpg\" ><\/p>\n<p>Figure 3.1 \u2013 Visual Studio scaffold<\/p>\n<p>By selecting Enable OpenAPI support, Visual Studio goes to add a NuGet package called Swashbuckle.AspNetCore and automatically configures it in the Program.cs file.<br \/>\n\u901a\u8fc7\u9009\u62e9\u201c\u542f\u7528 OpenAPI \u652f\u6301\u201d\uff0cVisual Studio \u5c06\u6dfb\u52a0\u4e00\u4e2a\u540d\u4e3a Swashbuckle.AspNetCore \u7684 NuGet \u5305\uff0c\u5e76\u81ea\u52a8\u5728 Program.cs \u6587\u4ef6\u4e2d\u5bf9\u5176\u8fdb\u884c\u914d\u7f6e\u3002<\/p>\n<p>We show the few lines that are added with a new project. With these few pieces of information, a web application is enabled only for the development environment, which allows the developer to test the API without generating a client or using tools external to the application:<br \/>\n\u6211\u4eec\u663e\u793a\u4e86\u968f\u65b0\u9879\u76ee\u6dfb\u52a0\u7684\u51e0\u884c\u3002\u6709\u4e86\u8fd9\u51e0\u6761\u4fe1\u606f\uff0cWeb \u5e94\u7528\u7a0b\u5e8f\u4ec5\u9488\u5bf9\u5f00\u53d1\u73af\u5883\u542f\u7528\uff0c\u8fd9\u5141\u8bb8\u5f00\u53d1\u4eba\u5458\u5728\u4e0d\u751f\u6210\u5ba2\u6237\u7aef\u6216\u4f7f\u7528\u5e94\u7528\u7a0b\u5e8f\u5916\u90e8\u5de5\u5177\u7684\u60c5\u51b5\u4e0b\u6d4b\u8bd5 API\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen();\nvar app = builder.Build();\nif (app.Environment.IsDevelopment())\n{\n    app.UseSwagger();\n    app.UseSwaggerUI();\n}<\/code><\/pre>\n<p>The graphical part generated by Swagger greatly increases productivity and allows the developer to share information with those who will interface with the application, be it a frontend application or a machine application.<br \/>\nSwagger \u751f\u6210\u7684\u56fe\u5f62\u90e8\u5206\u5927\u5927\u63d0\u9ad8\u4e86\u751f\u4ea7\u529b\uff0c\u5e76\u5141\u8bb8\u5f00\u53d1\u4eba\u5458\u4e0e\u5c06\u4e0e\u5e94\u7528\u7a0b\u5e8f\u4ea4\u4e92\u7684\u4eba\u5458\u5171\u4eab\u4fe1\u606f\uff0c\u65e0\u8bba\u662f\u524d\u7aef\u5e94\u7528\u7a0b\u5e8f\u8fd8\u662f\u673a\u5668\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>Note : We remind you that enabling Swagger in a production environment is strongly discouraged because sensitive information could be publicly exposed on the web or on the network where the application resides.<br \/>\n\u6ce8\u610f : \u6211\u4eec\u63d0\u9192\u60a8\uff0c\u5f3a\u70c8\u5efa\u8bae\u4e0d\u8981\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u542f\u7528 Swagger\uff0c\u56e0\u4e3a\u654f\u611f\u4fe1\u606f\u53ef\u80fd\u4f1a\u5728 Web \u6216\u5e94\u7528\u7a0b\u5e8f\u6240\u5728\u7684\u7f51\u7edc\u4e0a\u516c\u5f00\u66b4\u9732\u3002<\/p>\n<p>We have seen how to introduce Swagger into our API applications; this functionality allows us to document our API, as well as allow users to generate a client to call our application. Let\u2019s see the options we have to quickly interface an application with APIs described with OpenAPI.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u4e86\u89e3\u4e86\u5982\u4f55\u5c06 Swagger \u5f15\u5165\u6211\u4eec\u7684 API \u5e94\u7528\u7a0b\u5e8f;\u6b64\u529f\u80fd\u5141\u8bb8\u6211\u4eec\u8bb0\u5f55\u6211\u4eec\u7684 API\uff0c\u5e76\u5141\u8bb8\u7528\u6237\u751f\u6210\u5ba2\u6237\u7aef\u6765\u8c03\u7528\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u3002\u8ba9\u6211\u4eec\u770b\u770b\u6211\u4eec\u5fc5\u987b\u9009\u62e9\u54ea\u4e9b\u9009\u9879\u6765\u5feb\u901f\u5c06\u5e94\u7528\u7a0b\u5e8f\u4e0e OpenAPI \u4e2d\u63cf\u8ff0\u7684 API \u8fde\u63a5\u8d77\u6765\u3002<\/p>\n<p>OpenAPI Generator<br \/>\nOpenAPI \u751f\u6210\u5668<\/p>\n<p>With Swagger, and especially with the OpenAPI standard, you can automatically generate clients to connect to the web application. Clients can be generated for many languages but also for development tools. We know how tedious and repetitive it is to write clients to access the Web API. Open API Generator helps us automate code generation, inspect the API documentation made by Swagger and OpenAPI, and automatically generate code to interface with the API. Simple, easy, and above all, fast.<br \/>\n\u4f7f\u7528 Swagger\uff0c\u5c24\u5176\u662f OpenAPI \u6807\u51c6\uff0c\u60a8\u53ef\u4ee5\u81ea\u52a8\u751f\u6210\u5ba2\u6237\u7aef\u4ee5\u8fde\u63a5\u5230 Web \u5e94\u7528\u7a0b\u5e8f\u3002\u53ef\u4ee5\u4e3a\u591a\u79cd\u8bed\u8a00\u751f\u6210\u5ba2\u6237\u7aef\uff0c\u4e5f\u53ef\u4ee5\u4e3a\u5f00\u53d1\u5de5\u5177\u751f\u6210\u5ba2\u6237\u7aef\u3002\u6211\u4eec\u77e5\u9053\u7f16\u5199\u5ba2\u6237\u7aef\u6765\u8bbf\u95ee Web API \u662f\u591a\u4e48\u4e4f\u5473\u548c\u91cd\u590d\u3002Open API Generator \u5e2e\u52a9\u6211\u4eec\u81ea\u52a8\u751f\u6210\u4ee3\u7801\uff0c\u68c0\u67e5 Swagger \u548c OpenAPI \u5236\u4f5c\u7684 API \u6587\u6863\uff0c\u5e76\u81ea\u52a8\u751f\u6210\u4ee3\u7801\u4ee5\u4e0e API \u4ea4\u4e92\u3002\u7b80\u5355\u3001\u8f7b\u677e\uff0c\u6700\u91cd\u8981\u7684\u662f\uff0c\u5feb\u901f\u3002<\/p>\n<p>The @openapitools\/openapi-generator-cli npm package is a very well-known package wrapper for OpenAPI Generator, which you can find at <a href=\"https:\/\/openapi-generator.tech\/\">https:\/\/openapi-generator.tech\/<\/a>.<br \/>\n@openapitools\/openapi-generator-cli npm \u5305\u662f OpenAPI \u751f\u6210\u5668\u7684\u4e00\u4e2a\u975e\u5e38\u77e5\u540d\u7684\u5305\u5305\u88c5\u5668\uff0c\u60a8\u53ef\u4ee5\u5728 <a href=\"https:\/\/openapi-generator.tech\/\">https:\/\/openapi-generator.tech\/<\/a> \u4e2d\u627e\u5230\u5b83\u3002<\/p>\n<p>With this tool, you can generate clients for programming languages as well as load testing tools such as JMeter and K6.<br \/>\n\u4f7f\u7528\u6b64\u5de5\u5177\uff0c\u60a8\u53ef\u4ee5\u4e3a\u7f16\u7a0b\u8bed\u8a00\u751f\u6210\u5ba2\u6237\u7aef\u4ee5\u53ca JMeter \u548c K6 \u7b49\u8d1f\u8f7d\u6d4b\u8bd5\u5de5\u5177\u3002<\/p>\n<p>It is not necessary to install the tool on your machine, but if the URL of the application is accessible from the machine, you can use a Docker image, as described by the following command:<br \/>\n\u65e0\u9700\u5728\u8ba1\u7b97\u673a\u4e0a\u5b89\u88c5\u8be5\u5de5\u5177\uff0c\u4f46\u5982\u679c\u53ef\u4ee5\u4ece\u8ba1\u7b97\u673a\u8bbf\u95ee\u5e94\u7528\u7a0b\u5e8f\u7684 URL\uff0c\u5219\u53ef\u4ee5\u4f7f\u7528 Docker \u6620\u50cf\uff0c\u5982\u4ee5\u4e0b\u547d\u4ee4\u6240\u8ff0\uff1a<\/p>\n<pre><code>docker run --rm \\\n\n    -v ${PWD}:\/local openapitools\/openapi-generator-cli generate \\\n\n    -i \/local\/petstore.yaml \\\n\n    -g go \\\n\n    -o \/local\/out\/go<\/code><\/pre>\n<p>The command allows you to generate a Go client using the OpenAPI definition found in the petstore.yaml file that is mounted on the Docker volume.<br \/>\n\u8be5\u547d\u4ee4\u5141\u8bb8\u60a8\u4f7f\u7528\u6302\u8f7d\u5728 Docker \u5377\u4e0a\u7684 petstore.yaml \u6587\u4ef6\u4e2d\u627e\u5230\u7684 OpenAPI \u5b9a\u4e49\u751f\u6210 Go \u5ba2\u6237\u7aef\u3002<\/p>\n<p>Now, let\u2019s go into detail to understand how you can leverage Swagger in .NET 6 projects and with minimal APIs.<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u8be6\u7ec6\u4ecb\u7ecd\u5982\u4f55\u5728 .NET 6 \u9879\u76ee\u4e2d\u5229\u7528 Swagger \u5e76\u4f7f\u7528\u6700\u5c11\u7684 API\u3002<\/p>\n<p>Swagger in minimal APIs<br \/>\n\u5728\u6700\u5c11\u7684 API \u4e2d\u4f7f\u7528Swagger<\/p>\n<p>In ASP.NET Web API, as in the following code excerpt, we see a method documented with C# language annotations with the triple slash (\/\/\/).<br \/>\n\u5728 Web API ASP.NET\uff0c\u5982\u4ee5\u4e0b\u4ee3\u7801\u6458\u5f55\u6240\u793a\uff0c\u6211\u4eec\u770b\u5230\u4e00\u4e2a\u4f7f\u7528\u5e26\u6709\u4e09\u659c\u6760 \uff08\uff09 \u7684 C# \u8bed\u8a00\u6ce8\u91ca\u8bb0\u5f55\u7684\u65b9\u6cd5\u3002<\/p>\n<p>The documentation section is leveraged to add more information to the API description. In addition, the ProducesResponseType annotations help Swagger identify the possible codes that the client must handle as a result of the method call:<br \/>\n\u5229\u7528 documentation \u90e8\u5206\u5411 API \u63cf\u8ff0\u6dfb\u52a0\u66f4\u591a\u4fe1\u606f\u3002\u6b64\u5916\uff0cProducesResponseType \u6ce8\u91ca\u53ef\u5e2e\u52a9 Swagger \u8bc6\u522b\u5ba2\u6237\u7aef\u5728\u65b9\u6cd5\u8c03\u7528\u540e\u5fc5\u987b\u5904\u7406\u7684\u53ef\u80fd\u4ee3\u7801\uff1a<\/p>\n<pre><code>\/\/\/ &lt;summary&gt;\n\/\/\/ Creates a Contact.\n\/\/\/ &lt;\/summary&gt;\n\/\/\/ &lt;param name=&quot;contact&quot;&gt;&lt;\/param&gt;\n\/\/\/ &lt;returns&gt;A newly created Contact&lt;\/returns&gt;\n\/\/\/ &lt;response code=&quot;201&quot;&gt;Returns the newly created contact&lt;\/response&gt;\n\/\/\/ &lt;response code=&quot;400&quot;&gt;If the contact is null&lt;\/response&gt;\n[HttpPost]\n[ProducesResponseType(StatusCodes.Status201Created)]\n[ProducesResponseType(StatusCodes.Status400BadRequest)]\npublic async Task&lt;IActionResult&gt; Create(Contact contactItem)\n{\n     _context.Contacts.Add(contactItem);\n     await _context.SaveChangesAsync();\n     return CreatedAtAction(nameof(Get), new { id = \n     contactItem.Id }, contactItem);\n}<\/code><\/pre>\n<p>Swagger, in addition to the annotations on single methods, is also instructed by the documentation of the language to give further information to those who will then have to use the API application. A description of the methods of the parameters is always welcome by those who will have to interface; unfortunately, it is not possible to exploit this functionality in the minimal API.<br \/>\n\u9664\u4e86\u5355\u4e2a\u65b9\u6cd5\u7684\u6ce8\u91ca\u5916\uff0c\u8be5\u8bed\u8a00\u7684\u6587\u6863\u8fd8\u6307\u793a Swagger \u4e3a\u90a3\u4e9b\u968f\u540e\u5fc5\u987b\u4f7f\u7528 API \u5e94\u7528\u7a0b\u5e8f\u7684\u4eba\u63d0\u4f9b\u66f4\u591a\u4fe1\u606f\u3002\u5bf9\u53c2\u6570\u65b9\u6cd5\u7684\u63cf\u8ff0\u603b\u662f\u53d7\u5230\u90a3\u4e9b\u5fc5\u987b\u8fdb\u884c\u63a5\u53e3\u7684\u4eba\u7684\u6b22\u8fce;\u9057\u61be\u7684\u662f\uff0c\u65e0\u6cd5\u5728\u6700\u5c0f API \u4e2d\u5229\u7528\u6b64\u529f\u80fd\u3002<\/p>\n<p>Let\u2019s go in order and see how to start using Swagger on a single method:<br \/>\n\u8ba9\u6211\u4eec\u6309\u987a\u5e8f\u6765\u770b\u770b\u5982\u4f55\u5728\u5355\u4e2a\u65b9\u6cd5\u4e0a\u5f00\u59cb\u4f7f\u7528 Swagger\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen(c =&gt;\n{\n    c.SwaggerDoc(&quot;v1&quot;, new() \n    { \n        Title = builder.Environment.ApplicationName,\n        Version = &quot;v1&quot;, Contact = new() \n        { Name = &quot;PacktAuthor&quot;, Email = &quot;authors@packtpub.com&quot;,\n          Url = new Uri(&quot;https:\/\/www.packtpub.com\/&quot;) },\n          Description = &quot;PacktPub Minimal API - Swagger&quot;,\n          License = new Microsoft.OpenApi.Models.\n            OpenApiLicense(),\n          TermsOfService = new(&quot;https:\/\/www.packtpub.com\/&quot;)\n});\n});\nvar app = builder.Build();\nif (app.Environment.IsDevelopment())\n{\n    app.UseSwagger();\n    app.UseSwaggerUI();\n}<\/code><\/pre>\n<p>With this first example, we have configured Swagger and general Swagger information. We have included additional information that enriches Swagger\u2019s UI. The only mandatory information is the title, while the version, contact, description, license, and terms of service are optional.<br \/>\n\u5728\u7b2c\u4e00\u4e2a\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u914d\u7f6e\u4e86 Swagger \u548c\u5e38\u89c4 Swagger \u4fe1\u606f\u3002\u6211\u4eec\u6dfb\u52a0\u4e86\u4e30\u5bcc Swagger UI \u7684\u5176\u4ed6\u4fe1\u606f\u3002\u552f\u4e00\u7684\u5fc5\u586b\u4fe1\u606f\u662f\u6807\u9898\uff0c\u800c\u7248\u672c\u3001\u8054\u7cfb\u4eba\u3001\u63cf\u8ff0\u3001\u8bb8\u53ef\u8bc1\u548c\u670d\u52a1\u6761\u6b3e\u662f\u53ef\u9009\u7684\u3002<\/p>\n<p>The UseSwaggerUI() method automatically configures where to put the UI and the JSON file describing the API with the OpenAPI format.<br \/>\nUseSwaggerUI\uff08\uff09 \u65b9\u6cd5\u81ea\u52a8\u914d\u7f6e\u653e\u7f6e UI \u548c\u63cf\u8ff0 OpenAPI \u683c\u5f0f API \u7684 JSON \u6587\u4ef6\u7684\u4f4d\u7f6e\u3002<\/p>\n<p>Here is the result at the graphical level:<br \/>\n\u8fd9\u662f\u56fe\u5f62\u7ea7\u522b\u7684\u7ed3\u679c\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0302.jpg\" ><\/p>\n<p>Figure 3.2 \u2013 The Swagger UI<\/p>\n<p>We can immediately see that the OpenAPI contract information has been placed in the \/swagger\/v1\/swagger.json path.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u7acb\u5373\u770b\u5230 OpenAPI \u5408\u7ea6\u4fe1\u606f\u5df2\u7ecf\u653e\u5728 \/swagger\/v1\/swagger.json \u8def\u5f84\u4e0b\u3002<\/p>\n<p>The contact information is populated, but no operations are reported as we haven\u2019t entered any yet. Should the API have versioning? In the top-right section, we can select the available operations for each version.<br \/>\n\u8054\u7cfb\u4fe1\u606f\u5df2\u586b\u5145\uff0c\u4f46\u672a\u62a5\u544a\u4efb\u4f55\u4f5c\uff0c\u56e0\u4e3a\u6211\u4eec\u5c1a\u672a\u8f93\u5165\u4efb\u4f55\u4f5c\u3002API \u5e94\u8be5\u6709\u7248\u672c\u63a7\u5236\u5417\uff1f\u5728\u53f3\u4e0a\u89d2\uff0c\u6211\u4eec\u53ef\u4ee5\u4e3a\u6bcf\u4e2a\u7248\u672c\u9009\u62e9\u53ef\u7528\u7684\u4f5c\u3002<\/p>\n<p>We can customize the Swagger URL and insert the documentation on a new path; the important thing is to redefine SwaggerEndpoint, as follows:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u81ea\u5b9a\u4e49 Swagger URL \u5e76\u5c06\u6587\u6863\u63d2\u5165\u5230\u65b0\u8def\u5f84\u4e0a;\u91cd\u8981\u7684\u662f\u91cd\u65b0\u5b9a\u4e49 SwaggerEndpoint\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>app.UseSwaggerUI(c =&gt; c.SwaggerEndpoint(&quot;\/swagger\/v1\/swagger.json&quot;, $&quot;{builder.Environment.ApplicationName} v1&quot;));<\/code><\/pre>\n<p>Let\u2019s now go on to add the endpoints that describe the business logic.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u7ee7\u7eed\u6dfb\u52a0\u63cf\u8ff0\u4e1a\u52a1\u903b\u8f91\u7684\u7ec8\u7aef\u8282\u70b9\u3002<\/p>\n<p>It is very important to define RouteHandlerBuilder because it allows us to describe all the properties of the endpoint that we have written in code.<br \/>\n\u5b9a\u4e49 RouteHandlerBuilder \u975e\u5e38\u91cd\u8981\uff0c\u56e0\u4e3a\u5b83\u5141\u8bb8\u6211\u4eec\u63cf\u8ff0\u6211\u4eec\u5728\u4ee3\u7801\u4e2d\u7f16\u5199\u7684\u7aef\u70b9\u7684\u6240\u6709\u5c5e\u6027\u3002<\/p>\n<p>The UI of Swagger must be enriched as much as possible; we must describe at best what the minimal APIs allow us to specify. Unfortunately, not all the functionalities are available, as in ASP.NET Web API.<br \/>\n\u5fc5\u987b\u5c3d\u53ef\u80fd\u4e30\u5bcc Swagger \u7684 UI;\u6211\u4eec\u6700\u591a\u53ea\u80fd\u63cf\u8ff0\u6700\u5c0f API \u5141\u8bb8\u6211\u4eec\u6307\u5b9a\u7684\u5185\u5bb9\u3002\u9057\u61be\u7684\u662f\uff0c\u5e76\u975e\u6240\u6709\u529f\u80fd\u90fd\u53ef\u7528\uff0c\u5c31\u50cf ASP.NET Web API \u4e00\u6837\u3002<\/p>\n<p>Versioning in minimal APIs<br \/>\n\u5728\u6700\u5c11\u7684 API \u4e2d\u8fdb\u884c\u7248\u672c\u63a7\u5236<\/p>\n<p>Versioning in minimal APIs is not handled in the framework functionality; as a result, even Swagger cannot handle UI-side API versioning. So, we observe that when we go to the Select a definition section shown in Figure 3.2, only one entry for the current version of the API is visible.<br \/>\n\u6700\u5c0f API \u4e2d\u7684\u7248\u672c\u63a7\u5236\u4e0d\u5728\u6846\u67b6\u529f\u80fd\u4e2d\u5904\u7406;\u56e0\u6b64\uff0c\u5373\u4f7f\u662f Swagger \u4e5f\u65e0\u6cd5\u5904\u7406 UI \u7aef API \u7248\u672c\u63a7\u5236\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u89c2\u5bdf\u5230\uff0c\u5f53\u6211\u4eec\u8f6c\u5230\u56fe 3.2 \u6240\u793a\u7684 Select a definition \u90e8\u5206\u65f6\uff0c\u53ea\u6709\u5f53\u524d\u7248\u672c API \u7684\u4e00\u4e2a\u6761\u76ee\u53ef\u89c1\u3002<\/p>\n<p>Swagger features<br \/>\nSwagger \u529f\u80fd<\/p>\n<p>We just realized that not all features are available in Swagger; let\u2019s now explore what is available instead. To describe the possible output values of an endpoint, we can call functions that can be called after the handler, such as the Produces or WithTags functions, which we are now going to explore.<br \/>\n\u6211\u4eec\u521a\u521a\u610f\u8bc6\u5230\u5e76\u975e\u6240\u6709\u529f\u80fd\u5728 Swagger \u4e2d\u90fd\u53ef\u7528;\u73b0\u5728\u8ba9\u6211\u4eec\u6765\u63a2\u7d22\u4e00\u4e0b\u53ef\u7528\u7684\u5185\u5bb9\u3002\u4e3a\u4e86\u63cf\u8ff0\u7ec8\u7aef\u8282\u70b9\u7684\u53ef\u80fd\u8f93\u51fa\u503c\uff0c\u6211\u4eec\u53ef\u4ee5\u8c03\u7528\u53ef\u4ee5\u5728\u5904\u7406\u7a0b\u5e8f\u4e4b\u540e\u8c03\u7528\u7684\u51fd\u6570\uff0c\u4f8b\u5982 Produces \u6216 WithTags \u51fd\u6570\uff0c\u6211\u4eec\u73b0\u5728\u5c06\u63a2\u8ba8\u8fd9\u4e9b\u51fd\u6570\u3002<\/p>\n<p>The Produces function decorates the endpoint with all the possible responses that the client should be able to manage. We can add the name of the operation ID; this information will not appear in the Swagger screen, but it will be the name with which the client will create the method to call the endpoint. OperationId is the unique name of the operation made available by the handler.<br \/>\nProduces \u51fd\u6570\u4f7f\u7528\u5ba2\u6237\u7aef\u5e94\u8be5\u80fd\u591f\u7ba1\u7406\u7684\u6240\u6709\u53ef\u80fd\u7684\u54cd\u5e94\u6765\u88c5\u9970\u7ec8\u7aef\u8282\u70b9\u3002\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u4f5c ID \u7684\u540d\u79f0;\u6b64\u4fe1\u606f\u4e0d\u4f1a\u663e\u793a\u5728 Swagger \u5c4f\u5e55\u4e2d\uff0c\u4f46\u5b83\u5c06\u662f\u5ba2\u6237\u7aef\u521b\u5efa\u8c03\u7528\u7ec8\u7ed3\u70b9\u7684\u65b9\u6cd5\u65f6\u4f7f\u7528\u7684\u540d\u79f0\u3002OperationId \u662f\u5904\u7406\u7a0b\u5e8f\u53ef\u7528\u7684\u4f5c\u7684\u552f\u4e00\u540d\u79f0\u3002<\/p>\n<p>To exclude an endpoint from the API description, you need to call ExcludeFromDescription(). This function is rarely used, but it is very useful in cases where you don\u2019t want to expose endpoints to programmers who are developing the frontend because that particular endpoint is used by a machine application.<br \/>\n\u8981\u4ece API \u63cf\u8ff0\u4e2d\u6392\u9664\u7ec8\u7aef\u8282\u70b9\uff0c\u60a8\u9700\u8981\u8c03\u7528 ExcludeFromDescription\uff08\uff09\u3002\u6b64\u51fd\u6570\u5f88\u5c11\u4f7f\u7528\uff0c\u4f46\u5728\u60a8\u4e0d\u60f3\u5c06\u7aef\u70b9\u516c\u5f00\u7ed9\u6b63\u5728\u5f00\u53d1\u524d\u7aef\u7684\u7a0b\u5e8f\u5458\u7684\u60c5\u51b5\u4e0b\uff0c\u5b83\u975e\u5e38\u6709\u7528\uff0c\u56e0\u4e3a\u8be5\u7279\u5b9a\u7aef\u70b9\u7531\u673a\u5668\u5e94\u7528\u7a0b\u5e8f\u4f7f\u7528\u3002<\/p>\n<p>Finally, we can add and tag the various endpoints and segment them for better client management:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u548c\u6807\u8bb0\u5404\u79cd\u7ec8\u7aef\u8282\u70b9\uff0c\u5e76\u5bf9\u5176\u8fdb\u884c\u7ec6\u5206\u4ee5\u66f4\u597d\u5730\u7ba1\u7406\u5ba2\u6237\u7aef\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/sampleresponse&quot;, () =&gt;\n    {\n        return Results.Ok(new ResponseData(&quot;My Response&quot;));\n    })\n    .Produces&lt;ResponseData&gt;(StatusCodes.Status200OK)\n    .WithTags(&quot;Sample&quot;)\n    .WithName(&quot;SampleResponseOperation&quot;); \/\/ operation ids to \n                                             Open API\napp.MapGet(&quot;\/sampleresponseskipped&quot;, () =&gt;\n{\n    return Results.Ok(new ResponseData(&quot;My Response Skipped&quot;));\n})\n    .ExcludeFromDescription();\napp.MapGet(&quot;\/{id}&quot;, (int id) =&gt; Results.Ok(id));\napp.MapPost(&quot;\/&quot;, (ResponseData data) =&gt; Results.Ok(data))\n   .Accepts&lt;ResponseData&gt;(MediaTypeNames.Application.Json);<\/code><\/pre>\n<p>This is the graphical result of Swagger; as I anticipated earlier, the tags and operation IDs are not shown by the web client:<br \/>\n\u8fd9\u662f Swagger \u7684\u56fe\u5f62\u7ed3\u679c;\u6b63\u5982\u6211\u4e4b\u524d\u6240\u9884\u6599\u7684\u90a3\u6837\uff0cWeb \u5ba2\u6237\u7aef\u4e0d\u4f1a\u663e\u793a\u6807\u7b7e\u548c\u4f5c ID\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0303.jpg\" ><\/p>\n<p>Figure 3.3 \u2013 Swagger UI methods<br \/>\n\u56fe 3.3 \u2013 Swagger UI \u65b9\u6cd5<\/p>\n<p>The endpoint description, on the other hand, is very useful to include. It\u2019s very easy to implement: just insert C# comments in the method (just insert three slashes, \/\/\/, in the method). Minimal APIs don\u2019t have methods like we are used to in web-based controllers, so they are not natively supported.<br \/>\n\u53e6\u4e00\u65b9\u9762\uff0c\u7ec8\u7aef\u8282\u70b9\u63cf\u8ff0\u975e\u5e38\u6709\u7528\u3002\u8fd9\u5f88\u5bb9\u6613\u5b9e\u73b0\uff1a\u53ea\u9700\u5728\u65b9\u6cd5\u4e2d\u63d2\u5165 C# \u6ce8\u91ca\uff08\u53ea\u9700\u5728\u65b9\u6cd5\u4e2d\u63d2\u5165\u4e09\u4e2a\u659c\u6760 \uff0c \u5373\u53ef\uff09\u3002Minimal API \u6ca1\u6709\u6211\u4eec\u5728\u57fa\u4e8e Web \u7684\u63a7\u5236\u5668\u4e2d\u4e60\u60ef\u7684\u65b9\u6cd5\uff0c\u56e0\u6b64\u5b83\u4eec\u672c\u8eab\u4e0d\u53d7\u652f\u6301\u3002<\/p>\n<p>Swagger isn\u2019t just the GUI we\u2019re used to seeing. Above all, Swagger is the JSON file that supports the OpenAPI specification, of which the latest version is 3.1.0.<br \/>\nSwagger \u4e0d\u4ec5\u4ec5\u662f\u6211\u4eec\u4e60\u60ef\u770b\u5230\u7684 GUI\u3002\u9996\u5148\uff0cSwagger \u662f\u652f\u6301 OpenAPI \u89c4\u8303\u7684 JSON \u6587\u4ef6\uff0c\u6700\u65b0\u7248\u672c\u4e3a 3.1.0\u3002<\/p>\n<p>In the following snippet, we show the section containing the description of the first endpoint that we inserted in the API. We can infer both the tag and the operation ID; this information will be used by those who will interface with the API:<br \/>\n\u5728\u4ee5\u4e0b\u4ee3\u7801\u6bb5\u4e2d\uff0c\u6211\u4eec\u663e\u793a\u4e86\u5305\u542b\u6211\u4eec\u5728 API \u4e2d\u63d2\u5165\u7684\u7b2c\u4e00\u4e2a\u7ec8\u7aef\u8282\u70b9\u7684\u63cf\u8ff0\u7684\u90e8\u5206\u3002\u6211\u4eec\u53ef\u4ee5\u63a8\u65ad tag \u548c\u4f5c ID;\u6b64\u4fe1\u606f\u5c06\u7531\u5c06\u4e0e API \u4ea4\u4e92\u7684\u4eba\u5458\u4f7f\u7528\uff1a<\/p>\n<pre><code>&quot;paths&quot;: {\n         &quot;\/sampleresponse&quot;: {\n              &quot;get&quot;: {\n                   &quot;tags&quot;: [\n                        &quot;Sample&quot;\n                   ],\n                   &quot;operationId&quot;: &quot;SampleResponseOperation&quot;,\n                   &quot;responses&quot;: {\n                        &quot;200&quot;: {\n                             &quot;description&quot;: &quot;Success&quot;,\n                             &quot;content&quot;: {\n                                  &quot;application\/json&quot;: {\n                                       &quot;schema&quot;: {\n                                            &quot;$ref&quot;: &quot;#\/components\/schemas\/ResponseData&quot;\n                                       }\n                                  }\n                             }\n                        }\n                   }\n              }\n         },<\/code><\/pre>\n<p>In this section, we have seen how to configure Swagger and what is currently not yet supported.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u5982\u4f55\u914d\u7f6e Swagger \u4ee5\u53ca\u5f53\u524d\u5c1a\u4e0d\u652f\u6301\u7684\u5185\u5bb9\u3002<\/p>\n<p>In the following chapters, we will also see how to configure OpenAPI, both for the OpenID Connect standard and authentication via the API key.<br \/>\n\u5728\u63a5\u4e0b\u6765\u7684\u7ae0\u8282\u4e2d\uff0c\u6211\u4eec\u8fd8\u5c06\u4e86\u89e3\u5982\u4f55\u914d\u7f6e OpenAPI\uff0c\u5305\u62ec OpenID Connect \u6807\u51c6\u548c\u901a\u8fc7 API \u5bc6\u94a5\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1\u3002<\/p>\n<p>In the preceding code snippet of the Swagger UI, Swagger makes the schematics of the objects involved available, both inbound to the various endpoints and outbound from them.<br \/>\n\u5728 Swagger UI \u7684\u524d\u9762\u7684\u4ee3\u7801\u7247\u6bb5\u4e2d\uff0cSwagger \u4f7f\u6240\u6d89\u53ca\u5bf9\u8c61\u7684\u793a\u610f\u56fe\u53ef\u7528\uff0c\u5305\u62ec\u5165\u7ad9\u5230\u5404\u4e2a\u7aef\u70b9\u548c\u4ece\u5b83\u4eec\u51fa\u7ad9\u7684\u793a\u610f\u56fe\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0304.jpg\" ><\/p>\n<p>Figure 3.4 \u2013 Input and output data schema<br \/>\n\u56fe 3.4 \u2013 \u8f93\u5165\u548c\u8f93\u51fa\u6570\u636e\u67b6\u6784<\/p>\n<p>We will learn how to deal with these objects and how to validate and define them in Chapter 6, Exploring Validation and Mapping.<br \/>\n\u6211\u4eec\u5c06\u5728\u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04 \u4e2d\u5b66\u4e60\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u5bf9\u8c61\u4ee5\u53ca\u5982\u4f55\u9a8c\u8bc1\u548c\u5b9a\u4e49\u5b83\u4eec\u3002<\/p>\n<p>Swagger OperationFilter<br \/>\nSwagger OperationFilter<\/p>\n<p>The operation filter allows you to add behavior to all operations shown by Swagger. In the following example, we\u2019ll show you how to add an HTTP header to a particular call, filtering it by OperationId.<br \/>\n\u4f5c\u7b5b\u9009\u5668\u5141\u8bb8\u60a8\u5411 Swagger \u663e\u793a\u7684\u6240\u6709\u4f5c\u6dfb\u52a0\u884c\u4e3a\u3002\u5728\u4ee5\u4e0b\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5c06\u5411\u60a8\u5c55\u793a\u5982\u4f55\u5411\u7279\u5b9a\u8c03\u7528\u6dfb\u52a0 HTTP \u6807\u5934\uff0c\u5e76\u6309 OperationId \u5bf9\u5176\u8fdb\u884c\u7b5b\u9009\u3002<\/p>\n<p>When you go to define an operation filter, you can also set filters based on routes, tags, and operation IDs:<br \/>\n\u5728\u5b9a\u4e49\u4f5c\u7b5b\u9009\u6761\u4ef6\u65f6\uff0c\u60a8\u8fd8\u53ef\u4ee5\u6839\u636e\u8def\u7531\u3001\u6807\u7b7e\u548c\u4f5c ID \u8bbe\u7f6e\u7b5b\u9009\u6761\u4ef6\uff1a<\/p>\n<pre><code>public class CorrelationIdOperationFilter : IOperationFilter\n{\n    private readonly IWebHostEnvironment environment;\n    public CorrelationIdOperationFilter(IWebHostEnvironment \n    environment)\n    {\n        this.environment = environment;\n    }\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Apply header in parameter Swagger.\n    \/\/\/ We add default value in parameter for developer \n        environment\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;operation&quot;&gt;&lt;\/param&gt;\n    \/\/\/ &lt;param name=&quot;context&quot;&gt;&lt;\/param&gt;\n    public void Apply(OpenApiOperation operation, \n    OperationFilterContext context)\n    {\n        if (operation.Parameters == null)\n        {\n            operation.Parameters = new \n            List&lt;OpenApiParameter&gt;();\n        }\n        if (operation.OperationId == \n            &quot;SampleResponseOperation&quot;)\n        {\n             operation.Parameters.Add(new OpenApiParameter\n             {\n                 Name = &quot;x-correlation-id&quot;,\n                 In = ParameterLocation.Header,\n                 Required = false,\n                 Schema = new OpenApiSchema { Type = \n                 &quot;String&quot;, Default = new OpenApiString(&quot;42&quot;) }\n             });\n        }\n         }\n}<\/code><\/pre>\n<p>To define an operation filter, the IOperationFilter interface must be implemented.<br \/>\n\u8981\u5b9a\u4e49\u4f5c\u8fc7\u6ee4\u5668\uff0c\u5fc5\u987b\u5b9e\u73b0 IOperationFilter \u63a5\u53e3\u3002<\/p>\n<p>In the constructor, you can define all interfaces or objects that have been previously registered in the dependency inject engine.<br \/>\n\u5728\u6784\u9020\u51fd\u6570\u4e2d\uff0c\u60a8\u53ef\u4ee5\u5b9a\u4e49\u4e4b\u524d\u5728 dependency inject \u5f15\u64ce\u4e2d\u6ce8\u518c\u7684\u6240\u6709\u63a5\u53e3\u6216\u5bf9\u8c61\u3002<\/p>\n<p>The filter then consists of a single method, called Apply, which provides two objects:<br \/>\n\u7136\u540e\uff0c\u7b5b\u9009\u5668\u7531\u4e00\u4e2a\u540d\u4e3a Apply \u7684\u65b9\u6cd5\u7ec4\u6210\uff0c\u8be5\u65b9\u6cd5\u63d0\u4f9b\u4e24\u4e2a\u5bf9\u8c61\uff1a<\/p>\n<p>\u2022  OpenApiOperation: An operation where we can add parameters or check the operation ID of the current call<br \/>\n\u2022  OperationFilterContext: The filter context that allows you to read ApiDescription, where you can find the URL of the current endpoint<\/p>\n<p>Finally, to enable the operation filter in Swagger, we will need to register it inside the SwaggerGen method.<br \/>\n\u6700\u540e\uff0c\u8981\u5728 Swagger \u4e2d\u542f\u7528\u4f5c\u7b5b\u9009\u5668\uff0c\u6211\u4eec\u9700\u8981\u5728 SwaggerGen \u65b9\u6cd5\u4e2d\u6ce8\u518c\u5b83\u3002<\/p>\n<p>In this method, we should then add the filter, as follows:<br \/>\n\u5728\u6b64\u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u5e94\u8be5\u6dfb\u52a0\u8fc7\u6ee4\u5668\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>builder.Services.AddSwaggerGen(c =&gt;\n{\n         \u2026 removed for brevity\n         c.OperationFilter&lt;CorrelationIdOperationFilter&gt;();\n});<\/code><\/pre>\n<p>Here is the result at the UI level; in the endpoint and only for a particular operation ID, we would have a new mandatory header with a default parameter that, in development, will not have to be inserted:<br \/>\n\u4e0b\u9762\u662f UI \u7ea7\u522b\u7684\u7ed3\u679c;\u5728\u7ec8\u7aef\u8282\u70b9\u4e2d\uff0c\u5e76\u4e14\u4ec5\u9488\u5bf9\u7279\u5b9a\u7684\u4f5c ID\uff0c\u6211\u4eec\u5c06\u6709\u4e00\u4e2a\u5e26\u6709 default \u53c2\u6570\u7684\u65b0 mandatory \u6807\u5934\uff0c\u5728\u5f00\u53d1\u4e2d\uff0c\u4e0d\u5fc5\u63d2\u5165\u8be5\u53c2\u6570\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0305.jpg\" ><\/p>\n<p>Figure 3.5 \u2013 API key section<br \/>\n\u56fe 3.5 \u2013 API \u5bc6\u94a5\u90e8\u5206<\/p>\n<p>This case study helps us a lot when we have an API key that we need to set up and we don\u2019t want to insert it on every single call.<br \/>\n\u5f53\u6211\u4eec\u6709\u4e00\u4e2a\u9700\u8981\u8bbe\u7f6e\u7684 API \u5bc6\u94a5\u5e76\u4e14\u6211\u4eec\u4e0d\u60f3\u5728\u6bcf\u6b21\u8c03\u7528\u65f6\u90fd\u63d2\u5165\u5b83\u65f6\uff0c\u8fd9\u4e2a\u6848\u4f8b\u7814\u7a76\u5bf9\u6211\u4eec\u6709\u5f88\u5927\u5e2e\u52a9\u3002<\/p>\n<p>Operation filter in production<br \/>\n\u751f\u4ea7\u4e2d\u7684\u4f5c\u8fc7\u6ee4\u5668<\/p>\n<p>Since Swagger should not be enabled in the production environment, the filter and its default value will not create application security problems.<br \/>\n\u7531\u4e8e\u4e0d\u5e94\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u542f\u7528 Swagger\uff0c\u56e0\u6b64\u8fc7\u6ee4\u5668\u53ca\u5176\u9ed8\u8ba4\u503c\u4e0d\u4f1a\u9020\u6210\u5e94\u7528\u7a0b\u5e8f\u5b89\u5168\u95ee\u9898\u3002<\/p>\n<p>We recommend that you disable Swagger in the production environment.<br \/>\n\u5efa\u8bae\u60a8\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u5173\u95ed Swagger\u3002<\/p>\n<p>In this section, we figured out how to enable a UI tool that describes the API and allows us to test it. In the next section, we will see how to enable the call between single-page applications (SPAs) and the backend via CORS.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5f04\u6e05\u695a\u4e86\u5982\u4f55\u542f\u7528\u63cf\u8ff0 API \u5e76\u5141\u8bb8\u6211\u4eec\u6d4b\u8bd5\u5b83\u7684 UI \u5de5\u5177\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u901a\u8fc7 CORS \u542f\u7528\u5355\u9875\u5e94\u7528\u7a0b\u5e8f \uff08SPA\uff09 \u4e0e\u540e\u7aef\u4e4b\u95f4\u7684\u8c03\u7528\u3002<\/p>\n<p>Enabling CORS<br \/>\n\u542f\u7528 CORS<\/p>\n<p>CORS is a security mechanism whereby an HTTP\/S request is blocked if it arrives from a different domain than the one where the application is hosted. More information can be found in the Microsoft documentation or on the Mozilla site for developers.<br \/>\nCORS \u662f\u4e00\u79cd\u5b89\u5168\u673a\u5236\uff0c\u5982\u679c HTTP\/S \u8bf7\u6c42\u6765\u81ea\u4e0e\u6258\u7ba1\u5e94\u7528\u7a0b\u5e8f\u7684\u57df\u4e0d\u540c\u7684\u57df\uff0c\u5219 HTTP\/S \u8bf7\u6c42\u5c06\u88ab\u963b\u6b62\u3002\u6709\u5173\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 Microsoft \u6587\u6863\u6216 Mozilla \u5f00\u53d1\u4eba\u5458\u7f51\u7ad9\u3002<\/p>\n<p>A browser prevents a web page from making requests to a domain other than the domain that serves that web page. A web page, SPA, or server-side web page can make HTTP requests to several backend APIs that are hosted in different origins.<br \/>\n\u6d4f\u89c8\u5668\u4f1a\u963b\u6b62\u7f51\u9875\u5411\u63d0\u4f9b\u8be5\u7f51\u9875\u7684\u57df\u4ee5\u5916\u7684\u57df\u53d1\u51fa\u8bf7\u6c42\u3002\u7f51\u9875\u3001SPA \u6216\u670d\u52a1\u5668\u7aef\u7f51\u9875\u53ef\u4ee5\u5411\u6258\u7ba1\u5728\u4e0d\u540c\u6e90\u4e2d\u7684\u591a\u4e2a\u540e\u7aef API \u53d1\u51fa HTTP \u8bf7\u6c42\u3002<\/p>\n<p>This restriction is called the same-origin policy. The same-origin policy prevents a malicious site from reading data from another site. Browsers don\u2019t block HTTP requests but do block response data.<br \/>\n\u6b64\u9650\u5236\u79f0\u4e3a\u540c\u6e90\u7b56\u7565\u3002\u540c\u6e90\u7b56\u7565\u53ef\u9632\u6b62\u6076\u610f\u7ad9\u70b9\u4ece\u5176\u4ed6\u7ad9\u70b9\u8bfb\u53d6\u6570\u636e\u3002\u6d4f\u89c8\u5668\u4e0d\u4f1a\u963b\u6b62 HTTP \u8bf7\u6c42\uff0c\u4f46\u4f1a\u963b\u6b62\u54cd\u5e94\u6570\u636e\u3002<\/p>\n<p>We, therefore, understand that the CORS qualification, as it relates to safety, must be evaluated with caution.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u7406\u89e3\u5fc5\u987b\u8c28\u614e\u8bc4\u4f30\u4e0e\u5b89\u5168\u76f8\u5173\u7684 CORS \u8d44\u683c\u3002<\/p>\n<p>The most common scenario is that of SPAs that are released on web servers with different web addresses than the web server hosting the minimal API:<br \/>\n\u6700\u5e38\u89c1\u7684\u60c5\u51b5\u662f\u5728 Web \u670d\u52a1\u5668\u4e0a\u53d1\u5e03\u7684 SPA\uff0c\u8fd9\u4e9b SPA \u7684 Web \u5730\u5740\u4e0e\u6258\u7ba1\u6700\u5c0f API \u7684 Web \u670d\u52a1\u5668\u4e0d\u540c\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0306.jpg\" > <\/p>\n<p>Figure 3.6 \u2013 SPA and minimal API<br \/>\n\u56fe 3.6 \u2013 SPA \u548c\u6700\u5c0f API<\/p>\n<p>A similar scenario is that of microservices, which need to talk to each other. Each microservice will reside at a particular web address that will be different from the others.<br \/>\n\u7c7b\u4f3c\u7684\u573a\u666f\u662f\u5fae\u670d\u52a1\uff0c\u5b83\u4eec\u9700\u8981\u76f8\u4e92\u901a\u4fe1\u3002\u6bcf\u4e2a\u5fae\u670d\u52a1\u5c06\u9a7b\u7559\u5728\u4e00\u4e2a\u4e0e\u5176\u4ed6\u5fae\u670d\u52a1\u4e0d\u540c\u7684\u7279\u5b9a Web \u5730\u5740\u4e0a\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0307.jpg\" > <\/p>\n<p>Figure 3.7 \u2013 Microservices and minimal APIs<br \/>\n\u56fe 3.7 \u2013 \u5fae\u670d\u52a1\u548c\u6700\u5c11\u7684 API<\/p>\n<p>In all these cases, therefore, a CORS problem is encountered.<br \/>\n\u56e0\u6b64\uff0c\u5728\u6240\u6709\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c\u90fd\u4f1a\u9047\u5230 CORS \u95ee\u9898\u3002<\/p>\n<p>We now understand the cases in which a CORS request can occur. Now let\u2019s see what the correct HTTP request flow is and how the browser handles the request.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u53ef\u80fd\u53d1\u751f CORS \u8bf7\u6c42\u7684\u60c5\u51b5\u3002\u73b0\u5728\u8ba9\u6211\u4eec\u770b\u770b\u6b63\u786e\u7684 HTTP \u8bf7\u6c42\u6d41\u662f\u4ec0\u4e48\uff0c\u4ee5\u53ca\u6d4f\u89c8\u5668\u5982\u4f55\u5904\u7406\u8bf7\u6c42\u3002<\/p>\n<p>CORS flow from an HTTP request<br \/>\n\u6765\u81ea HTTP \u8bf7\u6c42\u7684 CORS \u6d41<\/p>\n<p>What happens when a call leaves the browser for a different address other than the one where the frontend is hosted?<br \/>\n\u5f53\u8c03\u7528\u79bb\u5f00\u6d4f\u89c8\u5668\u524d\u5f80\u6258\u7ba1\u524d\u7aef\u7684\u5730\u5740\u4ee5\u5916\u7684\u5176\u4ed6\u5730\u5740\u65f6\uff0c\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\uff1f<\/p>\n<p>The HTTP call is executed and it goes all the way to the backend code, which executes correctly.<br \/>\nHTTP \u8c03\u7528\u88ab\u6267\u884c\uff0c\u5e76\u4e00\u76f4\u8fdb\u5165\u540e\u7aef\u4ee3\u7801\uff0c\u540e\u7aef\u4ee3\u7801\u6b63\u786e\u6267\u884c\u3002<\/p>\n<p>The response, with the correct data inside, is blocked by the browser. That\u2019s why when we execute a call with Postman, Fiddler, or any HTTP client, the response reaches us correctly.<br \/>\n\u5305\u542b\u6b63\u786e\u6570\u636e\u7684\u54cd\u5e94\u88ab\u6d4f\u89c8\u5668\u963b\u6b62\u3002\u8fd9\u5c31\u662f\u4e3a\u4ec0\u4e48\u5f53\u6211\u4eec\u4f7f\u7528 Postman\u3001Fiddler \u6216\u4efb\u4f55 HTTP \u5ba2\u6237\u7aef\u6267\u884c\u8c03\u7528\u65f6\uff0c\u54cd\u5e94\u4f1a\u6b63\u786e\u5230\u8fbe\u6211\u4eec\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0308.jpg\" > <\/p>\n<p>Figure 3.8 \u2013 CORS flow<br \/>\n\u56fe 3.8 \u2013 CORS \u6d41\u7a0b<\/p>\n<p>In the following figure, we can see that the browser makes the first call with the OPTIONS method, to which the backend responds correctly with a 204 status code:<br \/>\n\u5728\u4e0b\u56fe\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u6d4f\u89c8\u5668\u4f7f\u7528 OPTIONS \u65b9\u6cd5\u8fdb\u884c\u4e86\u7b2c\u4e00\u6b21\u8c03\u7528\uff0c\u540e\u7aef\u4ee5 204 \u72b6\u6001\u7801\u6b63\u786e\u54cd\u5e94\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0309.jpg\" > <\/p>\n<p>Figure 3.9 \u2013 First request for the CORS call (204 No Content result)<br \/>\n\u56fe 3.9 \u2013 CORS \u8c03\u7528\u7684\u7b2c\u4e00\u4e2a\u8bf7\u6c42\uff08204 No Content \u7ed3\u679c\uff09<\/p>\n<p>In the second call that the browser makes, an error occurs; the strict-origin-when-cross-origin value is shown in Referrer Policy, which indicates the refusal by the browser to accept data from the backend:<br \/>\n\u5728\u6d4f\u89c8\u5668\u8fdb\u884c\u7684\u7b2c\u4e8c\u6b21\u8c03\u7528\u4e2d\uff0c\u4f1a\u53d1\u751f\u9519\u8bef;strict-origin-when-cross-origin \u503c\u663e\u793a\u5728 Referrer Policy \u4e2d\uff0c\u8be5\u503c\u8868\u793a\u6d4f\u89c8\u5668\u62d2\u7edd\u63a5\u53d7\u6765\u81ea\u540e\u7aef\u7684\u6570\u636e\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0310.jpg\" > <\/p>\n<p>Figure 3.10 \u2013 Second request for the CORS call (blocked by the browser)<br \/>\n\u56fe 3.10 \u2013 CORS \u8c03\u7528\u7684\u7b2c\u4e8c\u4e2a\u8bf7\u6c42\uff08\u88ab\u6d4f\u89c8\u5668\u963b\u6b62\uff09<\/p>\n<p>When CORS is enabled, in the response to the OPTIONS method call, three headers are inserted with the characteristics that the backend is willing to respect:<br \/>\n\u542f\u7528 CORS \u540e\uff0c\u5728\u5bf9 OPTIONS \u65b9\u6cd5\u8c03\u7528\u7684\u54cd\u5e94\u4e2d\uff0c\u5c06\u63d2\u5165\u4e09\u4e2a\u6807\u5934\uff0c\u8fd9\u4e9b\u6807\u5934\u5177\u6709\u540e\u7aef\u613f\u610f\u9075\u5faa\u7684\u7279\u5f81\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0311.jpg\" > <\/p>\n<p>Figure 3.11 \u2013 Request for CORS call (with CORS enabled)<br \/>\n\u56fe 3.11 \u2013 \u8bf7\u6c42 CORS \u8c03\u7528\uff08\u542f\u7528 CORS\uff09<\/p>\n<p>In this case, we can see that three headers are added that define Access-Control-Allow-Headers, Access-Control-Allow-Methods, and Access-Control-Allow-Origin.<br \/>\n\u5728\u672c\u4f8b\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u6dfb\u52a0\u4e86\u4e09\u4e2a\u6807\u5934\uff0c\u5206\u522b\u5b9a\u4e49 Access-Control-Allow-Headers\u3001Access-Control-Allow-Methods \u548c Access-Control-Allow-Origin\u3002<\/p>\n<p>The browser with this information can accept or block the response to this API.<br \/>\n\u5177\u6709\u6b64\u4fe1\u606f\u7684\u6d4f\u89c8\u5668\u53ef\u4ee5\u63a5\u53d7\u6216\u963b\u6b62\u5bf9\u6b64 API \u7684\u54cd\u5e94\u3002<\/p>\n<p>Setting CORS with a policy<br \/>\n\u4f7f\u7528\u7b56\u7565\u8bbe\u7f6e CORS<\/p>\n<p>Many configurations are possible within a .NET 6 application for activating CORS. We can define authorization policies in which the four available settings can be configured. CORS can also be activated by adding extension methods or annotations.<br \/>\n\u5728 .NET 6 \u5e94\u7528\u7a0b\u5e8f\u4e2d\u53ef\u4ee5\u4f7f\u7528\u8bb8\u591a\u914d\u7f6e\u6765\u6fc0\u6d3b CORS\u3002\u6211\u4eec\u53ef\u4ee5\u5b9a\u4e49\u6388\u6743\u7b56\u7565\uff0c\u5728\u5176\u4e2d\u53ef\u4ee5\u914d\u7f6e\u56db\u4e2a\u53ef\u7528\u8bbe\u7f6e\u3002\u8fd8\u53ef\u4ee5\u901a\u8fc7\u6dfb\u52a0\u6269\u5c55\u65b9\u6cd5\u6216\u6ce8\u91ca\u6765\u6fc0\u6d3b CORS\u3002<\/p>\n<p>But let us proceed in order.<br \/>\n\u4f46\u662f\uff0c\u8ba9\u6211\u4eec\u6309\u987a\u5e8f\u8fdb\u884c\u5427\u3002<\/p>\n<p>The CorsPolicyBuilder class allows us to define what is allowed or not allowed within the CORS acceptance policy.<br \/>\norsPolicyBuilder \u7c7b\u5141\u8bb8\u6211\u4eec\u5b9a\u4e49 CORS \u63a5\u53d7\u7b56\u7565\u4e2d\u5141\u8bb8\u6216\u4e0d\u5141\u8bb8\u7684\u5185\u5bb9\u3002<\/p>\n<p>We have, therefore, the possibility to set different methods, for example:<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u8bbe\u7f6e\u4e0d\u540c\u7684\u65b9\u6cd5\uff0c\u4f8b\u5982\uff1a<\/p>\n<p>\u2022  AllowAnyHeader<br \/>\n\u2022  AllowAnyMethod<br \/>\n\u2022  AllowAnyOrigin<br \/>\n\u2022  AllowCredentials<\/p>\n<p>While the first three methods are descriptive and allow us to enable any settings relating to the header, method, and origin of the HTTP call, respectively, AllowCredentials allows us to include the cookie with the authentication credentials.<br \/>\n\u867d\u7136\u524d\u4e09\u79cd\u65b9\u6cd5\u662f\u63cf\u8ff0\u6027\u7684\uff0c\u5e76\u5141\u8bb8\u6211\u4eec\u5206\u522b\u542f\u7528\u4e0e HTTP \u8c03\u7528\u7684\u6807\u5934\u3001\u65b9\u6cd5\u548c\u6765\u6e90\u76f8\u5173\u7684\u4efb\u4f55\u8bbe\u7f6e\uff0c\u4f46 AllowCredentials \u5141\u8bb8\u6211\u4eec\u5c06 Cookie \u4e0e\u8eab\u4efd\u9a8c\u8bc1\u51ed\u636e\u4e00\u8d77\u5305\u542b\u3002<\/p>\n<p>CORS policy recommendations<br \/>\nCORS \u7b56\u7565\u5efa\u8bae<\/p>\n<p>We recommend that you don\u2019t use the AllowAny methods but instead filter out the necessary information to allow for greater security. As a best practice, when enabling CORS, we recommend the use of these methods:<br \/>\n\u6211\u4eec\u5efa\u8bae\u60a8\u4e0d\u8981\u4f7f\u7528 AllowAny \u65b9\u6cd5\uff0c\u800c\u662f\u7b5b\u9009\u6389\u5fc5\u8981\u7684\u4fe1\u606f\u4ee5\u63d0\u9ad8\u5b89\u5168\u6027\u3002\u4f5c\u4e3a\u6700\u4f73\u5b9e\u8df5\uff0c\u5728\u542f\u7528 CORS \u65f6\uff0c\u6211\u4eec\u5efa\u8bae\u4f7f\u7528\u4ee5\u4e0b\u65b9\u6cd5\uff1a<\/p>\n<p>\u2022  WithExposedHeaders<br \/>\n\u2022  WithHeaders<br \/>\n\u2022  WithOrigins<\/p>\n<p>To simulate a scenario for CORS, we created a simple frontend application with three different buttons. Each button allows you to test one of the possible configurations of CORS within the minimal API. We will explain these configurations in a few lines.<br \/>\n\u4e3a\u4e86\u6a21\u62df CORS \u7684\u573a\u666f\uff0c\u6211\u4eec\u521b\u5efa\u4e86\u4e00\u4e2a\u5177\u6709\u4e09\u4e2a\u4e0d\u540c\u6309\u94ae\u7684\u7b80\u5355\u524d\u7aef\u5e94\u7528\u7a0b\u5e8f\u3002\u6bcf\u4e2a\u6309\u94ae\u90fd\u5141\u8bb8\u60a8\u5728\u6700\u5c0f API \u4e2d\u6d4b\u8bd5 CORS \u7684\u4e00\u79cd\u53ef\u80fd\u914d\u7f6e\u3002\u6211\u4eec\u5c06\u7528\u51e0\u884c\u6765\u89e3\u91ca\u8fd9\u4e9b\u914d\u7f6e\u3002<\/p>\n<p>To enable the CORS scenario, we have created a single-page application that can be launched on a web server in memory. We have used LiveReloadServer, a tool that can be installed with the .NET CLI. We talked about it at the start of the chapter and now it\u2019s time to use it.<br \/>\n\u4e3a\u4e86\u542f\u7528 CORS \u65b9\u6848\uff0c\u6211\u4eec\u521b\u5efa\u4e86\u4e00\u4e2a\u5355\u9875\u5e94\u7528\u7a0b\u5e8f\uff0c\u8be5\u5e94\u7528\u7a0b\u5e8f\u53ef\u4ee5\u5728\u5185\u5b58\u4e2d\u7684 Web \u670d\u52a1\u5668\u4e0a\u542f\u52a8\u3002\u6211\u4eec\u4f7f\u7528\u4e86 LiveReloadServer\uff0c\u8fd9\u662f\u4e00\u4e2a\u53ef\u4ee5\u4f7f\u7528 .NET CLI \u5b89\u88c5\u7684\u5de5\u5177\u3002\u6211\u4eec\u5728\u672c\u7ae0\u7684\u5f00\u5934\u8ba8\u8bba\u8fc7\u5b83\uff0c\u73b0\u5728\u662f\u65f6\u5019\u4f7f\u7528\u5b83\u4e86\u3002<\/p>\n<p>After installing it, you need to launch the SPA with the following command:<br \/>\n\u5b89\u88c5\u540e\uff0c\u60a8\u9700\u8981\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u542f\u52a8 SPA\uff1a<\/p>\n<pre><code>livereloadserver &quot;{BasePath}\\Chapter03\\2-CorsSample\\Frontend&quot;<\/code><\/pre>\n<p>Here, BasePath is the folder where you are going to download the examples available on GitHub.<br \/>\n\u6b64\u5904\uff0cBasePath \u662f\u60a8\u8981\u4e0b\u8f7d GitHub \u4e0a\u53ef\u7528\u793a\u4f8b\u7684\u6587\u4ef6\u5939\u3002<\/p>\n<p>Then you must start the application backend, either through Visual Studio or Visual Studio Code or through the .NET CLI with the following command:<br \/>\n\u7136\u540e\uff0c\u60a8\u5fc5\u987b\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u901a\u8fc7 Visual Studio \u6216 Visual Studio Code \u6216\u901a\u8fc7 .NET CLI \u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\u540e\u7aef\uff1a<\/p>\n<pre><code>dotnet run .\\Backend\\CorsSample.csproj<\/code><\/pre>\n<p>We\u2019ve figured out how to start an example that highlights the CORS problem; now we need to configure the server to accept the request and inform the browser that it is aware that the request is coming from a different source.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u60f3\u51fa\u4e86\u5982\u4f55\u5f00\u59cb\u4e00\u4e2a\u7a81\u51fa CORS \u95ee\u9898\u7684\u793a\u4f8b;\u73b0\u5728\u6211\u4eec\u9700\u8981\u914d\u7f6e\u670d\u52a1\u5668\u4ee5\u63a5\u53d7\u8bf7\u6c42\u5e76\u901a\u77e5\u6d4f\u89c8\u5668\u5b83\u77e5\u9053\u8bf7\u6c42\u6765\u81ea\u4e0d\u540c\u7684\u6765\u6e90\u3002<\/p>\n<p>Next, we will talk about policy configuration. We will understand the characteristics of the default policy as well as how to create a custom one.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u7b56\u7565\u914d\u7f6e\u3002\u6211\u4eec\u5c06\u4e86\u89e3\u9ed8\u8ba4\u7b56\u7565\u7684\u7279\u5f81\u4ee5\u53ca\u5982\u4f55\u521b\u5efa\u81ea\u5b9a\u4e49\u7b56\u7565\u3002<\/p>\n<p>Configuring a default policy<br \/>\n\u914d\u7f6e\u9ed8\u8ba4\u7b56\u7565<\/p>\n<p>To configure a single CORS enabling policy, you need to define the behavior in the Program.cs file and add the desired configurations. Let\u2019s implement a policy and define it as Default.<br \/>\n\u8981\u914d\u7f6e\u5355\u4e2a CORS \u542f\u7528\u7b56\u7565\uff0c\u60a8\u9700\u8981\u5728 Program.cs \u6587\u4ef6\u4e2d\u5b9a\u4e49\u884c\u4e3a\u5e76\u6dfb\u52a0\u6240\u9700\u7684\u914d\u7f6e\u3002\u8ba9\u6211\u4eec\u5b9e\u73b0\u4e00\u4e2a\u7b56\u7565\u5e76\u5c06\u5176\u5b9a\u4e49\u4e3a Default\u3002<\/p>\n<p>Then, to enable the policy for the whole application, simply add app.UseCors(); before defining the handlers:<br \/>\n\u7136\u540e\uff0c\u8981\u4e3a\u6574\u4e2a\u5e94\u7528\u7a0b\u5e8f\u542f\u7528\u7b56\u7565\uff0c\u53ea\u9700\u6dfb\u52a0 app.UseCors\uff08\uff09;\u5728\u5b9a\u4e49\u5904\u7406\u7a0b\u5e8f\u4e4b\u524d\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nvar corsPolicy = new CorsPolicyBuilder(&quot;http:\/\/localhost:5200&quot;)\n    .AllowAnyHeader()\n    .AllowAnyMethod()\n    .Build();\nbuilder.Services.AddCors(c =&gt; c.AddDefaultPolicy(corsPolicy));\nvar app = builder.Build();\napp.UseCors();\napp.MapGet(&quot;\/api\/cors&quot;, () =&gt;\n{\n         return Results.Ok(new { CorsResultJson = true });\n});\napp.Run();<\/code><\/pre>\n<p>Configuring custom policies<br \/>\n\u914d\u7f6e\u81ea\u5b9a\u4e49\u7b56\u7565<\/p>\n<p>We can create several policies within an application; each policy may have its own configuration and each policy may be associated with one or more endpoints.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u5728\u4e00\u4e2a\u5e94\u7528\u7a0b\u5e8f\u4e2d\u521b\u5efa\u591a\u4e2a\u7b56\u7565;\u6bcf\u4e2a\u7b56\u7565\u53ef\u80fd\u6709\u81ea\u5df1\u7684\u914d\u7f6e\uff0c\u5e76\u4e14\u6bcf\u4e2a\u7b56\u7565\u53ef\u80fd\u4e0e\u4e00\u4e2a\u6216\u591a\u4e2a\u7ec8\u7aef\u8282\u70b9\u5173\u8054\u3002<\/p>\n<p>In the case of microservices, having several policies helps to precisely segment access from a different source.<br \/>\n\u5bf9\u4e8e\u5fae\u670d\u52a1\uff0c\u62e5\u6709\u591a\u4e2a\u7b56\u7565\u6709\u52a9\u4e8e\u7cbe\u786e\u5206\u6bb5\u6765\u81ea\u4e0d\u540c\u6765\u6e90\u7684\u8bbf\u95ee\u3002<\/p>\n<p>In order to configure a new policy, it is necessary to add it and give it a name; this name will give access to the policy and allow it to be associated with the endpoint.<br \/>\n\u8981\u914d\u7f6e\u65b0\u7b56\u7565\uff0c\u5fc5\u987b\u6dfb\u52a0\u8be5\u7b56\u7565\u5e76\u4e3a\u5176\u547d\u540d;\u6b64\u540d\u79f0\u5c06\u6388\u4e88\u5bf9\u7b56\u7565\u7684\u8bbf\u95ee\u6743\u9650\uff0c\u5e76\u5141\u8bb8\u5b83\u4e0e\u7ec8\u7aef\u8282\u70b9\u5173\u8054\u3002<\/p>\n<p>The customized policy, as in the previous example, is assigned to the entire application:<br \/>\n\u5982\u524d\u9762\u7684\u793a\u4f8b\u6240\u793a\uff0c\u81ea\u5b9a\u4e49\u7b56\u7565\u88ab\u5206\u914d\u7ed9\u6574\u4e2a\u5e94\u7528\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nvar corsPolicy = new CorsPolicyBuilder(&quot;http:\/\/localhost:5200&quot;)\n    .AllowAnyHeader()\n    .AllowAnyMethod()\n    .Build();\nbuilder.Services.AddCors(options =&gt; options.AddPolicy(&quot;MyCustomPolicy&quot;, corsPolicy));\nvar app = builder.Build();\napp.UseCors(&quot;MyCustomPolicy&quot;);\napp.MapGet(&quot;\/api\/cors&quot;, () =&gt;\n{\n    return Results.Ok(new { CorsResultJson = true });\n});\napp.Run();<\/code><\/pre>\n<p>We next look at how to apply a single policy to a specific endpoint; to this end, two methods are available. The first is via an extension method to the IEndpointConventionBuilder interface. The second method is to add the EnableCors annotation followed by the name of the policy to be enabled for that method.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u5c06\u5355\u4e2a\u7b56\u7565\u5e94\u7528\u4e8e\u7279\u5b9a\u7ec8\u7aef\u8282\u70b9;\u4e3a\u6b64\uff0c\u6709\u4e24\u79cd\u65b9\u6cd5\u53ef\u4f9b\u9009\u62e9\u3002\u7b2c\u4e00\u79cd\u662f\u901a\u8fc7 IEndpointConventionBuilder \u63a5\u53e3\u7684\u6269\u5c55\u65b9\u6cd5\u3002\u7b2c\u4e8c\u79cd\u65b9\u6cd5\u662f\u6dfb\u52a0 EnableCors \u6ce8\u91ca\uff0c\u540e\u8ddf\u8981\u4e3a\u8be5\u65b9\u6cd5\u542f\u7528\u7684\u7b56\u7565\u7684\u540d\u79f0\u3002<\/p>\n<p>Setting CORS with extensions<br \/>\n\u4f7f\u7528\u6269\u5c55\u8bbe\u7f6e CORS<\/p>\n<p>It is necessary to use the RequireCors method followed by the name of the policy.<br \/>\n\u5fc5\u987b\u4f7f\u7528 RequireCors \u65b9\u6cd5\uff0c\u540e\u8ddf\u7b56\u7565\u7684\u540d\u79f0\u3002<\/p>\n<p>With this method, it is then possible to enable one or more policies for an endpoint:<br \/>\n\u4f7f\u7528\u6b64\u65b9\u6cd5\uff0c\u53ef\u4ee5\u4e3a\u7ec8\u7aef\u8282\u70b9\u542f\u7528\u4e00\u4e2a\u6216\u591a\u4e2a\u7b56\u7565\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/cors\/extension&quot;, () =&gt;\n{\n    return Results.Ok(new { CorsResultJson = true });\n})\n.RequireCors(&quot;MyCustomPolicy&quot;);<\/code><\/pre>\n<p>Setting CORS with an annotation<br \/>\n\u4f7f\u7528\u6ce8\u91ca\u8bbe\u7f6e CORS<\/p>\n<p>The second method is to add the EnableCors annotation followed by the name of the policy to be enabled for that method:<br \/>\n\u7b2c\u4e8c\u79cd\u65b9\u6cd5\u662f\u6dfb\u52a0 EnableCors \u6ce8\u91ca\uff0c\u540e\u8ddf\u8981\u4e3a\u8be5\u65b9\u6cd5\u542f\u7528\u7684\u7b56\u7565\u7684\u540d\u79f0\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/cors\/annotation&quot;, [EnableCors(&quot;MyCustomPolicy&quot;)] () =&gt;\n{\n   return Results.Ok(new { CorsResultJson = true });\n});<\/code><\/pre>\n<p>Regarding controller programming, it soon becomes apparent that it is not possible to apply a policy to all methods of a particular controller. It is also not possible to group controllers and enable the policy. It is therefore necessary to apply the individual policy to the method or the entire application.<br \/>\n\u5173\u4e8e\u63a7\u5236\u5668\u7f16\u7a0b\uff0c\u5f88\u5feb\u5c31\u4f1a\u53d1\u73b0\u4e0d\u53ef\u80fd\u5c06\u7b56\u7565\u5e94\u7528\u4e8e\u7279\u5b9a\u63a7\u5236\u5668\u7684\u6240\u6709\u65b9\u6cd5\u3002\u4e5f\u65e0\u6cd5\u5bf9\u63a7\u5236\u5668\u8fdb\u884c\u5206\u7ec4\u5e76\u542f\u7528\u7b56\u7565\u3002\u56e0\u6b64\uff0c\u6709\u5fc5\u8981\u5c06\u5355\u4e2a\u7b56\u7565\u5e94\u7528\u4e8e\u65b9\u6cd5\u6216\u6574\u4e2a\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>In this section, we found out how to configure browser protection for applications hosted on different domains.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u5982\u4f55\u4e3a\u6258\u7ba1\u5728\u4e0d\u540c\u57df\u4e0a\u7684\u5e94\u7528\u7a0b\u5e8f\u914d\u7f6e\u6d4f\u89c8\u5668\u4fdd\u62a4\u3002<\/p>\n<p>In the next section, we will start configuring our applications.<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u5f00\u59cb\u914d\u7f6e\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>Working with global API settings<br \/>\n\u4f7f\u7528\u5168\u5c40 API \u8bbe\u7f6e<\/p>\n<p>We have just defined how you can load data with the options pattern within an ASP.NET application. In this section, we want to describe how you can configure an application and take advantage of everything we saw in the previous section.<br \/>\n\u6211\u4eec\u521a\u521a\u5b9a\u4e49\u4e86\u5982\u4f55\u5728 ASP.NET \u5e94\u7528\u7a0b\u5e8f\u4e2d\u4f7f\u7528 options \u6a21\u5f0f\u52a0\u8f7d\u6570\u636e\u3002\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u60f3\u63cf\u8ff0\u5982\u4f55\u914d\u7f6e\u5e94\u7528\u7a0b\u5e8f\u5e76\u5229\u7528\u6211\u4eec\u5728\u4e0a\u4e00\u8282\u4e2d\u770b\u5230\u7684\u6240\u6709\u5185\u5bb9\u3002<\/p>\n<p>With the birth of .NET Core, the standard has moved from the Web.config file to the appsettings.json file. The configurations can also be read from other sources, such as other file formats like the old .ini file or a positional file.<br \/>\n\u968f\u7740 .NET Core \u7684\u8bde\u751f\uff0c\u8be5\u6807\u51c6\u5df2\u4ece Web.config \u6587\u4ef6\u79fb\u81f3 appsettings.json \u6587\u4ef6\u3002\u8fd8\u53ef\u4ee5\u4ece\u5176\u4ed6\u6765\u6e90\u8bfb\u53d6\u914d\u7f6e\uff0c\u4f8b\u5982\u5176\u4ed6\u6587\u4ef6\u683c\u5f0f\uff0c\u5982\u65e7.ini\u6587\u4ef6\u6216\u4f4d\u7f6e\u6587\u4ef6\u3002<\/p>\n<p>In minimal APIs, the options pattern feature remains unchanged, but in the next few paragraphs, we will see how to reuse the interfaces or the appsettings.json file structure.<br \/>\n\u5728\u6700\u5c0f API \u4e2d\uff0c\u9009\u9879\u6a21\u5f0f\u529f\u80fd\u4fdd\u6301\u4e0d\u53d8\uff0c\u4f46\u5728\u63a5\u4e0b\u6765\u7684\u51e0\u6bb5\u4e2d\uff0c\u6211\u4eec\u5c06\u770b\u5230\u5982\u4f55\u91cd\u7528\u63a5\u53e3\u6216 appsettings.json \u6587\u4ef6\u7ed3\u6784\u3002<\/p>\n<p>Configuration in .NET 6<br \/>\n.NET 6 \u4e2d\u7684\u914d\u7f6e<\/p>\n<p>The object provided from .NET is IConfiguration, which allows us to read some specific configurations inside the appsettings file.<br \/>\n\u4ece .NET \u63d0\u4f9b\u7684\u5bf9\u8c61\u662f IConfiguration\uff0c\u5b83\u5141\u8bb8\u6211\u4eec\u8bfb\u53d6 appsettings \u6587\u4ef6\u4e2d\u7684\u4e00\u4e9b\u7279\u5b9a\u914d\u7f6e\u3002<\/p>\n<p>But, as described earlier, this interface does much more than just access a file for reading.<br \/>\n\u4f46\u662f\uff0c\u5982\u524d\u6240\u8ff0\uff0c\u6b64\u63a5\u53e3\u7684\u4f5c\u7528\u4e0d\u4ec5\u4ec5\u662f\u8bbf\u95ee\u6587\u4ef6\u8fdb\u884c\u8bfb\u53d6\u3002<\/p>\n<p>The following extract from the official documentation helps us understand how the interface is the generic access point that allows us to access the data inserted in various services:<br \/>\n\u4ee5\u4e0b\u6458\u5f55\u81ea\u5b98\u65b9\u6587\u6863\u6709\u52a9\u4e8e\u6211\u4eec\u4e86\u89e3\u63a5\u53e3\u5982\u4f55\u6210\u4e3a\u5141\u8bb8\u6211\u4eec\u8bbf\u95ee\u63d2\u5165\u5404\u79cd\u670d\u52a1\u4e2d\u7684\u6570\u636e\u7684\u901a\u7528\u63a5\u5165\u70b9\uff1a<\/p>\n<p>Configuration in ASP.NET Core is performed using one or more configuration providers. Configuration providers read configuration data from key-value pairs using a variety of configuration sources.<br \/>\nASP.NET Core \u4e2d\u7684\u914d\u7f6e\u662f\u4f7f\u7528\u4e00\u4e2a\u6216\u591a\u4e2a\u914d\u7f6e\u63d0\u4f9b\u7a0b\u5e8f\u6267\u884c\u7684\u3002\u914d\u7f6e\u63d0\u4f9b\u7a0b\u5e8f\u4f7f\u7528\u5404\u79cd\u914d\u7f6e\u6e90\u4ece\u952e\u503c\u5bf9\u4e2d\u8bfb\u53d6\u914d\u7f6e\u6570\u636e\u3002<\/p>\n<p>The following is a list of configuration sources:<br \/>\n\u4ee5\u4e0b\u662f\u914d\u7f6e\u6e90\u7684\u5217\u8868\uff1a<\/p>\n<p>\u2022  Settings files, such as appsettings.json<br \/>\n\u2022  Environment variables<br \/>\n\u2022  Azure Key Vault<br \/>\n\u2022  Azure App Configuration<br \/>\n\u2022  Command-line arguments<br \/>\n\u2022  Custom providers, installed or created<br \/>\n\u2022  Directory files<br \/>\n\u2022  In-memory .NET objects<\/p>\n<p>(<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/configuration\/\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/configuration\/<\/a>)<\/p>\n<p>The IConfiguration and IOptions interfaces, which we will see in the next chapter, are designed to read data from the various providers. These interfaces are not suitable for reading and editing the configuration file while the program is running.<br \/>\n\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u7ae0\u4e2d\u770b\u5230\u7684 IConfiguration \u548c IOptions \u63a5\u53e3\u65e8\u5728\u4ece\u5404\u79cd\u63d0\u4f9b\u7a0b\u5e8f\u8bfb\u53d6\u6570\u636e\u3002\u8fd9\u4e9b\u63a5\u53e3\u4e0d\u9002\u5408\u5728\u7a0b\u5e8f\u8fd0\u884c\u65f6\u8bfb\u53d6\u548c\u7f16\u8f91\u914d\u7f6e\u6587\u4ef6\u3002<\/p>\n<p>The IConfiguration interface is available through the builder object, builder.Configuration, which provides all the methods needed to read a value, an object, or a connection string.<br \/>\nIConfiguration \u63a5\u53e3\u53ef\u901a\u8fc7 builder \u5bf9\u8c61 builder \u83b7\u5f97\u3002Configuration\uff0c\u5b83\u63d0\u4f9b\u8bfb\u53d6\u503c\u3001\u5bf9\u8c61\u6216\u8fde\u63a5\u5b57\u7b26\u4e32\u6240\u9700\u7684\u6240\u6709\u65b9\u6cd5\u3002<\/p>\n<p>After looking at one of the most important interfaces that we will use to configure the application, we want to define good development practices and use a fundamental building block for any developer: namely, classes. Copying the configuration into a class will allow us to better enjoy the content anywhere in the code.<br \/>\n\u5728\u67e5\u770b\u4e86\u6211\u4eec\u5c06\u7528\u4e8e\u914d\u7f6e\u5e94\u7528\u7a0b\u5e8f\u7684\u6700\u91cd\u8981\u7684\u63a5\u53e3\u4e4b\u4e00\u4e4b\u540e\uff0c\u6211\u4eec\u60f3\u8981\u5b9a\u4e49\u826f\u597d\u7684\u5f00\u53d1\u5b9e\u8df5\u5e76\u4e3a\u4efb\u4f55\u5f00\u53d1\u4eba\u5458\u4f7f\u7528\u4e00\u4e2a\u57fa\u672c\u6784\u5efa\u5757\uff1a\u5373\u7c7b\u3002\u5c06\u914d\u7f6e\u590d\u5236\u5230\u7c7b\u4e2d\u5c06\u4f7f\u6211\u4eec\u80fd\u591f\u66f4\u597d\u5730\u4eab\u53d7\u4ee3\u7801\u4e2d\u4efb\u4f55\u4f4d\u7f6e\u7684\u5185\u5bb9\u3002<\/p>\n<p>We define classes containing a property and classes corresponding appsettings file:<br \/>\n\u6211\u4eec\u5b9a\u4e49\u5305\u542b\u5c5e\u6027\u7684\u7c7b\u548c\u5bf9\u5e94\u7684 appsettings \u6587\u4ef6\u7684\u7c7b\uff1a<\/p>\n<p>Configuration classes<\/p>\n<pre><code>public class MyCustomObject\n{\n    public string? CustomProperty { get; init; }\n}\npublic class MyCustomStartupObject\n{\n    public string? CustomProperty { get; init; }\n}<\/code><\/pre>\n<p>And here, we bring back the corresponding JSON of the C# class that we just saw:<br \/>\n\u5728\u8fd9\u91cc\uff0c\u6211\u4eec\u8fd4\u56de\u6211\u4eec\u521a\u521a\u770b\u5230\u7684 C# \u7c7b\u7684\u76f8\u5e94 JSON\uff1a<\/p>\n<p>appsettings.json definition<br \/>\nappsettings.json\u5b9a\u4e49<\/p>\n<pre><code>{\n    &quot;MyCustomObject&quot;: {\n         &quot;CustomProperty&quot;: &quot;PropertyValue&quot;\n    },\n    &quot;MyCustomStartupObject&quot;: {\n         &quot;CustomProperty&quot;: &quot;PropertyValue&quot;\n    },\n    &quot;ConnectionStrings&quot;: {\n         &quot;Default&quot;: &quot;MyConnectionstringValueInAppsettings&quot;\n    }\n}<\/code><\/pre>\n<p>Next, we will be performing several operations.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u5c06\u6267\u884c\u51e0\u9879\u4f5c\u3002<\/p>\n<p>The first operation we perform creates an instance of the startupConfig object that will be of the MyCustomStartupObject type. To populate the instance of this object, through IConfiguration, we are going to read the data from the section called MyCustomStartupObject:<br \/>\n\u6211\u4eec\u6267\u884c\u7684\u7b2c\u4e00\u4e2a\u4f5c\u5c06\u521b\u5efa\u4e00\u4e2a startupConfig \u5bf9\u8c61\u7684\u5b9e\u4f8b\uff0c\u8be5\u5b9e\u4f8b\u5c06\u4e3a MyCustomStartupObject \u7c7b\u578b\u3002\u4e3a\u4e86\u586b\u5145\u6b64\u5bf9\u8c61\u7684\u5b9e\u4f8b\uff0c\u901a\u8fc7 IConfiguration\uff0c\u6211\u4eec\u5c06\u4ece\u540d\u4e3a MyCustomStartupObject \u7684\u90e8\u5206\u8bfb\u53d6\u6570\u636e\uff1a<\/p>\n<pre><code>var startupConfig = builder.Configuration.GetSection(nameof(MyCustomStartupObject)).Get&lt;MyCustomStartupObject&gt;();<\/code><\/pre>\n<p>The newly created object can then be used in the various handlers of the minimal APIs.<br \/>\n\u7136\u540e\uff0c\u65b0\u521b\u5efa\u7684\u5bf9\u8c61\u53ef\u4ee5\u5728\u6700\u5c0f API \u7684\u5404\u79cd\u5904\u7406\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u3002<\/p>\n<p>Instead, in this second operation, we use the dependency injection engine to request the instance of the IConfiguration object:<br \/>\n\u76f8\u53cd\uff0c\u5728\u7b2c\u4e8c\u4e2a\u4f5c\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528\u4f9d\u8d56\u9879\u6ce8\u5165\u5f15\u64ce\u6765\u8bf7\u6c42 IConfiguration \u5bf9\u8c61\u7684\u5b9e\u4f8b\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/read\/configurations&quot;, (IConfiguration configuration) =&gt;\n{\n    var customObject = configuration.\n    GetSection(nameof(MyCustomObject)).Get&lt;MyCustomObject&gt;();<\/code><\/pre>\n<p>With the IConfiguration object, we will retrieve the data similarly to the operation just described. We select the GetSection(nameof(MyCustomObject)) section and type the object with the <code>Get&lt;T&gt;()<\/code> method.<br \/>\n\u4f7f\u7528 IConfiguration \u5bf9\u8c61\uff0c\u6211\u4eec\u5c06\u68c0\u7d22\u6570\u636e\uff0c\u7c7b\u4f3c\u4e8e\u521a\u624d\u63cf\u8ff0\u7684\u4f5c\u3002\u6211\u4eec\u9009\u62e9 GetSection\uff08nameof\uff08MyCustomObject\uff09\uff09 \u90e8\u5206\uff0c\u5e76\u4f7f\u7528<code>Get&lt;T&gt;()<\/code>\u65b9\u6cd5\u952e\u5165\u5bf9\u8c61\u3002<\/p>\n<p>Finally, in these last two examples, we read a single key, present at the root level of the appsettings file:<br \/>\n\u6700\u540e\uff0c\u5728\u6700\u540e\u4e24\u4e2a\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u8bfb\u53d6\u4e86\u4e00\u4e2a\u952e\uff0c\u8be5\u952e\u4f4d\u4e8e appsettings \u6587\u4ef6\u7684\u6839\u7ea7\u522b\uff1a<\/p>\n<pre><code>MyCustomValue = configuration.GetValue&lt;string&gt;(&quot;MyCustomValue&quot;),\nConnectionString = configuration.GetConnectionString(&quot;Default&quot;),<\/code><\/pre>\n<p>The <code>configuration.GetValue&lt;T&gt;(\u201cJsonRootKey\u201d)<\/code> method extracts the value of a key and converts it into an object; this method is used to read strings or numbers from a root-level property.<br \/>\n<code>configuration.GetValue&lt;T&gt;(\u201cJsonRootKey\u201d)<\/code> \u65b9\u6cd5\u63d0\u53d6\u952e\u7684\u503c\u5e76\u5c06\u5176\u8f6c\u6362\u4e3a\u5bf9\u8c61;\u6b64\u65b9\u6cd5\u7528\u4e8e\u4ece\u6839\u7ea7\u522b\u5c5e\u6027\u4e2d\u8bfb\u53d6\u5b57\u7b26\u4e32\u6216\u6570\u5b57\u3002<\/p>\n<p>In the next line, we can see how you can leverage an IConfiguration method to read ConnectionString.<br \/>\n\u5728\u4e0b\u4e00\u884c\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u5982\u4f55\u5229\u7528 IConfiguration \u65b9\u6cd5\u6765\u8bfb\u53d6 ConnectionString\u3002<\/p>\n<p>In the appsettings file, connection strings are placed in a specific section, ConnectionStrings, that allows you to name the string and read it. Multiple connection strings can be placed in this section to exploit it in different objects.<br \/>\n\u5728 appsettings \u6587\u4ef6\u4e2d\uff0c\u8fde\u63a5\u5b57\u7b26\u4e32\u653e\u7f6e\u5728\u7279\u5b9a\u90e8\u5206 ConnectionStrings \u4e2d\uff0c\u8be5\u90e8\u5206\u5141\u8bb8\u4f60\u547d\u540d\u548c\u8bfb\u53d6\u5b57\u7b26\u4e32\u3002\u53ef\u4ee5\u5728\u6b64\u90e8\u5206\u4e2d\u653e\u7f6e\u591a\u4e2a\u8fde\u63a5\u5b57\u7b26\u4e32\uff0c\u4ee5\u4fbf\u5728\u4e0d\u540c\u7684\u5bf9\u8c61\u4e2d\u5229\u7528\u5b83\u3002<\/p>\n<p>In the configuration provider for Azure App Service, connection strings should be entered with a prefix that also indicates the SQL provider you are trying to use, as described in the following link: <a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/configure-common#configure-connection-strings\">https:\/\/docs.microsoft.com\/azure\/app-service\/configure-common#configure-connection-strings<\/a>.<br \/>\n\u5728 Azure \u5e94\u7528\u670d\u52a1\u7684\u914d\u7f6e\u63d0\u4f9b\u7a0b\u5e8f\u4e2d\uff0c\u5e94\u8f93\u5165\u8fde\u63a5\u5b57\u7b26\u4e32\uff0c\u5e76\u5e26\u6709\u4e00\u4e2a\u524d\u7f00\uff0c\u8be5\u524d\u7f00\u4e5f\u6307\u793a\u4f60\u5c1d\u8bd5\u4f7f\u7528\u7684 SQL \u63d0\u4f9b\u7a0b\u5e8f\uff0c\u5982\u4ee5\u4e0b\u94fe\u63a5\u6240\u8ff0\uff1a<a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/configure-common#configure-connection-strings\">https:\/\/docs.microsoft.com\/azure\/app-service\/configure-common#configure-connection-strings<\/a>\u3002<\/p>\n<p>At runtime, connection strings are available as environment variables, prefixed with the following connection types:<br \/>\n\u5728\u8fd0\u884c\u65f6\uff0c\u8fde\u63a5\u5b57\u7b26\u4e32\u53ef\u7528\u4f5c\u73af\u5883\u53d8\u91cf\uff0c\u524d\u7f00\u4e3a\u4ee5\u4e0b\u8fde\u63a5\u7c7b\u578b\uff1a<\/p>\n<p>\u2022  SQLServer: SQLCONNSTR<em><br \/>\n\u2022  MySQL: MYSQLCONNSTR<\/em><br \/>\n\u2022  SQLAzure: SQLAZURECONNSTR<em><br \/>\n\u2022  Custom: CUSTOMCONNSTR<\/em><br \/>\n\u2022  PostgreSQL: POSTGRESQLCONNSTR_<\/p>\n<p>For completeness, we will bring back the entire code just described in order to have a better general picture of how to exploit the IConfiguration object inside the code:<br \/>\n\u4e3a\u4e86\u5b8c\u6574\u8d77\u89c1\uff0c\u6211\u4eec\u5c06\u8fd4\u56de\u521a\u624d\u63cf\u8ff0\u7684\u6574\u4e2a\u4ee3\u7801\uff0c\u4ee5\u4fbf\u66f4\u597d\u5730\u4e86\u89e3\u5982\u4f55\u5728\u4ee3\u7801\u4e2d\u5229\u7528 IConfiguration \u5bf9\u8c61\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nvar startupConfig = builder.Configuration.GetSection(nameof(MyCustomStartupObject)).Get&lt;MyCustomStartupObject&gt;();\napp.MapGet(&quot;\/read\/configurations&quot;, (IConfiguration configuration) =&gt;\n{\n    var customObject = configuration.GetSection\n    (nameof(MyCustomObject)).Get&lt;MyCustomObject&gt;();\n    return Results.Ok(new\n    {\n        MyCustomValue = configuration.GetValue\n        &lt;string&gt;(&quot;MyCustomValue&quot;),\n         ConnectionString = configuration.\n         GetConnectionString(&quot;Default&quot;),\n         CustomObject = customObject,\n         StartupObject = startupConfig\n    });\n})\n.WithName(&quot;ReadConfigurations&quot;);<\/code><\/pre>\n<p>We\u2019ve seen how to take advantage of the appsettings file with connection strings, but very often, we have many different files for each environment. Let\u2019s see how to take advantage of one file for each environment.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u4e86\u89e3\u4e86\u5982\u4f55\u5229\u7528\u5e26\u6709\u8fde\u63a5\u5b57\u7b26\u4e32\u7684 appsettings \u6587\u4ef6\uff0c\u4f46\u901a\u5e38\uff0c\u6bcf\u4e2a\u73af\u5883\u90fd\u6709\u8bb8\u591a\u4e0d\u540c\u7684\u6587\u4ef6\u3002\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u4e3a\u6bcf\u4e2a\u73af\u5883\u5229\u7528\u4e00\u4e2a\u6587\u4ef6\u3002<\/p>\n<p>Priority in appsettings files<br \/>\nappsettings \u6587\u4ef6\u4e2d\u7684\u4f18\u5148\u7ea7<\/p>\n<p>The appsettings file can be managed according to the environments in which the application is located. In this case, the practice is to place key information for that environment in the appsettings.{ENVIRONMENT}.json file.<br \/>\n\u53ef\u4ee5\u6839\u636e\u5e94\u7528\u7a0b\u5e8f\u6240\u5728\u7684\u73af\u5883\u6765\u7ba1\u7406 appsettings \u6587\u4ef6\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u505a\u6cd5\u662f\u5c06\u8be5\u73af\u5883\u7684\u5173\u952e\u4fe1\u606f\u653e\u5728 appsettings.{ENVIRONMENT}.json\u6587\u4ef6\u3002<\/p>\n<p>The root file (that is, appsettings.json) should be used for the production environment only.<br \/>\n\u6839\u6587\u4ef6\uff08\u5373 appsettings.json\uff09\u5e94\u4ec5\u7528\u4e8e\u751f\u4ea7\u73af\u5883\u3002<\/p>\n<p>For example, if we created these examples in the two files for the \u201cPriority\u201d key, what would we get?<br \/>\n\u4f8b\u5982\uff0c\u5982\u679c\u6211\u4eec\u5728\u4e24\u4e2a\u6587\u4ef6\u4e2d\u4e3a \u201cPriority\u201d \u952e\u521b\u5efa\u8fd9\u4e9b\u793a\u4f8b\uff0c\u6211\u4eec\u4f1a\u5f97\u5230\u4ec0\u4e48\uff1f<\/p>\n<p>appsettings.json<\/p>\n<pre><code>&quot;Priority&quot;: &quot;Root&quot;<\/code><\/pre>\n<p>appsettings.Development.json<\/p>\n<pre><code>&quot;Priority&quot;:    &quot;Dev&quot;<\/code><\/pre>\n<p>If it is a Development environment, the value of the key would result in Dev, while in a Production environment, the value would result in Root.<br \/>\n\u5982\u679c\u662f Development \u73af\u5883\uff0c\u5219 key \u7684\u503c\u5c06\u5bfc\u81f4 Dev\uff0c\u800c\u5728 Production \u73af\u5883\u4e2d\uff0c\u8be5\u503c\u5c06\u5bfc\u81f4 Root\u3002<\/p>\n<p>What would happen if the environment was anything other than Production or Development? For example, if it were called Stage? In this case, having not specified any appsettings.Stage.json file, the read value would be that of one of the appsettings.json files and therefore, Root.<br \/>\n\u5982\u679c\u73af\u5883\u4e0d\u662f\u751f\u4ea7\u6216\u5f00\u53d1\uff0c\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\uff1f\u4f8b\u5982\uff0c\u5982\u679c\u5b83\u88ab\u79f0\u4e3a Stage\uff1f\u5728\u672c\u4f8b\u4e2d\uff0c\u672a\u6307\u5b9a\u4efb\u4f55 appsettings.Stage.json\u6587\u4ef6\u4e2d\uff0c\u8bfb\u53d6\u503c\u5c06\u662f\u5176\u4e2d\u4e00\u4e2aappsettings.json\u6587\u4ef6\u7684\u503c\uff0c\u56e0\u6b64\u662f Root\u3002<\/p>\n<p>However, if we specified the appsettings.Stage.json file, the value would be read from the that file.<br \/>\n\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u6307\u5b9a appsettings.Stage.json\u6587\u4ef6\u4e2d\uff0c\u5c06\u4ece\u8be5\u6587\u4ef6\u4e2d\u8bfb\u53d6\u8be5\u503c\u3002<\/p>\n<p>Next, let\u2019s visit the Options pattern. There are objects that the framework provides to load configuration information upon startup or when changes are made by the systems department. Let\u2019s go over how.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u8ba9\u6211\u4eec\u8bbf\u95ee Options \u6a21\u5f0f\u3002\u6846\u67b6\u63d0\u4f9b\u4e86\u4e00\u4e9b\u5bf9\u8c61\uff0c\u7528\u4e8e\u5728\u542f\u52a8\u65f6\u6216\u7cfb\u7edf\u90e8\u95e8\u8fdb\u884c\u66f4\u6539\u65f6\u52a0\u8f7d\u914d\u7f6e\u4fe1\u606f\u3002\u8ba9\u6211\u4eec\u6765\u770b\u770b\u5982\u4f55\u4f5c\u3002<\/p>\n<p>Options pattern<br \/>\n\u9009\u9879\u6a21\u5f0f<\/p>\n<p>The options pattern uses classes to provide strongly typed access to groups of related settings, that is, when configuration settings are isolated by scenario into separate classes.<br \/>\n\u9009\u9879\u6a21\u5f0f\u4f7f\u7528\u7c7b\u63d0\u4f9b\u5bf9\u76f8\u5173\u8bbe\u7f6e\u7ec4\u7684\u5f3a\u7c7b\u578b\u8bbf\u95ee\uff0c\u5373\uff0c\u5f53\u914d\u7f6e\u8bbe\u7f6e\u6309\u65b9\u6848\u9694\u79bb\u5230\u5355\u72ec\u7684\u7c7b\u4e2d\u65f6\u3002<\/p>\n<p>The options pattern will be implemented with different interfaces and different functionalities. Each interface (see the following subsection) has its own features that help us achieve certain goals.<br \/>\n\u9009\u9879\u6a21\u5f0f\u5c06\u4f7f\u7528\u4e0d\u540c\u7684\u63a5\u53e3\u548c\u4e0d\u540c\u7684\u529f\u80fd\u5b9e\u73b0\u3002\u6bcf\u4e2a\u754c\u9762\uff08\u8bf7\u53c2\u9605\u4ee5\u4e0b\u5c0f\u8282\uff09\u90fd\u6709\u81ea\u5df1\u7684\u529f\u80fd\uff0c\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u5b9e\u73b0\u67d0\u4e9b\u76ee\u6807\u3002<\/p>\n<p>But let\u2019s start in order. We define an object for each type of interface (we will do it to better represent the examples), but the same class can be used to register more options inside the configuration file. It is important to keep the structure of the file identical:<br \/>\n\u4f46\u8ba9\u6211\u4eec\u6309\u987a\u5e8f\u5f00\u59cb\u3002\u6211\u4eec\u4e3a\u6bcf\u79cd\u7c7b\u578b\u7684\u63a5\u53e3\u5b9a\u4e49\u4e00\u4e2a\u5bf9\u8c61\uff08\u6211\u4eec\u5c06\u8fd9\u6837\u505a\u4ee5\u66f4\u597d\u5730\u8868\u793a\u793a\u4f8b\uff09\uff0c\u4f46\u540c\u4e00\u4e2a\u7c7b\u53ef\u7528\u4e8e\u5728\u914d\u7f6e\u6587\u4ef6\u4e2d\u6ce8\u518c\u66f4\u591a\u9009\u9879\u3002\u4fdd\u6301\u6587\u4ef6\u7684\u7ed3\u6784\u76f8\u540c\u975e\u5e38\u91cd\u8981\uff1a<\/p>\n<pre><code>public class OptionBasic\n{\n    public string? Value { get; init; }\n}\n    public class OptionSnapshot\n    {\n        public string? Value { get; init; }\n    }\n    public class OptionMonitor\n    {\n        public string? Value { get; init; }\n    }\n    public class OptionCustomName\n    {\n        public string? Value { get; init; }\n    }<\/code><\/pre>\n<p>Each option is registered in the dependency injection engine via the Configure method, which also requires the registration of the T type present in the method signature. As you can see, in the registration phase, we declared the types and the section of the file where to retrieve the information, and nothing more:<br \/>\n\u6bcf\u4e2a\u9009\u9879\u90fd\u901a\u8fc7 Configure \u65b9\u6cd5\u5728\u4f9d\u8d56\u9879\u6ce8\u5165\u5f15\u64ce\u4e2d\u6ce8\u518c\uff0c\u8be5\u65b9\u6cd5\u8fd8\u9700\u8981\u6ce8\u518c\u65b9\u6cd5\u7b7e\u540d\u4e2d\u5b58\u5728\u7684 T \u7c7b\u578b\u3002\u5982\u4f60\u6240\u89c1\uff0c\u5728\u6ce8\u518c\u9636\u6bb5\uff0c\u6211\u4eec\u58f0\u660e\u4e86\u7c7b\u578b\u548c\u6587\u4ef6\u90e8\u5206\uff0c\u7528\u4e8e\u68c0\u7d22\u4fe1\u606f\uff0c\u4ec5\u6b64\u800c\u5df2\uff1a<\/p>\n<pre><code>builder.Services.Configure&lt;OptionBasic&gt;(builder.Configuration.GetSection(&quot;OptionBasic&quot;));\nbuilder.Services.Configure&lt;OptionMonitor&gt;(builder.Configuration.GetSection(&quot;OptionMonitor&quot;));\nbuilder.Services.Configure&lt;OptionSnapshot&gt;(builder.Configuration.GetSection(&quot;OptionSnapshot&quot;));\nbuilder.Services.Configure&lt;OptionCustomName&gt;(&quot;CustomName1&quot;, builder.Configuration.GetSection(&quot;CustomName1&quot;));\nbuilder.Services.Configure&lt;OptionCustomName&gt;(&quot;CustomName2&quot;, builder.Configuration.GetSection(&quot;CustomName2&quot;));<\/code><\/pre>\n<p>We have not yet defined how the object should be read, how often, and with what type of interface.<br \/>\n\u6211\u4eec\u5c1a\u672a\u5b9a\u4e49\u5e94\u8be5\u5982\u4f55\u8bfb\u53d6\u5bf9\u8c61\u3001\u8bfb\u53d6\u9891\u7387\u4ee5\u53ca\u4f7f\u7528\u4ec0\u4e48\u7c7b\u578b\u7684\u63a5\u53e3\u3002<\/p>\n<p>The only thing that changes is the parameter, as seen in the last two examples of the preceding code snippet. This parameter allows you to add a name to the option type. The name is required to match the type used in the method signature. This feature is called named options.<br \/>\n\u552f\u4e00\u66f4\u6539\u7684\u662f\u53c2\u6570\uff0c\u5982\u524d\u9762\u4ee3\u7801\u6bb5\u7684\u6700\u540e\u4e24\u4e2a\u793a\u4f8b\u6240\u793a\u3002\u6b64\u53c2\u6570\u5141\u8bb8\u60a8\u5411\u9009\u9879\u7c7b\u578b\u6dfb\u52a0\u540d\u79f0\u3002\u8be5\u540d\u79f0\u5fc5\u987b\u4e0e\u65b9\u6cd5\u7b7e\u540d\u4e2d\u4f7f\u7528\u7684\u7c7b\u578b\u5339\u914d\u3002\u6b64\u529f\u80fd\u79f0\u4e3a named options\u3002<\/p>\n<p>Different option interfaces<br \/>\n\u4e0d\u540c\u7684\u9009\u9879\u63a5\u53e3<\/p>\n<p>Different interfaces can take advantage of the recordings you just defined. Some support named options and some do not:<br \/>\n\u4e0d\u540c\u7684\u754c\u9762\u53ef\u4ee5\u5229\u7528\u60a8\u521a\u521a\u5b9a\u4e49\u7684\u8bb0\u5f55\u3002\u6709\u4e9b\u652f\u6301\u547d\u540d\u9009\u9879\uff0c\u6709\u4e9b\u5219\u4e0d\u652f\u6301\uff1a<\/p>\n<p>\u2022  <code>IOptions&lt;TOptions&gt;<\/code>:<br \/>\nIs registered as a singleton and can be injected into any service lifetime<br \/>\n\u6ce8\u518c\u4e3a\u5355\u4e00\u5b9e\u4f8b\uff0c\u53ef\u4ee5\u6ce8\u5165\u5230\u4efb\u4f55\u670d\u52a1\u751f\u547d\u5468\u671f\u4e2d<br \/>\nDoes not support the following:<br \/>\n\u4e0d\u652f\u6301\u4ee5\u4e0b\u5185\u5bb9\uff1a<br \/>\nReading of configuration data after the app has started<br \/>\n\u5728\u5e94\u7528\u7a0b\u5e8f\u542f\u52a8\u540e\u8bfb\u53d6\u914d\u7f6e\u6570\u636e<br \/>\nNamed options<br \/>\n\u547d\u540d\u9009\u9879<\/p>\n<p>\u2022  <code>IOptionsSnapshot&lt;TOptions&gt;<\/code>:<br \/>\nIs useful in scenarios where options should be recomputed on every request<br \/>\n\u5728\u5e94\u5728\u6bcf\u4e2a\u8bf7\u6c42\u4e0a\u91cd\u65b0\u8ba1\u7b97\u9009\u9879\u7684\u60c5\u51b5\u4e0b\u975e\u5e38\u6709\u7528<br \/>\nIs registered as scoped and therefore cannot be injected into a singleton service<br \/>\n\u6ce8\u518c\u4e3a scoped\uff0c\u56e0\u6b64\u4e0d\u80fd\u6ce8\u5165\u5230\u5355\u4e00\u5b9e\u4f8b\u670d\u52a1<br \/>\nSupports named options<br \/>\n\u652f\u6301\u547d\u540d\u9009\u9879<\/p>\n<p><code>IOptionsMonitor&lt;TOptions&gt;<\/code>:<br \/>\nIs used to retrieve options and manage options notifications for TOptions instances<br \/>\n\u7528\u4e8e\u68c0\u7d22\u9009\u9879\u548c\u7ba1\u7406 TOptions \u5b9e\u4f8b\u7684\u9009\u9879\u901a\u77e5<br \/>\nIs registered as a singleton and can be injected into any service lifetime<br \/>\n\u6ce8\u518c\u4e3a\u5355\u4e00\u5b9e\u4f8b\uff0c\u53ef\u4ee5\u6ce8\u5165\u5230\u4efb\u4f55\u670d\u52a1\u751f\u547d\u5468\u671f\u4e2d<br \/>\nSupports the following:<br \/>\n\u652f\u6301\u4ee5\u4e0b\u529f\u80fd\uff1a<br \/>\nChange notifications<br \/>\n\u66f4\u6539\u901a\u77e5<br \/>\nNamed options<br \/>\n\u547d\u540d\u9009\u9879<br \/>\nReloadable configuration<br \/>\n\u53ef\u91cd\u65b0\u52a0\u8f7d\u914d\u7f6e<br \/>\nSelective options invalidation (<code>IOptionsMonitorCache&lt;TOptions&gt;<\/code>)<br \/>\n\u9009\u62e9\u6027\u9009\u9879\u5931\u6548 \uff08<code>IOptionsMonitorCache&lt;TOptions&gt;<\/code>)<\/p>\n<p>We want to point you to the use of <code>IOptionsFactory&lt;TOptions&gt;<\/code>, which is responsible for creating new instances of options. It has a single Create method. The default implementation takes all registered <code>IConfigureOptions&lt;TOptions&gt; <\/code>and IPostConfigureOptions<TOptions> and performs all configurations first, followed by post-configuration (<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/configuration\/options#options-interfaces\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/configuration\/options#options-interfaces<\/a>).<br \/>\n\u6211\u4eec\u60f3\u5411\u60a8\u4ecb\u7ecd\u4e00\u4e0b <code>IOptionsFactory&lt;TOptions&gt;<\/code> \u7684\u4f7f\u7528\uff0c\u5b83\u8d1f\u8d23\u521b\u5efa\u65b0\u7684\u9009\u9879\u5b9e\u4f8b\u3002\u5b83\u53ea\u6709\u4e00\u4e2a Create \u65b9\u6cd5\u3002\u9ed8\u8ba4\u5b9e\u73b0\u91c7\u7528\u6240\u6709\u5df2\u6ce8\u518c\u7684<code> IConfigureOptions&lt;TOptions&gt; <\/code>\u548c <code>IPostConfigureOptions&lt;TOptions&gt;<\/code>\u5e76\u9996\u5148\u6267\u884c\u6240\u6709\u914d\u7f6e\uff0c\u7136\u540e\u6267\u884c\u540e\u914d\u7f6e \uff08<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/configuration\/options#options-interfaces\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/configuration\/options#options-interfaces<\/a>\uff09\u3002<\/p>\n<p>The Configure method can also be followed by another method in the configuration pipeline. This method is called PostConfigure and is intended to modify the configuration each time it is configured or reread. Here is an example of how to record this behavior:<br \/>\nConfigure \u65b9\u6cd5\u4e5f\u53ef\u4ee5\u540e\u8ddf\u914d\u7f6e\u7ba1\u9053\u4e2d\u7684\u53e6\u4e00\u4e2a\u65b9\u6cd5\u3002\u6b64\u65b9\u6cd5\u79f0\u4e3a PostConfigure\uff0c\u65e8\u5728\u5728\u6bcf\u6b21\u914d\u7f6e\u6216\u91cd\u65b0\u8bfb\u53d6\u914d\u7f6e\u65f6\u4fee\u6539\u914d\u7f6e\u3002\u4ee5\u4e0b\u662f\u5982\u4f55\u8bb0\u5f55\u6b64\u884c\u4e3a\u7684\u793a\u4f8b\uff1a<\/p>\n<pre><code>builder.Services.PostConfigure&lt;MyConfigOptions&gt;(myOptions =&gt;\n{\n   myOptions.Key1 = &quot;my_new_value_post_configuration&quot;;\n});<\/code><\/pre>\n<p>Putting it all together<br \/>\n\u628a\u5b83\u4eec\u653e\u5728\u4e00\u8d77<\/p>\n<p>Having defined the theory of these numerous interfaces, it remains for us to see IOptions at work with a concrete example.<br \/>\n\u5728\u5b9a\u4e49\u4e86\u8fd9\u4e9b\u4f17\u591a\u63a5\u53e3\u7684\u7406\u8bba\u4e4b\u540e\uff0c\u6211\u4eec\u4ecd\u7136\u9700\u8981\u901a\u8fc7\u4e00\u4e2a\u5177\u4f53\u7684\u4f8b\u5b50\u6765\u4e86\u89e3 IOptions \u7684\u5de5\u4f5c\u539f\u7406\u3002<\/p>\n<p>Let\u2019s see the use of the three interfaces just described and the use of IOptionsFactory, which, along with the Create method and with the named options function, retrieves the correct instance of the object:<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u521a\u624d\u63cf\u8ff0\u7684\u4e09\u4e2a\u63a5\u53e3\u7684\u7528\u6cd5\u4ee5\u53ca IOptionsFactory \u7684\u7528\u6cd5\uff0c\u5b83\u4e0e Create \u65b9\u6cd5\u548c\u547d\u540d\u9009\u9879\u51fd\u6570\u4e00\u8d77\u68c0\u7d22\u5bf9\u8c61\u7684\u6b63\u786e\u5b9e\u4f8b\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/read\/options&quot;, (IOptions&lt;OptionBasic&gt; optionsBasic,\n         IOptionsMonitor&lt;OptionMonitor&gt; optionsMonitor,\n         IOptionsSnapshot&lt;OptionSnapshot&gt; optionsSnapshot,\n         IOptionsFactory&lt;OptionCustomName&gt; optionsFactory) =&gt;\n{\n         return Results.Ok(new\n         {\n             Basic = optionsBasic.Value,\n             Monitor = optionsMonitor.CurrentValue,\n             Snapshot = optionsSnapshot.Value,\n             Custom1 = optionsFactory.Create(&quot;CustomName1&quot;),\n             Custom2 = optionsFactory.Create(&quot;CustomName2&quot;)\n         });\n})\n.WithName(&quot;ReadOptions&quot;);<\/code><\/pre>\n<p>In the previous code snippet, we want to bring attention to the use of the different interfaces available.<br \/>\n\u5728\u524d\u9762\u7684\u4ee3\u7801\u7247\u6bb5\u4e2d\uff0c\u6211\u4eec\u5e0c\u671b\u63d0\u8bf7\u6ce8\u610f\u53ef\u7528\u4e0d\u540c\u63a5\u53e3\u7684\u4f7f\u7528\u3002<\/p>\n<p>Each individual interface used in the previous snippet has a particular life cycle that characterizes its behavior. Finally, each interface has slight differences in the methods, as we have already described in the previous paragraphs.<br \/>\n\u4e0a\u4e00\u4e2a\u4ee3\u7801\u6bb5\u4e2d\u4f7f\u7528\u7684\u6bcf\u4e2a\u63a5\u53e3\u90fd\u6709\u4e00\u4e2a\u7279\u5b9a\u7684\u751f\u547d\u5468\u671f\uff0c\u7528\u4e8e\u63cf\u8ff0\u5176\u884c\u4e3a\u3002\u6700\u540e\uff0c\u6b63\u5982\u6211\u4eec\u5728\u524d\u9762\u7684\u6bb5\u843d\u4e2d\u5df2\u7ecf\u63cf\u8ff0\u7684\u90a3\u6837\uff0c\u6bcf\u4e2a\u63a5\u53e3\u5728\u65b9\u6cd5\u4e0a\u7565\u6709\u4e0d\u540c\u3002<\/p>\n<p>IOptions and validation<br \/>\n\u64cd\u4f5c\u548c\u9a8c\u8bc1<\/p>\n<p>Last but not least is the validation functionality of the data present in the configuration. This is very useful when the team that has to release the application still performs manual or delicate operations that need to be at least verified by the code.<br \/>\n\u6700\u540e\u4f46\u5e76\u975e\u6700\u4e0d\u91cd\u8981\u7684\u4e00\u70b9\u662f\u914d\u7f6e\u4e2d\u5b58\u5728\u7684\u6570\u636e\u7684\u9a8c\u8bc1\u529f\u80fd\u3002\u5f53\u5fc5\u987b\u53d1\u5e03\u5e94\u7528\u7a0b\u5e8f\u7684\u56e2\u961f\u4ecd\u7136\u6267\u884c\u81f3\u5c11\u9700\u8981\u7531\u4ee3\u7801\u9a8c\u8bc1\u7684\u624b\u52a8\u6216\u7cbe\u7ec6\u4f5c\u65f6\uff0c\u8fd9\u975e\u5e38\u6709\u7528\u3002<\/p>\n<p>Before the advent of .NET Core, very often, the application would not start because of an incorrect configuration. Now, with this feature, we can validate the data in the configuration and throw errors.<br \/>\n\u5728 .NET Core \u51fa\u73b0\u4e4b\u524d\uff0c\u5e94\u7528\u7a0b\u5e8f\u7ecf\u5e38\u7531\u4e8e\u914d\u7f6e\u4e0d\u6b63\u786e\u800c\u65e0\u6cd5\u542f\u52a8\u3002\u73b0\u5728\uff0c\u501f\u52a9\u6b64\u529f\u80fd\uff0c\u6211\u4eec\u53ef\u4ee5\u9a8c\u8bc1\u914d\u7f6e\u4e2d\u7684\u6570\u636e\u5e76\u5f15\u53d1\u9519\u8bef\u3002<\/p>\n<p>Here is an example:<br \/>\n\u4e0b\u9762\u662f\u4e00\u4e2a\u793a\u4f8b\uff1a<\/p>\n<p>Register option with validation<br \/>\n\u5e26\u9a8c\u8bc1\u7684 Register \u9009\u9879<\/p>\n<pre><code>builder.Services.AddOptions&lt;ConfigWithValidation&gt;().Bind(builder.Configuration.GetSection(nameof(ConfigWithValidation)))\n.ValidateDataAnnotations();\napp.MapGet(&quot;\/read\/options&quot;, (IOptions&lt;ConfigWithValidation&gt; optionsValidation) =&gt;\n{\n    return Results.Ok(new\n    {\n        Validation = optionsValidation.Value\n    });\n})\n.WithName(&quot;ReadOptions&quot;);<\/code><\/pre>\n<p>This is the configuration file where an error is explicitly reported:<br \/>\n\u8fd9\u662f\u660e\u786e\u62a5\u544a\u9519\u8bef\u7684\u914d\u7f6e\u6587\u4ef6\uff1a<\/p>\n<p>Appsettings section for configuration validation<br \/>\n\u7528\u4e8e\u914d\u7f6e\u9a8c\u8bc1\u7684 Appsettings \u90e8\u5206<\/p>\n<pre><code>&quot;ConfigWithValidation&quot;: {\n         &quot;Email&quot;: &quot;andrea.tosato@hotmail.it&quot;,\n         &quot;NumericRange&quot;: 1001\n    }<\/code><\/pre>\n<p>And here is the class containing the validation logic:<br \/>\n\u4e0b\u9762\u662f\u5305\u542b\u9a8c\u8bc1\u903b\u8f91\u7684\u7c7b\uff1a<\/p>\n<pre><code>public class ConfigWithValidation\n{\n    [RegularExpression(@&quot;^([\\w\\.\\-]+)@([\\w\\-]+)((\\.(\\w)\n                      {2,})+)$&quot;)]\n    public string? Email { get; set; }\n    [Range(0, 1000, ErrorMessage = &quot;Value for {0} must be \n                                    between {1} and {2}.&quot;)]\n    public int NumericRange { get; set; }\n}<\/code><\/pre>\n<p>The application then encounters errors while using the particular configuration and not at startup. This is also because, as we have seen before, IOptions could reload information following a change in appsettings:<br \/>\n\u7136\u540e\uff0c\u5e94\u7528\u7a0b\u5e8f\u5728\u4f7f\u7528\u7279\u5b9a\u914d\u7f6e\u65f6\u9047\u5230\u9519\u8bef\uff0c\u800c\u4e0d\u662f\u5728\u542f\u52a8\u65f6\u9047\u5230\u9519\u8bef\u3002\u8fd9\u4e5f\u662f\u56e0\u4e3a\uff0c\u6b63\u5982\u6211\u4eec\u4e4b\u524d\u770b\u5230\u7684\uff0cIOptions \u53ef\u4ee5\u5728 appsettings \u66f4\u6539\u540e\u91cd\u65b0\u52a0\u8f7d\u4fe1\u606f\uff1a<\/p>\n<p>Error validate option<br \/>\n\u9519\u8bef\u9a8c\u8bc1\u9009\u9879<\/p>\n<pre><code>Microsoft.Extensions.Options.OptionsValidationException: DataAnnotation validation failed for &#039;ConfigWithValidation&#039; members: &#039;NumericRange&#039; with the error: &#039;Value for NumericRange must be between 0 and 1000.&#039;.<\/code><\/pre>\n<p>Best practice for using validation in IOptions<br \/>\n\u5728 IOptions \u4e2d\u4f7f\u7528\u9a8c\u8bc1\u7684\u6700\u4f73\u5b9e\u8df5<\/p>\n<p>This setting is not suitable for all application scenarios. Only some options can have formal validations; if we think of a connection string, it is not necessarily formally incorrect, but the connection may not be working.<br \/>\n\u6b64\u8bbe\u7f6e\u5e76\u4e0d\u9002\u5408\u6240\u6709\u5e94\u7528\u7a0b\u5e8f\u65b9\u6848\u3002\u53ea\u6709\u67d0\u4e9b\u9009\u9879\u53ef\u4ee5\u8fdb\u884c\u6b63\u5f0f\u9a8c\u8bc1;\u5982\u679c\u6211\u4eec\u8003\u8651\u4e00\u4e2a\u8fde\u63a5\u5b57\u7b26\u4e32\uff0c\u5b83\u4e0d\u4e00\u5b9a\u5728\u5f62\u5f0f\u4e0a\u662f\u9519\u8bef\u7684\uff0c\u4f46\u8fde\u63a5\u53ef\u80fd\u65e0\u6cd5\u6b63\u5e38\u5de5\u4f5c\u3002<\/p>\n<p>Be cautious about applying this feature, especially since it reports errors at runtime and not during startup and gives an Internal Server Error, which is not a best practice in scenarios that should be handled.<br \/>\n\u5728\u5e94\u7528\u6b64\u529f\u80fd\u65f6\u8bf7\u8c28\u614e\uff0c\u5c24\u5176\u662f\u56e0\u4e3a\u5b83\u5728\u8fd0\u884c\u65f6\u800c\u4e0d\u662f\u5728\u542f\u52a8\u671f\u95f4\u62a5\u544a\u9519\u8bef\uff0c\u5e76\u7ed9\u51fa\u5185\u90e8\u670d\u52a1\u5668\u9519\u8bef\uff0c\u8fd9\u5728\u5e94\u8be5\u5904\u7406\u7684\u573a\u666f\u4e2d\u4e0d\u662f\u6700\u4f73\u5b9e\u8df5\u3002<\/p>\n<p>Everything we\u2019ve seen up to this point is about configuring the appsettings.json file, but what if we wanted to use other sources for configuration management? We\u2019ll look at that in the next section.<br \/>\n\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u6211\u4eec\u6240\u770b\u5230\u7684\u6240\u6709\u5185\u5bb9\u90fd\u662f\u5173\u4e8e\u914d\u7f6e appsettings.json \u6587\u4ef6\u7684\uff0c\u4f46\u662f\u5982\u679c\u6211\u4eec\u60f3\u4f7f\u7528\u5176\u4ed6\u6e90\u8fdb\u884c\u914d\u7f6e\u7ba1\u7406\u5462\uff1f\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u8282\u4e2d\u4ecb\u7ecd\u8fd9\u4e00\u70b9\u3002<\/p>\n<p>Configuration sources<br \/>\n\u914d\u7f6e\u6e90<\/p>\n<p>As we mentioned at the beginning of the section, the IConfiguration interface and all variants of IOptions work not only with the appsettings file but also on different sources.<br \/>\n\u6b63\u5982\u6211\u4eec\u5728\u672c\u8282\u5f00\u5934\u63d0\u5230\u7684\uff0cIConfiguration \u63a5\u53e3\u548c IOptions \u7684\u6240\u6709\u53d8\u4f53\u4e0d\u4ec5\u9002\u7528\u4e8e appsettings \u6587\u4ef6\uff0c\u4e5f\u9002\u7528\u4e8e\u4e0d\u540c\u7684\u6e90\u3002<\/p>\n<p>Each source has its own characteristics, and the syntax for accessing objects is very similar between providers. The main problem is when we must define a complex object or an array of objects; in this case, we will see how to behave and be able to replicate the dynamic structure of a JSON file.<br \/>\n\u6bcf\u4e2a\u6e90\u90fd\u6709\u5176\u81ea\u5df1\u7684\u7279\u5f81\uff0c\u5e76\u4e14\u8bbf\u95ee\u5bf9\u8c61\u7684\u8bed\u6cd5\u5728\u63d0\u4f9b\u7a0b\u5e8f\u4e4b\u95f4\u975e\u5e38\u76f8\u4f3c\u3002\u4e3b\u8981\u95ee\u9898\u662f\u5f53\u6211\u4eec\u5fc5\u987b\u5b9a\u4e49\u4e00\u4e2a\u590d\u6742\u5bf9\u8c61\u6216\u4e00\u4e2a\u5bf9\u8c61\u6570\u7ec4\u65f6;\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u4f5c\u5e76\u80fd\u591f\u590d\u5236 JSON \u6587\u4ef6\u7684\u52a8\u6001\u7ed3\u6784\u3002<\/p>\n<p>Let\u2019s look at two very common use cases.<br \/>\n\u8ba9\u6211\u4eec\u770b\u4e24\u4e2a\u975e\u5e38\u5e38\u89c1\u7684\u7528\u4f8b\u3002<\/p>\n<p>Configuring an application in Azure App Service<br \/>\n\u5728 Azure \u5e94\u7528\u670d\u52a1\u4e2d\u914d\u7f6e\u5e94\u7528\u7a0b\u5e8f<\/p>\n<p>Let\u2019s start with Azure, and in particular, the Azure Web Apps service.<br \/>\n\u8ba9\u6211\u4eec\u4ece Azure \u5f00\u59cb\uff0c\u7279\u522b\u662f Azure Web \u5e94\u7528\u670d\u52a1\u3002<\/p>\n<p>On the Configuration page, there are two sections: Application settings and Connection strings.<br \/>\n\u5728 Configuration \uff08\u914d\u7f6e\uff09 \u9875\u9762\u4e0a\uff0c\u6709\u4e24\u4e2a\u90e8\u5206\uff1a Application settings \uff08\u5e94\u7528\u7a0b\u5e8f\u8bbe\u7f6e\uff09 \u548c Connection strings \uff08\u8fde\u63a5\u5b57\u7b26\u4e32\uff09\u3002<\/p>\n<p>In the first section, we need to insert the keys and values or JSON objects that we saw in the previous examples.<br \/>\n\u5728\u7b2c\u4e00\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u9700\u8981\u63d2\u5165\u6211\u4eec\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\u770b\u5230\u7684\u952e\u548c\u503c\u6216 JSON \u5bf9\u8c61\u3002<\/p>\n<p>In the Connection strings section, you can insert the connection strings that are usually inserted in the appsettings.json file. In this section, in addition to the textual string, it is necessary to set the connection type, as we saw in the Configuration in .NET 6 section.<br \/>\n\u5728 Connection strings \uff08\u8fde\u63a5\u5b57\u7b26\u4e32\uff09 \u90e8\u5206\u4e2d\uff0c\u60a8\u53ef\u4ee5\u63d2\u5165\u901a\u5e38\u63d2\u5165 appsettings.json \u6587\u4ef6\u4e2d\u7684\u8fde\u63a5\u5b57\u7b26\u4e32\u3002\u5728\u672c\u8282\u4e2d\uff0c\u9664\u4e86\u6587\u672c\u5b57\u7b26\u4e32\u4e4b\u5916\uff0c\u8fd8\u9700\u8981\u8bbe\u7f6e\u8fde\u63a5\u7c7b\u578b\uff0c\u6b63\u5982\u6211\u4eec\u5728 .NET 6 \u4e2d\u7684\u914d\u7f6e\u90e8\u5206\u4e2d\u770b\u5230\u7684\u90a3\u6837\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0312.jpg\"><\/p>\n<p>Figure 3.12 \u2013 Azure App Service Application settings<br \/>\n\u56fe 3.12 \u2013 Azure \u5e94\u7528\u670d\u52a1\u5e94\u7528\u7a0b\u5e8f\u8bbe\u7f6e<\/p>\n<p>Inserting an object<br \/>\n\u63d2\u5165\u5bf9\u8c61<\/p>\n<p>To insert an object, we must specify the parent for each key.<br \/>\n\u8981\u63d2\u5165\u5bf9\u8c61\uff0c\u6211\u4eec\u5fc5\u987b\u4e3a\u6bcf\u4e2a\u952e\u6307\u5b9a parent\u3002<\/p>\n<p>The format is as follows:<br \/>\n\u683c\u5f0f\u5982\u4e0b\uff1a<\/p>\n<pre><code>parent__key<\/code><\/pre>\n<p>Note that there are two underscores.<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u6709\u4e24\u4e2a\u4e0b\u5212\u7ebf\u3002<\/p>\n<p>The object in the JSON file would be defined as follows:<br \/>\nJSON \u6587\u4ef6\u4e2d\u7684\u5bf9\u8c61\u5c06\u5b9a\u4e49\u5982\u4e0b\uff1a<\/p>\n<pre><code>&quot;MyCustomObject&quot;: {\n         &quot;CustomProperty&quot;: &quot;PropertyValue&quot;\n    }<\/code><\/pre>\n<p>So, we should write MyCustomObject<strong>CustomProperty.<br \/>\n\u6240\u4ee5\uff0c\u6211\u4eec\u5e94\u8be5\u5199MyCustomObject<\/strong>CustomProperty\u3002<\/p>\n<p>Inserting an array<br \/>\n\u63d2\u5165\u6570\u7ec4<\/p>\n<p>Inserting an array is much more verbose.<br \/>\n\u63d2\u5165\u6570\u7ec4\u8981\u8be6\u7ec6\u5f97\u591a\u3002<\/p>\n<p>The format is as follows:<br \/>\n\u683c\u5f0f\u5982\u4e0b\uff1a<\/p>\n<pre><code>parent__child__ArrayIndexNumber_key<\/code><\/pre>\n<p>The array in the JSON file would be defined as follows:<br \/>\nJSON \u6587\u4ef6\u4e2d\u7684\u6570\u7ec4\u5b9a\u4e49\u5982\u4e0b\uff1a<\/p>\n<p>{<br \/>\n&quot;MyCustomArray&quot;: {<br \/>\n&quot;CustomPropertyArray&quot;: [<br \/>\n{ &quot;CustomKey&quot;: &quot;ValueOne&quot; },<br \/>\n{ &quot;CustomKey &quot;: &quot;ValueTwo&quot; }<br \/>\n]<br \/>\n}<br \/>\n}<br \/>\nSo, to access the ValueOne value, we should write the following: MyCustomArray<strong>CustomPropertyArray<\/strong>0<strong>CustomKey.<br \/>\n\u56e0\u6b64\uff0c\u8981\u8bbf\u95ee ValueOne \u503c\uff0c\u6211\u4eec\u5e94\u8be5\u7f16\u5199\u4ee5\u4e0b\u5185\u5bb9\uff1aMyCustomArray<\/strong>CustomPropertyArray<strong>0<\/strong>CustomKey\u3002<\/p>\n<p>Configuring an application in Docker<br \/>\n\u5728 Docker \u4e2d\u914d\u7f6e\u5e94\u7528\u7a0b\u5e8f<\/p>\n<p>If we are developing for containers and therefore for Docker, appsettings files are usually replaced in the docker-compose file, and very often in the override file, because it behaves analogously to the settings files divided by the environment.<br \/>\n\u5982\u679c\u6211\u4eec\u9488\u5bf9\u5bb9\u5668\u548c Docker \u8fdb\u884c\u5f00\u53d1\uff0c\u5219 appsettings \u6587\u4ef6\u901a\u5e38\u4f1a\u5728 docker-compose \u6587\u4ef6\u4e2d\u88ab\u66ff\u6362\uff0c\u5e76\u4e14\u7ecf\u5e38\u5728 override \u6587\u4ef6\u4e2d\u88ab\u66ff\u6362\uff0c\u56e0\u4e3a\u5b83\u7684\u884c\u4e3a\u7c7b\u4f3c\u4e8e\u6309\u73af\u5883\u5212\u5206\u7684\u8bbe\u7f6e\u6587\u4ef6\u3002<\/p>\n<p>We want to provide a brief overview of the features that are usually leveraged to configure an application hosted in Docker. Let\u2019s see in detail how to define root keys and objects, and how to set the connection string. Here is an example:<br \/>\n\u6211\u4eec\u60f3\u7b80\u8981\u6982\u8ff0\u901a\u5e38\u7528\u4e8e\u914d\u7f6e Docker \u4e2d\u6258\u7ba1\u7684\u5e94\u7528\u7a0b\u5e8f\u7684\u529f\u80fd\u3002\u8ba9\u6211\u4eec\u8be6\u7ec6\u770b\u770b\u5982\u4f55\u5b9a\u4e49\u6839\u952e\u548c\u5bf9\u8c61\uff0c\u4ee5\u53ca\u5982\u4f55\u8bbe\u7f6e\u8fde\u63a5\u5b57\u7b26\u4e32\u3002\u4e0b\u9762\u662f\u4e00\u4e2a\u793a\u4f8b\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/env-test&quot;, (IConfiguration configuration) =&gt;\n{\n    var rootProperty = configuration.\n    GetValue&lt;string&gt;(&quot;RootProperty&quot;);\n    var sampleVariable = configuration.\n    GetValue&lt;string&gt;(&quot;RootSettings:SampleVariable&quot;);\n    var connectionString = configuration.\n    GetConnectionString(&quot;SqlConnection&quot;);\n    return Results.Ok(new\n    {\n        RootProperty = rootProperty,\n        SampleVariable = sampleVariable,\n        Connection String = connectionString\n    });\n})\n.WithName(&quot;EnvironmentTest&quot;);<\/code><\/pre>\n<p>Minimal APIs that use configuration<br \/>\n\u4f7f\u7528\u914d\u7f6e\u7684\u6700\u5c0f API<\/p>\n<p>The docker-compose.override.yaml file is as follows:<br \/>\ndocker-compose.override.yaml \u6587\u4ef6\u5982\u4e0b\uff1a<\/p>\n<pre><code>services:\n    dockerenvironment:\n         environment:\n              - ASPNETCORE_ENVIRONMENT=Development\n              - ASPNETCORE_URLS=https:\/\/+:443;http:\/\/+:80\n              - RootProperty=minimalapi-root-value\n              - RootSettings__SampleVariable=minimalapi-variable-value\n              - ConnectionStrings__SqlConnection=Server=minimal.db;Database=minimal_db;User Id=sa;Password=Taggia42!<\/code><\/pre>\n<p>There is only one application container for this example, and the service that instantiates it is called dockerenvironment.<br \/>\n\u6b64\u793a\u4f8b\u53ea\u6709\u4e00\u4e2a\u5e94\u7528\u7a0b\u5e8f\u5bb9\u5668\uff0c\u5b9e\u4f8b\u5316\u5b83\u7684\u670d\u52a1\u79f0\u4e3a dockerenvironment\u3002<\/p>\n<p>In the configuration section, we can see three particularities that we are going to analyze line by line.<br \/>\n\u5728\u914d\u7f6e\u90e8\u5206\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u6211\u4eec\u5c06\u9010\u884c\u5206\u6790\u7684\u4e09\u4e2a\u7279\u6027\u3002<\/p>\n<p>The snippet we want to show you has several very interesting components: a property in the configuration root, an object composed of a single property, and a connection string to a database.<br \/>\n\u6211\u4eec\u8981\u5411\u60a8\u5c55\u793a\u7684\u4ee3\u7801\u6bb5\u6709\u51e0\u4e2a\u975e\u5e38\u6709\u8da3\u7684\u7ec4\u4ef6\uff1a\u914d\u7f6e\u6839\u4e2d\u7684\u5c5e\u6027\u3001\u7531\u5355\u4e2a\u5c5e\u6027\u7ec4\u6210\u7684\u5bf9\u8c61\u4ee5\u53ca\u6570\u636e\u5e93\u7684\u8fde\u63a5\u5b57\u7b26\u4e32\u3002<\/p>\n<p>In this first configuration, you are going to set a property that is the root of the configurations. In this case, it is a simple string:<br \/>\n\u5728\u7b2c\u4e00\u4e2a\u914d\u7f6e\u4e2d\uff0c\u60a8\u5c06\u8bbe\u7f6e\u4e00\u4e2a\u5c5e\u6027\uff0c\u8be5\u5c5e\u6027\u662f\u914d\u7f6e\u7684\u6839\u3002\u5728\u672c\u4f8b\u4e2d\uff0c\u5b83\u662f\u4e00\u4e2a\u7b80\u5355\u7684\u5b57\u7b26\u4e32\uff1a<\/p>\n<pre><code># First configuration\n- RootProperty=minimalapi-root-value<\/code><\/pre>\n<p>In this second configuration, we are going to set up an object:<br \/>\n\u5728\u7b2c\u4e8c\u4e2a\u914d\u7f6e\u4e2d\uff0c\u6211\u4eec\u5c06\u8bbe\u7f6e\u4e00\u4e2a\u5bf9\u8c61\uff1a<\/p>\n<pre><code># Second configuration\n- RootSettings__SampleVariable=minimalapi-variable-value<\/code><\/pre>\n<p>The object is called RootSettings, while the only property it contains is called SampleVariable. This object can be read in different ways. We recommend using the Ioptions object that we have seen extensively before. In the preceding example, we show how to access a single property present in an object via code.<br \/>\n\u8be5\u5bf9\u8c61\u79f0\u4e3a RootSettings\uff0c\u800c\u5b83\u5305\u542b\u7684\u552f\u4e00\u5c5e\u6027\u79f0\u4e3a SampleVariable\u3002\u53ef\u4ee5\u901a\u8fc7\u4e0d\u540c\u7684\u65b9\u5f0f\u8bfb\u53d6\u6b64\u5bf9\u8c61\u3002\u6211\u4eec\u5efa\u8bae\u4f7f\u7528\u6211\u4eec\u4e4b\u524d\u5e7f\u6cdb\u770b\u5230\u7684 Ioptions \u5bf9\u8c61\u3002\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5c55\u793a\u4e86\u5982\u4f55\u901a\u8fc7\u4ee3\u7801\u8bbf\u95ee\u5bf9\u8c61\u4e2d\u5b58\u5728\u7684\u5355\u4e2a\u5c5e\u6027\u3002<\/p>\n<p>In this case, via code, you need to use the following notation to access the value: RootSettings:SampleVariable. This approach is useful if you need to read a single property, but we recommend using the Ioptions interfaces to access the object.<br \/>\n\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u9700\u8981\u901a\u8fc7\u4ee3\u7801\u4f7f\u7528\u4ee5\u4e0b\u8868\u793a\u6cd5\u6765\u8bbf\u95ee\u8be5\u503c\uff1aRootSettings\uff1aSampleVariable\u3002\u5982\u679c\u9700\u8981\u8bfb\u53d6\u5355\u4e2a\u5c5e\u6027\uff0c\u6b64\u65b9\u6cd5\u975e\u5e38\u6709\u7528\uff0c\u4f46\u6211\u4eec\u5efa\u8bae\u4f7f\u7528 Ioptions \u63a5\u53e3\u6765\u8bbf\u95ee\u5bf9\u8c61\u3002<\/p>\n<p>In this last example, we show you how to set the connection string called SqlConnection. This way, it will be easy to retrieve the information from the base methods available on Iconfiguration:<br \/>\n\u5728\u6700\u540e\u4e00\u4e2a\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5c06\u5411\u60a8\u5c55\u793a\u5982\u4f55\u8bbe\u7f6e\u540d\u4e3a SqlConnection \u7684\u8fde\u63a5\u5b57\u7b26\u4e32\u3002\u8fd9\u6837\uff0c\u5c31\u5f88\u5bb9\u6613\u4ece Iconfiguration \u4e0a\u53ef\u7528\u7684 base \u65b9\u6cd5\u4e2d\u68c0\u7d22\u4fe1\u606f\uff1a<\/p>\n<pre><code># Third configuration\n- ConnectionStrings__SqlConnection=Server=minimal.db;Database=minimal_db;User Id=sa;Password=Taggia42!<\/code><\/pre>\n<p>To read the information, it is necessary to exploit this method: GetConnectionString(\u201cSqlConnection\u201d).<br \/>\n\u8981\u8bfb\u53d6\u4fe1\u606f\uff0c\u5fc5\u987b\u5229\u7528\u6b64\u65b9\u6cd5\uff1a GetConnectionString\uff08\u201cSqlConnection\u201d\uff09\u3002<\/p>\n<p>There are a lot of scenarios for configuring our applications; in the next section, we will also see how to handle errors.<br \/>\n\u914d\u7f6e\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u6709\u5f88\u591a\u573a\u666f;\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u8fd8\u5c06\u4e86\u89e3\u5982\u4f55\u5904\u7406\u9519\u8bef\u3002<\/p>\n<p>Error handling<br \/>\n\u9519\u8bef\u5904\u7406<\/p>\n<p>Error handling is one of the features that every application must provide. The representation of an error allows the client to understand the error and possibly handle the request accordingly. Very often, we have our own customized methods of handling errors.<br \/>\n\u9519\u8bef\u5904\u7406\u662f\u6bcf\u4e2a\u5e94\u7528\u7a0b\u5e8f\u90fd\u5fc5\u987b\u63d0\u4f9b\u7684\u529f\u80fd\u4e4b\u4e00\u3002\u9519\u8bef\u7684\u8868\u793a\u5141\u8bb8\u5ba2\u6237\u7aef\u7406\u89e3\u9519\u8bef\u5e76\u53ef\u80fd\u76f8\u5e94\u5730\u5904\u7406\u8bf7\u6c42\u3002\u5f88\u591a\u65f6\u5019\uff0c\u6211\u4eec\u6709\u81ea\u5df1\u7684\u81ea\u5b9a\u4e49\u9519\u8bef\u5904\u7406\u65b9\u6cd5\u3002<\/p>\n<p>Since what we\u2019re describing is a key functionality of the application, we think it\u2019s fair to see what the framework provides and what is more correct to use.<br \/>\n\u7531\u4e8e\u6211\u4eec\u6240\u63cf\u8ff0\u7684\u662f\u5e94\u7528\u7a0b\u5e8f\u7684\u5173\u952e\u529f\u80fd\uff0c\u56e0\u6b64\u6211\u4eec\u8ba4\u4e3a\u67e5\u770b\u6846\u67b6\u63d0\u4f9b\u7684\u5185\u5bb9\u4ee5\u53ca\u4f7f\u7528\u8d77\u6765\u66f4\u6b63\u786e\u7684\u5185\u5bb9\u662f\u516c\u5e73\u7684\u3002<\/p>\n<p>Traditional approach<br \/>\n\u4f20\u7edf\u65b9\u6cd5<\/p>\n<p>.NET provides the same tool for minimal APIs that we can implement in traditional development: a Developer Exception Page. This is nothing but middleware that reports the error in plain text format. This middleware can\u2019t be removed from the ASP.NET pipeline and works exclusively in the development environment (<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/error-handling\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/error-handling<\/a>).<br \/>\n.NET \u4e3a\u6700\u5c0f API \u63d0\u4f9b\u4e86\u6211\u4eec\u53ef\u4ee5\u5728\u4f20\u7edf\u5f00\u53d1\u4e2d\u5b9e\u73b0\u7684\u76f8\u540c\u5de5\u5177\uff1a\u5f00\u53d1\u4eba\u5458\u5f02\u5e38\u9875\u3002\u8fd9\u53ea\u4e0d\u8fc7\u662f\u4ee5\u7eaf\u6587\u672c\u683c\u5f0f\u62a5\u544a\u9519\u8bef\u7684\u4e2d\u95f4\u4ef6\u3002\u6b64\u4e2d\u95f4\u4ef6\u65e0\u6cd5\u4ece ASP.NET \u7ba1\u9053\u4e2d\u5220\u9664\uff0c\u5e76\u4e14\u53ea\u80fd\u5728\u5f00\u53d1\u73af\u5883 \uff08<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/error-handling\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/error-handling<\/a>\uff09 \u4e2d\u8fd0\u884c\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0313.jpg\"><\/p>\n<p>Figure 3.13 \u2013 Minimal APIs pipeline, ExceptionHandler<br \/>\n\u56fe 3.13 \u2013 \u6700\u5c0f API \u7ba1\u9053 ExceptionHandler<\/p>\n<p>If exceptions are raised within our code, the only way to catch them in the application layer is through middleware that is activated before sending the response to the client.<br \/>\n\u5982\u679c\u5728\u6211\u4eec\u7684\u4ee3\u7801\u4e2d\u5f15\u53d1\u4e86\u5f02\u5e38\uff0c\u90a3\u4e48\u5728\u5e94\u7528\u7a0b\u5e8f\u5c42\u6355\u83b7\u5b83\u4eec\u7684\u552f\u4e00\u65b9\u6cd5\u662f\u901a\u8fc7\u5728\u5c06\u54cd\u5e94\u53d1\u9001\u5230\u5ba2\u6237\u7aef\u4e4b\u524d\u6fc0\u6d3b\u7684\u4e2d\u95f4\u4ef6\u3002<\/p>\n<p>Error handling middleware is standard and can be implemented as follows:<br \/>\n\u9519\u8bef\u5904\u7406\u4e2d\u95f4\u4ef6\u662f\u6807\u51c6\u7684\uff0c\u53ef\u4ee5\u6309\u5982\u4e0b\u65b9\u5f0f\u5b9e\u73b0\uff1a<\/p>\n<pre><code>app.UseExceptionHandler(exceptionHandlerApp =&gt;\n{\n    exceptionHandlerApp.Run(async context =&gt;\n    {\n        context.Response.StatusCode = StatusCodes.\n        Status500InternalServerError;\n        context.Response.ContentType = Application.Json;\n        var exceptionHandlerPathFeature = context.Features.\n          Get&lt;IExceptionHandlerPathFeature&gt;()!;\n        var errorMessage = new\n        {\n            Message = exceptionHandlerPathFeature.Error.Message\n        };\n        await context.Response.WriteAsync\n        (JsonSerializer.Serialize(errorMessage));\n         if (exceptionHandlerPathFeature?.\n             Error is FileNotFoundException)\n         {\n             await context.Response.\n             WriteAsync(&quot; The file was not found.&quot;);\n         }\n         if (exceptionHandlerPathFeature?.Path == &quot;\/&quot;)\n         {\n             await context.Response.WriteAsync(&quot;Page: Home.&quot;);\n         }\n    });\n});<\/code><\/pre>\n<p>We have shown here a possible implementation of the middleware. In order to be implemented, the UseExceptionHandler method must be exploited, allowing the writing of management code for the whole application.<br \/>\n\u6211\u4eec\u5728\u8fd9\u91cc\u5c55\u793a\u4e86\u4e2d\u95f4\u4ef6\u7684\u53ef\u80fd\u5b9e\u73b0\u3002\u4e3a\u4e86\u5b9e\u73b0\uff0c\u5fc5\u987b\u5229\u7528 UseExceptionHandler \u65b9\u6cd5\uff0c\u5141\u8bb8\u4e3a\u6574\u4e2a\u5e94\u7528\u7a0b\u5e8f\u7f16\u5199\u7ba1\u7406\u4ee3\u7801\u3002<\/p>\n<p>Through the var functionality called <code>exceptionHandlerPathFeature = context.Features.Get&lt;IExceptionHandlerPathFeature&gt;()!<\/code>;, we can access the error stack and return the information of interest for the caller in the output:<br \/>\n\u901a\u8fc7\u540d\u4e3a <code>exceptionHandlerPathFeature = context.Features.Get&lt;IExceptionHandlerPathFeature&gt;()!<\/code>;\uff0c\u6211\u4eec\u53ef\u4ee5\u8bbf\u95ee\u9519\u8bef\u5806\u6808\u5e76\u5728\u8f93\u51fa\u4e2d\u8fd4\u56de\u8c03\u7528\u65b9\u611f\u5174\u8da3\u7684\u4fe1\u606f\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/ok-result&quot;, () =&gt;\n{\n         throw new ArgumentNullException(&quot;taggia-parameter&quot;, \n         &quot;Taggia has an error&quot;);\n})\n.WithName(&quot;OkResult&quot;);<\/code><\/pre>\n<p>When an exception occurs in the code, as in the preceding example, the middleware steps in and handles the return message to the client.<br \/>\n\u5f53\u4ee3\u7801\u4e2d\u53d1\u751f\u5f02\u5e38\u65f6\uff0c\u5982\u524d\u9762\u7684\u793a\u4f8b\u6240\u793a\uff0c\u4e2d\u95f4\u4ef6\u4f1a\u4ecb\u5165\u5e76\u5904\u7406\u53d1\u9001\u7ed9\u5ba2\u6237\u7aef\u7684\u8fd4\u56de\u6d88\u606f\u3002<\/p>\n<p>If the exception were to occur in internal application stacks, the middleware would still intervene to provide the client with the correct error and appropriate indication.<br \/>\n\u5982\u679c\u5185\u90e8\u5e94\u7528\u7a0b\u5e8f\u5806\u6808\u4e2d\u53d1\u751f\u5f02\u5e38\uff0c\u4e2d\u95f4\u4ef6\u4ecd\u4f1a\u8fdb\u884c\u5e72\u9884\uff0c\u4e3a\u5ba2\u6237\u7aef\u63d0\u4f9b\u6b63\u786e\u7684\u9519\u8bef\u548c\u9002\u5f53\u7684\u6307\u793a\u3002<\/p>\n<p>Problem Details and the IETF standard<br \/>\n\u95ee\u9898\u8be6\u7ec6\u4fe1\u606f\u548c IETF \u6807\u51c6<\/p>\n<p>Problem Details for HTTP APIs is an IETF standard that was approved in 2016. This standard allows a set of information to be returned to the caller with standard fields and JSON notations that help identify the error.<br \/>\nHTTP API \u7684\u95ee\u9898\u8be6\u7ec6\u4fe1\u606f\u662f 2016 \u5e74\u6279\u51c6\u7684 IETF \u6807\u51c6\u3002\u6b64\u6807\u51c6\u5141\u8bb8\u4f7f\u7528\u6807\u51c6\u5b57\u6bb5\u548c JSON \u8868\u793a\u6cd5\u5c06\u4e00\u7ec4\u4fe1\u606f\u8fd4\u56de\u7ed9\u8c03\u7528\u65b9\uff0c\u4ee5\u5e2e\u52a9\u8bc6\u522b\u9519\u8bef\u3002<\/p>\n<p>HTTP status codes are sometimes not enough to convey enough information about an error to be useful. While the humans behind web browsers can be informed about the nature of the problem with an HTML response body, non-human consumers, such as machine, PC, and server, of so-called HTTP APIs usually cannot.<br \/>\nHTTP \u72b6\u6001\u4ee3\u7801\u6709\u65f6\u4e0d\u8db3\u4ee5\u4f20\u8fbe\u6709\u5173\u9519\u8bef\u7684\u8db3\u591f\u4fe1\u606f\uff0c\u56e0\u6b64\u6ca1\u6709\u7528\u3002\u867d\u7136 Web \u6d4f\u89c8\u5668\u80cc\u540e\u7684\u4eba\u7c7b\u53ef\u4ee5\u901a\u8fc7 HTML \u54cd\u5e94\u6b63\u6587\u4e86\u89e3\u95ee\u9898\u7684\u6027\u8d28\uff0c\u4f46\u6240\u8c13\u7684 HTTP API \u7684\u975e\u4eba\u7c7b\u4f7f\u7528\u8005\uff08\u5982\u673a\u5668\u3001PC \u548c\u670d\u52a1\u5668\uff09\u901a\u5e38\u4e0d\u80fd\u3002<\/p>\n<p>This specification defines simple JSON and XML document formats to suit this purpose. They are designed to be reused by HTTP APIs, which can identify distinct problem types specific to their needs.<br \/>\n\u6b64\u89c4\u8303\u5b9a\u4e49\u4e86\u7b80\u5355\u7684 JSON \u548c XML \u6587\u6863\u683c\u5f0f\u4ee5\u9002\u5e94\u6b64\u76ee\u7684\u3002\u5b83\u4eec\u65e8\u5728\u4f9b HTTP API \u91cd\u7528\uff0cHTTP API \u53ef\u4ee5\u8bc6\u522b\u7279\u5b9a\u4e8e\u5176\u9700\u6c42\u7684\u4e0d\u540c\u95ee\u9898\u7c7b\u578b\u3002<\/p>\n<p>Thus, API clients can be informed of both the high-level error class and the finer-grained details of the problem (<a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc7807\">https:\/\/datatracker.ietf.org\/doc\/html\/rfc7807<\/a>).<br \/>\n\u56e0\u6b64\uff0cAPI \u5ba2\u6237\u7aef\u53ef\u4ee5\u4e86\u89e3\u9ad8\u7ea7\u9519\u8bef\u7c7b\u548c\u95ee\u9898\u7684\u66f4\u7ec6\u7c92\u5ea6\u7684\u8be6\u7ec6\u4fe1\u606f \uff08<a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc7807\">https:\/\/datatracker.ietf.org\/doc\/html\/rfc7807<\/a>\uff09\u3002<\/p>\n<p>In .NET, there is a package with all the functionality that meets the IETF standard.<br \/>\n\u5728 .NET \u4e2d\uff0c\u6709\u4e00\u4e2a\u5305\uff0c\u5176\u4e2d\u5305\u542b\u6ee1\u8db3 IETF \u6807\u51c6\u7684\u6240\u6709\u529f\u80fd\u3002<\/p>\n<p>The package is called Hellang.Middleware.ProblemDetails, and you can download it at the following address: <a href=\"https:\/\/www.nuget.org\/packages\/Hellang.Middleware.ProblemDetails\/\">https:\/\/www.nuget.org\/packages\/Hellang.Middleware.ProblemDetails\/<\/a>.<br \/>\n\u8be5\u5305\u540d\u4e3a Hellang.Middleware.ProblemDetails\uff0c\u60a8\u53ef\u4ee5\u5728\u4ee5\u4e0b\u5730\u5740\u4e0b\u8f7d\uff1a<a href=\"https:\/\/www.nuget.org\/packages\/Hellang.Middleware.ProblemDetails\/\">https:\/\/www.nuget.org\/packages\/Hellang.Middleware.ProblemDetails\/<\/a>\u3002<\/p>\n<p>Let\u2019s see now how to insert the package into the project and configure it:<br \/>\n\u73b0\u5728\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u5c06\u5305\u63d2\u5165\u5230\u9879\u76ee\u4e2d\u5e76\u5bf9\u5176\u8fdb\u884c\u914d\u7f6e\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nbuilder.Services.TryAddSingleton&lt;IActionResultExecutor&lt;ObjectResult&gt;, ProblemDetailsResultExecutor&gt;();\nbuilder.Services.AddProblemDetails(options =&gt;\n{   options.MapToStatusCode&lt;NotImplementedException&gt;\n    (StatusCodes.Status501NotImplemented);\n});\nvar app = builder.Build();\napp.UseProblemDetails();<\/code><\/pre>\n<p>As you can see, there are only two instructions to make this package work:<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u53ea\u6709\u4e24\u6761\u8bf4\u660e\u53ef\u4ee5\u4f7f\u6b64\u8f6f\u4ef6\u5305\u6b63\u5e38\u5de5\u4f5c\uff1a<\/p>\n<pre><code>builder.Services.AddProblemDetails\napp.UseProblemDetails();<\/code><\/pre>\n<p>Since, in the minimal APIs, the IActionResultExecutor interface is not present in the ASP.NET pipeline, it is necessary to add a custom class to handle the response in case of an error.<br \/>\n\u7531\u4e8e\u5728\u6700\u5c0f API \u4e2d\uff0cASP.NET \u7ba1\u9053\u4e2d\u4e0d\u5b58\u5728 IActionResultExecutor \u63a5\u53e3\uff0c\u56e0\u6b64\u6709\u5fc5\u8981\u6dfb\u52a0\u81ea\u5b9a\u4e49\u7c7b\u4ee5\u5728\u51fa\u73b0\u9519\u8bef\u65f6\u5904\u7406\u54cd\u5e94\u3002<\/p>\n<p>To do this, you need to add a class (the following) and register it in the dependency injection engine: <code>builder.Services.TryAddSingleton&lt;IActionResultExecutor&lt;ObjectResult&gt;<\/code>, <code>ProblemDetailsResultExecutor&gt;()<\/code>; .<br \/>\n\u4e3a\u6b64\uff0c\u60a8\u9700\u8981\u6dfb\u52a0\u4e00\u4e2a\u7c7b\uff08\u5982\u4e0b\uff09\u5e76\u5728\u4f9d\u8d56\u9879\u6ce8\u5165\u5f15\u64ce builder \u4e2d\u6ce8\u518c\u5b83\u3002 <code>builder.Services.TryAddSingleton&lt;IActionResultExecutor&lt;ObjectResult&gt;<\/code>, <code>ProblemDetailsResultExecutor&gt;()<\/code>;\u3002<\/p>\n<p>Here is the class to support the package, also under minimal APIs:<br \/>\n\u4ee5\u4e0b\u662f\u652f\u6301\u8be5\u5305\u7684\u7c7b\uff0c\u4e5f\u5728\u6700\u5c0f API \u4e0b\uff1a<\/p>\n<pre><code>public class ProblemDetailsResultExecutor : IActionResultExecutor&lt;ObjectResult&gt;\n{\n    public virtual Task ExecuteAsync(ActionContext context, \n    ObjectResult result)\n{\n        ArgumentNullException.ThrowIfNull(context);\n        ArgumentNullException.ThrowIfNull(result);\n        var executor = Results.Json(result.Value, null, \n        &quot;application\/problem+json&quot;, result.StatusCode);\n        return executor.ExecuteAsync(context.HttpContext);\n    }\n}<\/code><\/pre>\n<p>As mentioned earlier, the standard for handling error messages has been present in the IETF standard for several years, but for the C# language, it is necessary to add the package just mentioned.<br \/>\n\u5982\u524d\u6240\u8ff0\uff0c\u5904\u7406\u9519\u8bef\u6d88\u606f\u7684\u6807\u51c6\u5728 IETF \u6807\u51c6\u4e2d\u5df2\u7ecf\u5b58\u5728\u4e86\u51e0\u5e74\uff0c\u4f46\u5bf9\u4e8e C# \u8bed\u8a00\uff0c\u6709\u5fc5\u8981\u6dfb\u52a0\u521a\u624d\u63d0\u5230\u7684\u5305\u3002<\/p>\n<p>Now, let\u2019s see how this package goes about handling errors on some endpoints that we report here:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u8fd9\u4e2a\u8f6f\u4ef6\u5305\u5982\u4f55\u5904\u7406\u6211\u4eec\u5728\u6b64\u5904\u62a5\u544a\u7684\u67d0\u4e9b\u7aef\u70b9\u4e0a\u7684\u9519\u8bef\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/internal-server-error&quot;, () =&gt;\n{\n    throw new ArgumentNullException(&quot;taggia-parameter&quot;, \n    &quot;Taggia has an error&quot;);\n})\n    .Produces&lt;ProblemDetails&gt;(StatusCodes.\n     Status500InternalServerError)\n         .WithName(&quot;internal-server-error&quot;);<\/code><\/pre>\n<p>We throw an application-level exception with this endpoint. In this case, the ProblemDetails middleware goes and returns a JSON error consistent with the error. We then have the handling of an unhandled exception for free:<br \/>\n\u6211\u4eec\u4f7f\u7528\u6b64\u7ec8\u7aef\u8282\u70b9\u5f15\u53d1\u5e94\u7528\u7a0b\u5e8f\u7ea7\u5f02\u5e38\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0cProblemDetails \u4e2d\u95f4\u4ef6\u4f1a\u8fd4\u56de\u4e0e\u9519\u8bef\u4e00\u81f4\u7684 JSON \u9519\u8bef\u3002\u7136\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u514d\u8d39\u5904\u7406\u672a\u5904\u7406\u7684\u5f02\u5e38\uff1a<\/p>\n<pre><code>{\n    &quot;type&quot;: &quot;https:\/\/httpstatuses.com\/500&quot;,\n    &quot;title&quot;: &quot;Internal Server Error&quot;,\n    &quot;status&quot;: 500,\n    &quot;detail&quot;: &quot;Taggia has an error (Parameter &#039;taggia-\n     parameter&#039;)&quot;,\n    &quot;exceptionDetails&quot;: [\n         {\n ------- for brevity\n         }\n    ],\n    &quot;traceId&quot;: &quot;00-f6ff69d6f7ba6d2692d87687d5be75c5-\n     e734f5f081d7a02a-00&quot;\n}<\/code><\/pre>\n<p>By inserting additional configurations in the Program file, you can map some specific exceptions to HTTP errors. Here is an example:<br \/>\n\u901a\u8fc7\u5728 Program \u6587\u4ef6\u4e2d\u63d2\u5165\u5176\u4ed6\u914d\u7f6e\uff0c\u60a8\u53ef\u4ee5\u5c06\u67d0\u4e9b\u7279\u5b9a\u5f02\u5e38\u6620\u5c04\u5230 HTTP \u9519\u8bef\u3002\u4e0b\u9762\u662f\u4e00\u4e2a\u793a\u4f8b\uff1a<\/p>\n<pre><code>builder.Services.AddProblemDetails(options =&gt;\n{\n    options.MapToStatusCode&lt;NotImplementedException&gt;\n      (StatusCodes.Status501NotImplemented);\n});<\/code><\/pre>\n<p>The code with the NotImplementedException exception is mapped to HTTP error code 501:<br \/>\n\u5177\u6709 NotImplementedException \u5f02\u5e38\u7684\u4ee3\u7801\u6620\u5c04\u5230 HTTP \u9519\u8bef\u4ee3\u7801 501\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/not-implemented-exception&quot;, () =&gt;\n{\n    throw new NotImplementedException\n      (&quot;This is an exception thrown from a Minimal API.&quot;);\n})\n    .Produces&lt;ProblemDetails&gt;(StatusCodes.\n     Status501NotImplemented)\n         .WithName(&quot;NotImplementedExceptions&quot;);<\/code><\/pre>\n<p>Finally, it is possible to create extensions to the ProblemDetails class of the framework with additional fields or to call the base method by adding custom text.<br \/>\n\u6700\u540e\uff0c\u53ef\u4ee5\u4f7f\u7528\u5176\u4ed6\u5b57\u6bb5\u521b\u5efa\u6846\u67b6\u7684 ProblemDetails \u7c7b\u7684\u6269\u5c55\uff0c\u6216\u8005\u901a\u8fc7\u6dfb\u52a0\u81ea\u5b9a\u4e49\u6587\u672c\u6765\u8c03\u7528\u57fa\u65b9\u6cd5\u3002<\/p>\n<p>Here are the last two examples of MapGet endpoint handlers:<br \/>\n\u4ee5\u4e0b\u662f MapGet \u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u6700\u540e\u4e24\u4e2a\u793a\u4f8b\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/problems&quot;, () =&gt;\n{\n    return Results.Problem(detail: &quot;This will end up in \n                                    the &#039;detail&#039; field.&quot;);\n})\n    .Produces&lt;ProblemDetails&gt;(StatusCodes.Status400BadRequest)\n    .WithName(&quot;Problems&quot;);\napp.MapGet(&quot;\/custom-error&quot;, () =&gt;\n{\n    var problem = new OutOfCreditProblemDetails\n    {\n        Type = &quot;https:\/\/example.com\/probs\/out-of-credit&quot;,\n        Title = &quot;You do not have enough credit.&quot;,\n        Detail = &quot;Your current balance is 30, \n        but that costs 50.&quot;,\n        Instance = &quot;\/account\/12345\/msgs\/abc&quot;,\n        Balance = 30.0m, Accounts = \n        { &quot;\/account\/12345&quot;, &quot;\/account\/67890&quot; }\n    };\n    return Results.Problem(problem);\n})\n    .Produces&lt;OutOfCreditProblemDetails&gt;(StatusCodes.\n     Status400BadRequest)\n     .WithName(&quot;CreditProblems&quot;);\napp.Run();\npublic class OutOfCreditProblemDetails : ProblemDetails\n{\n    public OutOfCreditProblemDetails()\n    {\n        Accounts = new List&lt;string&gt;();\n    }\n    public decimal Balance { get; set; }\n    public ICollection&lt;string&gt; Accounts { get; }\n}<\/code><\/pre>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>In this chapter, we have seen several advanced aspects regarding the implementation of minimal APIs. We explored Swagger, which is used to document APIs and provide the developer with a convenient, working debugging environment. We saw how CORS handles the issue of applications hosted on different addresses other than the current API. Finally, we saw how to load configuration information and handle unexpected errors in the application.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u6709\u5173\u5b9e\u73b0\u6700\u5c0f API \u7684\u51e0\u4e2a\u9ad8\u7ea7\u65b9\u9762\u3002\u6211\u4eec\u63a2\u7d22\u4e86 Swagger\uff0c\u5b83\u7528\u4e8e\u8bb0\u5f55 API\uff0c\u5e76\u4e3a\u5f00\u53d1\u4eba\u5458\u63d0\u4f9b\u65b9\u4fbf\u3001\u6709\u6548\u7684\u8c03\u8bd5\u73af\u5883\u3002\u6211\u4eec\u4e86\u89e3\u4e86 CORS \u5982\u4f55\u5904\u7406\u6258\u7ba1\u5728\u5f53\u524d API \u4ee5\u5916\u7684\u4e0d\u540c\u5730\u5740\u4e0a\u7684\u5e94\u7528\u7a0b\u5e8f\u95ee\u9898\u3002\u6700\u540e\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u5982\u4f55\u52a0\u8f7d\u914d\u7f6e\u4fe1\u606f\u548c\u5904\u7406\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u610f\u5916\u9519\u8bef\u3002<\/p>\n<p>We explored the nuts and bolts that will allow us to be productive in a short amount of time.<br \/>\n\u6211\u4eec\u63a2\u7d22\u4e86\u4f7f\u6211\u4eec\u80fd\u591f\u5728\u77ed\u65f6\u95f4\u5185\u63d0\u9ad8\u5de5\u4f5c\u6548\u7387\u7684\u5177\u4f53\u7ec6\u8282\u3002<\/p>\n<p>In the next chapter, we will add a fundamental building block for SOLID pattern-oriented programming, namely the dependency injection engine, which will help us to better manage the application code scattered in the various layers.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4e3a SOLID \u9762\u5411\u6a21\u5f0f\u7684\u7f16\u7a0b\u6dfb\u52a0\u4e00\u4e2a\u57fa\u672c\u6784\u5efa\u5757\uff0c\u5373\u4f9d\u8d56\u6ce8\u5165\u5f15\u64ce\uff0c\u8fd9\u5c06\u5e2e\u52a9\u6211\u4eec\u66f4\u597d\u5730\u7ba1\u7406\u5206\u6563\u5728\u5404\u4e2a\u5c42\u4e2d\u7684\u5e94\u7528\u7a0b\u5e8f\u4ee3\u7801\u3002<\/p>\n<h1>Part 2: What\u2019s New in .NET 6?<\/h1>\n<p>\u7b2c 2 \u90e8\u5206\uff1a.NET 6 \u4e2d\u7684\u65b0\u589e\u529f\u80fd<\/p>\n<p>In the second part of the book, we want to show you the features of the .NET 6 framework and how they can also be used in minimal APIs.<br \/>\n\u5728\u672c\u4e66\u7684\u7b2c\u4e8c\u90e8\u5206\uff0c\u6211\u4eec\u60f3\u5411\u4f60\u5c55\u793a .NET 6 \u6846\u67b6\u7684\u529f\u80fd\uff0c\u4ee5\u53ca\u5982\u4f55\u5728\u6700\u5c0f\u7684 API \u4e2d\u4f7f\u7528\u5b83\u4eec\u3002<\/p>\n<p>We will cover the following chapters in this section:<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u7ae0\u8282\uff1a<\/p>\n<p>Chapter 4, Dependency Injection in a Minimal API Project<br \/>\n\u7b2c 4 \u7ae0 \u6700\u5c0f API \u9879\u76ee\u4e2d\u7684\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165<\/p>\n<p>Chapter 5, Using Logging to Identify Errors<br \/>\n\u7b2c 5 \u7ae0 \u4f7f\u7528\u65e5\u5fd7\u8bb0\u5f55\u8bc6\u522b\u9519\u8bef<\/p>\n<p>Chapter 6, Exploring Validation and Mapping<br \/>\n\u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04<\/p>\n<p>Chapter 7, Integration with the Data Access Layer<br \/>\n\u7b2c 7 \u7ae0 \u4e0e\u6570\u636e\u8bbf\u95ee\u5c42\u96c6\u6210<\/p>\n<h1>4 Dependency Injection in a Minimal API Project<\/h1>\n<p>\u6700\u5c0f API \u9879\u76ee\u4e2d\u7684\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165<\/p>\n<p>In this chapter of the book, we will discuss some basic topics of minimal APIs in .NET 6.0. We will learn how they differ from the controller-based Web APIs that we were used to using in the previous version of .NET. We will also try to underline the pros and the cons of this new approach of writing APIs.<br \/>\n\u5728\u672c\u4e66\u7684\u8fd9\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba .NET 6.0 \u4e2d\u6700\u5c0f API \u7684\u4e00\u4e9b\u57fa\u672c\u4e3b\u9898\u3002\u6211\u4eec\u5c06\u4e86\u89e3\u5b83\u4eec\u4e0e\u6211\u4eec\u4ee5\u524d\u5728 .NET \u7248\u672c\u4e2d\u4e60\u60ef\u4f7f\u7528\u7684\u57fa\u4e8e\u63a7\u5236\u5668\u7684 Web API \u6709\u4f55\u4e0d\u540c\u3002\u6211\u4eec\u8fd8\u5c06\u5c1d\u8bd5\u5f3a\u8c03\u8fd9\u79cd\u7f16\u5199 API \u7684\u65b0\u65b9\u6cd5\u7684\u4f18\u7f3a\u70b9\u3002<\/p>\n<p>In this chapter, we will be covering the following topics:<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u4e3b\u9898\uff1a<\/p>\n<p>\u2022  What is dependency injection?<br \/>\n\u4ec0\u4e48\u662f\u4f9d\u8d56\u9879\u6ce8\u5165\uff1f<\/p>\n<p>\u2022  Implementing dependency injection in a minimal API project<br \/>\n\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u5b9e\u73b0\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>To follow the explanations in this chapter, you will need to create an ASP.NET Core 6.0 Web API application. You can refer the Technical requirements section of Chapter 2, Exploring Minimal APIs and Their Advantages to know how to do it.<br \/>\n\u8981\u6309\u7167\u672c\u7ae0\u4e2d\u7684\u8bf4\u660e\u8fdb\u884c\u4f5c\uff0c\u60a8\u9700\u8981\u521b\u5efa\u4e00\u4e2a ASP.NET Core 6.0 Web API \u5e94\u7528\u7a0b\u5e8f\u3002\u60a8\u53ef\u4ee5\u53c2\u8003 \u7b2c 2 \u7ae0 \u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf \u7684\u6280\u672f\u8981\u6c42 \u90e8\u5206\u6765\u4e86\u89e3\u5982\u4f55\u4f5c\u3002<\/p>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter04\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter04<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter04\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter04<\/a>\u3002<\/p>\n<p>What is dependency injection?<br \/>\n\u4ec0\u4e48\u662f\u4f9d\u8d56\u9879\u6ce8\u5165\uff1f<\/p>\n<p>For a while, .NET has natively supported the dependency injection (often referred to as DI) software design pattern.<br \/>\n\u4e00\u6bb5\u65f6\u95f4\u4ee5\u6765\uff0c.NET \u672c\u8eab\u5c31\u652f\u6301\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165\uff08\u901a\u5e38\u79f0\u4e3a DI\uff09\u8f6f\u4ef6\u8bbe\u8ba1\u6a21\u5f0f\u3002<\/p>\n<p>Dependency injection is a way to implement in .NET the Inversion of Control (IoC) pattern between service classes and their dependencies. By the way, in .NET, many fundamental services are built with dependency injection, such as logging, configuration, and other services.<br \/>\n\u4f9d\u8d56\u9879\u6ce8\u5165\u662f\u5728 .NET \u4e2d\u5b9e\u73b0\u670d\u52a1\u7c7b\u53ca\u5176\u4f9d\u8d56\u9879\u4e4b\u95f4\u7684\u63a7\u5236\u53cd\u8f6c \uff08IoC\uff09 \u6a21\u5f0f\u7684\u4e00\u79cd\u65b9\u5f0f\u3002\u987a\u4fbf\u8bf4\u4e00\u53e5\uff0c\u5728 .NET \u4e2d\uff0c\u8bb8\u591a\u57fa\u672c\u670d\u52a1\u90fd\u662f\u901a\u8fc7\u4f9d\u8d56\u9879\u6ce8\u5165\u6784\u5efa\u7684\uff0c\u4f8b\u5982\u65e5\u5fd7\u8bb0\u5f55\u3001\u914d\u7f6e\u548c\u5176\u4ed6\u670d\u52a1\u3002<\/p>\n<p>Let\u2019s look at a practical example to get a good understanding of how it works.<br \/>\n\u8ba9\u6211\u4eec\u770b\u4e00\u4e2a\u5b9e\u9645\u793a\u4f8b\uff0c\u4ee5\u66f4\u597d\u5730\u7406\u89e3\u5b83\u662f\u5982\u4f55\u5de5\u4f5c\u7684\u3002<\/p>\n<p>Generally speaking, a dependency is an object that depends on another object. In the following example, we have a LogWriter class with only one method inside, called Log:<br \/>\n\u4e00\u822c\u6765\u8bf4\uff0c\u4f9d\u8d56\u9879\u662f\u4f9d\u8d56\u4e8e\u53e6\u4e00\u4e2a\u5bf9\u8c61\u7684\u5bf9\u8c61\u3002\u5728\u4e0b\u9762\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u6709\u4e00\u4e2a LogWriter \u7c7b\uff0c\u5176\u4e2d\u53ea\u6709\u4e00\u4e2a\u65b9\u6cd5\uff0c\u79f0\u4e3a Log\uff1a<\/p>\n<pre><code>public class LogWriter\n{\n    public void Log(string message)\n    {\n        Console.WriteLine($&quot;LogWriter.Write\n          (message: \\&quot;{message}\\&quot;)&quot;);\n    }\n}<\/code><\/pre>\n<p>Other classes in the project, or in another project, can create an instance of the LogWriter class and use the Log method.<br \/>\n\u9879\u76ee\u6216\u5176\u4ed6\u9879\u76ee\u4e2d\u7684\u5176\u4ed6\u7c7b\u53ef\u4ee5\u521b\u5efa LogWriter \u7c7b\u7684\u5b9e\u4f8b\u5e76\u4f7f\u7528 Log \u65b9\u6cd5\u3002<\/p>\n<p>Take a look at the following example:<br \/>\n\u8bf7\u770b\u4ee5\u4e0b\u793a\u4f8b\uff1a<\/p>\n<pre><code>public class Worker\n{\n    private readonly LogWriter _logWriter = new LogWriter();\n    protected async Task ExecuteAsync(CancellationToken \n                                      stoppingToken)\n    {\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            _logWriter.Log($&quot;Worker running at: \n             {DateTimeOffset.Now}&quot;);\n             await Task.Delay(1000, stoppingToken);\n        }\n    }\n}<\/code><\/pre>\n<p>This class depends directly on the LogWriter class, and it\u2019s hardcoded in each class of your projects.<br \/>\n\u6b64\u7c7b\u76f4\u63a5\u4f9d\u8d56\u4e8e LogWriter \u7c7b\uff0c\u5e76\u4e14\u5728\u9879\u76ee\u7684\u6bcf\u4e2a\u7c7b\u4e2d\u90fd\u662f\u786c\u7f16\u7801\u7684\u3002<\/p>\n<p>This means that you will have some issues if you want to change the Log method; for instance, you will have to replace the implementation in each class of your solution.<br \/>\n\u8fd9\u610f\u5473\u7740\uff0c\u5982\u679c\u8981\u66f4\u6539 Log \u65b9\u6cd5\uff0c\u60a8\u5c06\u9047\u5230\u4e00\u4e9b\u95ee\u9898;\u4f8b\u5982\uff0c\u60a8\u5fc5\u987b\u66ff\u6362\u89e3\u51b3\u65b9\u6848\u7684\u6bcf\u4e2a\u7c7b\u4e2d\u7684 implementation\u3002<\/p>\n<p>The preceding implementation has some issues if you want to implement unit tests in your solution. It\u2019s not easy to create a mock of the LogWriter class.<br \/>\n\u5982\u679c\u8981\u5728\u89e3\u51b3\u65b9\u6848\u4e2d\u5b9e\u73b0\u5355\u5143\u6d4b\u8bd5\uff0c\u524d\u9762\u7684\u5b9e\u73b0\u5b58\u5728\u4e00\u4e9b\u95ee\u9898\u3002\u521b\u5efa LogWriter \u7c7b\u7684 mock \u5e76\u4e0d\u5bb9\u6613\u3002<\/p>\n<p>Dependency injection can solve these problems with some changes in our code:<br \/>\n\u4f9d\u8d56\u9879\u6ce8\u5165\u53ef\u4ee5\u901a\u8fc7\u5bf9\u4ee3\u7801\u8fdb\u884c\u4e00\u4e9b\u66f4\u6539\u6765\u89e3\u51b3\u8fd9\u4e9b\u95ee\u9898\uff1a<\/p>\n<ol>\n<li>\n<p>Use an interface to abstract the dependency.<br \/>\n\u4f7f\u7528\u63a5\u53e3\u62bd\u8c61\u4f9d\u8d56\u9879\u3002<\/p>\n<\/li>\n<li>\n<p>Register the dependency injection in the built-in service connecte to .NET.<br \/>\n\u5728\u5185\u7f6e\u670d\u52a1 connecte to .NET \u4e2d\u6ce8\u518c\u4f9d\u8d56\u9879\u6ce8\u5165\u3002<\/p>\n<\/li>\n<li>\n<p>Inject the service into the constructor of the class.<br \/>\n\u5c06\u670d\u52a1\u6ce8\u5165\u5230\u7c7b\u7684\u6784\u9020\u51fd\u6570\u4e2d\u3002<\/p>\n<\/li>\n<\/ol>\n<p>The preceding things might seem like they require big change in your code, but they are very easy to implement.<br \/>\n\u4e0a\u8ff0\u5185\u5bb9\u4f3c\u4e4e\u9700\u8981\u5bf9\u4ee3\u7801\u8fdb\u884c\u5927\u91cf\u66f4\u6539\uff0c\u4f46\u5b83\u4eec\u5f88\u5bb9\u6613\u5b9e\u73b0\u3002<\/p>\n<p>Let\u2019s see how we can achieve this goal with our previous example:<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u901a\u8fc7\u524d\u9762\u7684\u793a\u4f8b\u6765\u5b9e\u73b0\u8fd9\u4e2a\u76ee\u6807\uff1a<\/p>\n<ol>\n<li>First, we will create an ILogWriter interface with the abstraction of our logger:<br \/>\npublic interface ILogWriter<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u8bb0\u5f55\u5668\u7684\u62bd\u8c61\u521b\u5efa\u4e00\u4e2a ILogWriter \u63a5\u53e3\uff1a<\/li>\n<\/ol>\n<pre><code>{\n    void Log(string message);\n}<\/code><\/pre>\n<ol start=\"2\">\n<li>\n<p>Next, implement this ILogWriter interface in a real class called ConsoleLogWriter:<br \/>\npublic class ConsoleLogWriter : ILogWriter<br \/>\n\u63a5\u4e0b\u6765\uff0c\u5728\u540d\u4e3a ConsoleLogWriter \u7684\u5b9e\u9645\u7c7b\u4e2d\u5b9e\u73b0\u6b64 ILogWriter \u63a5\u53e3\uff1a<\/p>\n<pre><code>{\npublic void Log(string message)\n{\n    Console.WriteLine($&quot;ConsoleLogWriter.\n    Write(message: \\&quot;{message}\\&quot;)&quot;);\n}\n}<\/code><\/pre>\n<\/li>\n<li>\n<p>Now, change the Worker class and replace the explicit LogWriter class with the new ILogWriter interface:<\/p>\n<\/li>\n<\/ol>\n<p>\u73b0\u5728\uff0c\u66f4\u6539 Worker \u7c7b\uff0c\u5e76\u5c06\u663e\u5f0f LogWriter \u7c7b\u66ff\u6362\u4e3a\u65b0\u7684 ILogWriter \u63a5\u53e3\uff1a<\/p>\n<pre><code>public class Worker\n{\n    private readonly ILogWriter _logWriter;\n    public Worker(ILogWriter logWriter)\n    {\n        _logWriter = logWriter;\n    }\n\n    protected async Task ExecuteAsync\n      (CancellationToken stoppingToken)\n    {\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            _logWriter.Log($&quot;Worker running at:\n                             {DateTimeOffset.Now}&quot;);\n             await Task.Delay(1000, stoppingToken);\n        }\n    }\n}<\/code><\/pre>\n<p>As you can see, it\u2019s very easy to work in this new way, and the advantages are substantial. Here are a few advantages of dependency injection:<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u4ee5\u8fd9\u79cd\u65b0\u65b9\u5f0f\u5de5\u4f5c\u975e\u5e38\u5bb9\u6613\uff0c\u800c\u4e14\u4f18\u52bf\u975e\u5e38\u5927\u3002\u4ee5\u4e0b\u662f\u4f9d\u8d56\u9879\u6ce8\u5165\u7684\u4e00\u4e9b\u4f18\u70b9\uff1a<\/p>\n<p>\u2022  Maintainability \u53ef\u7ef4\u62a4\u6027<br \/>\n\u2022  Testability \u6d4b\u8bd5<br \/>\n\u2022  Reusability \u53ef\u91cd\u7528<\/p>\n<p>Now we need to perform the last step, that is, register the dependency when the application starts up.<br \/>\n\u73b0\u5728\u6211\u4eec\u9700\u8981\u6267\u884c\u6700\u540e\u4e00\u6b65\uff0c\u5373\u5728\u5e94\u7528\u7a0b\u5e8f\u542f\u52a8\u65f6\u6ce8\u518c\u4f9d\u8d56\u9879\u3002<\/p>\n<p>4.At the top of the Program.cs file, add this line of code:<br \/>\n\u5728 Program.cs \u6587\u4ef6\u7684\u9876\u90e8\uff0c\u6dfb\u52a0\u4ee5\u4e0b\u4ee3\u7801\u884c\uff1a<\/p>\n<pre><code>builder.Services.AddScoped&lt;ILogWriter, ConsoleLogWriter&gt;();<\/code><\/pre>\n<p>In the next section, we will discuss the difference between dependency injection lifetimes, another concept that you need to understand before using dependency injection in your minimal API project.<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u4f9d\u8d56\u6ce8\u5165\u751f\u547d\u5468\u671f\u4e4b\u95f4\u7684\u533a\u522b\uff0c\u8fd9\u662f\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u4f7f\u7528\u4f9d\u8d56\u6ce8\u5165\u4e4b\u524d\u9700\u8981\u4e86\u89e3\u7684\u53e6\u4e00\u4e2a\u6982\u5ff5\u3002<\/p>\n<p>Understanding dependency injection lifetimes<br \/>\n\u4e86\u89e3\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165\u751f\u547d\u5468\u671f<\/p>\n<p>In the previous section, we learned the benefits of using dependency injection in our project and how to transform our code to use it.<br \/>\n\u5728\u4e0a\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u5728\u9879\u76ee\u4e2d\u4f7f\u7528\u4f9d\u8d56\u9879\u6ce8\u5165\u7684\u597d\u5904\uff0c\u4ee5\u53ca\u5982\u4f55\u8f6c\u6362\u4ee3\u7801\u4ee5\u4f7f\u7528\u5b83\u3002<\/p>\n<p>In one of the last paragraphs, we added our class as a service to ServiceCollection of .NET.<br \/>\n\u5728\u6700\u540e\u4e00\u6bb5\u4e2d\uff0c\u6211\u4eec\u5c06\u7c7b\u4f5c\u4e3a\u670d\u52a1\u6dfb\u52a0\u5230 .NET \u7684 ServiceCollection \u4e2d\u3002<\/p>\n<p>In this section, we will try to understand the difference between each dependency injection\u2019s lifetime.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u5c1d\u8bd5\u4e86\u89e3\u6bcf\u4e2a\u4f9d\u8d56\u6ce8\u5165\u7684\u751f\u547d\u5468\u671f\u4e4b\u95f4\u7684\u5dee\u5f02\u3002<\/p>\n<p>The service lifetime defines how long an object will be alive after it has been created by the container.<br \/>\n\u670d\u52a1\u751f\u5b58\u671f\u5b9a\u4e49\u5bf9\u8c61\u5728\u5bb9\u5668\u521b\u5efa\u540e\u5c06\u5904\u4e8e\u6d3b\u52a8\u72b6\u6001\u7684\u65f6\u95f4\u3002<\/p>\n<p>When they are registered, dependencies require a lifetime definition. This defines the conditions when a new service instance is created.<br \/>\n\u6ce8\u518c\u4f9d\u8d56\u9879\u65f6\uff0c\u5b83\u4eec\u9700\u8981\u751f\u547d\u5468\u671f\u5b9a\u4e49\u3002\u8fd9\u5b9a\u4e49\u4e86\u521b\u5efa\u65b0\u670d\u52a1\u5b9e\u4f8b\u65f6\u7684\u6761\u4ef6\u3002<\/p>\n<p>In the following list, you can find the lifetimes defined in .NET:<br \/>\n\u5728\u4ee5\u4e0b\u5217\u8868\u4e2d\uff0c\u60a8\u53ef\u4ee5\u627e\u5230 .NET \u4e2d\u5b9a\u4e49\u7684\u751f\u5b58\u671f\uff1a<\/p>\n<p>\u2022  Transient: A new instance of the class is created every time it is requested.<br \/>\nTransient\uff1a\u6bcf\u6b21\u8bf7\u6c42\u65f6\u90fd\u4f1a\u521b\u5efa\u7c7b\u7684\u65b0\u5b9e\u4f8b\u3002<\/p>\n<p>\u2022  Scoped: A new instance of the class is created once per scope, for instance, for the same HTTP request.<br \/>\n\u8303\u56f4\uff1a\u6bcf\u4e2a\u8303\u56f4\u521b\u5efa\u4e00\u6b21\u7c7b\u7684\u65b0\u5b9e\u4f8b\uff0c\u4f8b\u5982\uff0c\u9488\u5bf9\u540c\u4e00 HTTP \u8bf7\u6c42\u3002<\/p>\n<p>\u2022  Singleton: A new instance of the class is created only on the first request. The next request will use the same instance of the same class.<br \/>\nSingleton\uff1a\u4ec5\u5728\u7b2c\u4e00\u4e2a\u8bf7\u6c42\u65f6\u521b\u5efa\u7c7b\u7684\u65b0\u5b9e\u4f8b\u3002\u4e0b\u4e00\u4e2a\u8bf7\u6c42\u5c06\u4f7f\u7528\u540c\u4e00\u7c7b\u7684\u76f8\u540c\u5b9e\u4f8b\u3002<\/p>\n<p>Very often, in web applications, you only find the first two lifetimes, that is, transient and scoped.<br \/>\n\u5f88\u591a\u65f6\u5019\uff0c\u5728 Web \u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u4f60\u53ea\u80fd\u627e\u5230\u524d\u4e24\u4e2a\u751f\u547d\u5468\u671f\uff0c\u5373 transient \u548c scoped\u3002<\/p>\n<p>If you have a particular use case that requires a singleton, it\u2019s not prohibited, but for best practice, it is recommended to avoid them in web applications.<br \/>\n\u5982\u679c\u60a8\u6709\u9700\u8981\u5355\u4f8b\u7684\u7279\u5b9a\u7528\u4f8b\uff0c\u5219\u4e0d\u7981\u6b62\u8fd9\u6837\u505a\uff0c\u4f46\u4e3a\u4e86\u6700\u4f73\u5b9e\u8df5\uff0c\u5efa\u8bae\u5728 Web \u5e94\u7528\u7a0b\u5e8f\u4e2d\u907f\u514d\u4f7f\u7528\u5b83\u4eec\u3002<\/p>\n<p>In the first two cases, transient and scoped, the services are disposed of at the end of the request.<br \/>\n\u5728\u524d\u4e24\u79cd\u60c5\u51b5\u4e2d\uff0ctransient \u548c scoped\uff0c\u670d\u52a1\u5c06\u5728\u8bf7\u6c42\u7ed3\u675f\u65f6\u88ab\u91ca\u653e\u3002<\/p>\n<p>In the next section, we will see how to implement all the concepts that we have mentioned in the last two sections (the definition of dependency injection and its lifetime) in a short demo that you can use as a starting point for your next project.<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u901a\u8fc7\u4e00\u4e2a\u7b80\u77ed\u7684\u6f14\u793a\u6765\u4e86\u89e3\u5982\u4f55\u5b9e\u73b0\u6211\u4eec\u5728\u6700\u540e\u4e24\u8282\u4e2d\u63d0\u5230\u7684\u6240\u6709\u6982\u5ff5\uff08\u4f9d\u8d56\u6ce8\u5165\u7684\u5b9a\u4e49\u53ca\u5176\u751f\u547d\u5468\u671f\uff09\uff0c\u60a8\u53ef\u4ee5\u5c06\u5176\u7528\u4f5c\u4e0b\u4e00\u4e2a\u9879\u76ee\u7684\u8d77\u70b9\u3002<\/p>\n<p>Implementing dependency injection in a minimal API project<br \/>\n\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u5b9e\u73b0\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165<\/p>\n<p>After understanding how to use dependency injection in an ASP.NET Core project, let\u2019s try to understand how to use dependency injection in our minimal API project, starting with the default project using the WeatherForecast endpoint.<br \/>\n\u5728\u4e86\u89e3\u4e86\u5982\u4f55\u5728 ASP.NET Core \u9879\u76ee\u4e2d\u4f7f\u7528\u4f9d\u8d56\u9879\u6ce8\u5165\u4e4b\u540e\uff0c\u8ba9\u6211\u4eec\u5c1d\u8bd5\u4e86\u89e3\u5982\u4f55\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u4f7f\u7528\u4f9d\u8d56\u9879\u6ce8\u5165\uff0c\u4ece\u4f7f\u7528 WeatherForecast \u7aef\u70b9\u7684\u9ed8\u8ba4\u9879\u76ee\u5f00\u59cb\u3002<\/p>\n<p>This is the actual code of the WeatherForecast GET endpoint:<br \/>\n\u8fd9\u662f WeatherForecast GET \u7aef\u70b9\u7684\u5b9e\u9645\u4ee3\u7801\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/weatherforecast&quot;, () =&gt;\n{\n    var forecast = Enumerable.Range(1, 5).Select(index =&gt;\n    new WeatherForecast\n    (\n        DateTime.Now.AddDays(index),\n        Random.Shared.Next(-20, 55),\n        summaries[Random.Shared.\n        Next(summaries.Length)]\n    ))\n    .ToArray();\n    return forecast;\n});<\/code><\/pre>\n<p>As we mentioned before, this code works but it\u2019s not easy to test it, especially the creation of the new values of the weather.<br \/>\n\u6b63\u5982\u6211\u4eec\u4e4b\u524d\u63d0\u5230\u7684\uff0c\u8fd9\u6bb5\u4ee3\u7801\u53ef\u4ee5\u5de5\u4f5c\uff0c\u4f46\u5e76\u4e0d\u5bb9\u6613\u6d4b\u8bd5\u5b83\uff0c\u5c24\u5176\u662f\u521b\u5efa weather \u7684\u65b0\u503c\u3002<\/p>\n<p>The best choice is to use a service to create fake values and use it with dependency injection.<br \/>\n\u6700\u597d\u7684\u9009\u62e9\u662f\u4f7f\u7528\u670d\u52a1\u521b\u5efa\u5047\u503c\u5e76\u5c06\u5176\u4e0e\u4f9d\u8d56\u9879\u6ce8\u5165\u4e00\u8d77\u4f7f\u7528\u3002<\/p>\n<p>Let\u2019s see how we can better implement our code:<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u66f4\u597d\u5730\u5b9e\u73b0\u6211\u4eec\u7684\u4ee3\u7801\uff1a<\/p>\n<ol>\n<li>First of all, in the Program.cs file, add a new interface called IWeatherForecastService and define a method that returns an array of the WeatherForecast entity:<br \/>\n\u9996\u5148\uff0c\u5728 Program.cs \u6587\u4ef6\u4e2d\uff0c\u6dfb\u52a0\u4e00\u4e2a\u540d\u4e3a IWeatherForecastService \u7684\u65b0\u63a5\u53e3\uff0c\u5e76\u5b9a\u4e49\u4e00\u4e2a\u8fd4\u56de WeatherForecast \u5b9e\u4f53\u6570\u7ec4\u7684\u65b9\u6cd5\uff1a<\/li>\n<\/ol>\n<pre><code>public interface IWeatherForecastService\n{\n           WeatherForecast[] GetForecast();\n}<\/code><\/pre>\n<ol start=\"2\">\n<li>The next step is to create the real implementation of the class inherited from the interface.<br \/>\n\u4e0b\u4e00\u6b65\u662f\u521b\u5efa\u4ece\u63a5\u53e3\u7ee7\u627f\u7684\u7c7b\u7684\u771f\u6b63\u5b9e\u73b0\u3002<\/li>\n<\/ol>\n<p>The code should look like this:<br \/>\n\u4ee3\u7801\u5e94\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>public class WeatherForecastService : IWeatherForecastService\n{\n}<\/code><\/pre>\n<ol start=\"3\">\n<li>\n<p>Now cut and paste the code from the project template inside our new implementation of the service. The final code looks like this:<br \/>\n\u73b0\u5728\uff0c\u5c06\u9879\u76ee\u6a21\u677f\u4e2d\u7684\u4ee3\u7801\u526a\u5207\u5e76\u7c98\u8d34\u5230\u6211\u4eec\u65b0\u7684\u670d\u52a1\u5b9e\u73b0\u4e2d\u3002\u6700\u7ec8\u4ee3\u7801\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>public class WeatherForecastService : IWeatherForecastService\n{\npublic WeatherForecast[] GetForecast()\n{\n    var summaries = new[]\n    {\n        &quot;Freezing&quot;, &quot;Bracing&quot;, &quot;Chilly&quot;, &quot;Cool&quot;,\n        &quot;Mild&quot;, &quot;Warm&quot;, &quot;Balmy&quot;, &quot;Hot&quot;, &quot;Sweltering&quot;,\n        &quot;Scorching&quot;\n    };\n    var forecast = Enumerable.Range(1, 5).\n    Select(index =&gt;\n    new WeatherForecast\n    (\n        DateTime.Now.AddDays(index),\n        Random.Shared.Next(-20, 55),\n        summaries[Random.Shared.Next\n        (summaries.Length)]\n    ))\n    .ToArray();\n    return forecast;\n}\n}<\/code><\/pre>\n<\/li>\n<li>\n<p>We are now ready to add our implementation of WeatherForecastService as a dependency injection in our project. To do that, insert the following line below the first line of code in the Program.cs file:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5df2\u51c6\u5907\u597d\u5c06 WeatherForecastService \u7684\u5b9e\u73b0\u4f5c\u4e3a\u4f9d\u8d56\u9879\u6ce8\u5165\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u9879\u76ee\u4e2d\u3002\u4e3a\u6b64\uff0c\u8bf7\u5728 Program.cs \u6587\u4ef6\u4e2d\u7684\u7b2c\u4e00\u884c\u4ee3\u7801\u4e0b\u65b9\u63d2\u5165\u4ee5\u4e0b\u884c\uff1a<\/p>\n<\/li>\n<\/ol>\n<pre><code>builder.Services.AddScoped&lt;IWeatherForecastService, WeatherForecastService&gt;();<\/code><\/pre>\n<p>When the application starts, insert our service into the services collection. Our work is not finished yet.<br \/>\n\u5f53\u5e94\u7528\u7a0b\u5e8f\u542f\u52a8\u65f6\uff0c\u5c06\u6211\u4eec\u7684\u670d\u52a1\u63d2\u5165\u5230\u670d\u52a1\u96c6\u5408\u4e2d\u3002\u6211\u4eec\u7684\u5de5\u4f5c\u8fd8\u6ca1\u6709\u5b8c\u6210\u3002<\/p>\n<p>We need to use our service in the default MapGet implementation of the WeatherForecast endpoint.<br \/>\n\u6211\u4eec\u9700\u8981\u5728 WeatherForecast \u7aef\u70b9\u7684\u9ed8\u8ba4 MapGet \u5b9e\u73b0\u4e2d\u4f7f\u7528\u6211\u4eec\u7684\u670d\u52a1\u3002<\/p>\n<p>The minimal API has his own parameter binding implementation and it\u2019s very easy to use.<br \/>\n\u6700\u5c0f\u7684 API \u6709\u81ea\u5df1\u7684\u53c2\u6570\u7ed1\u5b9a\u5b9e\u73b0\uff0c\u975e\u5e38\u6613\u4e8e\u4f7f\u7528\u3002<\/p>\n<p>First of all, to implement our service with dependency injection, we need to remove all the old code from the endpoint.<br \/>\n\u9996\u5148\uff0c\u8981\u4f7f\u7528\u4f9d\u8d56\u9879\u6ce8\u5165\u5b9e\u73b0\u6211\u4eec\u7684\u670d\u52a1\uff0c\u6211\u4eec\u9700\u8981\u4ece\u7aef\u70b9\u4e2d\u5220\u9664\u6240\u6709\u65e7\u4ee3\u7801\u3002<\/p>\n<p>The code of the endpoint, after removing the code, looks like this:<br \/>\n\u5220\u9664\u4ee3\u7801\u540e\uff0c\u7aef\u70b9\u7684\u4ee3\u7801\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/weatherforecast&quot;, () =&gt;\n{\n});<\/code><\/pre>\n<p>We can improve our code and use the dependency injection very easily by simply replacing the old code with the new code:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u7b80\u5355\u5730\u5c06\u65e7\u4ee3\u7801\u66ff\u6362\u4e3a\u65b0\u4ee3\u7801\u6765\u975e\u5e38\u8f7b\u677e\u5730\u6539\u8fdb\u6211\u4eec\u7684\u4ee3\u7801\u5e76\u4f7f\u7528\u4f9d\u8d56\u6ce8\u5165\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/weatherforecast&quot;, (IWeatherForecastService weatherForecastService) =&gt;\n{\n    return weatherForecastService.GetForecast();\n});<\/code><\/pre>\n<p>In the minimal API project, the real implementations of the services in the service collection are passed as parameters to the functions and you can use them directly.<br \/>\n\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\uff0c\u670d\u52a1\u96c6\u5408\u4e2d\u670d\u52a1\u7684\u771f\u5b9e\u5b9e\u73b0\u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u7ed9\u51fd\u6570\uff0c\u60a8\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u5b83\u4eec\u3002<\/p>\n<p>From time to time, you may have to use a service from the dependency injection directly in the main function during the startup phase. In this case, you must retrieve the instance of the implementation directly from the services collection, as shown in the following code snippet:<br \/>\n\u6709\u65f6\uff0c\u60a8\u53ef\u80fd\u5fc5\u987b\u5728\u542f\u52a8\u9636\u6bb5\u76f4\u63a5\u5728 main \u51fd\u6570\u4e2d\u4f7f\u7528\u4f9d\u8d56\u9879\u6ce8\u5165\u4e2d\u7684\u670d\u52a1\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u5fc5\u987b\u76f4\u63a5\u4ece services \u96c6\u5408\u4e2d\u68c0\u7d22\u5b9e\u73b0\u7684\u5b9e\u4f8b\uff0c\u5982\u4ee5\u4e0b\u4ee3\u7801\u7247\u6bb5\u6240\u793a\uff1a<\/p>\n<pre><code>using (var scope = app.Services.CreateScope())\n{\n    var service = scope.ServiceProvider.GetRequiredService\n                  &lt;IWeatherForecastService&gt;();\n    service.GetForecast();\n}<\/code><\/pre>\n<p>In this section, we have implemented dependency injection in a minimal API project, starting from the default template.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u4ece\u9ed8\u8ba4\u6a21\u677f\u5f00\u59cb\uff0c\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u5b9e\u73b0\u4e86\u4f9d\u8d56\u6ce8\u5165\u3002<\/p>\n<p>We reused the existing code but implemented it with logic that\u2019s more geared toward an architecture that\u2019s better suited to being maintained and tested in the future.<br \/>\n\u6211\u4eec\u91cd\u7528\u4e86\u73b0\u6709\u4ee3\u7801\uff0c\u4f46\u4f7f\u7528\u66f4\u9002\u5408\u5c06\u6765\u7ef4\u62a4\u548c\u6d4b\u8bd5\u7684\u67b6\u6784\u7684\u903b\u8f91\u6765\u5b9e\u73b0\u5b83\u3002<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>Dependency injection is a very important approach to implement in modern applications. In this chapter, we learned what dependency injection is and discussed its fundamentals. Then, we saw how to use dependency injection in a minimal API project.<br \/>\n\u4f9d\u8d56\u9879\u6ce8\u5165\u662f\u5728\u73b0\u4ee3\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5b9e\u73b0\u7684\u4e00\u79cd\u975e\u5e38\u91cd\u8981\u7684\u65b9\u6cd5\u3002\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u4ec0\u4e48\u662f\u4f9d\u8d56\u6ce8\u5165\u5e76\u8ba8\u8bba\u4e86\u5b83\u7684\u57fa\u7840\u77e5\u8bc6\u3002\u7136\u540e\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u5982\u4f55\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u4f7f\u7528\u4f9d\u8d56\u6ce8\u5165\u3002<\/p>\n<p>In the next chapter, we will focus on another important layer of modern applications and discuss how to implement a logging strategy in a minimal API project.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u91cd\u70b9\u4ecb\u7ecd\u73b0\u4ee3\u5e94\u7528\u7a0b\u5e8f\u7684\u53e6\u4e00\u4e2a\u91cd\u8981\u5c42\uff0c\u5e76\u8ba8\u8bba\u5982\u4f55\u5728\u6700\u5c0f\u7684 API \u9879\u76ee\u4e2d\u5b9e\u73b0\u65e5\u5fd7\u8bb0\u5f55\u7b56\u7565\u3002<\/p>\n<h1>5 Using Logging to Identify Errors<\/h1>\n<p>5 \u4f7f\u7528\u65e5\u5fd7\u8bb0\u5f55\u8bc6\u522b\u9519\u8bef<\/p>\n<p>In this chapter, we will begin to learn about the logging tools that .NET provides us with. A logger is one of the tools that developers must use to debug an application or understand its failure in production. The log library has been built into ASP.NET with several features enabled by design. The purpose of this chapter is to delve into the things we take for granted and add more information as we go.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u5f00\u59cb\u4e86\u89e3 .NET \u4e3a\u6211\u4eec\u63d0\u4f9b\u7684\u65e5\u5fd7\u8bb0\u5f55\u5de5\u5177\u3002\u8bb0\u5f55\u5668\u662f\u5f00\u53d1\u4eba\u5458\u7528\u6765\u8c03\u8bd5\u5e94\u7528\u7a0b\u5e8f\u6216\u4e86\u89e3\u5176\u5728\u751f\u4ea7\u4e2d\u7684\u6545\u969c\u65f6\u5fc5\u987b\u4f7f\u7528\u7684\u5de5\u5177\u4e4b\u4e00\u3002\u65e5\u5fd7\u5e93\u5df2\u5185\u7f6e\u4e8e ASP.NET \u4e2d\uff0c\u901a\u8fc7\u8bbe\u8ba1\u542f\u7528\u4e86\u591a\u9879\u529f\u80fd\u3002\u672c\u7ae0\u7684\u76ee\u7684\u662f\u6df1\u5165\u7814\u7a76\u6211\u4eec\u8ba4\u4e3a\u7406\u6240\u5f53\u7136\u7684\u4e8b\u60c5\uff0c\u5e76\u5728\u6b64\u8fc7\u7a0b\u4e2d\u6dfb\u52a0\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>The themes we will touch on in this chapter are as follows:<br \/>\n\u6211\u4eec\u5c06\u5728\u672c\u7ae0\u4e2d\u8ba8\u8bba\u7684\u4e3b\u9898\u5982\u4e0b\uff1a<\/p>\n<p>\u2022  Exploring logging in .NET<br \/>\n\u63a2\u7d22 .NET \u4e2d\u7684\u65e5\u5fd7\u8bb0\u5f55<\/p>\n<p>\u2022  Leveraging the logging framework<br \/>\n\u5229\u7528\u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6<\/p>\n<p>\u2022  Storing a structured log with Serilog<br \/>\n\u4f7f\u7528 Serilog \u5b58\u50a8\u7ed3\u6784\u5316\u65e5\u5fd7<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>As reported in the previous chapters, it will be necessary to have the .NET 6 development framework.<br \/>\n\u5982\u524d\u51e0\u7ae0\u6240\u8ff0\uff0c\u6709\u5fc5\u8981\u5177\u6709 .NET 6 \u5f00\u53d1\u6846\u67b6\u3002<\/p>\n<p>There are no special requirements in this chapter for beginning to test the examples described.<br \/>\n\u672c\u7ae0\u4e2d\u6ca1\u6709\u5bf9\u5f00\u59cb\u6d4b\u8bd5\u6240\u63cf\u8ff0\u7684\u793a\u4f8b\u7684\u7279\u6b8a\u8981\u6c42\u3002<\/p>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter05\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter05<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter05\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter05<\/a>\u3002<\/p>\n<p>Exploring logging in .NET<br \/>\n\u63a2\u7d22 .NET \u4e2d\u7684\u65e5\u5fd7\u8bb0\u5f55<\/p>\n<p>ASP.NET Core templates create a WebApplicationBuilder and a WebApplication, which provide a simplified way to configure and run web applications without a startup class.<br \/>\nASP.NET Core \u6a21\u677f\u521b\u5efa WebApplicationBuilder \u548c WebApplication\uff0c\u5b83\u4eec\u63d0\u4f9b\u4e86\u4e00\u79cd\u65e0\u9700\u542f\u52a8\u7c7b\u5373\u53ef\u914d\u7f6e\u548c\u8fd0\u884c Web \u5e94\u7528\u7a0b\u5e8f\u7684\u7b80\u5316\u65b9\u6cd5\u3002<\/p>\n<p>As mentioned previously, with .NET 6, the Startup.cs file is eliminated in favor of the existing Program.cs file. All startup configurations are placed in this file, and in the case of minimal APIs, endpoint implementations are also placed.<br \/>\n\u5982\u524d\u6240\u8ff0\uff0c\u5728 .NET 6 \u4e2d\uff0cStartup.cs \u6587\u4ef6\u88ab\u6d88\u9664\uff0c\u53d6\u800c\u4ee3\u4e4b\u7684\u662f\u73b0\u6709\u7684 Program.cs \u6587\u4ef6\u3002\u6240\u6709\u542f\u52a8\u914d\u7f6e\u90fd\u653e\u7f6e\u5728\u6b64\u6587\u4ef6\u4e2d\uff0c\u5bf9\u4e8e\u6700\u5c0f\u7684 API\uff0c\u8fd8\u4f1a\u653e\u7f6e\u7aef\u70b9\u5b9e\u73b0\u3002<\/p>\n<p>What we have just described is the starting point of every .NET application and its various configurations.<br \/>\n\u6211\u4eec\u521a\u624d\u63cf\u8ff0\u7684\u662f\u6bcf\u4e2a .NET \u5e94\u7528\u7a0b\u5e8f\u53ca\u5176\u5404\u79cd\u914d\u7f6e\u7684\u8d77\u70b9\u3002<\/p>\n<p>Logging into an application means tracking the evidence in different points of the code to check whether it is running as expected. The purpose of logging is to track over time all the conditions that led to an unexpected result or event in the application. Logging in an application can be useful both during development and while the application is in production.<br \/>\n\u767b\u5f55\u5230\u5e94\u7528\u7a0b\u5e8f\u610f\u5473\u7740\u8ddf\u8e2a\u4ee3\u7801\u4e0d\u540c\u70b9\u7684\u8bc1\u636e\uff0c\u4ee5\u68c0\u67e5\u5b83\u662f\u5426\u6309\u9884\u671f\u8fd0\u884c\u3002\u65e5\u5fd7\u8bb0\u5f55\u7684\u76ee\u7684\u662f\u968f\u7740\u65f6\u95f4\u7684\u63a8\u79fb\u8ddf\u8e2a\u5bfc\u81f4\u5e94\u7528\u7a0b\u5e8f\u4e2d\u51fa\u73b0\u610f\u5916\u7ed3\u679c\u6216\u4e8b\u4ef6\u7684\u6240\u6709\u6761\u4ef6\u3002\u5728\u5f00\u53d1\u671f\u95f4\u548c\u5e94\u7528\u7a0b\u5e8f\u5904\u4e8e\u751f\u4ea7\u72b6\u6001\u65f6\uff0c\u767b\u5f55\u5e94\u7528\u7a0b\u5e8f\u90fd\u975e\u5e38\u6709\u7528\u3002<\/p>\n<p>However, for logging, as many as four providers are added for tracking application information:<br \/>\n\u4f46\u662f\uff0c\u5bf9\u4e8e\u65e5\u5fd7\u8bb0\u5f55\uff0c\u5c06\u6dfb\u52a0\u591a\u8fbe\u56db\u4e2a\u63d0\u4f9b\u7a0b\u5e8f\u6765\u8ddf\u8e2a\u5e94\u7528\u7a0b\u5e8f\u4fe1\u606f\uff1a<\/p>\n<p>\u2022  Console: The Console provider logs output to the console. This log is unusable in production because the console of a web application is usually not visible. This kind of log is useful during development to make logging fast when you are running your app under Kestrel on your desktop machine in the app console window.<br \/>\n\u63a7\u5236\u53f0\uff1a\u63a7\u5236\u53f0\u63d0\u4f9b\u7a0b\u5e8f\u5c06\u8f93\u51fa\u8bb0\u5f55\u5230\u63a7\u5236\u53f0\u3002\u6b64\u65e5\u5fd7\u5728\u751f\u4ea7\u4e2d\u4e0d\u53ef\u7528\uff0c\u56e0\u4e3a Web \u5e94\u7528\u7a0b\u5e8f\u7684\u63a7\u5236\u53f0\u901a\u5e38\u4e0d\u53ef\u89c1\u3002\u5728\u5f00\u53d1\u8fc7\u7a0b\u4e2d\uff0c\u8fd9\u79cd\u65e5\u5fd7\u975e\u5e38\u6709\u7528\uff0c\u5f53\u60a8\u5728\u5e94\u7528\u7a0b\u5e8f\u63a7\u5236\u53f0\u7a97\u53e3\u4e2d\u7684\u684c\u9762\u8ba1\u7b97\u673a\u4e0a\u7684 Kestrel \u4e0b\u8fd0\u884c\u5e94\u7528\u7a0b\u5e8f\u65f6\uff0c\u53ef\u4ee5\u5feb\u901f\u8fdb\u884c\u65e5\u5fd7\u8bb0\u5f55\u3002<\/p>\n<p>\u2022  Debug: The Debug provider writes log output by using the System.Diagnostics.Debug class. When we develop, we are used to seeing this section in the Visual Studio output window.<br \/>\n\u8c03\u8bd5\uff1a\u8c03\u8bd5\u63d0\u4f9b\u7a0b\u5e8f\u4f7f\u7528 System.Diagnostics.Debug \u7c7b\u5199\u5165\u65e5\u5fd7\u8f93\u51fa\u3002\u5728\u5f00\u53d1\u65f6\uff0c\u6211\u4eec\u4e60\u60ef\u4e8e Visual Studio \u8f93\u51fa\u7a97\u53e3\u4e2d\u770b\u5230\u6b64\u90e8\u5206\u3002<\/p>\n<p>Under the Linux operating system, information is tracked depending on the distribution in the following locations: \/var\/log\/message and \/var\/log\/syslog.<br \/>\n\u5728 Linux\u4f5c\u7cfb\u7edf\u4e0b\uff0c\u6839\u636e\u4ee5\u4e0b\u4f4d\u7f6e\u7684\u5206\u53d1\u60c5\u51b5\u8ddf\u8e2a\u4fe1\u606f\uff1a\/var\/log\/message \u548c \/var\/log\/syslog\u3002<\/p>\n<p>\u2022  EventSource: On Windows, this information can be viewed in the EventTracing window.<br \/>\nEventSource\uff1a\u5728 Windows \u4e0a\uff0c\u53ef\u4ee5\u5728 EventTracing \u7a97\u53e3\u4e2d\u67e5\u770b\u6b64\u4fe1\u606f\u3002<\/p>\n<p>\u2022  EventLog (only when running on Windows): This information is displayed in the native Windows window, so you can only see it if you run the application on the Windows operating system.<br \/>\nEventLog \uff08\u4ec5\u5728 Windows \u4e0a\u8fd0\u884c\u65f6\uff09\uff1a\u6b64\u4fe1\u606f\u663e\u793a\u5728\u672c\u673a Windows \u7a97\u53e3\u4e2d\uff0c\u56e0\u6b64\u53ea\u6709\u5728 Windows\u4f5c\u7cfb\u7edf\u4e0a\u8fd0\u884c\u5e94\u7528\u7a0b\u5e8f\u65f6\u624d\u80fd\u770b\u5230\u5b83\u3002<\/p>\n<p>A new feature in the latest .NET release<br \/>\n\u6700\u65b0 .NET \u7248\u672c\u4e2d\u7684\u65b0\u529f\u80fd<\/p>\n<p>New logging providers have been added in the latest versions of .NET. However, these providers are not enabled within the framework.<br \/>\n\u6700\u65b0\u7248\u672c\u7684 .NET \u4e2d\u6dfb\u52a0\u4e86\u65b0\u7684\u65e5\u5fd7\u8bb0\u5f55\u63d0\u4f9b\u7a0b\u5e8f\u3002\u4f46\u662f\uff0c\u8fd9\u4e9b\u63d0\u4f9b\u7a0b\u5e8f\u672a\u5728\u6846\u67b6\u5185\u542f\u7528\u3002<\/p>\n<p>Use these extensions to enable new logging scenarios: AddSystemdConsole, AddJsonConsole, and AddSimpleConsole.<br \/>\n\u4f7f\u7528\u4ee5\u4e0b\u6269\u5c55\u542f\u7528\u65b0\u7684\u65e5\u5fd7\u8bb0\u5f55\u65b9\u6848\uff1aAddSystemdConsole\u3001AddJsonConsole \u548c AddSimpleConsole\u3002<\/p>\n<p>You can find more details on how to configure the log and what the basic ASP.NET settings are at this link: <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/host\/generic-host\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/host\/generic-host<\/a>.<br \/>\n\u60a8\u53ef\u4ee5\u5728\u4ee5\u4e0b\u94fe\u63a5\u4e2d\u627e\u5230\u6709\u5173\u5982\u4f55\u914d\u7f6e\u65e5\u5fd7\u4ee5\u53ca\u57fa\u672c ASP.NET \u8bbe\u7f6e\u7684\u66f4\u591a\u8be6\u7ec6\u4fe1\u606f\uff1a<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/host\/generic-host\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/host\/generic-host<\/a>\u3002<\/p>\n<p>We\u2019ve started to see what the framework gives us; now we need to understand how to leverage it within our applications. Before proceeding, we need to understand what a logging layer is. It is a fundamental concept that will help us break down information into different layers and enable them as needed:<br \/>\n\u6211\u4eec\u5df2\u7ecf\u5f00\u59cb\u770b\u5230\u6846\u67b6\u7ed9\u6211\u4eec\u5e26\u6765\u4e86\u4ec0\u4e48;\u73b0\u5728\u6211\u4eec\u9700\u8981\u4e86\u89e3\u5982\u4f55\u5728\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5229\u7528\u5b83\u3002\u5728\u7ee7\u7eed\u4e4b\u524d\uff0c\u6211\u4eec\u9700\u8981\u4e86\u89e3\u4ec0\u4e48\u662f\u65e5\u5fd7\u5c42\u3002\u8fd9\u662f\u4e00\u4e2a\u57fa\u672c\u6982\u5ff5\uff0c\u53ef\u5e2e\u52a9\u6211\u4eec\u5c06\u4fe1\u606f\u5206\u89e3\u4e3a\u4e0d\u540c\u7684\u5c42\u5e76\u6839\u636e\u9700\u8981\u542f\u7528\u5b83\u4eec\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/\/masteringminimalapsinaspnetcore\/05T1.jpg\" ><\/p>\n<p>Table 5.1 \u2013 Log levels<br \/>\n\u8868 5.1 \u2013 \u65e5\u5fd7\u7ea7\u522b<\/p>\n<p>Table 5.1 shows the most verbose levels down to the least verbose level.<br \/>\n\u8868 5.1 \u663e\u793a\u4e86\u6700\u8be6\u7ec6\u7684\u7ea7\u522b\u5230\u6700\u4e0d\u8be6\u7ec6\u7684\u7ea7\u522b\u3002<\/p>\n<p>To learn more, you can read the article titled Logging in .NET Core and ASP.NET Core, which explains the logging process in detail here: <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/logging\/\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/logging\/<\/a>.<br \/>\n\u82e5\u8981\u4e86\u89e3\u8be6\u7ec6\u4fe1\u606f\uff0c\u53ef\u4ee5\u9605\u8bfb\u6807\u9898\u4e3a\u201c\u5728 .NET Core \u548c ASP.NET Core \u4e2d\u767b\u5f55\u201d\u7684\u6587\u7ae0\uff0c\u5176\u4e2d\u8be6\u7ec6\u4ecb\u7ecd\u4e86\u65e5\u5fd7\u8bb0\u5f55\u8fc7\u7a0b\uff1a<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/logging\/\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/logging\/<\/a>\u3002<\/p>\n<p>If we select our log level as Information, everything at this level will be tracked down to the Critical level, skipping Debug and Trace.<br \/>\n\u5982\u679c\u6211\u4eec\u5c06\u65e5\u5fd7\u7ea7\u522b\u9009\u4e3a Information\uff0c\u5219\u6b64\u7ea7\u522b\u7684\u6240\u6709\u5185\u5bb9\u90fd\u5c06\u88ab\u8ddf\u8e2a\u5230 Critical \u7ea7\u522b\uff0c\u8df3\u8fc7 Debug \u548c Trace\u3002<\/p>\n<p>We\u2019ve seen how to take advantage of the log layers; now, let\u2019s move on to writing a single statement that will log information and can allow us to insert valuable content into the tracking system.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u770b\u5230\u4e86\u5982\u4f55\u5229\u7528\u65e5\u5fd7\u5c42;\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u7ee7\u7eed\u7f16\u5199\u4e00\u4e2a\u8bed\u53e5\uff0c\u8be5\u8bed\u53e5\u5c06\u8bb0\u5f55\u4fe1\u606f\uff0c\u5e76\u5141\u8bb8\u6211\u4eec\u5c06\u6709\u4ef7\u503c\u7684\u5185\u5bb9\u63d2\u5165\u5230\u8ddf\u8e2a\u7cfb\u7edf\u4e2d\u3002<\/p>\n<p>Configuring logging<br \/>\n\u914d\u7f6e\u65e5\u5fd7\u8bb0\u5f55<\/p>\n<p>To start using the logging component, you need to know a couple of pieces of information to start tracking data. Each logger object (<code>ILogger&lt;T&gt;<\/code>) must have an associated category. The log category allows you to segment the tracking layer with a high definition. For example, if we want to track everything that happens in a certain class or in an ASP.NET controller, without having to rewrite all our code, we need to enable the category or categories of our interest.<br \/>\n\u8981\u5f00\u59cb\u4f7f\u7528 logging \u7ec4\u4ef6\uff0c\u60a8\u9700\u8981\u4e86\u89e3\u4e00\u4e9b\u4fe1\u606f\u624d\u80fd\u5f00\u59cb\u8ddf\u8e2a\u6570\u636e\u3002\u6bcf\u4e2a\u8bb0\u5f55\u5668\u5bf9\u8c61 \uff08<code>ILogger&lt;T&gt;<\/code>\uff09 \u5fc5\u987b\u5177\u6709\u5173\u8054\u7684\u7c7b\u522b\u3002\u65e5\u5fd7\u7c7b\u522b\u5141\u8bb8\u60a8\u5bf9\u9ad8\u6e05\u6670\u5ea6\u7684\u8ddf\u8e2a\u5c42\u8fdb\u884c\u5206\u6bb5\u3002\u4f8b\u5982\uff0c\u5982\u679c\u6211\u4eec\u60f3\u8ddf\u8e2a\u67d0\u4e2a\u7c7b\u6216 ASP.NET \u63a7\u5236\u5668\u4e2d\u53d1\u751f\u7684\u6240\u6709\u4e8b\u60c5\uff0c\u800c\u4e0d\u5fc5\u91cd\u5199\u6240\u6709\u4ee3\u7801\uff0c\u6211\u4eec\u9700\u8981\u542f\u7528\u6211\u4eec\u611f\u5174\u8da3\u7684\u4e00\u4e2a\u6216\u591a\u4e2a\u7c7b\u522b\u3002<\/p>\n<p>A category is a T class. Nothing could be simpler. You can reuse typed objects of the class where the log method is injected. For example, if we\u2019re implementing MyService, and we want to track everything that happens in the service with the same category, we just need to request an <code>ILogger&lt;MyService&gt;<\/code> object instance from the dependency injection engine.<br \/>\n\u7c7b\u522b\u662f T \u7c7b\u3002\u6ca1\u6709\u6bd4\u8fd9\u66f4\u7b80\u5355\u7684\u4e86\u3002\u60a8\u53ef\u4ee5\u91cd\u7528\u6ce8\u5165 log \u65b9\u6cd5\u7684\u7c7b\u7684\u7c7b\u578b\u5316\u5bf9\u8c61\u3002\u4f8b\u5982\uff0c\u5982\u679c\u6211\u4eec\u6b63\u5728\u5b9e\u73b0 MyService\uff0c\u5e76\u4e14\u60f3\u8981\u8ddf\u8e2a\u5177\u6709\u76f8\u540c\u7c7b\u522b\u7684\u670d\u52a1\u4e2d\u53d1\u751f\u7684\u6240\u6709\u4e8b\u60c5\uff0c\u5219\u53ea\u9700\u4ece\u4f9d\u8d56\u9879\u6ce8\u5165\u5f15\u64ce\u8bf7\u6c42 <code>ILogger&lt;MyService&gt;<\/code> \u5bf9\u8c61\u5b9e\u4f8b\u3002<\/p>\n<p>Once the log categories are defined, we need to call the <code>ILogger&lt;T&gt;<\/code> object and take advantage of the object\u2019s public methods. In the previous section, we looked at the log layers. Each log layer has its own method for tracking information. For example, LogDebug is the method specified to track information with a Debug layer.<br \/>\n\u5b9a\u4e49\u65e5\u5fd7\u7c7b\u522b\u540e\uff0c\u6211\u4eec\u9700\u8981\u8c03\u7528 <code>ILogger&lt;T&gt;<\/code> \u5bf9\u8c61\u5e76\u5229\u7528\u8be5\u5bf9\u8c61\u7684\u516c\u5171\u65b9\u6cd5\u3002\u5728\u4e0a\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u65e5\u5fd7\u5c42\u3002\u6bcf\u4e2a\u65e5\u5fd7\u5c42\u90fd\u6709\u81ea\u5df1\u7684\u8ddf\u8e2a\u4fe1\u606f\u65b9\u6cd5\u3002\u4f8b\u5982\uff0cLogDebug \u662f\u6307\u5b9a\u7528\u4e8e\u4f7f\u7528 Debug \u5c42\u8ddf\u8e2a\u4fe1\u606f\u7684\u65b9\u6cd5\u3002<\/p>\n<p>Let\u2019s now look at an example. I created a record in the Program.cs file:<br \/>\n\u73b0\u5728\u8ba9\u6211\u4eec\u770b\u4e00\u4e2a\u793a\u4f8b\u3002\u6211\u5728 Program.cs \u6587\u4ef6\u4e2d\u521b\u5efa\u4e86\u4e00\u6761\u8bb0\u5f55\uff1a<\/p>\n<pre><code>internal record CategoryFiltered();<\/code><\/pre>\n<p>This record is used to define a particular category of logs that I want to track only when necessary. To do this, it is advisable to define a class or a record as an end in itself and enable the necessary trace level.<br \/>\n\u6b64\u8bb0\u5f55\u7528\u4e8e\u5b9a\u4e49\u6211\u53ea\u60f3\u5728\u5fc5\u8981\u65f6\u8ddf\u8e2a\u7684\u7279\u5b9a\u65e5\u5fd7\u7c7b\u522b\u3002\u4e3a\u6b64\uff0c\u5efa\u8bae\u5c06\u7c7b\u6216\u8bb0\u5f55\u5b9a\u4e49\u4e3a\u5176\u672c\u8eab\u7684 end\uff0c\u5e76\u542f\u7528\u5fc5\u8981\u7684\u8ddf\u8e2a\u7ea7\u522b\u3002<\/p>\n<p>A record that is defined in the Program.cs file has no namespace; we must remember this when we define the appsettings file with all the necessary information.<br \/>\n\u5728 Program.cs \u6587\u4ef6\u4e2d\u5b9a\u4e49\u7684\u8bb0\u5f55\u6ca1\u6709\u547d\u540d\u7a7a\u95f4;\u5f53\u6211\u4eec\u4f7f\u7528\u6240\u6709\u5fc5\u8981\u7684\u4fe1\u606f\u5b9a\u4e49 AppSettings \u6587\u4ef6\u65f6\uff0c\u6211\u4eec\u5fc5\u987b\u8bb0\u4f4f\u8fd9\u4e00\u70b9\u3002<\/p>\n<p>If the log category is within a namespace, we must consider the full name of the class. In this case, it is LoggingSamples.Categories.MyCategoryAlert:<br \/>\n\u5982\u679c\u65e5\u5fd7\u7c7b\u522b\u4f4d\u4e8e\u547d\u540d\u7a7a\u95f4\u5185\uff0c\u5219\u5fc5\u987b\u8003\u8651\u7c7b\u7684\u5168\u540d\u3002\u5728\u672c\u4f8b\u4e2d\uff0c\u5b83\u662f LoggingSamples.Categories.MyCategoryAlert\uff1a<\/p>\n<pre><code>namespace LoggingSamples.Categories\n{\n    public class MyCategoryAlert\n    {\n    }\n}<\/code><\/pre>\n<p>If we do not specify the category, as in the following example, the selected log level is the default:<br \/>\n\u5982\u679c\u6211\u4eec\u4e0d\u6307\u5b9a\u7c7b\u522b\uff0c\u5982\u4ee5\u4e0b\u793a\u4f8b\u6240\u793a\uff0c\u5219\u6240\u9009\u65e5\u5fd7\u7ea7\u522b\u4e3a\u9ed8\u8ba4\u65e5\u5fd7\u7ea7\u522b\uff1a<\/p>\n<pre><code>  &quot;Logging&quot;: {\n    &quot;LogLevel&quot;: {\n      &quot;Default&quot;: &quot;Information&quot;,\n      &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;,\n      &quot;CategoryFiltered&quot;: &quot;Information&quot;,\n      &quot;LoggingSamples.Categories.MyCategoryAlert&quot;: &quot;Debug&quot;\n    }\n  }<\/code><\/pre>\n<p>Anything that comprises infrastructure logs, such as Microsoft logs, stays in special categories such as Microsoft.AspNetCore or Microsoft.EntityFrameworkCore.<br \/>\n\u6784\u6210\u57fa\u7840\u7ed3\u6784\u65e5\u5fd7\u7684\u4efb\u4f55\u5185\u5bb9\uff08\u5982 Microsoft \u65e5\u5fd7\uff09\u90fd\u5c5e\u4e8e\u7279\u6b8a\u7c7b\u522b\uff0c\u5982 Microsoft.AspNetCore \u6216 Microsoft.EntityFrameworkCore\u3002<\/p>\n<p>The full list of Microsoft log categories can be found at the following link:<br \/>\nMicrosoft \u65e5\u5fd7\u7c7b\u522b\u7684\u5b8c\u6574\u5217\u8868\u53ef\u5728\u4ee5\u4e0b\u94fe\u63a5\u4e2d\u627e\u5230\uff1a<br \/>\n<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/logging\/#aspnet-core-and-ef-core-categories\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/logging\/#aspnet-core-and-ef-core-categories<\/a><\/p>\n<p>Sometimes, we need to define certain log levels depending on the tracking provider. For example, during development, we want to see all the information in the log console, but we only want to see errors in the log file.<br \/>\n\u6709\u65f6\uff0c\u6211\u4eec\u9700\u8981\u6839\u636e\u8ddf\u8e2a\u63d0\u4f9b\u5546\u5b9a\u4e49\u67d0\u4e9b\u65e5\u5fd7\u7ea7\u522b\u3002\u4f8b\u5982\uff0c\u5728\u5f00\u53d1\u8fc7\u7a0b\u4e2d\uff0c\u6211\u4eec\u5e0c\u671b\u5728\u65e5\u5fd7\u63a7\u5236\u53f0\u4e2d\u770b\u5230\u6240\u6709\u4fe1\u606f\uff0c\u4f46\u6211\u4eec\u53ea\u60f3\u5728\u65e5\u5fd7\u6587\u4ef6\u4e2d\u770b\u5230\u9519\u8bef\u3002<\/p>\n<p>To do this, we don\u2019t need to change the configuration code but just define its level for each provider. The following is an example that shows how everything that is tracked in the Microsoft categories is shown from the Information layer to the ones below it:<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u4e0d\u9700\u8981\u66f4\u6539\u914d\u7f6e\u4ee3\u7801\uff0c\u53ea\u9700\u4e3a\u6bcf\u4e2a\u63d0\u4f9b\u7a0b\u5e8f\u5b9a\u4e49\u5176\u7ea7\u522b\u3002\u4ee5\u4e0b\u793a\u4f8b\u663e\u793a\u4e86\u5982\u4f55\u4ece\u4fe1\u606f\u5c42\u5411\u5176\u4e0b\u65b9\u7684 Microsoft \u7c7b\u522b\u4e2d\u8ddf\u8e2a\u7684\u6240\u6709\u5185\u5bb9\u663e\u793a\uff1a<\/p>\n<pre><code>{\n  &quot;Logging&quot;: {      \/\/ Default, all providers.\n    &quot;LogLevel&quot;: {\n      &quot;Microsoft&quot;: &quot;Warning&quot;\n    },\n    &quot;Console&quot;: { \/\/ Console provider.\n      &quot;LogLevel&quot;: {\n        &quot;Microsoft&quot;: &quot;Information&quot;\n      }\n    }\n  }\n}<\/code><\/pre>\n<p>Now that we\u2019ve figured out how to enable logging and how to filter the various categories, all that\u2019s left is to apply this information to a minimal API.<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u5f04\u6e05\u695a\u4e86\u5982\u4f55\u542f\u7528\u65e5\u5fd7\u8bb0\u5f55\u4ee5\u53ca\u5982\u4f55\u7b5b\u9009\u5404\u79cd\u7c7b\u522b\uff0c\u5269\u4e0b\u7684\u5de5\u4f5c\u5c31\u662f\u5c06\u6b64\u4fe1\u606f\u5e94\u7528\u4e8e\u6700\u5c0f\u7684 API\u3002<\/p>\n<p>In the following code, we inject two ILogger instances with different categories. This is not a common practice, but we did it to make the example more concrete and show how the logger works:<br \/>\n\u5728\u4e0b\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u6ce8\u5165\u4e86\u4e24\u4e2a\u4e0d\u540c\u7c7b\u522b\u7684 ILogger \u5b9e\u4f8b\u3002\u8fd9\u4e0d\u662f\u4e00\u79cd\u5e38\u89c1\u7684\u505a\u6cd5\uff0c\u4f46\u6211\u4eec\u8fd9\u6837\u505a\u662f\u4e3a\u4e86\u4f7f\u793a\u4f8b\u66f4\u52a0\u5177\u4f53\u5e76\u5c55\u793a Logger \u7684\u5de5\u4f5c\u539f\u7406\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/first-log&quot;, (ILogger&lt;CategoryFiltered&gt; loggerCategory, ILogger&lt;MyCategoryAlert&gt; loggerAlertCategory) =&gt;\n{\n    loggerCategory.LogInformation(&quot;I&#039;m information \n      {MyName}&quot;, &quot;My Name Information&quot;);\n    loggerAlertCategory.LogInformation(&quot;I&#039;m information\n      {MyName}&quot;, &quot;Alert Information&quot;);\n    return Results.Ok();\n})\n.WithName(&quot;GetFirstLog&quot;);<\/code><\/pre>\n<p>In the preceding snippet, we inject two instances of the logger with different categories; each category tracks a single piece of information. The information is written according to a template that we will describe shortly. The effect of this example is that based on the level, we can show or disable the information displayed for a single category, without changing the code.<br \/>\n\u5728\u524d\u9762\u7684\u4ee3\u7801\u6bb5\u4e2d\uff0c\u6211\u4eec\u6ce8\u5165\u4e86\u4e24\u4e2a\u4e0d\u540c\u7c7b\u522b\u7684 Logger \u5b9e\u4f8b;\u6bcf\u4e2a\u7c7b\u522b\u8ddf\u8e2a\u4e00\u6761\u4fe1\u606f\u3002\u8be5\u4fe1\u606f\u662f\u6839\u636e\u6211\u4eec\u7a0d\u540e\u5c06\u4ecb\u7ecd\u7684\u6a21\u677f\u7f16\u5199\u7684\u3002\u6b64\u793a\u4f8b\u7684\u6548\u679c\u662f\uff0c\u6839\u636e\u7ea7\u522b\uff0c\u6211\u4eec\u53ef\u4ee5\u663e\u793a\u6216\u7981\u7528\u4e3a\u5355\u4e2a\u7c7b\u522b\u663e\u793a\u7684\u4fe1\u606f\uff0c\u800c\u65e0\u9700\u66f4\u6539\u4ee3\u7801\u3002<\/p>\n<p>We started filtering the logo by levels and categories. Now, we want to show you how to define a template that will allow us to define a message and make it dynamic in some of its parts.<br \/>\n\u6211\u4eec\u5f00\u59cb\u6309\u7ea7\u522b\u548c\u7c7b\u522b\u8fc7\u6ee4\u5fbd\u6807\u3002\u73b0\u5728\uff0c\u6211\u4eec\u60f3\u5411\u60a8\u5c55\u793a\u5982\u4f55\u5b9a\u4e49\u4e00\u4e2a\u6a21\u677f\uff0c\u8be5\u6a21\u677f\u5c06\u5141\u8bb8\u6211\u4eec\u5b9a\u4e49\u6d88\u606f\u5e76\u4f7f\u5176\u5728\u67d0\u4e9b\u90e8\u5206\u4e2d\u662f\u52a8\u6001\u7684\u3002<\/p>\n<p>Customizing log message<br \/>\n\u81ea\u5b9a\u4e49\u65e5\u5fd7\u6d88\u606f<\/p>\n<p>The message field that is asked by the log methods is a simple string object that we can enrich and serialize through the logging frameworks in proper structures. The message is therefore essential to identify malfunctions and errors, and inserting objects in it can significantly help us to identify the problem:<br \/>\nlog \u65b9\u6cd5\u8be2\u95ee\u7684 message \u5b57\u6bb5\u662f\u4e00\u4e2a\u7b80\u5355\u7684\u5b57\u7b26\u4e32\u5bf9\u8c61\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\u4ee5\u9002\u5f53\u7684\u7ed3\u6784\u5bf9\u5176\u8fdb\u884c\u6269\u5145\u548c\u5e8f\u5217\u5316\u3002\u56e0\u6b64\uff0c\u8be5\u6d88\u606f\u5bf9\u4e8e\u8bc6\u522b\u6545\u969c\u548c\u9519\u8bef\u81f3\u5173\u91cd\u8981\uff0c\u5728\u5176\u4e2d\u63d2\u5165\u5bf9\u8c61\u53ef\u4ee5\u663e\u7740\u5e2e\u52a9\u6211\u4eec\u8bc6\u522b\u95ee\u9898\uff1a<\/p>\n<pre><code>string apples = &quot;apples&quot;;\nstring pears = &quot;pears&quot;;\nstring bananas = &quot;bananas&quot;;\nlogger.LogInformation(&quot;My fruit box has: {pears}, {bananas}, {apples}&quot;, apples, pears, bananas);<\/code><\/pre>\n<p>The message template contains placeholders that interpolate content into the textual message.<br \/>\n\u6d88\u606f\u6a21\u677f\u5305\u542b\u5c06\u5185\u5bb9\u63d2\u5165\u5230\u6587\u672c\u6d88\u606f\u4e2d\u7684\u5360\u4f4d\u7b26\u3002<\/p>\n<p>In addition to the text, it is necessary to pass the arguments to replace the placeholders. Therefore, the order of the parameters is valid but not the name of the placeholders for the substitution.<br \/>\n\u9664\u4e86\u6587\u672c\u4e4b\u5916\uff0c\u8fd8\u9700\u8981\u4f20\u9012\u53c2\u6570\u6765\u66ff\u6362\u5360\u4f4d\u7b26\u3002\u56e0\u6b64\uff0c\u53c2\u6570\u7684\u987a\u5e8f\u6709\u6548\uff0c\u4f46\u66ff\u6362\u7684\u5360\u4f4d\u7b26\u540d\u79f0\u65e0\u6548\u3002<\/p>\n<p>The result then considers the positional parameters and not the placeholder names:<br \/>\n\u7136\u540e\uff0c\u7ed3\u679c\u4f1a\u8003\u8651\u4f4d\u7f6e\u53c2\u6570\uff0c\u800c\u4e0d\u662f\u5360\u4f4d\u7b26\u540d\u79f0\uff1a<\/p>\n<pre><code>My fruit box has: apples, pears, bananas<\/code><\/pre>\n<p>Now you know how to customize log messages. Next, let us learn about infrastructure logging, which is essential while working in more complex scenarios.<br \/>\n\u73b0\u5728\u60a8\u77e5\u9053\u5982\u4f55\u81ea\u5b9a\u4e49\u65e5\u5fd7\u6d88\u606f\u4e86\u3002\u63a5\u4e0b\u6765\uff0c\u8ba9\u6211\u4eec\u4e86\u89e3\u4e00\u4e0b\u57fa\u7840\u8bbe\u65bd\u65e5\u5fd7\u8bb0\u5f55\uff0c\u8fd9\u5728\u66f4\u590d\u6742\u7684\u573a\u666f\u4e2d\u5de5\u4f5c\u65f6\u662f\u5fc5\u4e0d\u53ef\u5c11\u7684\u3002<\/p>\n<p>Infrastructure logging<br \/>\n\u57fa\u7840\u8bbe\u65bd\u65e5\u5fd7\u8bb0\u5f55<\/p>\n<p>In this section, we want to tell you about a little-known and little-used theme within ASP.NET applications: the W3C log.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u60f3\u5411\u60a8\u4ecb\u7ecd ASP.NET \u5e94\u7528\u7a0b\u5e8f\u4e2d\u4e00\u4e2a\u9c9c\u4e3a\u4eba\u77e5\u4e14\u5f88\u5c11\u4f7f\u7528\u7684\u4e3b\u9898\uff1aW3C \u65e5\u5fd7\u3002<\/p>\n<p>This log is a standard that is used by all web servers, not only Internet Information Services (IIS). It also works on NGINX and many other web servers and can be used on Linux, too. It is also used to trace various requests. However, the log cannot understand what happened inside the call.<br \/>\n\u6b64\u65e5\u5fd7\u662f\u6240\u6709 Web \u670d\u52a1\u5668\u90fd\u4f7f\u7528\u7684\u6807\u51c6\uff0c\u800c\u4e0d\u4ec5\u4ec5\u662f Internet Information Services \uff08IIS\uff09\u3002\u5b83\u4e5f\u9002\u7528\u4e8e NGINX \u548c\u8bb8\u591a\u5176\u4ed6 Web \u670d\u52a1\u5668\uff0c\u4e5f\u53ef\u4ee5\u5728 Linux \u4e0a\u4f7f\u7528\u3002\u5b83\u8fd8\u7528\u4e8e\u8ddf\u8e2a\u5404\u79cd\u8bf7\u6c42\u3002\u4f46\u662f\uff0c\u65e5\u5fd7\u65e0\u6cd5\u7406\u89e3\u8c03\u7528\u4e2d\u53d1\u751f\u7684\u60c5\u51b5\u3002<\/p>\n<p>Thus, this feature focuses on the infrastructure, that is, how many calls are made and to which endpoint.<br \/>\n\u56e0\u6b64\uff0c\u6b64\u529f\u80fd\u4fa7\u91cd\u4e8e\u57fa\u7840\u8bbe\u65bd\uff0c\u5373\u8fdb\u884c\u591a\u5c11\u6b21\u8c03\u7528\u4ee5\u53ca\u8c03\u7528\u5230\u54ea\u4e2a\u7ec8\u7aef\u8282\u70b9\u3002<\/p>\n<p>In this section, we will see how to enable tracking, which, by default, is stored on a file. The functionality takes a little time to find but enables more complex scenarios that must be managed with appropriate practices and tools, such as OpenTelemetry.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u542f\u7528\u8ddf\u8e2a\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u8ddf\u8e2a\u5b58\u50a8\u5728\u6587\u4ef6\u4e2d\u3002\u8be5\u529f\u80fd\u9700\u8981\u4e00\u70b9\u65f6\u95f4\u624d\u80fd\u627e\u5230\uff0c\u4f46\u652f\u6301\u66f4\u590d\u6742\u7684\u573a\u666f\uff0c\u8fd9\u4e9b\u573a\u666f\u5fc5\u987b\u4f7f\u7528\u9002\u5f53\u7684\u5b9e\u8df5\u548c\u5de5\u5177\uff08\u5982 OpenTelemetry\uff09\u8fdb\u884c\u7ba1\u7406\u3002<\/p>\n<p>OpenTelemetry<br \/>\n\u5f00\u653e\u9065\u6d4b<\/p>\n<p>OpenTelemetry is a collection of tools, APIs, and SDKs. We use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help analyze software performance and behavior. You can learn more at the OpenTelemetry official website: <a href=\"https:\/\/opentelemetry.io\/\">https:\/\/opentelemetry.io\/<\/a>.<br \/>\nOpenTelemetry \u662f\u5de5\u5177\u3001API \u548c SDK \u7684\u96c6\u5408\u3002\u6211\u4eec\u4f7f\u7528\u5b83\u6765\u68c0\u6d4b\u3001\u751f\u6210\u3001\u6536\u96c6\u548c\u5bfc\u51fa\u9065\u6d4b\u6570\u636e\uff08\u6307\u6807\u3001\u65e5\u5fd7\u548c\u8ddf\u8e2a\uff09\uff0c\u4ee5\u5e2e\u52a9\u5206\u6790\u8f6f\u4ef6\u6027\u80fd\u548c\u884c\u4e3a\u3002\u60a8\u53ef\u4ee5\u5728 OpenTelemetry \u5b98\u65b9\u7f51\u7ad9\u4e0a\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\uff1a <a href=\"https:\/\/opentelemetry.io\/\">https:\/\/opentelemetry.io\/<\/a>.<\/p>\n<p>To configure W3C logging, you need to register the AddW3CLogging method and configure all available options.<br \/>\n\u8981\u914d\u7f6e W3C \u65e5\u5fd7\u8bb0\u5f55\uff0c\u60a8\u9700\u8981\u6ce8\u518c AddW3CLogging \u65b9\u6cd5\u5e76\u914d\u7f6e\u6240\u6709\u53ef\u7528\u9009\u9879\u3002<\/p>\n<p>To enable logging, you only need to add UseW3CLogging.<br \/>\n\u8981\u542f\u7528\u65e5\u5fd7\u8bb0\u5f55\uff0c\u60a8\u53ea\u9700\u6dfb\u52a0 UseW3CLogging\u3002<\/p>\n<p>The writing of the log does not change; the two methods enable the scenario just described and start writing data to the W3C log standard:<br \/>\n\u65e5\u5fd7\u7684\u5199\u5165\u4e0d\u4f1a\u6539\u53d8;\u8fd9\u4e24\u79cd\u65b9\u6cd5\u542f\u7528\u521a\u624d\u63cf\u8ff0\u7684\u65b9\u6848\u5e76\u5f00\u59cb\u5c06\u6570\u636e\u5199\u5165 W3C \u65e5\u5fd7\u6807\u51c6\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddW3CLogging(logging =&gt;\n{\n    logging.LoggingFields = W3CLoggingFields.All;\n});\nvar app = builder.Build();\napp.UseW3CLogging();\napp.MapGet(&quot;\/first-w3c-log&quot;, (IWebHostEnvironment webHostEnvironment) =&gt;\n{\n    return Results.Ok(new { PathToWrite = \n      webHostEnvironment.ContentRootPath });\n})\n.WithName(&quot;GetW3CLog&quot;);<\/code><\/pre>\n<p>We report the header of the file that is created (the headers of the information will be tracked later):<br \/>\n\u6211\u4eec\u62a5\u544a\u6240\u521b\u5efa\u6587\u4ef6\u7684\u6807\u9898\uff08\u7a0d\u540e\u5c06\u8ddf\u8e2a\u4fe1\u606f\u7684\u6807\u9898\uff09\uff1a<\/p>\n<pre><code>#Version: 1.0\n#Start-Date: 2022-01-03 10:34:15\n#Fields: date time c-ip cs-username s-computername s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status time-taken cs-version cs-host cs(User-Agent) cs(Cookie) cs(Referer)<\/code><\/pre>\n<p>We\u2019ve seen how to track information about the infrastructure hosting our application; now, we want to increase log performance with new features in .NET 6 that help us set up standard log messages and avoid errors.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u4e86\u89e3\u4e86\u5982\u4f55\u8ddf\u8e2a\u6709\u5173\u6258\u7ba1\u5e94\u7528\u7a0b\u5e8f\u7684\u57fa\u7840\u8bbe\u65bd\u7684\u4fe1\u606f;\u73b0\u5728\uff0c\u6211\u4eec\u5e0c\u671b\u901a\u8fc7 .NET 6 \u4e2d\u7684\u65b0\u529f\u80fd\u6765\u63d0\u9ad8\u65e5\u5fd7\u6027\u80fd\uff0c\u8fd9\u4e9b\u529f\u80fd\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u8bbe\u7f6e\u6807\u51c6\u65e5\u5fd7\u6d88\u606f\u5e76\u907f\u514d\u9519\u8bef\u3002<\/p>\n<p>Source generators<br \/>\n\u6e90\u751f\u6210\u5668<\/p>\n<p>One of the novelties of .NET 6 is the source generators; they are performance optimization tools that generate executable code at compile time. The creation of executable code at compile time, therefore, generates an increase in performance. During the execution phase of the program, all structures are comparable to code written by the programmer before compilation.<br \/>\n.NET 6 \u7684\u65b0\u9896\u4e4b\u5904\u4e4b\u4e00\u662f\u6e90\u751f\u6210\u5668;\u5b83\u4eec\u662f\u5728\u7f16\u8bd1\u65f6\u751f\u6210\u53ef\u6267\u884c\u4ee3\u7801\u7684\u6027\u80fd\u4f18\u5316\u5de5\u5177\u3002\u56e0\u6b64\uff0c\u5728\u7f16\u8bd1\u65f6\u521b\u5efa\u53ef\u6267\u884c\u4ee3\u7801\u4f1a\u63d0\u9ad8\u6027\u80fd\u3002\u5728\u7a0b\u5e8f\u7684\u6267\u884c\u9636\u6bb5\uff0c\u6240\u6709\u7ed3\u6784\u90fd\u4e0e\u7a0b\u5e8f\u5458\u5728\u7f16\u8bd1\u524d\u7f16\u5199\u7684\u4ee3\u7801\u76f8\u5f53\u3002<\/p>\n<p>String interpolation using $\u201d\u201d is generally great, and it makes for much more readable code than string.Format(), but you should almost never use it when writing log messages:<br \/>\n\u4f7f\u7528 $\u201c\u201d \u7684\u5b57\u7b26\u4e32\u63d2\u503c\u901a\u5e38\u5f88\u68d2\uff0c\u5e76\u4e14\u5b83\u4f7f\u4ee3\u7801\u6bd4 string \u66f4\u5177\u53ef\u8bfb\u6027\u3002Format\uff08\uff09\uff0c\u4f46\u5728\u7f16\u5199\u65e5\u5fd7\u6d88\u606f\u65f6\u51e0\u4e4e\u4e0d\u5e94\u8be5\u4f7f\u7528\u5b83\uff1a<\/p>\n<pre><code>logger.LogInformation($&quot;I&#039;m {person.Name}-{person.Surname}&quot;)<\/code><\/pre>\n<p>The output of this method to the Console will be the same when using string interpolation or structural logging, but there are several problems:<br \/>\n\u4f7f\u7528\u5b57\u7b26\u4e32\u63d2\u503c\u6216\u7ed3\u6784\u65e5\u5fd7\u8bb0\u5f55\u65f6\uff0c\u6b64\u65b9\u6cd5\u5bf9 Console \u7684\u8f93\u51fa\u5c06\u76f8\u540c\uff0c\u4f46\u5b58\u5728\u51e0\u4e2a\u95ee\u9898\uff1a<\/p>\n<p>\u2022  You lose the structured logs and you won\u2019t be able to filter by the format values or archive the log message in the custom field of NoSQL products.<br \/>\n\u60a8\u5c06\u4e22\u5931\u7ed3\u6784\u5316\u65e5\u5fd7\uff0c\u5e76\u4e14\u65e0\u6cd5\u6309\u683c\u5f0f\u503c\u8fdb\u884c\u7b5b\u9009\uff0c\u4e5f\u65e0\u6cd5\u5728 NoSQL \u4ea7\u54c1\u7684\u81ea\u5b9a\u4e49\u5b57\u6bb5\u4e2d\u5b58\u6863\u65e5\u5fd7\u6d88\u606f\u3002<\/p>\n<p>\u2022  Similarly, you no longer have a constant message template to find all identical logs.<br \/>\n\u540c\u6837\uff0c\u60a8\u4e0d\u518d\u6709\u56fa\u5b9a\u7684\u6d88\u606f\u6a21\u677f\u6765\u67e5\u627e\u6240\u6709\u76f8\u540c\u7684\u65e5\u5fd7\u3002<\/p>\n<p>\u2022  The serialization of the person is done ahead of time before the string is passed into LogInformation.<br \/>\n\u5c06\u5b57\u7b26\u4e32\u4f20\u9012\u5230 LogInformation \u4e4b\u524d\uff0c\u4f1a\u63d0\u524d\u5b8c\u6210\u4eba\u5458\u7684\u5e8f\u5217\u5316\u3002<\/p>\n<p>\u2022  The serialization is done even though the log filter is not enabled. To avoid processing the log, it is necessary to check whether the layer is active, which would make the code much less readable.<br \/>\n\u5373\u4f7f\u672a\u542f\u7528\u65e5\u5fd7\u8fc7\u6ee4\u5668\uff0c\u4e5f\u4f1a\u5b8c\u6210\u5e8f\u5217\u5316\u3002\u4e3a\u907f\u514d\u5904\u7406\u65e5\u5fd7\uff0c\u6709\u5fc5\u8981\u68c0\u67e5\u8be5\u5c42\u662f\u5426\u5904\u4e8e\u6d3b\u52a8\u72b6\u6001\uff0c\u8fd9\u5c06\u4f7f\u4ee3\u7801\u7684\u53ef\u8bfb\u6027\u5927\u5927\u964d\u4f4e\u3002<\/p>\n<p>Let us say you decide to update the log message to include Age to clarify why the log is being written:<br \/>\n\u5047\u8bbe\u60a8\u51b3\u5b9a\u66f4\u65b0\u65e5\u5fd7\u6d88\u606f\u4ee5\u5305\u542b Age \u4ee5\u9610\u660e\u5199\u5165\u65e5\u5fd7\u7684\u539f\u56e0\uff1a<\/p>\n<pre><code>logger.LogInformation(&quot;I&#039;m {Name}-{Surname} with {Age}&quot;, person.Name, person.Surname);<\/code><\/pre>\n<p>In the previous code snippet, I added Age in the message template but not in the method signature. At compile time, there is no compile-time error, but when this line is executed, an exception is thrown due to the lack of a third parameter.<br \/>\n\u5728\u524d\u9762\u7684\u4ee3\u7801\u6bb5\u4e2d\uff0c\u6211\u5728\u6d88\u606f\u6a21\u677f\u4e2d\u6dfb\u52a0\u4e86 Age\uff0c\u4f46\u6ca1\u6709\u5728\u65b9\u6cd5\u7b7e\u540d\u4e2d\u6dfb\u52a0\u3002\u5728\u7f16\u8bd1\u65f6\uff0c\u6ca1\u6709\u7f16\u8bd1\u65f6\u9519\u8bef\uff0c\u4f46\u662f\u5f53\u6267\u884c\u6b64\u884c\u65f6\uff0c\u7531\u4e8e\u7f3a\u5c11\u7b2c\u4e09\u4e2a\u53c2\u6570\uff0c\u4f1a\u5f15\u53d1\u5f02\u5e38\u3002<\/p>\n<p>LoggerMessage in .NET 6 comes to our rescue, automatically generating the code to log the necessary data. The methods will require the correct number of parameters and the text will be formatted in a standard way.<br \/>\n.NET 6 \u4e2d\u7684 LoggerMessage \u53ef\u4ee5\u5e2e\u6211\u4eec\u5fd9\uff0c\u81ea\u52a8\u751f\u6210\u4ee3\u7801\u6765\u8bb0\u5f55\u5fc5\u8981\u7684\u6570\u636e\u3002\u8fd9\u4e9b\u65b9\u6cd5\u5c06\u9700\u8981\u6b63\u786e\u6570\u91cf\u7684\u53c2\u6570\uff0c\u5e76\u4e14\u6587\u672c\u5c06\u4ee5\u6807\u51c6\u65b9\u5f0f\u683c\u5f0f\u5316\u3002<\/p>\n<p>To use the LoggerMessage syntax, you can take advantage of a partial class or a static class. Inside the class, it will be possible to define the method or methods with all the various log cases:<br \/>\n\u8981\u4f7f\u7528 LoggerMessage \u8bed\u6cd5\uff0c\u60a8\u53ef\u4ee5\u5229\u7528\u5206\u90e8\u7c7b\u6216\u9759\u6001\u7c7b\u3002\u5728\u7c7b\u4e2d\uff0c\u53ef\u4ee5\u4f7f\u7528\u6240\u6709\u4e0d\u540c\u7684\u65e5\u5fd7\u60c5\u51b5\u5b9a\u4e49\u4e00\u4e2a\u6216\u591a\u4e2a\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public partial class LogGenerator\n    {\n        private readonly ILogger&lt;LogGeneratorCategory&gt; \n          _logger;\n        public LogGenerator(ILogger&lt;LogGeneratorCategory&gt;\n          logger)\n        {\n            _logger = logger;\n        }\n        [LoggerMessage(\n            EventId = 100,\n            EventName = &quot;Start&quot;,\n            Level = LogLevel.Debug,\n            Message = &quot;Start Endpoint: {endpointName} with\n              data {dataIn}&quot;)]\n        public partial void StartEndpointSignal(string \n          endpointName, object dataIn);\n        [LoggerMessage(\n           EventId = 101,\n           EventName = &quot;StartFiltered&quot;,\n           Message = &quot;Log level filtered: {endpointName} \n             with data {dataIn}&quot;)]\n        public partial void LogLevelFilteredAtRuntime(\n          LogLevel, string endpointName, object dataIn);\n    }\n    public class LogGeneratorCategory { }<\/code><\/pre>\n<p>In the previous example, we created a partial class, injected the logger and its category, and implemented two methods. The methods are used in the following code:<br \/>\n\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u521b\u5efa\u4e86\u4e00\u4e2a\u5206\u90e8\u7c7b\uff0c\u6ce8\u5165\u4e86 Logger \u53ca\u5176\u7c7b\u522b\uff0c\u5e76\u5b9e\u73b0\u4e86\u4e24\u4e2a\u65b9\u6cd5\u3002\u8fd9\u4e9b\u65b9\u6cd5\u5728\u4ee5\u4e0b\u4ee3\u7801\u4e2d\u4f7f\u7528\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/start-log&quot;, (PostData data, LogGenerator logGenerator) =&gt;\n{\n    logGenerator.StartEndpointSignal(&quot;start-log&quot;, data);\n    logGenerator.LogLevelFilteredAtRuntime(LogLevel.Trace,\n      &quot;start-log&quot;, data);\n})\n.WithName(&quot;StartLog&quot;);\ninternal record PostData(DateTime Date, string Name);<\/code><\/pre>\n<p>Notice how in the second method, we also have the possibility to define the log level at runtime.<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u5728\u7b2c\u4e8c\u79cd\u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u8fd8\u53ef\u4ee5\u5728\u8fd0\u884c\u65f6\u5b9a\u4e49\u65e5\u5fd7\u7ea7\u522b\u3002<\/p>\n<p>Behind the scenes, the [LoggerMessage] source generator generates the LoggerMessage.Define() code to optimize your method call. The following output shows the generated code:<br \/>\n\u5728\u540e\u53f0\uff0c[LoggerMessage] \u6e90\u751f\u6210\u5668\u4f1a\u751f\u6210 LoggerMessage.Define\uff08\uff09 \u4ee3\u7801\u6765\u4f18\u5316\u65b9\u6cd5\u8c03\u7528\u3002\u4ee5\u4e0b\u8f93\u51fa\u663e\u793a\u4e86\u751f\u6210\u7684\u4ee3\u7801\uff1a<\/p>\n<pre><code>[global::System.CodeDom.Compiler.GeneratedCodeAttribute(&quot;Microsoft.Extensions.Logging.Generators&quot;, &quot;6.0.5.2210&quot;)]\n        public partial void LogLevelFilteredAtRuntime(\n          global::Microsoft.Extensions.Logging.LogLevel \n          logLevel, global::System.String endpointName,\n          global::System.Object dataIn)\n        {\n            if (_logger.IsEnabled(logLevel))\n            {\n                _logger.Log(\n                    logLevel,\n                    new global::Microsoft.Extensions.\n                     Logging.EventId(101, &quot;StartFiltered&quot;),\n                    new __LogLevelFilteredAtRuntimeStruct(\n                      endpointName, dataIn),\n                    null,\n                      __LogLevelFilteredAtRuntimeStruct.\n                          Format);\n            }\n        }<\/code><\/pre>\n<p>In this section, you have learned about some logging providers, different log levels, how to configure them, what parts of the message template to modify, enabling logging, and the benefits of source generators. In the next section, we will focus more on logging providers.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u4e86\u89e3\u4e86\u4e00\u4e9b\u65e5\u5fd7\u8bb0\u5f55\u63d0\u4f9b\u7a0b\u5e8f\u3001\u4e0d\u540c\u7684\u65e5\u5fd7\u7ea7\u522b\u3001\u5982\u4f55\u914d\u7f6e\u5b83\u4eec\u3001\u8981\u4fee\u6539\u6d88\u606f\u6a21\u677f\u7684\u54ea\u4e9b\u90e8\u5206\u3001\u542f\u7528\u65e5\u5fd7\u8bb0\u5f55\u4ee5\u53ca\u6e90\u751f\u6210\u5668\u7684\u597d\u5904\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u66f4\u591a\u5730\u5173\u6ce8\u65e5\u5fd7\u63d0\u4f9b\u7a0b\u5e8f\u3002<\/p>\n<p>Leveraging the logging framework<br \/>\n\u5229\u7528\u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6<\/p>\n<p>The logging framework, as mentioned at the beginning of the chapter, already has by design a series of providers that do not require adding any additional packages. Now, let us explore how to work with these providers and how to build custom ones. We will analyze only the Console log provider because it has all the sufficient elements to replicate the same reasoning on other log providers.<br \/>\n\u5982\u672c\u7ae0\u5f00\u5934\u6240\u8ff0\uff0c\u65e5\u5fd7\u6846\u67b6\u5728\u8bbe\u8ba1\u4e0a\u5df2\u7ecf\u6709\u4e00\u7cfb\u5217\u4e0d\u9700\u8981\u6dfb\u52a0\u4efb\u4f55\u5176\u4ed6\u5305\u7684\u63d0\u4f9b\u7a0b\u5e8f\u3002\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u63a2\u7d22\u5982\u4f55\u4e0e\u8fd9\u4e9b\u63d0\u4f9b\u5546\u5408\u4f5c\u4ee5\u53ca\u5982\u4f55\u6784\u5efa\u81ea\u5b9a\u4e49\u63d0\u4f9b\u5546\u3002\u6211\u4eec\u5c06\u4ec5\u5206\u6790 Console \u65e5\u5fd7\u63d0\u4f9b\u7a0b\u5e8f\uff0c\u56e0\u4e3a\u5b83\u5177\u6709\u5728\u5176\u4ed6\u65e5\u5fd7\u63d0\u4f9b\u7a0b\u5e8f\u4e0a\u590d\u5236\u76f8\u540c\u63a8\u7406\u7684\u6240\u6709\u8db3\u591f\u5143\u7d20\u3002<\/p>\n<p>Console log<br \/>\n\u63a7\u5236\u53f0\u65e5\u5fd7<\/p>\n<p>The Console log provider is the most used one because, during the development, it gives us a lot of information and collects all the application errors.<br \/>\nConsole \u65e5\u5fd7\u63d0\u4f9b\u7a0b\u5e8f\u662f\u6700\u5e38\u7528\u7684\u4e00\u79cd\uff0c\u56e0\u4e3a\u5728\u5f00\u53d1\u8fc7\u7a0b\u4e2d\uff0c\u5b83\u4e3a\u6211\u4eec\u63d0\u4f9b\u4e86\u5927\u91cf\u4fe1\u606f\u5e76\u6536\u96c6\u4e86\u6240\u6709\u5e94\u7528\u7a0b\u5e8f\u9519\u8bef\u3002<\/p>\n<p>Since .NET 6, this provider has been joined by the AddJsonConsole provider, which, besides tracing the errors like the console, serializes them in a JSON object readable by the human eye.<br \/>\n\u4ece .NET 6 \u5f00\u59cb\uff0c\u6b64\u63d0\u4f9b\u7a0b\u5e8f\u5df2\u7531 AddJsonConsole \u63d0\u4f9b\u7a0b\u5e8f\u52a0\u5165\uff0c\u8be5\u63d0\u4f9b\u7a0b\u5e8f\u9664\u4e86\u50cf\u63a7\u5236\u53f0\u4e00\u6837\u8ddf\u8e2a\u9519\u8bef\u5916\uff0c\u8fd8\u4f1a\u5c06\u5b83\u4eec\u5e8f\u5217\u5316\u4e3a\u4eba\u773c\u53ef\u8bfb\u7684 JSON \u5bf9\u8c61\u3002<\/p>\n<p>In the following example, we show how to configure the JsonConsole provider and also add indentation when writing the JSON payload:<br \/>\n\u5728\u4ee5\u4e0b\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5c06\u5c55\u793a\u5982\u4f55\u914d\u7f6e JsonConsole \u63d0\u4f9b\u7a0b\u5e8f\uff0c\u5e76\u5728\u5199\u5165 JSON \u6709\u6548\u8d1f\u8f7d\u65f6\u6dfb\u52a0\u7f29\u8fdb\uff1a<\/p>\n<pre><code>builder.Logging.AddJsonConsole(options =&gt;\n        options.JsonWriterOptions = new JsonWriterOptions()\n        {\n            Indented = true\n        });<\/code><\/pre>\n<p>As we\u2019ve seen in the previous examples, we\u2019re going to track the information with the message template:<br \/>\n\u6b63\u5982\u6211\u4eec\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\u6240\u770b\u5230\u7684\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 message \u6a21\u677f\u8ddf\u8e2a\u4fe1\u606f\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/first-log&quot;, (ILogger&lt;CategoryFiltered&gt; loggerCategory, ILogger&lt;MyCategoryAlert&gt; loggerAlertCategory) =&gt;\n{\n    loggerCategory.LogInformation(&quot;I&#039;m information \n      {MyName}&quot;, &quot;My Name Information&quot;);\n    loggerCategory.LogDebug(&quot;I&#039;m debug {MyName}&quot;,\n      &quot;My Name Debug&quot;);\n    loggerCategory.LogInformation(&quot;I&#039;m debug {Data}&quot;, \n      new PayloadData(&quot;CategoryRoot&quot;, &quot;Debug&quot;));\n    loggerAlertCategory.LogInformation(&quot;I&#039;m information \n      {MyName}&quot;, &quot;Alert Information&quot;);\n    loggerAlertCategory.LogDebug(&quot;I&#039;m debug {MyName}&quot;,\n      &quot;Alert Debug&quot;);\n    var p = new PayloadData(&quot;AlertCategory&quot;, &quot;Debug&quot;);\n    loggerAlertCategory.LogDebug(&quot;I&#039;m debug {Data}&quot;, p);\n    return Results.Ok();\n})\n.WithName(&quot;GetFirstLog&quot;);<\/code><\/pre>\n<p>Finally, an important note: the Console and JsonConsole providers do not serialize objects passed via the message template but only write the class name.<br \/>\n\u6700\u540e\uff0c\u9700\u8981\u6ce8\u610f\u7684\u662f\uff1aConsole \u548c JsonConsole \u63d0\u4f9b\u7a0b\u5e8f\u4e0d\u4f1a\u5e8f\u5217\u5316\u901a\u8fc7\u6d88\u606f\u6a21\u677f\u4f20\u9012\u7684\u5bf9\u8c61\uff0c\u800c\u53ea\u5199\u5165\u7c7b\u540d\u3002<\/p>\n<pre><code>var p = new PayloadData(&quot;AlertCategory&quot;, &quot;Debug&quot;);\nloggerAlertCategory.LogDebug(&quot;I&#039;m debug {Data}&quot;, p);<\/code><\/pre>\n<p>This is definitely a limitation of providers. Thus, we suggest using structured logging tools such as NLog, log4net, and Serilog, which we will talk about shortly.<br \/>\n\u8fd9\u7edd\u5bf9\u662f\u63d0\u4f9b\u5546\u7684\u9650\u5236\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u5efa\u8bae\u4f7f\u7528\u7ed3\u6784\u5316\u65e5\u5fd7\u8bb0\u5f55\u5de5\u5177\uff0c\u4f8b\u5982 NLog\u3001log4net \u548c Serilog\uff0c\u6211\u4eec\u7a0d\u540e\u4f1a\u8ba8\u8bba\u8fd9\u4e9b\u5de5\u5177\u3002<\/p>\n<p>We present the outputs of the previous lines with the two providers just described:<br \/>\n\u6211\u4eec\u5c06\u524d\u9762\u51e0\u884c\u7684\u8f93\u51fa\u4e0e\u521a\u624d\u63cf\u8ff0\u7684\u4e24\u4e2a\u63d0\u4f9b\u5546\u4e00\u8d77\u5448\u73b0\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0501.jpg\"><\/p>\n<p>Figure 5.1 \u2013 AddJsonConsole output<br \/>\n\u56fe 5.1 \u2013 AddJsonConsole \u8f93\u51fa<\/p>\n<p>Figure 5.1 shows the log formatted as JSON, with several additional details compared to the traditional console log.<br \/>\n\u56fe 5.1 \u663e\u793a\u4e86\u683c\u5f0f\u4e3a JSON \u7684\u65e5\u5fd7\uff0c\u4e0e\u4f20\u7edf\u63a7\u5236\u53f0\u65e5\u5fd7\u76f8\u6bd4\uff0c\u8fd8\u6709\u4e00\u4e9b\u989d\u5916\u7684\u7ec6\u8282\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0502.jpg\"><\/p>\n<p>Figure 5.2 \u2013 Default logging provider Console output<br \/>\n\u56fe 5.2 \u2013 \u9ed8\u8ba4\u65e5\u5fd7\u8bb0\u5f55\u63d0\u4f9b\u7a0b\u5e8f\u63a7\u5236\u53f0\u8f93\u51fa<\/p>\n<p>Figure 5.2 shows the default logging provider Console output.<br \/>\n\u56fe 5.2 \u663e\u793a\u4e86\u9ed8\u8ba4\u7684\u65e5\u5fd7\u8bb0\u5f55\u63d0\u4f9b\u7a0b\u5e8f Console \u8f93\u51fa\u3002<\/p>\n<p>Given the default providers, we want to show you how you can create a custom one that fits the needs of your application.<br \/>\n\u7ed9\u5b9a\u9ed8\u8ba4\u63d0\u4f9b\u7a0b\u5e8f\uff0c\u6211\u4eec\u60f3\u5411\u60a8\u5c55\u793a\u5982\u4f55\u521b\u5efa\u9002\u5408\u60a8\u5e94\u7528\u7a0b\u5e8f\u9700\u6c42\u7684\u81ea\u5b9a\u4e49\u63d0\u4f9b\u7a0b\u5e8f\u3002<\/p>\n<p>Creating a custom provider<br \/>\n\u521b\u5efa\u81ea\u5b9a\u4e49\u63d0\u4f9b\u7a0b\u5e8f<\/p>\n<p>The logging framework designed by Microsoft can be customized with little effort. Thus, let us learn how to create a custom provider.<br \/>\nMicrosoft \u8bbe\u8ba1\u7684\u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\u53ef\u4ee5\u6beb\u4e0d\u8d39\u529b\u5730\u8fdb\u884c\u81ea\u5b9a\u4e49\u3002\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u5b66\u4e60\u5982\u4f55\u521b\u5efa\u81ea\u5b9a\u4e49\u63d0\u4f9b\u5546(provider)\u3002<\/p>\n<p>Why create a custom provider? Well, put simply, to not have dependencies with logging libraries and to better manage the performance of the application. Finally, it also encapsulates some custom logic of your specific scenario and makes your code more manageable and readable.<br \/>\n\u4e3a\u4ec0\u4e48\u8981\u521b\u5efa\u81ea\u5b9a\u4e49\u63d0\u4f9b\u5546\uff1f\u55ef\uff0c\u7b80\u5355\u5730\u8bf4\uff0c\u4e0d\u8981\u4f9d\u8d56\u65e5\u5fd7\u5e93\uff0c\u5e76\u66f4\u597d\u5730\u7ba1\u7406\u5e94\u7528\u7a0b\u5e8f\u7684\u6027\u80fd\u3002\u6700\u540e\uff0c\u5b83\u8fd8\u5c01\u88c5\u4e86\u7279\u5b9a\u65b9\u6848\u7684\u4e00\u4e9b\u81ea\u5b9a\u4e49\u903b\u8f91\uff0c\u5e76\u4f7f\u4ee3\u7801\u66f4\u6613\u4e8e\u7ba1\u7406\u548c\u53ef\u8bfb\u3002<\/p>\n<p>In the following example, we have simplified the usage scenario to show you the minimum components needed to create a working logging provider for profit.<br \/>\n\u5728\u4ee5\u4e0b\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u7b80\u5316\u4e86\u4f7f\u7528\u573a\u666f\uff0c\u5411\u60a8\u5c55\u793a\u4e86\u521b\u5efa\u6709\u6548\u7684\u65e5\u5fd7\u8bb0\u5f55\u63d0\u4f9b\u5546\u4ee5\u83b7\u53d6\u5229\u6da6\u6240\u9700\u7684\u6700\u5c11\u7ec4\u4ef6\u3002<\/p>\n<p>One of the fundamental parts of a provider is the ability to configure its behavior. Let us create a class that can be customized at application startup or retrieve information from appsettings.<br \/>\n\u63d0\u4f9b\u7a0b\u5e8f\u7684\u57fa\u672c\u90e8\u5206\u4e4b\u4e00\u662f\u914d\u7f6e\u5176\u884c\u4e3a\u7684\u80fd\u529b\u3002\u8ba9\u6211\u4eec\u521b\u5efa\u4e00\u4e2a\u7c7b\uff0c\u8be5\u7c7b\u53ef\u4ee5\u5728\u5e94\u7528\u7a0b\u5e8f\u542f\u52a8\u65f6\u81ea\u5b9a\u4e49\u6216\u4ece appsettings \u4e2d\u68c0\u7d22\u4fe1\u606f\u3002<\/p>\n<p>In our example, we define a fixed EventId to verify a daily rolling file logic and a path of where to write the file:<br \/>\n\u5728\u6211\u4eec\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5b9a\u4e49\u4e86\u4e00\u4e2a\u56fa\u5b9a\u7684 EventId \u6765\u9a8c\u8bc1\u6bcf\u65e5\u6eda\u52a8\u6587\u4ef6\u903b\u8f91\u548c\u5199\u5165\u6587\u4ef6\u7684\u8def\u5f84\uff1a<\/p>\n<pre><code>public class FileLoggerConfiguration\n{\n        public int EventId { get; set; }\n        public string PathFolderName { get; set; } = \n          &quot;logs&quot;;\n        public bool IsRollingFile { get; set; }\n}<\/code><\/pre>\n<p>The custom provider we are writing will be responsible for writing the log information to a text file. We achieve this by implementing the log class, which we call FileLogger, which implements the ILogger interface.<br \/>\n\u6211\u4eec\u6b63\u5728\u7f16\u5199\u7684\u81ea\u5b9a\u4e49\u63d0\u4f9b\u7a0b\u5e8f\u5c06\u8d1f\u8d23\u5c06\u65e5\u5fd7\u4fe1\u606f\u5199\u5165\u6587\u672c\u6587\u4ef6\u3002\u6211\u4eec\u901a\u8fc7\u5b9e\u73b0 log \u7c7b\u6765\u5b9e\u73b0\u8fd9\u4e00\u70b9\uff0c\u6211\u4eec\u79f0\u4e4b\u4e3a FileLogger\uff0c\u5b83\u5b9e\u73b0 ILogger \u63a5\u53e3\u3002<\/p>\n<p>In the class logic, all we do is implement the log method and check which file to put the information in.<br \/>\n\u5728 class logic\u4e2d\uff0c\u6211\u4eec\u6240\u505a\u7684\u53ea\u662f\u5b9e\u73b0 log \u65b9\u6cd5\u5e76\u68c0\u67e5\u5c06\u4fe1\u606f\u653e\u5165\u54ea\u4e2a\u6587\u4ef6\u3002<\/p>\n<p>We put the directory verification in the next file, but it\u2019s more correct to put all the control logic in this method. We also need to make sure that the log method does not throw exceptions at the application level. The logger should never affect the stability of the application:<br \/>\n\u6211\u4eec\u5c06\u76ee\u5f55\u9a8c\u8bc1\u653e\u5728\u4e0b\u4e00\u4e2a\u6587\u4ef6\u4e2d\uff0c\u4f46\u5c06\u6240\u6709 control logic \u90fd\u653e\u5728\u6b64\u65b9\u6cd5\u4e2d\u66f4\u4e3a\u6b63\u786e\u3002\u6211\u4eec\u8fd8\u9700\u8981\u786e\u4fdd log \u65b9\u6cd5\u4e0d\u4f1a\u5728\u5e94\u7528\u7a0b\u5e8f\u7ea7\u522b\u5f15\u53d1\u5f02\u5e38\u3002\u8bb0\u5f55\u5668\u4e0d\u5e94\u5f71\u54cd\u5e94\u7528\u7a0b\u5e8f\u7684\u7a33\u5b9a\u6027\uff1a<\/p>\n<pre><code>    public class FileLogger : ILogger\n    {\n        private readonly string name;\n        private readonly Func&lt;FileLoggerConfiguration&gt; \n          getCurrentConfig;\n        public FileLogger(string name,\n          Func&lt;FileLoggerConfiguration&gt; getCurrentConfig)\n        {\n            this.name = name;\n            this.getCurrentConfig = getCurrentConfig;\n        }\n        public IDisposable BeginScope&lt;TState&gt;(TState state)\n          =&gt; default!;\n        public bool IsEnabled(LogLevel logLevel) =&gt; true;\n        public void Log&lt;TState&gt;(LogLevel logLevel, EventId\n          , TState state, Exception? exception, \n          Func&lt;TState, Exception?, string&gt; formatter)\n        {\n            if (!IsEnabled(logLevel))\n            {\n                return;\n            }\n            var config = getCurrentConfig();\n            if (config.EventId == 0 || config.EventId ==\n                eventId.Id)\n            {\n                string line = $&quot;{name} - {formatter(state,\n                  exception)}&quot;;\n                string fileName = config.IsRollingFile ? \n                  RollingFileName : FullFileName;\n                string fullPath = Path.Combine(\n                  config.PathFolderName, fileName);\n                File.AppendAllLines(fullPath, new[] { line });\n            }\n        }\n        private static string RollingFileName =&gt; \n          $&quot;log-{DateTime.UtcNow:yyyy-MM-dd}.txt&quot;;\n        private const string FullFileName = &quot;logs.txt&quot;;\n    }<\/code><\/pre>\n<p>Now, we need to implement the ILoggerProvider interface, which is intended to create one or more instances of the logger class just discussed.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u9700\u8981\u5b9e\u73b0 ILoggerProvider \u63a5\u53e3\uff0c\u8be5\u63a5\u53e3\u65e8\u5728\u521b\u5efa\u521a\u624d\u8ba8\u8bba\u7684 Logger \u7c7b\u7684\u4e00\u4e2a\u6216\u591a\u4e2a\u5b9e\u4f8b\u3002<\/p>\n<p>In this class, we check the directory we mentioned in the previous paragraph, but we also check whether the settings in the appsettings file change, via <code>IOptionsMonitor&lt;T&gt;<\/code>:<br \/>\n\u5728\u8fd9\u4e2a\u7c7b\u4e2d\uff0c\u6211\u4eec\u68c0\u67e5\u4e86\u6211\u4eec\u5728\u4e0a\u4e00\u6bb5\u4e2d\u63d0\u5230\u7684\u76ee\u5f55\uff0c\u4f46\u6211\u4eec\u4e5f\u4f1a\u901a\u8fc7 <code>IOptionsMonitor&lt;T&gt;<\/code>\u68c0\u67e5 appsettings \u6587\u4ef6\u4e2d\u7684\u8bbe\u7f6e\u662f\u5426\u53d1\u751f\u4e86\u53d8\u5316\uff1a<\/p>\n<pre><code>public class FileLoggerProvider : ILoggerProvider\n{\n    private readonly IDisposable onChangeToken;\n    private FileLoggerConfiguration currentConfig;\n    private readonly ConcurrentDictionary&lt;string,\n      FileLogger&gt; _loggers = new();\n    public FileLoggerProvider(\n      IOptionsMonitor&lt;FileLoggerConfiguration&gt; config)\n    {\n        currentConfig = config.CurrentValue;\n        CheckDirectory();\n        onChangeToken = config.OnChange(updateConfig =&gt;\n        {\n            currentConfig = updateConfig;\n            CheckDirectory();\n        });\n    }\n    public ILogger CreateLogger(string categoryName)\n    {\n        return _loggers.GetOrAdd(categoryName, name =&gt; new \n          FileLogger(name, () =&gt; currentConfig));\n    }\n    public void Dispose()\n    {\n        _loggers.Clear();\n        onChangeToken.Dispose();\n    }\n    private void CheckDirectory()\n    {\n        if (!Directory.Exists(currentConfig.PathFolderName))\n            Directory.CreateDirectory(currentConfig.\n            PathFolderName);\n    }\n}<\/code><\/pre>\n<p>Finally, to simplify its use and configuration during the application startup phase, we also define an extension method for registering the various classes just mentioned.<br \/>\n\u6700\u540e\uff0c\u4e3a\u4e86\u7b80\u5316\u5b83\u5728\u5e94\u7528\u7a0b\u5e8f\u542f\u52a8\u9636\u6bb5\u7684\u4f7f\u7528\u548c\u914d\u7f6e\uff0c\u6211\u4eec\u8fd8\u5b9a\u4e49\u4e86\u4e00\u4e2a\u6269\u5c55\u65b9\u6cd5\uff0c\u7528\u4e8e\u6ce8\u518c\u521a\u624d\u63d0\u5230\u7684\u5404\u79cd\u7c7b\u3002<\/p>\n<p>The AddFile method will register ILoggerProvider and couple it to its configuration (very simple as an example, but it encapsulates several aspects of configuring and using a custom provider):<br \/>\nAddFile \u65b9\u6cd5\u5c06\u6ce8\u518c ILoggerProvider \u5e76\u5c06\u5176\u8026\u5408\u5230\u5176\u914d\u7f6e\uff08\u793a\u4f8b\u975e\u5e38\u7b80\u5355\uff0c\u4f46\u5b83\u5c01\u88c5\u4e86\u914d\u7f6e\u548c\u4f7f\u7528\u81ea\u5b9a\u4e49\u63d0\u4f9b\u7a0b\u5e8f\u7684\u51e0\u4e2a\u65b9\u9762\uff09\uff1a<\/p>\n<pre><code>public static class FileLoggerExtensions\n    {\n        public static ILoggingBuilder AddFile(\n        this ILoggingBuilder builder)\n        {\n            builder.AddConfiguration();\n           builder.Services.TryAddEnumerable(\n             ServiceDescriptor.Singleton&lt;ILoggerProvider,\n             FileLoggerProvider&gt;());\n            LoggerProviderOptions.RegisterProviderOptions&lt;\n              FileLoggerConfiguration, FileLoggerProvider&gt;\n              (builder.Services);\n            return builder;\n        }\n        public static ILoggingBuilder AddFile(\n            this ILoggingBuilder builder,\n            Action&lt;FileLoggerConfiguration&gt; configure)\n        {\n            builder.AddFile();\n            builder.Services.Configure(configure);\n            return builder;\n        }\n    }<\/code><\/pre>\n<p>We record everything seen in the Program.cs file with the AddFile extension as shown:<br \/>\n\u6211\u4eec\u4f7f\u7528 AddFile \u6269\u5c55\u540d\u8bb0\u5f55 Program.cs \u6587\u4ef6\u4e2d\u770b\u5230\u7684\u6240\u6709\u5185\u5bb9\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>builder.Logging.AddFile(configuration =&gt;\n{\n    configuration.PathFolderName = Path.Combine(\n      builder.Environment.ContentRootPath, &quot;logs&quot;);\n    configuration.IsRollingFile = true;\n});<\/code><\/pre>\n<p>The output is shown in Figure 5.3, where we can see both Microsoft log categories in the first five lines (this is the classic application startup information):<br \/>\n\u8f93\u51fa\u5982\u56fe 5.3 \u6240\u793a\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u524d\u4e94\u884c\u4e2d\u770b\u5230\u4e24\u4e2a Microsoft \u65e5\u5fd7\u7c7b\u522b\uff08\u8fd9\u662f\u7ecf\u5178\u5e94\u7528\u7a0b\u5e8f\u542f\u52a8\u4fe1\u606f\uff09\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0503.jpg\"><\/p>\n<p>Figure 5.3 \u2013 File log provider output<br \/>\n\u56fe 5.3 \u2013 \u6587\u4ef6\u65e5\u5fd7\u63d0\u4f9b\u7a0b\u5e8f\u8f93\u51fa<\/p>\n<p>Then, the handler of the minimal APIs that we reported in the previous sections is called. As you can see, no exception data or data passed to the logger is serialized.<br \/>\n\u7136\u540e\uff0c\u8c03\u7528\u6211\u4eec\u5728\u524d\u9762\u51e0\u8282\u4e2d\u62a5\u544a\u7684\u6700\u5c0f API \u7684\u5904\u7406\u7a0b\u5e8f\u3002\u5982\u60a8\u6240\u89c1\uff0c\u4e0d\u4f1a\u5e8f\u5217\u5316\u4efb\u4f55\u5f02\u5e38\u6570\u636e\u6216\u4f20\u9012\u7ed9 logger \u7684\u6570\u636e\u3002<\/p>\n<p>To add this functionality as well, it is necessary to rewrite ILogger formatter and support serialization of the object. This will give you everything you need to have in a useful logging framework for production scenarios.<br \/>\n\u82e5\u8981\u540c\u65f6\u6dfb\u52a0\u6b64\u529f\u80fd\uff0c\u5fc5\u987b\u91cd\u5199 ILogger \u683c\u5f0f\u5316\u7a0b\u5e8f\u5e76\u652f\u6301\u5bf9\u8c61\u7684\u5e8f\u5217\u5316\u3002\u8fd9\u5c06\u4e3a\u60a8\u63d0\u4f9b\u7528\u4e8e\u751f\u4ea7\u573a\u666f\u7684\u6709\u7528\u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\u6240\u9700\u7684\u4e00\u5207\u3002<\/p>\n<p>We\u2019ve seen how to configure the log and how to customize the provider object to create a structured log to send to a service or storage.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u4e86\u89e3\u4e86\u5982\u4f55\u914d\u7f6e\u65e5\u5fd7\u4ee5\u53ca\u5982\u4f55\u81ea\u5b9a\u4e49 provider \u5bf9\u8c61\u4ee5\u521b\u5efa\u8981\u53d1\u9001\u5230\u670d\u52a1\u6216\u5b58\u50a8\u7684\u7ed3\u6784\u5316\u65e5\u5fd7\u3002<\/p>\n<p>In the next section, we want to describe the Azure Application Insights service, which is very useful for both logging and application monitoring.<br \/>\n\u5728\u4e0b\u4e00\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd Azure Application Insights \u670d\u52a1\uff0c\u8be5\u670d\u52a1\u5bf9\u4e8e\u65e5\u5fd7\u8bb0\u5f55\u548c\u5e94\u7528\u7a0b\u5e8f\u76d1\u89c6\u90fd\u975e\u5e38\u6709\u7528\u3002<\/p>\n<p>Application Insights<br \/>\n\u5e94\u7528\u7a0b\u5e8f\u6d1e\u5bdf<\/p>\n<p>In addition to the already seen providers, one of the most used ones is Azure Application Insights. This provider allows you to send every single log event in the Azure service. In order to insert the provider into our project, all we would have to do is install the following NuGet package:<br \/>\n\u9664\u4e86\u5df2\u7ecf\u770b\u5230\u7684\u63d0\u4f9b\u7a0b\u5e8f\u4e4b\u5916\uff0c\u6700\u5e38\u7528\u7684\u63d0\u4f9b\u7a0b\u5e8f\u4e4b\u4e00\u662f Azure Application Insights\u3002\u6b64\u63d0\u4f9b\u7a0b\u5e8f\u5141\u8bb8\u60a8\u53d1\u9001 Azure \u670d\u52a1\u4e2d\u7684\u6bcf\u4e2a\u65e5\u5fd7\u4e8b\u4ef6\u3002\u4e3a\u4e86\u5c06\u63d0\u4f9b\u7a0b\u5e8f\u63d2\u5165\u5230\u6211\u4eec\u7684\u9879\u76ee\u4e2d\uff0c\u6211\u4eec\u53ea\u9700\u5b89\u88c5\u4ee5\u4e0b NuGet \u5305\uff1a<\/p>\n<pre><code>&lt;PackageReference Include=&quot;Microsoft.ApplicationInsights.AspNetCore&quot; Version=&quot;2.20.0&quot; \/&gt;<\/code><\/pre>\n<p>Registering the provider is very easy.<br \/>\n\u6ce8\u518c\u63d0\u4f9b\u5546\u975e\u5e38\u7b80\u5355\u3002<\/p>\n<p>We first register the Application Insights framework, AddApplicationInsightsTelemetry, and then register its extension on the AddApplicationInsights logging framework.<br \/>\n\u6211\u4eec\u9996\u5148\u6ce8\u518c Application Insights \u6846\u67b6 AddApplicationInsightsTelemetry\uff0c\u7136\u540e\u5728 AddApplicationInsights \u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\u4e0a\u6ce8\u518c\u5176\u6269\u5c55\u3002<\/p>\n<p>In the NuGet package previously described, the one for logging the component to the logging framework is also present as a reference:<br \/>\n\u5728\u524d\u9762\u63cf\u8ff0\u7684 NuGet \u5305\u4e2d\uff0c\u7528\u4e8e\u5c06\u7ec4\u4ef6\u8bb0\u5f55\u5230\u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\u7684\u5305\u4e5f\u4f5c\u4e3a\u53c2\u8003\u5b58\u5728\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddApplicationInsightsTelemetry();\nbuilder.Logging.AddApplicationInsights();<\/code><\/pre>\n<p>To register the instrumentation key, which is the key that is issued after registering the service on Azure, you will need to pass this information to the registration method. We can avoid hardcoding this information by placing it in the appsettings.json file using the following format:<br \/>\n\u82e5\u8981\u6ce8\u518c\u68c0\u6d4b\u5bc6\u94a5\uff08\u5728 Azure \u4e0a\u6ce8\u518c\u670d\u52a1\u540e\u9881\u53d1\u7684\u5bc6\u94a5\uff09\uff0c\u60a8\u9700\u8981\u5c06\u6b64\u4fe1\u606f\u4f20\u9012\u7ed9\u6ce8\u518c\u65b9\u6cd5\u3002\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u683c\u5f0f\u5c06\u6b64\u4fe1\u606f\u653e\u5728 appsettings.json \u6587\u4ef6\u4e2d\uff0c\u4ece\u800c\u907f\u514d\u5bf9\u6b64\u4fe1\u606f\u8fdb\u884c\u786c\u7f16\u7801\uff1a<\/p>\n<pre><code>&quot;ApplicationInsights&quot;: {\n    &quot;InstrumentationKey&quot;: &quot;your-key&quot;\n  },<\/code><\/pre>\n<p>This process is also described in the documentation (<a href=\"https:\/\/docs.microsoft.com\/it-it\/azure\/azure-monitor\/app\/asp-net-core#enable-application-insights-server-side-telemetry-no-visual-studio\">https:\/\/docs.microsoft.com\/it-it\/azure\/azure-monitor\/app\/asp-net-core#enable-application-insights-server-side-telemetry-no-visual-studio<\/a>).<br \/>\n\u6587\u6863 \uff08<a href=\"https:\/\/docs.microsoft.com\/it-it\/azure\/azure-monitor\/app\/asp-net-core#enable-application-insights-server-side-telemetry-no-visual-studio\">https:\/\/docs.microsoft.com\/it-it\/azure\/azure-monitor\/app\/asp-net-core#enable-application-insights-server-side-telemetry-no-visual-studio<\/a>\uff09 \u4e2d\u4e5f\u4ecb\u7ecd\u4e86\u6b64\u8fc7\u7a0b\u3002<\/p>\n<p>By launching the method already discussed in the previous sections, we have all the information hooked into Application Insights.<br \/>\n\u901a\u8fc7\u542f\u52a8\u524d\u9762\u90e8\u5206\u4e2d\u5df2\u8ba8\u8bba\u7684\u65b9\u6cd5\uff0c\u6211\u4eec\u5c06\u6240\u6709\u4fe1\u606f\u6302\u63a5\u5230 Application Insights \u4e2d\u3002<\/p>\n<p>Application Insights groups the logs under a particular trace. A trace is a call to an API, so everything that happens in that call is logically grouped together. This feature takes advantage of the WebServer information and, in particular, TraceParentId issued by the W3C standard for each call.<br \/>\nApplication Insights \u5c06\u65e5\u5fd7\u5206\u7ec4\u5230\u7279\u5b9a\u8ddf\u8e2a\u4e0b\u3002\u8ddf\u8e2a\u662f\u5bf9 API \u7684\u8c03\u7528\uff0c\u56e0\u6b64\u8be5\u8c03\u7528\u4e2d\u53d1\u751f\u7684\u6240\u6709\u4e8b\u60c5\u90fd\u5728\u903b\u8f91\u4e0a\u5206\u7ec4\u5728\u4e00\u8d77\u3002\u6b64\u529f\u80fd\u5229\u7528 WebServer \u4fe1\u606f\uff0c\u7279\u522b\u662f W3C \u6807\u51c6\u4e3a\u6bcf\u4e2a\u8c03\u7528\u9881\u53d1\u7684 TraceParentId\u3002<\/p>\n<p>In this way, Application Insights can bind calls between various minimal APIs, should we be in a microservice application or with multiple services collaborating with each other.<br \/>\n\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0cApplication Insights \u53ef\u4ee5\u5728\u5404\u79cd\u6700\u5c0f API \u4e4b\u95f4\u7ed1\u5b9a\u8c03\u7528\uff0c\u524d\u63d0\u662f\u6211\u4eec\u4f4d\u4e8e\u5fae\u670d\u52a1\u5e94\u7528\u7a0b\u5e8f\u4e2d\u6216\u591a\u4e2a\u670d\u52a1\u76f8\u4e92\u534f\u4f5c\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0504.jpg\"><\/p>\n<p>Figure 5.4 \u2013 Application Insights with a standard log provider<br \/>\n\u56fe 5.4 \u2013 \u5177\u6709\u6807\u51c6\u65e5\u5fd7\u63d0\u4f9b\u7a0b\u5e8f\u7684 Application Insights<\/p>\n<p>We notice how the default formatter of the logging framework does not serialize the PayloadData object but only writes the text of the object.<br \/>\n\u6211\u4eec\u6ce8\u610f\u5230\u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\u7684\u9ed8\u8ba4\u683c\u5f0f\u5316\u7a0b\u5e8f\u4e0d\u4f1a\u5e8f\u5217\u5316 PayloadData \u5bf9\u8c61\uff0c\u800c\u53ea\u5199\u5165\u5bf9\u8c61\u7684\u6587\u672c\u3002<\/p>\n<p>In the applications that we will bring into production, it will be necessary to also trace the serialization of the objects. Understanding the state of the object on time is fundamental to analyzing the errors that occurred during a particular call while running queries in the database or reading the data read from the same.<br \/>\n\u5728\u6211\u4eec\u5373\u5c06\u6295\u5165\u751f\u4ea7\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u8fd8\u9700\u8981\u8ddf\u8e2a\u5bf9\u8c61\u7684\u5e8f\u5217\u5316\u3002\u4e86\u89e3\u5bf9\u8c61\u7684\u6309\u65f6\u72b6\u6001\u5bf9\u4e8e\u5728\u6570\u636e\u5e93\u4e2d\u8fd0\u884c\u67e5\u8be2\u6216\u8bfb\u53d6\u4ece\u6570\u636e\u5e93\u4e2d\u8bfb\u53d6\u7684\u6570\u636e\u65f6\u5206\u6790\u7279\u5b9a\u8c03\u7528\u671f\u95f4\u53d1\u751f\u7684\u9519\u8bef\u81f3\u5173\u91cd\u8981\u3002<\/p>\n<p>Storing a structured log with Serilog<br \/>\n\u4f7f\u7528 Serilog \u5b58\u50a8\u7ed3\u6784\u5316\u65e5\u5fd7<\/p>\n<p>As we just discussed, tracking structured objects in the log helps us tremendously in understanding errors.<br \/>\n\u6b63\u5982\u6211\u4eec\u521a\u624d\u8ba8\u8bba\u7684\uff0c\u8ddf\u8e2a\u65e5\u5fd7\u4e2d\u7684\u7ed3\u6784\u5316\u5bf9\u8c61\u5bf9\u6211\u4eec\u7406\u89e3\u9519\u8bef\u6709\u5f88\u5927\u5e2e\u52a9\u3002<\/p>\n<p>We, therefore, suggest one of the many logging frameworks: Serilog.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u5efa\u8bae\u4f7f\u7528\u4f17\u591a\u65e5\u5fd7\u6846\u67b6\u4e4b\u4e00\uff1aSerilog\u3002<\/p>\n<p>Serilog is a comprehensive library that has many sinks already written that allow you to store log data and search it later.<br \/>\nSerilog \u662f\u4e00\u4e2a\u7efc\u5408\u5e93\uff0c\u5b83\u5df2\u7ecf\u7f16\u5199\u4e86\u8bb8\u591a\u63a5\u6536\u5668\uff0c\u5141\u8bb8\u60a8\u5b58\u50a8\u65e5\u5fd7\u6570\u636e\u5e76\u5728\u4ee5\u540e\u8fdb\u884c\u641c\u7d22\u3002<\/p>\n<p>Serilog is a logging library that allows you to track information on multiple data sources. In Serilog, these sources are called sinks, and they allow you to write structured data inside the log applying a serialization of the data passed to the logging system.<br \/>\nSerilog \u662f\u4e00\u4e2a\u65e5\u5fd7\u8bb0\u5f55\u5e93\uff0c\u5141\u8bb8\u60a8\u8ddf\u8e2a\u6709\u5173\u591a\u4e2a\u6570\u636e\u6e90\u7684\u4fe1\u606f\u3002\u5728 Serilog \u4e2d\uff0c\u8fd9\u4e9b\u6e90\u79f0\u4e3a sink\uff0c\u5b83\u4eec\u5141\u8bb8\u60a8\u5728\u65e5\u5fd7\u4e2d\u5199\u5165\u7ed3\u6784\u5316\u6570\u636e\uff0c\u5e94\u7528\u4f20\u9012\u7ed9\u65e5\u5fd7\u8bb0\u5f55\u7cfb\u7edf\u7684\u6570\u636e\u7684\u5e8f\u5217\u5316\u3002<\/p>\n<p>Let\u2019s see how to get started using Serilog for a minimal API application. Let\u2019s install these NuGet packages. Our goal will be to track the same information we\u2019ve been using so far, specifically Console and ApplicationInsights:<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u5f00\u59cb\u5c06 Serilog \u7528\u4e8e\u6700\u5c0f\u7684 API \u5e94\u7528\u7a0b\u5e8f\u3002\u8ba9\u6211\u4eec\u5b89\u88c5\u8fd9\u4e9b NuGet \u5305\u3002\u6211\u4eec\u7684\u76ee\u6807\u662f\u8ddf\u8e2a\u6211\u4eec\u76ee\u524d\u4e00\u76f4\u5728\u4f7f\u7528\u7684\u76f8\u540c\u4fe1\u606f\uff0c\u7279\u522b\u662f\u63a7\u5236\u53f0\u548c ApplicationInsights\uff1a<\/p>\n<pre><code>&lt;PackageReference Include=&quot;Microsoft.ApplicationInsights.AspNetCore&quot; Version=&quot;2.20.0&quot; \/&gt;\n&lt;PackageReference Include=&quot;Serilog.AspNetCore&quot; Version=&quot;4.1.0&quot; \/&gt;\n&lt;PackageReference Include=&quot;Serilog.Settings.Configuration&quot; Version=&quot;3.3.0&quot; \/&gt;\n&lt;PackageReference Include=&quot;Serilog.Sinks.ApplicationInsights&quot; Version=&quot;3.1.0&quot; \/&gt;<\/code><\/pre>\n<p>The first package is the one needed for the ApplicationInsights SDK in the application. The second package allows us to register Serilog in the ASP.NET pipeline and to be able to exploit Serilog. The third package allows us to configure the framework in the appsettings file and not have to rewrite the application to change a parameter or code. Finally, we have the package to add the ApplicationInsights sink.<br \/>\n\u7b2c\u4e00\u4e2a\u5305\u662f\u5e94\u7528\u7a0b\u5e8f\u4e2d ApplicationInsights SDK \u6240\u9700\u7684\u5305\u3002\u7b2c\u4e8c\u4e2a\u5305\u5141\u8bb8\u6211\u4eec\u5728 ASP.NET \u7ba1\u9053\u4e2d\u6ce8\u518c Serilog\uff0c\u5e76\u80fd\u591f\u5229\u7528 Serilog\u3002\u7b2c\u4e09\u4e2a\u5305\u5141\u8bb8\u6211\u4eec\u5728 appsettings \u6587\u4ef6\u4e2d\u914d\u7f6e\u6846\u67b6\uff0c\u800c\u4e0d\u5fc5\u91cd\u5199\u5e94\u7528\u7a0b\u5e8f\u6765\u66f4\u6539\u53c2\u6570\u6216\u4ee3\u7801\u3002\u6700\u540e\uff0c\u6211\u4eec\u6709\u4e86\u7528\u4e8e\u6dfb\u52a0 ApplicationInsights \u63a5\u6536\u5668\u7684\u5305\u3002<\/p>\n<p>In the appsettings file, we create a new Serilog section, in which we should register the various sinks in the Using section. We register the log level, the sinks, the enrichers that enrich the information for each event, and the properties, such as the application name:<br \/>\n\u5728 appsettings \u6587\u4ef6\u4e2d\uff0c\u6211\u4eec\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 Serilog \u90e8\u5206\uff0c\u6211\u4eec\u5e94\u8be5\u5728\u5176\u4e2d\u6ce8\u518c Using \u90e8\u5206\u7684\u5404\u79cd\u63a5\u6536\u5668\u3002\u6211\u4eec\u6ce8\u518c\u65e5\u5fd7\u7ea7\u522b\u3001\u63a5\u6536\u5668\u3001\u6269\u5145\u6bcf\u4e2a\u4e8b\u4ef6\u4fe1\u606f\u7684 enricher \u4ee5\u53ca\u5c5e\u6027\uff0c\u4f8b\u5982\u5e94\u7528\u7a0b\u5e8f\u540d\u79f0\uff1a<\/p>\n<pre><code>&quot;Serilog&quot;: {\n    &quot;Using&quot;: [ &quot;Serilog.Sinks.Console&quot;,\n      &quot;Serilog.Sinks.ApplicationInsights&quot; ],\n    &quot;MinimumLevel&quot;: &quot;Verbose&quot;,\n    &quot;WriteTo&quot;: [\n      { &quot;Name&quot;: &quot;Console&quot; },\n      {\n        &quot;Name&quot;: &quot;ApplicationInsights&quot;,\n        &quot;Args&quot;: {\n          &quot;restrictedToMinimumLevel&quot;: &quot;Information&quot;,\n          &quot;telemetryConverter&quot;: &quot;Serilog.Sinks.\n           ApplicationInsights.Sinks.ApplicationInsights.\n           TelemetryConverters.TraceTelemetryConverter, \n           Serilog.Sinks.ApplicationInsights&quot;\n        }\n      }\n    ],\n    &quot;Enrich&quot;: [ &quot;FromLogContext&quot;],   \n    &quot;Properties&quot;: {\n      &quot;Application&quot;: &quot;MinimalApi.Packt&quot;\n    }\n  }<\/code><\/pre>\n<p>Now, we just have to register Serilog in the ASP.NET pipeline:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ea\u9700\u8981\u5728 ASP.NET \u7ba1\u9053\u4e2d\u6ce8\u518c Serilog\uff1a<\/p>\n<pre><code>using Microsoft.ApplicationInsights.Extensibility;\nusing Serilog;\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Logging.AddSerilog();\nbuilder.Services.AddApplicationInsightsTelemetry();\nvar app = builder.Build();\nLog.Logger = new LoggerConfiguration()\n.WriteTo.ApplicationInsights(app.Services.GetRequiredService&lt;TelemetryConfiguration&gt;(), TelemetryConverter.Traces)\n.CreateLogger();<\/code><\/pre>\n<p>With the builder.Logging.AddSerilog() statement, we register Serilog with the logging framework to which all logged events will be passed with the usual ILogger interface. Since the framework needs to register the TelemetryConfiguration class to register ApplicationInsights, we are forced to hook the configuration to the static Logger object of Serilog. This is all because Serilog will turn the information from the Microsoft logging framework over to the Serilog framework and add all the necessary information.<br \/>\nbuilder.Logging.AddSerilog()\u8bed\u53e5\u4e2d\uff0c\u6211\u4eec\u5c06 Serilog \u6ce8\u518c\u5230\u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\uff0c\u6240\u6709\u8bb0\u5f55\u7684\u4e8b\u4ef6\u90fd\u5c06\u4f7f\u7528\u901a\u5e38\u7684 ILogger \u63a5\u53e3\u4f20\u9012\u5230\u8be5\u6846\u67b6\u3002\u7531\u4e8e\u6846\u67b6\u9700\u8981\u6ce8\u518c TelemetryConfiguration \u7c7b\u6765\u6ce8\u518c ApplicationInsights\uff0c\u56e0\u6b64\u6211\u4eec\u88ab\u8feb\u5c06\u914d\u7f6e\u6302\u63a5\u5230 Serilog \u7684\u9759\u6001 Logger \u5bf9\u8c61\u3002\u8fd9\u90fd\u662f\u56e0\u4e3a Serilog \u4f1a\u5c06\u4fe1\u606f\u4ece Microsoft \u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\u8f6c\u79fb\u5230 Serilog \u6846\u67b6\u5e76\u6dfb\u52a0\u6240\u6709\u5fc5\u8981\u7684\u4fe1\u606f\u3002<\/p>\n<p>The usage is very similar to the previous one, but this time, we add an @ (at) to the message template that will tell Serilog to serialize the sent object.<br \/>\n\u7528\u6cd5\u4e0e\u524d\u4e00\u4e2a\u975e\u5e38\u76f8\u4f3c\uff0c\u4f46\u8fd9\u6b21\uff0c\u6211\u4eec\u5728\u6d88\u606f\u6a21\u677f\u4e2d\u6dfb\u52a0\u4e00\u4e2a @ \uff08at\uff09\uff0c\u5b83\u5c06\u544a\u8bc9 Serilog \u5e8f\u5217\u5316\u53d1\u9001\u7684\u5bf9\u8c61\u3002<\/p>\n<p>With this very simple {@Person} wording, we will be able to achieve the goal of serializing the object and sending it to the ApplicationInsights service:<br \/>\n\u4f7f\u7528\u8fd9\u4e2a\u975e\u5e38\u7b80\u5355\u7684 {@Person} \u63aa\u8f9e\uff0c\u6211\u4eec\u5c06\u80fd\u591f\u5b9e\u73b0\u5e8f\u5217\u5316\u5bf9\u8c61\u5e76\u5c06\u5176\u53d1\u9001\u5230 ApplicationInsights \u670d\u52a1\u7684\u76ee\u6807\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/serilog&quot;, (ILogger&lt;CategoryFiltered&gt; loggerCategory) =&gt;\n{\n    loggerCategory.LogInformation(&quot;I&#039;m {@Person}&quot;, new\n      Person(&quot;Andrea&quot;, &quot;Tosato&quot;, new DateTime(1986, 11, \n      9)));\n    return Results.Ok();\n})\n.WithName(&quot;GetFirstLog&quot;);\ninternal record Person(string Name, string Surname, DateTime Birthdate);<\/code><\/pre>\n<p>Finally, we have to find the complete data, serialized with the JSON format, in the Application Insights service.<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u5728 Application Insights \u670d\u52a1\u4e2d\u627e\u5230\u4f7f\u7528 JSON \u683c\u5f0f\u5e8f\u5217\u5316\u7684\u5b8c\u6574\u6570\u636e\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0505.jpg\" ><\/p>\n<p>Figure 5.5 \u2013 Application Insights with structured data<br \/>\n\u56fe 5.5 \u2013 \u5305\u542b\u7ed3\u6784\u5316\u6570\u636e\u7684 Application Insights<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>In this chapter, we have seen several logging aspects of the implementation of minimal APIs.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u6700\u5c0f API \u5b9e\u73b0\u7684\u51e0\u4e2a\u65e5\u5fd7\u8bb0\u5f55\u65b9\u9762\u3002<\/p>\n<p>We started to appreciate the ASP.NET churned logging framework, and we understood how to configure and customize it. We focused on how to define a message template and how to avoid errors with the source generator.<br \/>\n\u6211\u4eec\u5f00\u59cb\u6b23\u8d4f ASP.NET \u7684 churned \u65e5\u5fd7\u8bb0\u5f55\u6846\u67b6\uff0c\u5e76\u4e14\u6211\u4eec\u4e86\u89e3\u5982\u4f55\u914d\u7f6e\u548c\u81ea\u5b9a\u4e49\u5b83\u3002\u6211\u4eec\u91cd\u70b9\u4ecb\u7ecd\u4e86\u5982\u4f55\u5b9a\u4e49\u6d88\u606f\u6a21\u677f\u4ee5\u53ca\u5982\u4f55\u907f\u514d\u6e90\u751f\u6210\u5668\u51fa\u9519\u3002<\/p>\n<p>We saw how to use the new provider to serialize logs with the JSON format and create a custom provider. These elements turned out to be very important for mastering the logging tool and customizing it to your liking.<br \/>\n\u6211\u4eec\u4e86\u89e3\u4e86\u5982\u4f55\u4f7f\u7528\u65b0\u7684\u63d0\u4f9b\u7a0b\u5e8f\u4ee5 JSON \u683c\u5f0f\u5e8f\u5217\u5316\u65e5\u5fd7\u5e76\u521b\u5efa\u81ea\u5b9a\u4e49\u63d0\u4f9b\u7a0b\u5e8f\u3002\u4e8b\u5b9e\u8bc1\u660e\uff0c\u8fd9\u4e9b\u5143\u7d20\u5bf9\u4e8e\u638c\u63e1\u65e5\u5fd7\u8bb0\u5f55\u5de5\u5177\u5e76\u6839\u636e\u60a8\u7684\u559c\u597d\u5bf9\u5176\u8fdb\u884c\u81ea\u5b9a\u4e49\u975e\u5e38\u91cd\u8981\u3002<\/p>\n<p>Not only was the application log mentioned but also the infrastructure log, which together with Application Insights becomes a key element to monitoring your application. Finally, we understood that there are ready-made tools, such as Serilog, that help us to have ready-to-use functionalities with a few steps thanks to some packages installed by NuGet.<br \/>\n\u4e0d\u4ec5\u63d0\u5230\u4e86\u5e94\u7528\u7a0b\u5e8f\u65e5\u5fd7\uff0c\u8fd8\u63d0\u5230\u4e86\u57fa\u7840\u7ed3\u6784\u65e5\u5fd7\uff0c\u5b83\u4e0e Application Insights \u4e00\u8d77\u6210\u4e3a\u76d1\u89c6\u5e94\u7528\u7a0b\u5e8f\u7684\u5173\u952e\u5143\u7d20\u3002\u6700\u540e\uff0c\u6211\u4eec\u4e86\u89e3\u5230\u6709\u4e00\u4e9b\u73b0\u6210\u7684\u5de5\u5177\uff0c\u4f8b\u5982 Serilog\uff0c\u7531\u4e8e NuGet \u5b89\u88c5\u7684\u4e00\u4e9b\u8f6f\u4ef6\u5305\uff0c\u5b83\u4eec\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u901a\u8fc7\u51e0\u4e2a\u6b65\u9aa4\u83b7\u5f97\u5373\u7528\u578b\u529f\u80fd\u3002<\/p>\n<p>In the next chapter, we will present the mechanisms for validating an input object to the API. This is a fundamental feature to return a correct error to the calls and discard inaccurate requests or those promoted by illicit activities such as spam and attacks, aimed at generating load on our servers.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u9a8c\u8bc1 API \u7684\u8f93\u5165\u5bf9\u8c61\u7684\u673a\u5236\u3002\u8fd9\u662f\u4e00\u9879\u57fa\u672c\u529f\u80fd\uff0c\u53ef\u5411\u8c03\u7528\u8fd4\u56de\u6b63\u786e\u7684\u9519\u8bef\u5e76\u4e22\u5f03\u4e0d\u51c6\u786e\u7684\u8bf7\u6c42\u6216\u7531\u975e\u6cd5\u6d3b\u52a8\uff08\u5982\u5783\u573e\u90ae\u4ef6\u548c\u653b\u51fb\uff09\u63a8\u52a8\u7684\u8bf7\u6c42\uff0c\u65e8\u5728\u5728\u6211\u4eec\u7684\u670d\u52a1\u5668\u4e0a\u4ea7\u751f\u8d1f\u8f7d\u3002<\/p>\n<h1>6 Exploring Validation and Mapping<\/h1>\n<p>6 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04<\/p>\n<p>In this chapter of the book, we will discuss how to perform data validation and mapping with minimal APIs, showing what features we currently have, what is missing, and what the most interesting alternatives are. Learning about these concepts will help us to develop more robust and maintainable applications.<br \/>\n\u5728\u672c\u4e66\u7684\u8fd9\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u5982\u4f55\u4f7f\u7528\u6700\u5c11\u7684 API \u6267\u884c\u6570\u636e\u9a8c\u8bc1\u548c\u6620\u5c04\uff0c\u5c55\u793a\u6211\u4eec\u76ee\u524d\u62e5\u6709\u7684\u529f\u80fd\u3001\u7f3a\u5c11\u7684\u529f\u80fd\u4ee5\u53ca\u6700\u6709\u8da3\u7684\u66ff\u4ee3\u65b9\u6848\u3002\u4e86\u89e3\u8fd9\u4e9b\u6982\u5ff5\u5c06\u6709\u52a9\u4e8e\u6211\u4eec\u5f00\u53d1\u66f4\u5065\u58ee\u4e14\u53ef\u7ef4\u62a4\u7684\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>In this chapter, we will be covering the following topics:<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u4e3b\u9898\uff1a<\/p>\n<p>\u2022  Handling validation<br \/>\n\u5904\u7406\u9a8c\u8bc1<\/p>\n<p>\u2022  Mapping data to and from APIs<br \/>\n\u5c06\u6570\u636e\u6620\u5c04\u5230 API \u6216\u4ece API \u6620\u5c04\u6570\u636e<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>To follow the descriptions in this chapter, you will need to create an ASP.NET Core 6.0 Web API application. Refer to the Technical requirements section in Chapter 2, Exploring Minimal APIs and Their Advantages, for instructions on how to do so.<br \/>\n\u8981\u6309\u7167\u672c\u7ae0\u4e2d\u7684\u63cf\u8ff0\u8fdb\u884c\u4f5c\uff0c\u60a8\u9700\u8981\u521b\u5efa\u4e00\u4e2a ASP.NET Core 6.0 Web API \u5e94\u7528\u7a0b\u5e8f\u3002\u6709\u5173\u5982\u4f55\u6267\u884c\u6b64\u4f5c\u7684\u8bf4\u660e\uff0c\u8bf7\u53c2\u9605\u7b2c 2 \u7ae0 \u201c\u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf\u201d\u4e2d\u7684\u201c\u6280\u672f\u8981\u6c42\u201d\u90e8\u5206\u3002<\/p>\n<p>If you\u2019re using your console, shell, or bash terminal to create the API, remember to change your working directory to the current chapter number (Chapter06).<br \/>\n\u5982\u679c\u60a8\u4f7f\u7528\u63a7\u5236\u53f0\u3001shell \u6216 bash \u7ec8\u7aef\u521b\u5efa API\uff0c\u8bf7\u8bb0\u4f4f\u5c06\u5de5\u4f5c\u76ee\u5f55\u66f4\u6539\u4e3a\u5f53\u524d\u7ae0\u8282\u7f16\u53f7 \uff08Chapter06\uff09\u3002<\/p>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter06\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter06<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter06\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter06<\/a>\u3002<\/p>\n<p>Handling validation<br \/>\n\u5904\u7406\u9a8c\u8bc1<\/p>\n<p>Data validation is one of the most important processes in any working software. In the context of a Web API, we perform the validation process to ensure that the information passed to our endpoints respects certain rules \u2013 for example, that a Person object has both the FirstName and LastName properties defined, an email address is valid, or an appointment date isn\u2019t in the past.<br \/>\n\u6570\u636e\u9a8c\u8bc1\u662f\u4efb\u4f55\u5de5\u4f5c\u8f6f\u4ef6\u4e2d\u6700\u91cd\u8981\u7684\u8fc7\u7a0b\u4e4b\u4e00\u3002\u5728 Web API \u7684\u4e0a\u4e0b\u6587\u4e2d\uff0c\u6211\u4eec\u6267\u884c\u9a8c\u8bc1\u8fc7\u7a0b\u4ee5\u786e\u4fdd\u4f20\u9012\u7ed9\u7ec8\u7aef\u8282\u70b9\u7684\u4fe1\u606f\u7b26\u5408\u67d0\u4e9b\u89c4\u5219\uff0c\u4f8b\u5982\uff0cPerson \u5bf9\u8c61\u540c\u65f6\u5b9a\u4e49\u4e86 FirstName \u548c LastName \u5c5e\u6027\u3001\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u6709\u6548\u6216\u7ea6\u4f1a\u65e5\u671f\u4e0d\u662f\u8fc7\u53bb\u7684\u65e5\u671f\u3002<\/p>\n<p>In controller-based projects, we can perform these checks, also termed model validation, directly on the model, using data annotations. In fact, the ApiController attribute that is placed on a controller makes model validation errors automatically trigger a 400 Bad Request response if one or more validation rules fail. Therefore, in controller-based projects, we typically don\u2019t need to perform explicit model validation at all: if the validation fails, our endpoint will never be invoked.<br \/>\n\u5728\u57fa\u4e8e\u63a7\u5236\u5668\u7684\u9879\u76ee\u4e2d\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u6570\u636e\u6ce8\u91ca\u76f4\u63a5\u5728\u6a21\u578b\u4e0a\u6267\u884c\u8fd9\u4e9b\u68c0\u67e5\uff0c\u4e5f\u79f0\u4e3a\u6a21\u578b\u9a8c\u8bc1\u3002\u4e8b\u5b9e\u4e0a\uff0c\u653e\u7f6e\u5728\u63a7\u5236\u5668\u4e0a\u7684 ApiController \u5c5e\u6027\u4f1a\u4f7f\u6a21\u578b\u9a8c\u8bc1\u9519\u8bef\u5728\u4e00\u4e2a\u6216\u591a\u4e2a\u9a8c\u8bc1\u89c4\u5219\u5931\u8d25\u65f6\u81ea\u52a8\u89e6\u53d1 400 Bad Request \u54cd\u5e94\u3002\u56e0\u6b64\uff0c\u5728\u57fa\u4e8e\u63a7\u5236\u5668\u7684\u9879\u76ee\u4e2d\uff0c\u6211\u4eec\u901a\u5e38\u6839\u672c\u4e0d\u9700\u8981\u6267\u884c\u663e\u5f0f\u6a21\u578b\u9a8c\u8bc1\uff1a\u5982\u679c\u9a8c\u8bc1\u5931\u8d25\uff0c\u6211\u4eec\u7684\u7aef\u70b9\u5c06\u6c38\u8fdc\u4e0d\u4f1a\u88ab\u8c03\u7528\u3002<\/p>\n<p>Note : The ApiController attribute enables the automatic model validation behavior using the ModelStateInvalidFilter action filter.<br \/>\n\u6ce8\u610f : ApiController \u5c5e\u6027\u4f7f\u7528 ModelStateInvalidFilter\u4f5c\u7b5b\u9009\u5668\u542f\u7528\u81ea\u52a8\u6a21\u578b\u9a8c\u8bc1\u884c\u4e3a\u3002<\/p>\n<p>Unfortunately, minimal APIs do not provide built-in support for validation. The IModelValidator interface and all related objects cannot be used. Thus, we don\u2019t have a ModelState; we can\u2019t prevent the execution of our endpoint if there is a validation error and must explicitly return a 400 Bad Request response.<br \/>\n\u9057\u61be\u7684\u662f\uff0c\u6700\u5c0f API \u4e0d\u63d0\u4f9b\u5bf9\u9a8c\u8bc1\u7684\u5185\u7f6e\u652f\u6301\u3002\u4e0d\u80fd\u4f7f\u7528 IModelValidator \u63a5\u53e3\u548c\u6240\u6709\u76f8\u5173\u5bf9\u8c61\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u6ca1\u6709 ModelState;\u5982\u679c\u5b58\u5728\u9a8c\u8bc1\u9519\u8bef\uff0c\u6211\u4eec\u65e0\u6cd5\u963b\u6b62\u7ec8\u7aef\u8282\u70b9\u7684\u6267\u884c\uff0c\u5e76\u4e14\u5fc5\u987b\u663e\u5f0f\u8fd4\u56de 400 Bad Request \u54cd\u5e94\u3002<\/p>\n<p>So, for example, let\u2019s see the following code:<br \/>\n\u56e0\u6b64\uff0c\u4f8b\u5982\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/people&quot;, (Person person) =&gt;\n{\n    return Results.NoContent();\n});\npublic class Person\n{\n    [Required]\n    [MaxLength(30)]\n    public string FirstName { get; set; }\n    [Required]\n    [MaxLength(30)]\n    public string LastName { get; set; }\n    [EmailAddress]\n    [StringLength(100, MinimumLength = 6)]\n    public string Email { get; set; }\n}<\/code><\/pre>\n<p>As we can see, the endpoint will be invoked even if the Person argument does not respect the validation rules. There is only one exception: if we use nullable reference types and we don\u2019t pass a body in the request, we effectively get a 400 Bad Request response. As mentioned in Chapter 2, Exploring Minimal APIs and Their Advantages, nullable reference types are enabled by default in .NET 6.0 projects.<br \/>\n\u6b63\u5982\u6211\u4eec\u6240\u770b\u5230\u7684\uff0c\u5373\u4f7f Person \u53c2\u6570\u4e0d\u9075\u5b88\u9a8c\u8bc1\u89c4\u5219\uff0c\u4e5f\u4f1a\u8c03\u7528\u7aef\u70b9\u3002\u53ea\u6709\u4e00\u4e2a\u4f8b\u5916\uff1a\u5982\u679c\u6211\u4eec\u4f7f\u7528\u53ef\u4e3a null \u7684\u5f15\u7528\u7c7b\u578b\uff0c\u5e76\u4e14\u6211\u4eec\u6ca1\u6709\u5728\u8bf7\u6c42\u4e2d\u4f20\u9012\u6b63\u6587\uff0c\u6211\u4eec\u5b9e\u9645\u4e0a\u4f1a\u5f97\u5230 400 Bad Request \u54cd\u5e94\u3002\u5982\u7b2c 2 \u7ae0 \u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u70b9\u4e2d\u6240\u8ff0\uff0c\u5728 .NET 6.0 \u9879\u76ee\u4e2d\u9ed8\u8ba4\u542f\u7528\u53ef\u4e3a null \u7684\u5f15\u7528\u7c7b\u578b\u3002<\/p>\n<p>If we want to accept a null body (if ever there was a need), we need to declare the parameter as Person?. But, as long as there is a body, the endpoint will always be invoked.<br \/>\n\u5982\u679c\u6211\u4eec\u60f3\u63a5\u53d7\u4e00\u4e2a null body\uff08\u5982\u679c\u6709\u9700\u8981\uff09\uff0c\u6211\u4eec\u9700\u8981\u5c06\u53c2\u6570\u58f0\u660e\u4e3a Person\uff1f\u3002\u4f46\u662f\uff0c\u53ea\u8981\u6709 body\uff0c\u7aef\u70b9\u5c31\u4f1a\u59cb\u7ec8\u88ab\u8c03\u7528\u3002<\/p>\n<p>So, with minimal APIs, it is necessary to perform validation inside each route handler and return the appropriate response if some rules fail. We can either implement a validation library compatible with the existing attributes so that we can perform validation using the classic data annotations approach, as described in the next section, or use a third-party solution such as FluentValidation, as we will see in the Integrating FluentValidation section.<br \/>\n\u56e0\u6b64\uff0c\u4f7f\u7528\u6700\u5c11\u7684 API\uff0c\u6709\u5fc5\u8981\u5728\u6bcf\u4e2a\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u4e2d\u6267\u884c\u9a8c\u8bc1\uff0c\u5e76\u5728\u67d0\u4e9b\u89c4\u5219\u5931\u8d25\u65f6\u8fd4\u56de\u76f8\u5e94\u7684\u54cd\u5e94\u3002\u6211\u4eec\u53ef\u4ee5\u5b9e\u73b0\u4e0e\u73b0\u6709\u5c5e\u6027\u517c\u5bb9\u7684\u9a8c\u8bc1\u5e93\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u7ecf\u5178\u6570\u636e\u6ce8\u91ca\u65b9\u6cd5\u6267\u884c\u9a8c\u8bc1\uff0c\u5982\u4e0b\u4e00\u8282\u6240\u8ff0\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528\u7b2c\u4e09\u65b9\u89e3\u51b3\u65b9\u6848\uff0c\u4f8b\u5982 FluentValidation\uff0c\u6b63\u5982\u6211\u4eec\u5c06\u5728\u96c6\u6210 FluentValidation \u90e8\u5206\u4e2d\u770b\u5230\u7684\u90a3\u6837\u3002<\/p>\n<p>Performing validation with data annotations<br \/>\n\u4f7f\u7528\u6570\u636e\u6ce8\u91ca\u6267\u884c\u9a8c\u8bc1<\/p>\n<p>If we want to use the common validation pattern based on data annotations, we need to rely on reflection to retrieve all the validation attributes in a model and invoke their IsValid methods, which are provided by the ValidationAttribute base class.<br \/>\n\u5982\u679c\u6211\u4eec\u60f3\u4f7f\u7528\u57fa\u4e8e\u6570\u636e\u6ce8\u91ca\u7684\u901a\u7528\u9a8c\u8bc1\u6a21\u5f0f\uff0c\u5219\u9700\u8981\u4f9d\u9760\u53cd\u5c04\u6765\u68c0\u7d22\u6a21\u578b\u4e2d\u7684\u6240\u6709\u9a8c\u8bc1\u5c5e\u6027\uff0c\u5e76\u8c03\u7528\u5b83\u4eec\u7684 IsValid \u65b9\u6cd5\uff0c\u8fd9\u4e9b\u65b9\u6cd5\u7531 ValidationAttribute \u57fa\u7c7b\u63d0\u4f9b\u3002<\/p>\n<p>This behavior is a simplification of what ASP.NET Core actually does to handle validations. However, this is the way validation in controller-based projects works.<br \/>\n\u6b64\u884c\u4e3a\u7b80\u5316\u4e86 ASP.NET Core \u5b9e\u9645\u5904\u7406\u9a8c\u8bc1\u7684\u4f5c\u3002\u4f46\u662f\uff0c\u8fd9\u5c31\u662f\u57fa\u4e8e controller \u7684 projects \u4e2d validation \u7684\u5de5\u4f5c\u65b9\u5f0f\u3002<\/p>\n<p>While we can also manually implement a solution of this kind with minimal APIs, if we decide to use data annotations for validation, we can leverage a small but interesting library, MiniValidation, which is available on GitHub (<a href=\"https:\/\/github.com\/DamianEdwards\/MiniValidation\">https:\/\/github.com\/DamianEdwards\/MiniValidation<\/a>) and NuGet (<a href=\"https:\/\/www.nuget.org\/packages\/MiniValidation\">https:\/\/www.nuget.org\/packages\/MiniValidation<\/a>).<br \/>\n\u867d\u7136\u6211\u4eec\u4e5f\u53ef\u4ee5\u4f7f\u7528\u6700\u5c11\u7684 API \u624b\u52a8\u5b9e\u73b0\u6b64\u7c7b\u89e3\u51b3\u65b9\u6848\uff0c\u4f46\u5982\u679c\u6211\u4eec\u51b3\u5b9a\u4f7f\u7528\u6570\u636e\u6ce8\u91ca\u8fdb\u884c\u9a8c\u8bc1\uff0c\u6211\u4eec\u53ef\u4ee5\u5229\u7528\u4e00\u4e2a\u5c0f\u800c\u6709\u8da3\u7684\u5e93 MiniValidation\uff0c\u8be5\u5e93\u53ef\u5728 GitHub \uff08<a href=\"https:\/\/github.com\/DamianEdwards\/MiniValidation\">https:\/\/github.com\/DamianEdwards\/MiniValidation<\/a>\uff09 \u548c NuGet \uff08<a href=\"https:\/\/www.nuget.org\/packages\/MiniValidation\">https:\/\/www.nuget.org\/packages\/MiniValidation<\/a>\uff09 \u4e0a\u4f7f\u7528\u3002<\/p>\n<p>Important note : At the time of writing, MiniValidation is available on NuGet as a prerelease.<br \/>\n\u91cd\u8981\u63d0\u793a : \u5728\u64b0\u5199\u672c\u6587\u65f6\uff0cMiniValidation \u5728 NuGet \u4e0a\u4f5c\u4e3a\u9884\u53d1\u884c\u7248\u63d0\u4f9b\u3002<\/p>\n<p>We can add this library to our project in one of the following ways:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u4ee5\u4e0b\u65b9\u5f0f\u4e4b\u4e00\u5c06\u6b64\u5e93\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u9879\u76ee\u4e2d\uff1a<\/p>\n<p>\u2022  Option 1: If you\u2019re using Visual Studio 2022, right-click on the project and choose the Manage NuGet Packages command to open the Package Manager GUI; then, search for MiniValidation. Be sure to check the Include prerelease option and click Install.<br \/>\n\u9009\u9879 1\uff1a\u5982\u679c\u60a8\u4f7f\u7528\u7684\u662f Visual Studio 2022\uff0c\u8bf7\u53f3\u952e\u5355\u51fb\u9879\u76ee\u5e76\u9009\u62e9\u201c\u7ba1\u7406 NuGet \u5305\u201d\u547d\u4ee4\u4ee5\u6253\u5f00\u5305\u7ba1\u7406\u5668 GUI;\u7136\u540e\uff0c\u641c\u7d22 MiniValidation\u3002\u8bf7\u52a1\u5fc5\u9009\u4e2d Include prerelease \u9009\u9879\uff0c\u7136\u540e\u5355\u51fb Install\u3002<\/p>\n<p>\u2022  Option 2: Open the Package Manager Console if you\u2019re inside Visual Studio 2022, or open your console, shell, or bash terminal, go to your project directory, and execute the following command: dotnet add package MiniValidation --prerelease<br \/>\n\u9009\u9879 2\uff1a\u5982\u679c\u60a8\u5728 Visual Studio 2022 \u4e2d\uff0c\u8bf7\u6253\u5f00\u5305\u7ba1\u7406\u5668\u63a7\u5236\u53f0\uff0c\u6216\u8005\u6253\u5f00\u63a7\u5236\u53f0\u3001shell \u6216 bash \u7ec8\u7aef\uff0c\u8f6c\u5230\u60a8\u7684\u9879\u76ee\u76ee\u5f55\uff0c\u7136\u540e\u6267\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1adotnet add package MiniValidation --prerelease<\/p>\n<p>Now, we can validate a Person object using the following code:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u4ee3\u7801\u9a8c\u8bc1 Person \u5bf9\u8c61\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/people&quot;, (Person person) =&gt;\n{\n    var isValid = MiniValidator.TryValidate(person, \n      out var errors);\n    if (!isValid)\n    {\n        return Results.ValidationProblem(errors);\n    }\n    return Results.NoContent();\n});<\/code><\/pre>\n<p>As we can see, the MiniValidator.TryValidate static method provided by MiniValidation takes an object as input and automatically verifies all the validation rules that are defined on its properties. If the validation fails, it returns false and populates the out parameter with all the validation errors that have occurred. In this case, because it is our responsibility to return the appropriate response code, we use Results.ValidationProblem, which produces a 400 Bad Request response with a ProblemDetails object (as described in Chapter 3, Working with Minimal APIs) and also contains the validation issues.<br \/>\n\u6b63\u5982\u6211\u4eec\u6240\u770b\u5230\u7684\uff0cMiniValidation \u63d0\u4f9b\u7684 MiniValidator.TryValidate \u9759\u6001\u65b9\u6cd5\u5c06\u5bf9\u8c61\u4f5c\u4e3a\u8f93\u5165\uff0c\u5e76\u81ea\u52a8\u9a8c\u8bc1\u5728\u5176\u5c5e\u6027\u4e0a\u5b9a\u4e49\u7684\u6240\u6709\u9a8c\u8bc1\u89c4\u5219\u3002\u5982\u679c\u9a8c\u8bc1\u5931\u8d25\uff0c\u5b83\u5c06\u8fd4\u56de false \u5e76\u4f7f\u7528\u5df2\u53d1\u751f\u7684\u6240\u6709\u9a8c\u8bc1\u9519\u8bef\u586b\u5145 out \u53c2\u6570\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u7531\u4e8e\u6211\u4eec\u6709\u8d23\u4efb\u8fd4\u56de\u9002\u5f53\u7684\u54cd\u5e94\u4ee3\u7801\uff0c\u56e0\u6b64\u6211\u4eec\u4f7f\u7528 Results.ValidationProblem\uff0c\u5b83\u751f\u6210\u5e26\u6709 ProblemDetails \u5bf9\u8c61\u7684 400 Bad Request \u54cd\u5e94\uff08\u5982\u7b2c 3 \u7ae0 \u4f7f\u7528\u6700\u5c0f API \u4e2d\u6240\u8ff0\uff09\uff0c\u5e76\u4e14\u8fd8\u5305\u542b\u9a8c\u8bc1\u95ee\u9898\u3002<\/p>\n<p>Now, as an example, we can invoke the endpoint using the following invalid input:<br \/>\n\u73b0\u5728\uff0c\u4f8b\u5982\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u65e0\u6548\u8f93\u5165\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\uff1a<\/p>\n<pre><code>{\n  &quot;lastName&quot;: &quot;MyLastName&quot;,\n  &quot;email&quot;: &quot;email&quot;\n}<\/code><\/pre>\n<p>This is the response we will obtain:<br \/>\n\u8fd9\u662f\u6211\u4eec\u5c06\u83b7\u5f97\u7684\u54cd\u5e94\uff1a<\/p>\n<pre><code>{\n  &quot;type&quot;: \n    &quot;https:\/\/tools.ietf.org\/html\/rfc7231#section-6.5.1&quot;,\n  &quot;title&quot;: &quot;One or more validation errors occurred.&quot;,\n  &quot;status&quot;: 400,\n  &quot;errors&quot;: {\n    &quot;FirstName&quot;: [\n      &quot;The FirstName field is required.&quot;\n    ],\n    &quot;Email&quot;: [\n      &quot;The Email field is not a valid e-mail address.&quot;,\n      &quot;The field Email must be a string with a minimum\n       length of 6 and a maximum length of 100.&quot;\n    ]\n  }\n}<\/code><\/pre>\n<p>In this way, besides the fact that we need to execute validation manually, we can implement the approach of using data annotations on our models in the same way we were accustomed to in previous versions of ASP.NET Core. We can also customize error messages and define custom rules by creating classes that inherit from ValidationAttribute.<br \/>\n\u8fd9\u6837\uff0c\u9664\u4e86\u9700\u8981\u624b\u52a8\u6267\u884c\u9a8c\u8bc1\u4e4b\u5916\uff0c\u6211\u4eec\u8fd8\u53ef\u4ee5\u50cf\u4ee5\u524d\u7248\u672c\u7684 ASP.NET Core \u4e00\u6837\uff0c\u5728\u6a21\u578b\u4e0a\u5b9e\u73b0\u4f7f\u7528\u6570\u636e\u6ce8\u91ca\u7684\u65b9\u6cd5\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u901a\u8fc7\u521b\u5efa\u7ee7\u627f\u81ea ValidationAttribute \u7684\u7c7b\u6765\u81ea\u5b9a\u4e49\u9519\u8bef\u6d88\u606f\u548c\u5b9a\u4e49\u81ea\u5b9a\u4e49\u89c4\u5219\u3002<\/p>\n<p>Note : The full list of validation attributes available in ASP.NET Core 6.0 is published at <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.componentmodel.dataannotations\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.componentmodel.dataannotations<\/a>. If you\u2019re interested in creating custom attributes, you can refer to <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/mvc\/models\/validation#custom-attributes\">https:\/\/docs.microsoft.com\/aspnet\/core\/mvc\/models\/validation#custom-attributes<\/a>.<br \/>\n\u6ce8\u610f : ASP.NET Core 6.0 \u4e2d\u53ef\u7528\u7684\u9a8c\u8bc1\u5c5e\u6027\u7684\u5b8c\u6574\u5217\u8868\u53d1\u5e03\u5728 <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.componentmodel.dataannotations\u3002\u5982\u679c\u4f60\u5bf9\u521b\u5efa\u81ea\u5b9a\u4e49\u5c5e\u6027\u611f\u5174\u8da3\uff0c\u53ef\u4ee5\u53c2\u8003\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.componentmodel.dataannotations\u3002\u5982\u679c\u4f60\u5bf9\u521b\u5efa\u81ea\u5b9a\u4e49\u5c5e\u6027\u611f\u5174\u8da3\uff0c\u53ef\u4ee5\u53c2\u8003<\/a> <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/mvc\/models\/validation#custom-attributes\">https:\/\/docs.microsoft.com\/aspnet\/core\/mvc\/models\/validation#custom-attributes<\/a>\u3002<\/p>\n<p>Although data annotations are the most used solution, we can also handle validations using a so-called fluent approach, which has the benefit of completely decoupling validation rules from the model, as we\u2019ll see in the next section.<br \/>\n\u5c3d\u7ba1\u6570\u636e\u6ce8\u91ca\u662f\u6700\u5e38\u7528\u7684\u89e3\u51b3\u65b9\u6848\uff0c\u4f46\u6211\u4eec\u4e5f\u53ef\u4ee5\u4f7f\u7528\u6240\u8c13\u7684 Fluent \u65b9\u6cd5\u5904\u7406\u9a8c\u8bc1\uff0c\u5176\u4f18\u70b9\u662f\u5c06\u9a8c\u8bc1\u89c4\u5219\u4e0e\u6a21\u578b\u5b8c\u5168\u89e3\u8026\uff0c\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u8282\u4e2d\u770b\u5230\u3002<\/p>\n<p>Integrating FluentValidation<br \/>\n\u96c6\u6210 FluentValidation<\/p>\n<p>In every application, it is important to correctly organize our code. This is also true for validation. While data annotations are a working solution, we should think about alternatives that can help us write more maintainable projects. This is the purpose of FluentValidation \u2013 a library, part of the .NET Foundation, that allows us to build validation rules using a fluent interface with lambda expressions. The library is available on GitHub (<a href=\"https:\/\/github.com\/FluentValidation\/FluentValidation\">https:\/\/github.com\/FluentValidation\/FluentValidation<\/a>) and NuGet (<a href=\"https:\/\/www.nuget.org\/packages\/FluentValidation\">https:\/\/www.nuget.org\/packages\/FluentValidation<\/a>). This library can be used in any kind of project, but when working with ASP.NET Core, there is an ad-hoc NuGet package (<a href=\"https:\/\/www.nuget.org\/packages\/FluentValidation.AspNetCore\">https:\/\/www.nuget.org\/packages\/FluentValidation.AspNetCore<\/a>) that contains useful methods that help to integrate it.<br \/>\n\u5728\u6bcf\u4e2a\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u6b63\u786e\u7ec4\u7ec7\u6211\u4eec\u7684\u4ee3\u7801\u90fd\u5f88\u91cd\u8981\u3002\u9a8c\u8bc1\u4e5f\u662f\u5982\u6b64\u3002\u867d\u7136\u6570\u636e\u6ce8\u91ca\u662f\u4e00\u79cd\u6709\u6548\u7684\u89e3\u51b3\u65b9\u6848\uff0c\u4f46\u6211\u4eec\u5e94\u8be5\u8003\u8651\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u7f16\u5199\u66f4\u53ef\u7ef4\u62a4\u9879\u76ee\u7684\u66ff\u4ee3\u65b9\u6848\u3002\u8fd9\u5c31\u662f FluentValidation \u7684\u7528\u9014 \u2013 \u4e00\u4e2a\u5e93\uff0c\u662f .NET Foundation \u7684\u4e00\u90e8\u5206\uff0c\u5b83\u5141\u8bb8\u6211\u4eec\u4f7f\u7528\u5e26\u6709 lambda \u8868\u8fbe\u5f0f\u7684 Fluent \u63a5\u53e3\u6784\u5efa\u9a8c\u8bc1\u89c4\u5219\u3002\u8be5\u5e93\u5728 GitHub \uff08<a href=\"https:\/\/github.com\/FluentValidation\/FluentValidation\">https:\/\/github.com\/FluentValidation\/FluentValidation<\/a>\uff09 \u548c NuGet \uff08<a href=\"https:\/\/www.nuget.org\/packages\/FluentValidation\">https:\/\/www.nuget.org\/packages\/FluentValidation<\/a>\uff09 \u4e0a\u63d0\u4f9b\u3002\u6b64\u5e93\u53ef\u7528\u4e8e\u4efb\u4f55\u7c7b\u578b\u7684\u9879\u76ee\uff0c\u4f46\u5728\u4f7f\u7528 ASP.NET Core \u65f6\uff0c\u6709\u4e00\u4e2a\u4e34\u65f6 NuGet \u5305 \uff08<a href=\"https:\/\/www.nuget.org\/packages\/FluentValidation.AspNetCore\">https:\/\/www.nuget.org\/packages\/FluentValidation.AspNetCore<\/a>\uff09 \u5305\u542b\u6709\u52a9\u4e8e\u96c6\u6210\u5b83\u7684\u6709\u7528\u65b9\u6cd5\u3002<\/p>\n<p>Note : .NET Foundation is an independent organization that aims to support open source software development and collaboration around the .NET platform. You can learn more at <a href=\"https:\/\/dotnetfoundation.org\">https:\/\/dotnetfoundation.org<\/a>.<br \/>\n\u6ce8\u610f : .NET Foundation \u662f\u4e00\u4e2a\u72ec\u7acb\u7684\u7ec4\u7ec7\uff0c\u65e8\u5728\u652f\u6301\u56f4\u7ed5 .NET \u5e73\u53f0\u7684\u5f00\u6e90\u8f6f\u4ef6\u5f00\u53d1\u548c\u534f\u4f5c\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"https:\/\/dotnetfoundation.org\">https:\/\/dotnetfoundation.org<\/a> \u4e2d\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>As stated before, with this library, we can decouple validation rules from the model to create a more structured application. Moreover, FluentValidation allows us to define even more complex rules with a fluent syntax without the need to create custom classes based on ValidationAttribute. The library also natively supports the localization of standard error messages.<br \/>\n\u5982\u524d\u6240\u8ff0\uff0c\u501f\u52a9\u6b64\u5e93\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u9a8c\u8bc1\u89c4\u5219\u4e0e\u6a21\u578b\u89e3\u8026\uff0c\u4ee5\u521b\u5efa\u66f4\u52a0\u7ed3\u6784\u5316\u7684\u5e94\u7528\u7a0b\u5e8f\u3002\u6b64\u5916\uff0cFluentValidation \u5141\u8bb8\u6211\u4eec\u4f7f\u7528 Fluent \u8bed\u6cd5\u5b9a\u4e49\u66f4\u590d\u6742\u7684\u89c4\u5219\uff0c\u800c\u65e0\u9700\u57fa\u4e8e ValidationAttribute \u521b\u5efa\u81ea\u5b9a\u4e49\u7c7b\u3002\u8be5\u5e93\u8fd8\u539f\u751f\u652f\u6301\u6807\u51c6\u9519\u8bef\u6d88\u606f\u7684\u672c\u5730\u5316\u3002<\/p>\n<p>So, let\u2019s see how we can integrate FluentValidation into a minimal API project. First, we need to add this library to our project in one of the following ways:<br \/>\n\u90a3\u4e48\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u5c06 FluentValidation \u96c6\u6210\u5230\u4e00\u4e2a\u6700\u5c0f\u7684 API \u9879\u76ee\u4e2d\u3002\u9996\u5148\uff0c\u6211\u4eec\u9700\u8981\u901a\u8fc7\u4ee5\u4e0b\u65b9\u5f0f\u4e4b\u4e00\u5c06\u6b64\u5e93\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u9879\u76ee\u4e2d\uff1a<\/p>\n<p>\u2022  Option 1: If you\u2019re using Visual Studio 2022, right-click on the project and choose the Manage NuGet Packages command to open Package Manager GUI. Then, search for FluentValidation.DependencyInjectionExtensions and click Install.<br \/>\n\u9009\u9879 1\uff1a\u5982\u679c\u60a8\u4f7f\u7528\u7684\u662f Visual Studio 2022\uff0c\u8bf7\u53f3\u952e\u5355\u51fb\u9879\u76ee\u5e76\u9009\u62e9\u201c\u7ba1\u7406 NuGet \u5305\u201d\u547d\u4ee4\u4ee5\u6253\u5f00\u5305\u7ba1\u7406\u5668 GUI\u3002\u7136\u540e\uff0c\u641c\u7d22 FluentValidation.DependencyInjectionExtensions \u5e76\u5355\u51fb Install\u3002<\/p>\n<p>\u2022  Option 2: Open Package Manager Console if you\u2019re inside Visual Studio 2022, or open your console, shell, or bash terminal, go to your project directory, and execute the following command: dotnet add package FluentValidation.DependencyInjectionExtensions<br \/>\n\u9009\u9879 2\uff1a\u5982\u679c\u60a8\u5728 Visual Studio 2022 \u4e2d\uff0c\u8bf7\u6253\u5f00\u5305\u7ba1\u7406\u5668\u63a7\u5236\u53f0\uff0c\u6216\u8005\u6253\u5f00\u63a7\u5236\u53f0\u3001shell \u6216 bash \u7ec8\u7aef\uff0c\u8f6c\u5230\u60a8\u7684\u9879\u76ee\u76ee\u5f55\uff0c\u7136\u540e\u6267\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a<br \/>\ndotnet add \u5305 FluentValidation.DependencyInjectionExtensions<\/p>\n<p>Now, we can rewrite the validation rules for the Person object and put them in a PersonValidator class:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u91cd\u5199 Person \u5bf9\u8c61\u7684\u9a8c\u8bc1\u89c4\u5219\uff0c\u5e76\u5c06\u5b83\u4eec\u653e\u5165 PersonValidator \u7c7b\u4e2d\uff1a<\/p>\n<pre><code>public class PersonValidator : AbstractValidator&lt;Person&gt;\n{\n    public PersonValidator() \n    {\n        RuleFor(p =&gt;\n          p.FirstName).NotEmpty().MaximumLength(30);\n        RuleFor(p =&gt; \n          p.LastName).NotEmpty().MaximumLength(30);\n        RuleFor(p =&gt; p.Email).EmailAddress().Length(6,\n          100);\n    }\n}<\/code><\/pre>\n<p>PersonValidator inherits from <code>AbstractValidator&lt;T&gt;<\/code>, a base class provided by FluentValidation that contains all the methods we need to define the validation rules. For example, we fluently say that we have a rule for the FirstName property, which is that it must not be empty and it can have a maximum length of 30 characters.<br \/>\nPersonValidator \u7ee7\u627f\u81ea <code>AbstractValidator&lt;T&gt;<\/code>\uff0c\u540e\u8005\u662f FluentValidation \u63d0\u4f9b\u7684\u57fa\u7c7b\uff0c\u5305\u542b\u5b9a\u4e49\u9a8c\u8bc1\u89c4\u5219\u6240\u9700\u7684\u6240\u6709\u65b9\u6cd5\u3002\u4f8b\u5982\uff0c\u6211\u4eec\u6d41\u7545\u5730\u8bf4\u6211\u4eec\u6709\u4e00\u6761 FirstName \u5c5e\u6027\u7684\u89c4\u5219\uff0c\u5373\u5b83\u4e0d\u80fd\u4e3a\u7a7a\uff0c\u5e76\u4e14\u6700\u5927\u957f\u5ea6\u4e3a 30 \u4e2a\u5b57\u7b26\u3002<\/p>\n<p>The next step is to register the validator in the service provider so that we can use it in our route handlers. We can perform this task with a simple instruction:<br \/>\n\u4e0b\u4e00\u6b65\u662f\u5728 service provider \u4e2d\u6ce8\u518c validator\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ef\u4ee5\u5728 route handlers \u4e2d\u4f7f\u7528\u5b83\u3002\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u4e00\u4e2a\u7b80\u5355\u7684\u6307\u4ee4\u6765\u6267\u884c\u8fd9\u9879\u4efb\u52a1\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/...\nbuilder.Services.AddValidatorsFromAssemblyContaining&lt;Program&gt;();<\/code><\/pre>\n<p>The AddValidatorsFromAssemblyContaining method automatically registers all the validators derived from AbstractValidator within the assembly containing the specified type. In particular, this method registers the validators and makes them accessible through dependency injection via the <code>IValidator&lt;T&gt;<\/code> interface, which in turn, is implemented by the <code>AbstractValidator&lt;T&gt;<\/code> class. If we have multiple validators, we can register them all with this single instruction. We can also easily put our validators in external assemblies.<br \/>\nAddValidatorsFromAssemblyContaining \u65b9\u6cd5\u4f1a\u81ea\u52a8\u5728\u5305\u542b\u6307\u5b9a\u7c7b\u578b\u7684\u7a0b\u5e8f\u96c6\u4e2d\u6ce8\u518c\u4ece AbstractValidator \u6d3e\u751f\u7684\u6240\u6709\u9a8c\u8bc1\u7a0b\u5e8f\u3002\u7279\u522b\u662f\uff0c\u6b64\u65b9\u6cd5\u6ce8\u518c\u9a8c\u8bc1\u5668\uff0c\u5e76\u901a\u8fc7 <code>IValidator&lt;T&gt;<\/code> \u63a5\u53e3\u901a\u8fc7\u4f9d\u8d56\u9879\u6ce8\u5165\u4f7f\u5b83\u4eec\u53ef\u8bbf\u95ee\uff0c\u800c <code>IValidator&lt;T&gt;<\/code> \u63a5\u53e3\u53c8\u7531 AbstractValidatorT \u7c7b\u5b9e\u73b0\u3002\u5982\u679c\u6211\u4eec\u6709\u591a\u4e2a\u9a8c\u8bc1\u8005\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u8fd9\u4e2a\u6307\u4ee4\u5c06\u5b83\u4eec\u5168\u90e8\u6ce8\u518c\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u8f7b\u677e\u5730\u5c06\u9a8c\u8bc1\u5668\u653e\u5728\u5916\u90e8\u7a0b\u5e8f\u96c6\u4e2d\u3002<\/p>\n<p>Now that everything is in place, remembering that with minimal APIs we don\u2019t have automatic model validation, we must update our route handler in this way:<br \/>\n\u73b0\u5728\u4e00\u5207\u90fd\u5df2\u51c6\u5907\u5c31\u7eea\uff0c\u8bf7\u8bb0\u4f4f\uff0c\u4f7f\u7528\u6700\u5c11\u7684 API \u65f6\uff0c\u6211\u4eec\u6ca1\u6709\u81ea\u52a8\u6a21\u578b\u9a8c\u8bc1\uff0c\u6211\u4eec\u5fc5\u987b\u4ee5\u8fd9\u79cd\u65b9\u5f0f\u66f4\u65b0\u6211\u4eec\u7684\u8def\u7531\u5904\u7406\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/people&quot;, async (Person person, IValidator&lt;Person&gt; validator) =&gt;\n{\n    var validationResult = \n      await validator.ValidateAsync(person);\n    if (!validationResult.IsValid)\n    {\n        var errors = validationResult.ToDictionary();\n        return Results.ValidationProblem(errors);\n    }\n    return Results.NoContent();\n});<\/code><\/pre>\n<p>We have added an IValidator<Person> argument in the route handler parameter list, so now we can invoke its ValidateAsync method to apply the validation rules against the input Person object. If the validation fails, we extract all the error messages and return them to the client with the usual Results.ValidationProblem method, as described in the previous section.<br \/>\n\u6211\u4eec\u5728\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u5217\u8868\u4e2d\u6dfb\u52a0\u4e86 IValidator<Person> \u53c2\u6570\uff0c\u56e0\u6b64\u73b0\u5728\u6211\u4eec\u53ef\u4ee5\u8c03\u7528\u5176 ValidateAsync \u65b9\u6cd5\uff0c\u4ee5\u5bf9\u8f93\u5165 Person \u5bf9\u8c61\u5e94\u7528\u9a8c\u8bc1\u89c4\u5219\u3002\u5982\u679c\u9a8c\u8bc1\u5931\u8d25\uff0c\u6211\u4eec\u5c06\u63d0\u53d6\u6240\u6709\u9519\u8bef\u6d88\u606f\uff0c\u5e76\u4f7f\u7528\u901a\u5e38\u7684 Results.ValidationProblem \u65b9\u6cd5\u5c06\u5b83\u4eec\u8fd4\u56de\u7ed9\u5ba2\u6237\u7aef\uff0c\u5982\u4e0a\u4e00\u8282\u6240\u8ff0\u3002<\/p>\n<p>In conclusion, let\u2019s see what happens if we try to invoke the endpoint using the following input as before:<br \/>\n\u603b\u4e4b\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u5982\u679c\u6211\u4eec\u50cf\u4ee5\u524d\u4e00\u6837\u5c1d\u8bd5\u4f7f\u7528\u4ee5\u4e0b\u8f93\u5165\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\uff1a<\/p>\n<pre><code>{\n  &quot;lastName&quot;: &quot;MyLastName&quot;,\n  &quot;email&quot;: &quot;email&quot;\n}<\/code><\/pre>\n<p>We\u2019ll get the following response:<br \/>\n\u6211\u4eec\u5c06\u6536\u5230\u4ee5\u4e0b\u54cd\u5e94\uff1a<\/p>\n<pre><code>{\n  &quot;type&quot;: \n    &quot;https:\/\/tools.ietf.org\/html\/rfc7231#section-6.5.1&quot;,\n  &quot;title&quot;: &quot;One or more validation errors occurred.&quot;,\n  &quot;status&quot;: 400,\n  &quot;errors&quot;: {\n    &quot;FirstName&quot;: [\n      &quot;&#039;First Name&#039; non pu\u00f2 essere vuoto.&quot;\n    ],\n    &quot;Email&quot;: [\n      &quot;&#039;Email&#039; non \u00e8 un indirizzo email valido.&quot;,\n      &quot;&#039;Email&#039; deve essere lungo tra i 6 e 100 caratteri.\n        Hai inserito 5 caratteri.&quot;\n    ]\n  }\n}<\/code><\/pre>\n<p>As mentioned earlier, FluentValidation provides translations for standard error messages, so this is the response you get when running on an Italian system. Of course, we can completely customize the messages with the typical fluent approach, using the WithMessage method chained to the validation methods defined in the validator. For example, see the following:<br \/>\n\u5982\u524d\u6240\u8ff0\uff0cFluentValidation \u4e3a\u6807\u51c6\u9519\u8bef\u6d88\u606f\u63d0\u4f9b\u7ffb\u8bd1\uff0c\u56e0\u6b64\u8fd9\u662f\u60a8\u5728\u610f\u5927\u5229\u8bed\u7cfb\u7edf\u4e0a\u8fd0\u884c\u65f6\u5f97\u5230\u7684\u54cd\u5e94\u3002\u5f53\u7136\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u5178\u578b\u7684 Fluent \u65b9\u6cd5\u5b8c\u5168\u81ea\u5b9a\u4e49\u6d88\u606f\uff0c\u4f7f\u7528\u94fe\u63a5\u5230\u9a8c\u8bc1\u5668\u4e2d\u5b9a\u4e49\u7684\u9a8c\u8bc1\u65b9\u6cd5\u7684 WithMessage \u65b9\u6cd5\u3002\u4f8b\u5982\uff0c\u8bf7\u53c2\u9605\u4ee5\u4e0b\u5185\u5bb9\uff1a<\/p>\n<pre><code>RuleFor(p =&gt; p.FirstName).NotEmpty().WithMessage(&quot;You must provide the first name&quot;);<\/code><\/pre>\n<p>We\u2019ll talk about localization in further detail in Chapter 9, Leveraging Globalization and Localization.<br \/>\n\u6211\u4eec\u5c06\u5728\u7b2c 9 \u7ae0 \u5229\u7528\u5168\u7403\u5316\u548c\u672c\u5730\u5316 \u4e2d\u66f4\u8be6\u7ec6\u5730\u8ba8\u8bba\u672c\u5730\u5316\u3002<\/p>\n<p>This is just a quick example of how to define validation rules with FluentValidation and use them with minimal APIs. This library allows many more complex scenarios that are comprehensively described in the official documentation available at <a href=\"https:\/\/fluentvalidation.net\">https:\/\/fluentvalidation.net<\/a>.<br \/>\n\u8fd9\u53ea\u662f\u4e00\u4e2a\u5feb\u901f\u793a\u4f8b\uff0c\u8bf4\u660e\u5982\u4f55\u4f7f\u7528 FluentValidation \u5b9a\u4e49\u9a8c\u8bc1\u89c4\u5219\u5e76\u5c06\u5176\u4e0e\u6700\u5c11\u7684 API \u4e00\u8d77\u4f7f\u7528\u3002\u6b64\u5e93\u5141\u8bb8\u8bb8\u591a\u66f4\u590d\u6742\u7684\u573a\u666f\uff0c\u8fd9\u4e9b\u573a\u666f\u5728 <a href=\"https:\/\/fluentvalidation.net\">https:\/\/fluentvalidation.net<\/a> \u4e0a\u63d0\u4f9b\u7684\u5b98\u65b9\u6587\u6863\u4e2d\u8fdb\u884c\u4e86\u5168\u9762\u63cf\u8ff0\u3002<\/p>\n<p>Now that we have seen how to add validation to our route handlers, it is important to understand how we can update the documentation created by Swagger with this information.<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u4e86\u89e3\u4e86\u5982\u4f55\u5c06\u9a8c\u8bc1\u6dfb\u52a0\u5230\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u4e2d\uff0c\u4e86\u89e3\u5982\u4f55\u4f7f\u7528\u6b64\u4fe1\u606f\u66f4\u65b0 Swagger \u521b\u5efa\u7684\u6587\u6863\u975e\u5e38\u91cd\u8981\u3002<\/p>\n<p>Adding validation information to Swagger<br \/>\n\u5411 Swagger \u6dfb\u52a0\u9a8c\u8bc1\u4fe1\u606f<\/p>\n<p>Regardless of the solution that has been chosen to handle validation, it is important to update the OpenAPI definition with the indication that a handler can produce a validation problem response, calling the ProducesValidationProblem method after the endpoint declaration:<br \/>\n\u65e0\u8bba\u9009\u62e9\u54ea\u79cd\u89e3\u51b3\u65b9\u6848\u6765\u5904\u7406\u9a8c\u8bc1\uff0c\u90fd\u5fc5\u987b\u66f4\u65b0 OpenAPI \u5b9a\u4e49\uff0c\u5e76\u6307\u793a\u5904\u7406\u7a0b\u5e8f\u53ef\u4ee5\u751f\u6210\u9a8c\u8bc1\u95ee\u9898\u54cd\u5e94\uff0c\u5e76\u5728\u7aef\u70b9\u58f0\u660e\u540e\u8c03\u7528 ProducesValidationProblem \u65b9\u6cd5\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/people&quot;, (Person person) =&gt;\n{\n    \/\/...\n})\n.Produces(StatusCodes.Status204NoContent)\n.ProducesValidationProblem();<\/code><\/pre>\n<p>In this way, a new response type for the 400 Bad Request status code will be added to Swagger, as we can see in Figure 6.1:<br \/>\n\u8fd9\u6837\uff0c400 Bad Request \u72b6\u6001\u7801\u7684\u65b0\u54cd\u5e94\u7c7b\u578b\u5c31\u4f1a\u88ab\u6dfb\u52a0\u5230 Swagger \u4e2d\uff0c\u5982\u56fe 6.1 \u6240\u793a\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0601.jpg\" ><\/p>\n<p>Figure 6.1 \u2013 The validation problem response added to Swagger<br \/>\n\u56fe 6.1 \u2013 \u6dfb\u52a0\u5230 Swagger \u7684\u9a8c\u8bc1\u95ee\u9898\u54cd\u5e94<\/p>\n<p>Moreover, the JSON schemas that are shown at the bottom of the Swagger UI can show the rules of the corresponding models. One of the benefits of defining validation rules using data annotations is that they are automatically reflected in these schemas:<br \/>\n\u6b64\u5916\uff0cSwagger UI \u5e95\u90e8\u663e\u793a\u7684 JSON \u67b6\u6784\u53ef\u4ee5\u663e\u793a\u76f8\u5e94\u6a21\u578b\u7684\u89c4\u5219\u3002\u4f7f\u7528\u6570\u636e\u6ce8\u91ca\u5b9a\u4e49\u9a8c\u8bc1\u89c4\u5219\u7684\u597d\u5904\u4e4b\u4e00\u662f\uff0c\u5b83\u4eec\u4f1a\u81ea\u52a8\u53cd\u6620\u5728\u8fd9\u4e9b\u67b6\u6784\u4e2d\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0602.jpg\" ><\/p>\n<p>Figure 6.2 \u2013 The validation rules for the Person object in Swagger<br \/>\n\u56fe 6.2 \u2013 Swagger \u4e2d Person \u5bf9\u8c61\u7684\u9a8c\u8bc1\u89c4\u5219<\/p>\n<p>Unfortunately, validation rules defined with FluentValidation aren\u2019t automatically shown in the JSON schema of Swagger. We can overcome this limitation by using MicroElements.Swashbuckle.FluentValidation, a small library that, as usual, is available on GitHub (<a href=\"https:\/\/github.com\/micro-elements\/MicroElements.Swashbuckle.FluentValidation\">https:\/\/github.com\/micro-elements\/MicroElements.Swashbuckle.FluentValidation<\/a>) and NuGet (<a href=\"https:\/\/www.nuget.org\/packages\/MicroElements.Swashbuckle.FluentValidation\">https:\/\/www.nuget.org\/packages\/MicroElements.Swashbuckle.FluentValidation<\/a>). After adding it to our project, following the same steps described before for the other NuGet packages we have introduced, we just need to call the AddFluentValidationRulesToSwagger extension method:<br \/>\n\u9057\u61be\u7684\u662f\uff0c\u4f7f\u7528 FluentValidation \u5b9a\u4e49\u7684\u9a8c\u8bc1\u89c4\u5219\u4e0d\u4f1a\u81ea\u52a8\u663e\u793a\u5728 Swagger \u7684 JSON \u67b6\u6784\u4e2d\u3002\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u4f7f\u7528 MicroElements.Swashbuckle.FluentValidation \u6765\u514b\u670d\u8fd9\u4e00\u9650\u5236\uff0c\u8fd9\u662f\u4e00\u4e2a\u5c0f\u578b\u5e93\uff0c\u901a\u5e38\u53ef\u5728 GitHub \uff08<a href=\"https:\/\/github.com\/micro-elements\/MicroElements.Swashbuckle.FluentValidation\">https:\/\/github.com\/micro-elements\/MicroElements.Swashbuckle.FluentValidation<\/a>\uff09 \u548c NuGet \uff08<a href=\"https:\/\/www.nuget.org\/packages\/MicroElements.Swashbuckle.FluentValidation\">https:\/\/www.nuget.org\/packages\/MicroElements.Swashbuckle.FluentValidation<\/a>\uff09 \u4e0a\u4f7f\u7528\u3002\u5c06\u5176\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u9879\u76ee\u540e\uff0c\u6309\u7167\u4e4b\u524d\u9488\u5bf9\u6211\u4eec\u4ecb\u7ecd\u7684\u5176\u4ed6 NuGet \u5305\u7684\u76f8\u540c\u6b65\u9aa4\uff0c\u6211\u4eec\u53ea\u9700\u8c03\u7528 AddFluentValidationRulesToSwagger \u6269\u5c55\u65b9\u6cd5\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/...\nbuilder.Services.AddFluentValidationRulesToSwagger();<\/code><\/pre>\n<p>In this way, the JSON schema shown in Swagger will reflect the validation rules, as with the data annotations. However, it\u2019s worth remembering that, at the time of writing, this library does not support all the validators available in FluentValidation. For more information, we can refer to the GitHub page of the library.<br \/>\n\u8fd9\u6837\uff0cSwagger \u4e2d\u663e\u793a\u7684 JSON \u67b6\u6784\u5c06\u53cd\u6620\u9a8c\u8bc1\u89c4\u5219\uff0c\u5c31\u50cf\u6570\u636e\u6ce8\u91ca\u4e00\u6837\u3002\u4f46\u662f\uff0c\u503c\u5f97\u8bb0\u4f4f\u7684\u662f\uff0c\u5728\u64b0\u5199\u672c\u6587\u65f6\uff0c\u6b64\u5e93\u5e76\u4e0d\u652f\u6301 FluentValidation \u4e2d\u53ef\u7528\u7684\u6240\u6709\u9a8c\u8bc1\u5668\u3002\u6709\u5173\u66f4\u591a\u4fe1\u606f\uff0c\u6211\u4eec\u53ef\u4ee5\u53c2\u8003\u8be5\u5e93\u7684 GitHub \u9875\u9762\u3002<\/p>\n<p>This ends our overview of validation in minimal APIs. In the next section, we\u2019ll analyze another important theme of every API: how to correctly handle the mapping of data to and from our services.<br \/>\n\u6211\u4eec\u5bf9\u6700\u5c0f API \u4e2d\u7684\u9a8c\u8bc1\u7684\u6982\u8ff0\u5230\u6b64\u7ed3\u675f\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u5206\u6790\u6bcf\u4e2a API \u7684\u53e6\u4e00\u4e2a\u91cd\u8981\u4e3b\u9898\uff1a\u5982\u4f55\u6b63\u786e\u5904\u7406\u8fdb\u51fa\u6211\u4eec\u670d\u52a1\u7684\u6570\u636e\u3002<\/p>\n<p>Mapping data to and from APIs<br \/>\n\u5c06\u6570\u636e\u6620\u5c04\u5230 API \u6216\u4ece API \u6620\u5c04\u6570\u636e<\/p>\n<p>When dealing with APIs that can be called by any system, there is one golden rule: we should never expose our internal objects to the callers. If we don\u2019t follow this decoupling idea and, for some reason, need to change our internal data structures, we could end up breaking all the clients that interact with us. Both the internal data structures and the objects that are used to dialog with the clients must be able to evolve independently from one another.<br \/>\n\u5728\u5904\u7406\u4efb\u4f55\u7cfb\u7edf\u90fd\u53ef\u4ee5\u8c03\u7528\u7684 API \u65f6\uff0c\u6709\u4e00\u6761\u9ec4\u91d1\u6cd5\u5219\uff1a\u6211\u4eec\u6c38\u8fdc\u4e0d\u5e94\u8be5\u5c06\u6211\u4eec\u7684\u5185\u90e8\u5bf9\u8c61\u66b4\u9732\u7ed9\u8c03\u7528\u8005\u3002\u5982\u679c\u6211\u4eec\u4e0d\u9075\u5faa\u8fd9\u79cd\u89e3\u8026\u7684\u60f3\u6cd5\uff0c\u5e76\u4e14\u51fa\u4e8e\u67d0\u79cd\u539f\u56e0\u9700\u8981\u6539\u53d8\u6211\u4eec\u7684\u5185\u90e8\u6570\u636e\u7ed3\u6784\uff0c\u6211\u4eec\u6700\u7ec8\u53ef\u80fd\u4f1a\u7834\u574f\u6240\u6709\u4e0e\u6211\u4eec\u4ea4\u4e92\u7684\u5ba2\u6237\u7aef\u3002\u5185\u90e8\u6570\u636e\u7ed3\u6784\u548c\u7528\u4e8e\u4e0e Client \u7aef\u5bf9\u8bdd\u7684\u5bf9\u8c61\u90fd\u5fc5\u987b\u80fd\u591f\u5f7c\u6b64\u72ec\u7acb\u5730\u53d1\u5c55\u3002<\/p>\n<p>This requirement for dialog is the reason why mapping is so important. We need to transform input objects of one type into output objects of a different type and vice versa. In this way, we can achieve two objectives:<br \/>\n\u8fd9\u79cd\u5bf9\u5bf9\u8bdd\u7684\u8981\u6c42\u662f\u6620\u5c04\u5982\u6b64\u91cd\u8981\u7684\u539f\u56e0\u3002\u6211\u4eec\u9700\u8981\u5c06\u4e00\u79cd\u7c7b\u578b\u7684\u8f93\u5165\u5bf9\u8c61\u8f6c\u6362\u4e3a\u4e0d\u540c\u7c7b\u578b\u7684\u8f93\u51fa\u5bf9\u8c61\uff0c\u53cd\u4e4b\u4ea6\u7136\u3002\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u6211\u4eec\u53ef\u4ee5\u5b9e\u73b0\u4e24\u4e2a\u76ee\u6807\uff1a<\/p>\n<p>\u2022  Evolve our internal data structures without introducing breaking changes with the contracts that are exposed to the callers<br \/>\n\u6539\u8fdb\u6211\u4eec\u7684\u5185\u90e8\u6570\u636e\u7ed3\u6784\uff0c\u800c\u4e0d\u4f1a\u5bf9\u66b4\u9732\u7ed9\u8c03\u7528\u65b9\u7684\u5408\u7ea6\u5f15\u5165\u4e2d\u65ad\u6027\u53d8\u66f4<\/p>\n<p>\u2022  Modify the format of the objects used to communicate with the clients without the need to change the way these objects are handled internally<br \/>\n\u4fee\u6539\u7528\u4e8e\u4e0e Client \u7aef\u901a\u4fe1\u7684\u5bf9\u8c61\u7684\u683c\u5f0f\uff0c\u800c\u65e0\u9700\u66f4\u6539\u5185\u90e8\u5904\u7406\u8fd9\u4e9b\u5bf9\u8c61\u7684\u65b9\u5f0f<\/p>\n<p>In other words, mapping means transforming one object into another, literally, by copying and converting an object\u2019s properties from a source to a destination. However, mapping code is boring, and testing mapping code is even more boring. Nevertheless, we need to fully understand that the process is crucial and strive to adopt it in all scenarios.<br \/>\n\u6362\u53e5\u8bdd\u8bf4\uff0c\u6620\u5c04\u610f\u5473\u7740\u901a\u8fc7\u5c06\u5bf9\u8c61\u7684\u5c5e\u6027\u4ece\u6e90\u590d\u5236\u5e76\u8f6c\u6362\u4e3a\u76ee\u6807\uff0c\u5c06\u4e00\u4e2a\u5bf9\u8c61\u8f6c\u6362\u4e3a\u53e6\u4e00\u4e2a\u5bf9\u8c61\u3002\u4f46\u662f\uff0c\u6620\u5c04\u4ee3\u7801\u5f88\u65e0\u804a\uff0c\u6d4b\u8bd5\u6620\u5c04\u4ee3\u7801\u66f4\u65e0\u804a\u3002\u5c3d\u7ba1\u5982\u6b64\uff0c\u6211\u4eec\u9700\u8981\u5145\u5206\u7406\u89e3\u8fd9\u4e2a\u8fc7\u7a0b\u662f\u81f3\u5173\u91cd\u8981\u7684\uff0c\u5e76\u52aa\u529b\u5728\u6240\u6709\u60c5\u51b5\u4e0b\u91c7\u7528\u5b83\u3002<\/p>\n<p>So, let\u2019s consider the following object, which could represent a person saved in a database using Entity Framework Core:<br \/>\n\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u8003\u8651\u4ee5\u4e0b\u5bf9\u8c61\uff0c\u5b83\u53ef\u4ee5\u8868\u793a\u4f7f\u7528 Entity Framework Core \u4fdd\u5b58\u5728\u6570\u636e\u5e93\u4e2d\u7684\u4eba\u5458\uff1a<\/p>\n<pre><code>public class PersonEntity\n{\n    public int Id { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public DateTime BirthDate { get; set; }\n    public string City { get; set; }\n}<\/code><\/pre>\n<p>We have set endpoints for getting a list of people or retrieving a specific person.<br \/>\n\u6211\u4eec\u8bbe\u7f6e\u4e86\u7528\u4e8e\u83b7\u53d6\u4eba\u5458\u5217\u8868\u6216\u68c0\u7d22\u7279\u5b9a\u4eba\u5458\u7684\u7aef\u70b9\u3002<\/p>\n<p>The first thought could be to directly return PersonEntity to the caller. The following code is highly simplified, enough for us to understand the scenario:<br \/>\n\u7b2c\u4e00\u4e2a\u60f3\u6cd5\u53ef\u80fd\u662f\u76f4\u63a5\u5c06 PersonEntity \u8fd4\u56de\u7ed9\u8c03\u7528\u65b9\u3002\u4ee5\u4e0b\u4ee3\u7801\u7ecf\u8fc7\u9ad8\u5ea6\u7b80\u5316\uff0c\u8db3\u4ee5\u8ba9\u6211\u4eec\u7406\u89e3\u8be5\u573a\u666f\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/people\/{id:int}&quot;, (int id) =&gt;\n{\n    \/\/ In a real application, this entity could be\n    \/\/ retrieved from a database, checking if the person\n    \/\/ with the given ID exists.\n    var person = new PersonEntity();\n    return Results.Ok(person);\n})\n.Produces(StatusCodes.Status200OK, typeof(PersonEntity));<\/code><\/pre>\n<p>What happens if we need to modify the schema of the database, adding, for example, the creation date of the entity? In this case, we need to change PersonEntity with a new property that maps the relevant date. However, the callers also get this information now, which we probably don\u2019t want to be exposed. Instead, if we use a so-called data transformation object (DTO) to expose the person, this problem will be redundant:<br \/>\n\u5982\u679c\u6211\u4eec\u9700\u8981\u4fee\u6539\u6570\u636e\u5e93\u7684\u67b6\u6784\uff0c\u4f8b\u5982\u6dfb\u52a0\u5b9e\u4f53\u7684\u521b\u5efa\u65e5\u671f\uff0c\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\uff1f\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u9700\u8981\u4f7f\u7528\u6620\u5c04\u76f8\u5173\u65e5\u671f\u7684\u65b0\u5c5e\u6027\u66f4\u6539 PersonEntity\u3002\u4f46\u662f\uff0c\u8c03\u7528\u65b9\u73b0\u5728\u4e5f\u4f1a\u83b7\u5f97\u6b64\u4fe1\u606f\uff0c\u6211\u4eec\u53ef\u80fd\u4e0d\u5e0c\u671b\u8fd9\u4e9b\u4fe1\u606f\u88ab\u516c\u5f00\u3002\u76f8\u53cd\uff0c\u5982\u679c\u6211\u4eec\u4f7f\u7528\u6240\u8c13\u7684\u6570\u636e\u8f6c\u6362\u5bf9\u8c61 \uff08DTO\uff09 \u6765\u516c\u5f00\u4eba\u5458\uff0c\u5219\u6b64\u95ee\u9898\u5c06\u662f\u591a\u4f59\u7684\uff1a<\/p>\n<pre><code>public class PersonDto\n{\n    public int Id { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public DateTime BirthDate { get; set; }\n    public string City { get; set; }\n}<\/code><\/pre>\n<p>This means that our API should return an object of the PersonDto type instead of PersonEntity, performing a conversion between the two objects. At first sight, the exercise appears to be a useless duplication of code, as the two classes contain the same properties. However, if we consider the fact that PersonEntity could evolve with new properties that are necessary for the database, or change structure with a new semantic that the caller shouldn\u2019t know, the importance of mapping becomes clear. An example is storing the city in a separate table and exposing it through an Address property. Or suppose that, for security reasons, we don\u2019t want to expose the exact birth date anymore, only the age of the person. Using an ad-hoc DTO, we can easily change the schema and update the mapping without touching our entity, having a better separation of concerns.<br \/>\n\u8fd9\u610f\u5473\u7740\u6211\u4eec\u7684 API \u5e94\u8fd4\u56de PersonDto \u7c7b\u578b\u7684\u5bf9\u8c61\uff0c\u800c\u4e0d\u662f PersonEntity\uff0c\u4ece\u800c\u5728\u4e24\u4e2a\u5bf9\u8c61\u4e4b\u95f4\u6267\u884c\u8f6c\u6362\u3002\u4e4d\u4e00\u770b\uff0c\u8be5\u7ec3\u4e60\u4f3c\u4e4e\u662f\u65e0\u7528\u7684\u4ee3\u7801\u91cd\u590d\uff0c\u56e0\u4e3a\u8fd9\u4e24\u4e2a\u7c7b\u5305\u542b\u76f8\u540c\u7684\u5c5e\u6027\u3002\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u8003\u8651\u5230 PersonEntity \u53ef\u80fd\u4f1a\u4f7f\u7528\u6570\u636e\u5e93\u6240\u9700\u7684\u65b0\u5c5e\u6027\u8fdb\u884c\u6f14\u53d8\uff0c\u6216\u8005\u4f7f\u7528\u8c03\u7528\u65b9\u4e0d\u5e94\u77e5\u9053\u7684\u65b0\u8bed\u4e49\u66f4\u6539\u7ed3\u6784\uff0c\u5219\u6620\u5c04\u7684\u91cd\u8981\u6027\u5c31\u4f1a\u53d8\u5f97\u663e\u800c\u6613\u89c1\u3002\u4f8b\u5982\uff0c\u5c06\u57ce\u5e02\u5b58\u50a8\u5728\u5355\u72ec\u7684\u8868\u4e2d\uff0c\u5e76\u901a\u8fc7 Address \u5c5e\u6027\u516c\u5f00\u5b83\u3002\u6216\u8005\u5047\u8bbe\uff0c\u51fa\u4e8e\u5b89\u5168\u539f\u56e0\uff0c\u6211\u4eec\u4e0d\u60f3\u518d\u516c\u5f00\u786e\u5207\u7684\u51fa\u751f\u65e5\u671f\uff0c\u800c\u53ea\u60f3\u516c\u5f00\u4eba\u7684\u5e74\u9f84\u3002\u4f7f\u7528\u4e34\u65f6 DTO\uff0c\u6211\u4eec\u53ef\u4ee5\u8f7b\u677e\u66f4\u6539\u67b6\u6784\u5e76\u66f4\u65b0\u6620\u5c04\uff0c\u800c\u65e0\u9700\u63a5\u89e6\u6211\u4eec\u7684\u5b9e\u4f53\uff0c\u4ece\u800c\u66f4\u597d\u5730\u5206\u79bb\u5173\u6ce8\u70b9\u3002<\/p>\n<p>Of course, mapping can be bidirectional. In our example, we need to convert PersonEntity to PersonDto before returning it to the client. However, we could also do the opposite \u2013 that is, convert the PersonDto type that comes from a client into PersonEntity to save it to a database. All the solutions we\u2019re talking about are valid for both scenarios.<br \/>\n\u5f53\u7136\uff0c\u6620\u5c04\u53ef\u4ee5\u662f\u53cc\u5411\u7684\u3002\u5728\u6211\u4eec\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u9700\u8981\u5148\u5c06 PersonEntity \u8f6c\u6362\u4e3a PersonDto\uff0c\u7136\u540e\u518d\u5c06\u5176\u8fd4\u56de\u7ed9\u5ba2\u6237\u7aef\u3002\u4f46\u662f\uff0c\u6211\u4eec\u4e5f\u53ef\u4ee5\u6267\u884c\u76f8\u53cd\u7684\u4f5c\uff0c\u5373\uff0c\u5c06\u6765\u81ea\u5ba2\u6237\u7aef\u7684 PersonDto \u7c7b\u578b\u8f6c\u6362\u4e3a PersonEntity \u4ee5\u5c06\u5176\u4fdd\u5b58\u5230\u6570\u636e\u5e93\u3002\u6211\u4eec\u8ba8\u8bba\u7684\u6240\u6709\u89e3\u51b3\u65b9\u6848\u90fd\u9002\u7528\u4e8e\u8fd9\u4e24\u79cd\u60c5\u51b5\u3002<\/p>\n<p>We can either perform mapping manually or adopt a third-party library that provides us with this feature. In the following sections, we\u2019ll analyze both approaches, understanding the pros and cons of the available solutions.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u624b\u52a8\u6267\u884c\u6620\u5c04\uff0c\u4e5f\u53ef\u4ee5\u91c7\u7528\u4e3a\u6211\u4eec\u63d0\u4f9b\u6b64\u529f\u80fd\u7684\u7b2c\u4e09\u65b9\u5e93\u3002\u5728\u4ee5\u4e0b\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06\u5206\u6790\u8fd9\u4e24\u79cd\u65b9\u6cd5\uff0c\u4e86\u89e3\u53ef\u7528\u89e3\u51b3\u65b9\u6848\u7684\u4f18\u7f3a\u70b9\u3002<\/p>\n<p>Performing manual mapping<br \/>\n\u6267\u884c\u624b\u52a8\u6620\u5c04<\/p>\n<p>In the previous section, we said that mapping essentially means copying the properties of a source object into the properties of a destination and applying some sort of conversion. The easiest and most effective way to perform this task is to do it manually.<br \/>\n\u5728\u4e0a\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u8bf4\u8fc7\u6620\u5c04\u5b9e\u8d28\u4e0a\u610f\u5473\u7740\u5c06\u6e90\u5bf9\u8c61\u7684\u5c5e\u6027\u590d\u5236\u5230\u76ee\u6807\u7684\u5c5e\u6027\u4e2d\uff0c\u5e76\u5e94\u7528\u67d0\u79cd\u8f6c\u6362\u3002\u6267\u884c\u6b64\u4efb\u52a1\u7684\u6700\u7b80\u5355\u3001\u6700\u6709\u6548\u7684\u65b9\u6cd5\u662f\u624b\u52a8\u6267\u884c\u3002<\/p>\n<p>With this approach, we need to take care of all the mapping code by ourselves. From this point of view, there is nothing much more to say; we need a method that takes an object as input and transforms it into another as output, remembering to apply mapping recursively if a class contains a complex property that must be mapped in turn. The only suggestion is to use an extension method so that we can easily call it everywhere we need.<br \/>\n\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff0c\u6211\u4eec\u9700\u8981\u81ea\u5df1\u5904\u7406\u6240\u6709\u7684 map \u4ee3\u7801\u3002\u4ece\u8fd9\u4e2a\u89d2\u5ea6\u6765\u770b\uff0c\u6ca1\u6709\u4ec0\u4e48\u53ef\u8bf4\u7684\u4e86;\u6211\u4eec\u9700\u8981\u4e00\u4e2a\u65b9\u6cd5\uff0c\u5c06\u4e00\u4e2a\u5bf9\u8c61\u4f5c\u4e3a\u8f93\u5165\u5e76\u5c06\u5176\u8f6c\u6362\u4e3a\u53e6\u4e00\u4e2a\u4f5c\u4e3a\u8f93\u51fa\uff0c\u5982\u679c\u4e00\u4e2a\u7c7b\u5305\u542b\u5fc5\u987b\u4f9d\u6b21\u6620\u5c04\u7684\u590d\u6742\u5c5e\u6027\uff0c\u8bf7\u8bb0\u4f4f\u9012\u5f52\u5730\u5e94\u7528\u6620\u5c04\u3002\u552f\u4e00\u7684\u5efa\u8bae\u662f\u4f7f\u7528\u6269\u5c55\u65b9\u6cd5\uff0c\u8fd9\u6837\u6211\u4eec\u5c31\u53ef\u4ee5\u8f7b\u677e\u5730\u5728\u9700\u8981\u7684\u4efb\u4f55\u5730\u65b9\u8c03\u7528\u5b83\u3002<\/p>\n<p>A full example of this mapping process is available in the GitHub repository: <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter06\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter06<\/a>.<br \/>\nGitHub \u5b58\u50a8\u5e93\u4e2d\u63d0\u4f9b\u4e86\u6b64\u6620\u5c04\u8fc7\u7a0b\u7684\u5b8c\u6574\u793a\u4f8b\uff1a<a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter06\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter06<\/a>\u3002<\/p>\n<p>This solution guarantees the best performance because we explicitly write all mapping instructions without relying on an automatic system (such as reflection). However, the manual method has a drawback: every time we add a property in the entity that must be mapped to a DTO, we need to change the mapping code. On the other hand, some approaches can simplify mapping, but at the cost of performance overhead. In the next section, we look at one such approach using AutoMapper.<br \/>\n\u6b64\u89e3\u51b3\u65b9\u6848\u4fdd\u8bc1\u4e86\u6700\u4f73\u6027\u80fd\uff0c\u56e0\u4e3a\u6211\u4eec\u663e\u5f0f\u7f16\u5199\u4e86\u6240\u6709\u6620\u5c04\u6307\u4ee4\uff0c\u800c\u65e0\u9700\u4f9d\u8d56\u81ea\u52a8\u7cfb\u7edf\uff08\u4f8b\u5982\u53cd\u5c04\uff09\u3002\u4f46\u662f\uff0c\u624b\u52a8\u65b9\u6cd5\u6709\u4e00\u4e2a\u7f3a\u70b9\uff1a\u6bcf\u6b21\u6211\u4eec\u5728\u5b9e\u4f53\u4e2d\u6dfb\u52a0\u5fc5\u987b\u6620\u5c04\u5230 DTO \u7684\u5c5e\u6027\u65f6\uff0c\u90fd\u9700\u8981\u66f4\u6539\u6620\u5c04\u4ee3\u7801\u3002\u53e6\u4e00\u65b9\u9762\uff0c\u67d0\u4e9b\u65b9\u6cd5\u53ef\u4ee5\u7b80\u5316\u6620\u5c04\uff0c\u4f46\u4f1a\u4ee5\u6027\u80fd\u5f00\u9500\u4e3a\u4ee3\u4ef7\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4e00\u79cd\u4f7f\u7528 AutoMapper \u7684\u6b64\u7c7b\u65b9\u6cd5\u3002<\/p>\n<p>Mapping with AutoMapper<br \/>\n\u4f7f\u7528 AutoMapper \u8fdb\u884c\u6620\u5c04<\/p>\n<p>AutoMapper is probably one the most famous mapping framework for .NET. It uses a fluent configuration API that works with a convention-based matching algorithm to match source values to destination values. As with FluentValidation, the framework is part of the .NET Foundation and is available either on GitHub (<a href=\"https:\/\/github.com\/AutoMapper\/AutoMapper\">https:\/\/github.com\/AutoMapper\/AutoMapper<\/a>) or NuGet (<a href=\"https:\/\/www.nuget.org\/packages\/AutoMapper\">https:\/\/www.nuget.org\/packages\/AutoMapper<\/a>). Again, in this case, we have a specific NuGet package, <a href=\"https:\/\/www.nuget.org\/packages\/AutoMapper.Extensions.Microsoft.DependencyInjection\">https:\/\/www.nuget.org\/packages\/AutoMapper.Extensions.Microsoft.DependencyInjection<\/a>, that simplifies its integration into ASP.NET Core projects.<br \/>\nAutoMapper \u53ef\u80fd\u662f\u6700\u8457\u540d\u7684 .NET \u6620\u5c04\u6846\u67b6\u4e4b\u4e00\u3002\u5b83\u4f7f\u7528 Fluent \u914d\u7f6e API\uff0c\u8be5 API \u4e0e\u57fa\u4e8e\u7ea6\u5b9a\u7684\u5339\u914d\u7b97\u6cd5\u914d\u5408\u4f7f\u7528\uff0c\u4ee5\u5c06\u6e90\u503c\u4e0e\u76ee\u6807\u503c\u5339\u914d\u3002\u4e0e FluentValidation \u4e00\u6837\uff0c\u8be5\u6846\u67b6\u662f .NET Foundation \u7684\u4e00\u90e8\u5206\uff0c\u53ef\u5728 GitHub \uff08<a href=\"https:\/\/github.com\/AutoMapper\/AutoMapper\">https:\/\/github.com\/AutoMapper\/AutoMapper<\/a>\uff09 \u6216 NuGet \uff08<a href=\"https:\/\/www.nuget.org\/packages\/AutoMapper\">https:\/\/www.nuget.org\/packages\/AutoMapper<\/a>\uff09 \u4e0a\u4f7f\u7528\u3002\u540c\u6837\uff0c\u5728\u672c\u4f8b\u4e2d\uff0c\u6211\u4eec\u6709\u4e00\u4e2a\u7279\u5b9a\u7684 NuGet \u5305 <a href=\"https:\/\/www.nuget.org\/packages\/AutoMapper.Extensions.Microsoft.DependencyInjection\uff0c\u5b83\u7b80\u5316\u4e86\u5b83\u4e0e\">https:\/\/www.nuget.org\/packages\/AutoMapper.Extensions.Microsoft.DependencyInjection\uff0c\u5b83\u7b80\u5316\u4e86\u5b83\u4e0e<\/a> ASP.NET Core \u9879\u76ee\u7684\u96c6\u6210\u3002<\/p>\n<p>Let\u2019s take a quick look at how to integrate AutoMapper in a minimal API project, showing its main features. The full documentation of the library is available at <a href=\"https:\/\/docs.automapper.org\">https:\/\/docs.automapper.org<\/a>.<br \/>\n\u8ba9\u6211\u4eec\u5feb\u901f\u770b\u4e00\u4e0b\u5982\u4f55\u5c06 AutoMapper \u96c6\u6210\u5230\u4e00\u4e2a\u6700\u5c0f\u7684 API \u9879\u76ee\u4e2d\uff0c\u5c55\u793a\u5b83\u7684\u4e3b\u8981\u529f\u80fd\u3002\u8be5\u5e93\u7684\u5b8c\u6574\u6587\u6863\u53ef\u5728 <a href=\"https:\/\/docs.automapper.org\">https:\/\/docs.automapper.org<\/a> \u4e0a\u83b7\u5f97\u3002<\/p>\n<p>As usual, the first thing to do is to add the library to our project, following the same instructions we used in the previous sections. Then, we need to configure AutoMapper, telling it how to perform mapping. There are several ways to perform this task, but the recommended approach is to create classes that are inherited from the Profile base class provided by the library and put the configuration into the constructor:<br \/>\n\u50cf\u5f80\u5e38\u4e00\u6837\uff0c\u9996\u5148\u8981\u505a\u7684\u662f\u6309\u7167\u6211\u4eec\u5728\u524d\u9762\u90e8\u5206\u4e2d\u4f7f\u7528\u7684\u76f8\u540c\u8bf4\u660e\u5c06\u5e93\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u9879\u76ee\u4e2d\u3002\u7136\u540e\uff0c\u6211\u4eec\u9700\u8981\u914d\u7f6e AutoMapper\uff0c\u544a\u8bc9\u5b83\u5982\u4f55\u6267\u884c\u6620\u5c04\u3002\u6709\u591a\u79cd\u65b9\u6cd5\u53ef\u4ee5\u6267\u884c\u6b64\u4efb\u52a1\uff0c\u4f46\u63a8\u8350\u7684\u65b9\u6cd5\u662f\u521b\u5efa\u4ece\u5e93\u63d0\u4f9b\u7684 Profile \u57fa\u7c7b\u7ee7\u627f\u7684\u7c7b\uff0c\u5e76\u5c06\u914d\u7f6e\u653e\u5165\u6784\u9020\u51fd\u6570\u4e2d\uff1a<\/p>\n<pre><code>public class PersonProfile : Profile\n{\n    public PersonProfile()\n    {\n        CreateMap&lt;PersonEntity, PersonDto&gt;();\n    }\n}<\/code><\/pre>\n<p>That\u2019s all we need to start: a single instruction to indicate that we want to map PersonEntity to PersonDto, without any other details. We have said that AutoMapper is convention-based. This means that, by default, it maps properties with the same name from the source to the destination, while also performing automatic conversions into compatible types, if necessary. For example, an int property on the source can be automatically mapped to a double property with the same name on the destination. In other words, if source and destination objects have the same property, there is no need for any explicit mapping instruction. However, in our case, we need to perform some transformations, so we can add them fluently after CreateMap:<br \/>\n\u8fd9\u5c31\u662f\u6211\u4eec\u9700\u8981\u5f00\u59cb\u7684\u5168\u90e8\u5185\u5bb9\uff1a\u4e00\u6761\u6307\u4ee4\uff0c\u6307\u793a\u6211\u4eec\u60f3\u8981\u5c06 PersonEntity \u6620\u5c04\u5230 PersonDto\uff0c\u6ca1\u6709\u4efb\u4f55\u5176\u4ed6\u7ec6\u8282\u3002\u6211\u4eec\u5df2\u7ecf\u8bf4\u8fc7 AutoMapper \u662f\u57fa\u4e8e\u7ea6\u5b9a\u7684\u3002\u8fd9\u610f\u5473\u7740\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5b83\u5c06\u5177\u6709\u76f8\u540c\u540d\u79f0\u7684\u5c5e\u6027\u4ece\u6e90\u6620\u5c04\u5230\u76ee\u6807\uff0c\u540c\u65f6\u8fd8\u4f1a\u6839\u636e\u9700\u8981\u6267\u884c\u81ea\u52a8\u8f6c\u6362\u4e3a\u517c\u5bb9\u7c7b\u578b\u3002\u4f8b\u5982\uff0c\u6e90\u4e0a\u7684 int \u5c5e\u6027\u53ef\u4ee5\u81ea\u52a8\u6620\u5c04\u5230\u76ee\u6807\u4e0a\u5177\u6709\u76f8\u540c\u540d\u79f0\u7684 double \u5c5e\u6027\u3002\u6362\u53e5\u8bdd\u8bf4\uff0c\u5982\u679c\u6e90\u5bf9\u8c61\u548c\u76ee\u6807\u5bf9\u8c61\u5177\u6709\u76f8\u540c\u7684\u5c5e\u6027\uff0c\u5219\u4e0d\u9700\u8981\u4efb\u4f55\u663e\u5f0f\u6620\u5c04\u6307\u4ee4\u3002\u4f46\u662f\uff0c\u5728\u6211\u4eec\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u9700\u8981\u6267\u884c\u4e00\u4e9b\u8f6c\u6362\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ef\u4ee5\u5728 CreateMap \u4e4b\u540e\u6d41\u7545\u5730\u6dfb\u52a0\u5b83\u4eec\uff1a<\/p>\n<pre><code>public class PersonProfile : Profile\n{\n    public PersonProfile()\n    {\n        CreateMap&lt;PersonEntity, PersonDto&gt;()\n            .ForMember(dst =&gt; dst.Age, opt =&gt;\n           opt.MapFrom(src =&gt; CalculateAge(src.BirthDate)))\n            .ForMember(dst =&gt; dst.City, opt =&gt; \n              opt.MapFrom(src =&gt; src.Address.City));\n    }\n    private static int CalculateAge(DateTime dateOfBirth)\n    {\n        var today = DateTime.Today;\n        var age = today.Year - dateOfBirth.Year;\n        if (today.DayOfYear &lt; dateOfBirth.DayOfYear)\n        {\n            age--;\n        }\n        return age;\n    }\n}<\/code><\/pre>\n<p>With the ForMember method, we can specify how to map destination properties, dst.Age and dst.City, using conversion expressions. We still don\u2019t need to explicitly map the Id, FirstName, or LastName properties because they exist with these names at both the source and destination.<br \/>\n\u4f7f\u7528 ForMember \u65b9\u6cd5\uff0c\u6211\u4eec\u53ef\u4ee5\u6307\u5b9a\u5982\u4f55\u6620\u5c04\u76ee\u6807\u5c5e\u6027 dst\u3002\u5e74\u9f84\u548c dst\u3002City\uff0c\u4f7f\u7528\u8f6c\u6362\u8868\u8fbe\u5f0f\u3002\u6211\u4eec\u4ecd\u7136\u4e0d\u9700\u8981\u663e\u5f0f\u6620\u5c04 Id\u3001FirstName \u6216 LastName \u5c5e\u6027\uff0c\u56e0\u4e3a\u5b83\u4eec\u4e0e\u8fd9\u4e9b\u540d\u79f0\u4e00\u8d77\u5b58\u5728\u4e8e\u6e90\u548c\u76ee\u6807\u4e2d\u3002<\/p>\n<p>Now that we have defined the mapping profile, we need to register it at startup so that ASP.NET Core can use it. As with FluentValidation, we can invoke an extension method on IServiceCollection:<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u5b9a\u4e49\u4e86\u6620\u5c04\u914d\u7f6e\u6587\u4ef6\uff0c\u6211\u4eec\u9700\u8981\u5728\u542f\u52a8\u65f6\u6ce8\u518c\u5b83\uff0c\u4ee5\u4fbf ASP.NET Core \u53ef\u4ee5\u4f7f\u7528\u5b83\u3002\u4e0e FluentValidation \u4e00\u6837\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 IServiceCollection \u4e0a\u8c03\u7528\u6269\u5c55\u65b9\u6cd5\uff1a<\/p>\n<pre><code>builder.Services.AddAutoMapper(typeof(Program).Assembly);<\/code><\/pre>\n<p>With this line of code, we automatically register all the profiles that are contained in the specified assembly. If we add more profiles to our project, such as a separate Profile class for every entity to map, we don\u2019t need to change the registration instructions.<br \/>\n\u4f7f\u7528\u8fd9\u884c\u4ee3\u7801\uff0c\u6211\u4eec\u4f1a\u81ea\u52a8\u6ce8\u518c\u6307\u5b9a\u7a0b\u5e8f\u96c6\u4e2d\u5305\u542b\u7684\u6240\u6709\u914d\u7f6e\u6587\u4ef6\u3002\u5982\u679c\u6211\u4eec\u5411\u9879\u76ee\u6dfb\u52a0\u66f4\u591a\u914d\u7f6e\u6587\u4ef6\uff0c\u4f8b\u5982\u8981\u6620\u5c04\u7684\u6bcf\u4e2a\u5b9e\u4f53\u7684\u5355\u72ec Profile \u7c7b\uff0c\u5219\u65e0\u9700\u66f4\u6539\u6ce8\u518c\u8bf4\u660e\u3002<\/p>\n<p>In this way, we can now use the IMapper interface through dependency injection:<br \/>\n\u8fd9\u6837\uff0c\u6211\u4eec\u73b0\u5728\u53ef\u4ee5\u901a\u8fc7\u4f9d\u8d56\u6ce8\u5165\u6765\u4f7f\u7528 IMapper \u63a5\u53e3\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/people\/{id:int}&quot;, (int id, IMapper mapper) =&gt;\n{\n    var personEntity = new PersonEntity();\n    \/\/...\n    var personDto = mapper.Map&lt;PersonDto&gt;(personEntity);\n    return Results.Ok(personDto);\n})\n.Produces(StatusCodes.Status200OK, typeof(PersonDto));<\/code><\/pre>\n<p>After retrieving PersonEntity, for example, from a database using Entity Framework Core, we call the Map method on the IMapper interface, specifying the type of the resulting object and the input class. With this line of code, AutoMapper will use the corresponding profile to convert PersonEntity into a PersonDto instance.<br \/>\n\u4f8b\u5982\uff0c\u5728\u4f7f\u7528 Entity Framework Core \u4ece\u6570\u636e\u5e93\u4e2d\u68c0\u7d22 PersonEntity \u540e\uff0c\u6211\u4eec\u5728 IMapper \u63a5\u53e3\u4e0a\u8c03\u7528 Map \u65b9\u6cd5\uff0c\u5e76\u6307\u5b9a\u7ed3\u679c\u5bf9\u8c61\u7684\u7c7b\u578b\u548c\u8f93\u5165\u7c7b\u3002\u901a\u8fc7\u8fd9\u884c\u4ee3\u7801\uff0cAutoMapper \u5c06\u4f7f\u7528\u76f8\u5e94\u7684\u914d\u7f6e\u6587\u4ef6\u5c06 PersonEntity \u8f6c\u6362\u4e3a PersonDto \u5b9e\u4f8b\u3002<\/p>\n<p>With this solution in place, mapping is now much easier to maintain because, as long as we add properties with the same name on the source and destination, we don\u2019t need to change the profile at all. Moreover, AutoMapper supports list mapping and recursive mapping too. So, if we have an entity that must be mapped, such as a property of the AddressEntity type on the PersonEntity class, and the corresponding profile is available, the conversion is again performed automatically.<br \/>\n\u6709\u4e86\u8fd9\u4e2a\u89e3\u51b3\u65b9\u6848\uff0c\u6620\u5c04\u73b0\u5728\u66f4\u5bb9\u6613\u7ef4\u62a4\uff0c\u56e0\u4e3a\u53ea\u8981\u6211\u4eec\u5728\u6e90\u548c\u76ee\u6807\u4e0a\u6dfb\u52a0\u5177\u6709\u76f8\u540c\u540d\u79f0\u7684\u5c5e\u6027\uff0c\u6211\u4eec\u5c31\u6839\u672c\u4e0d\u9700\u8981\u66f4\u6539\u914d\u7f6e\u6587\u4ef6\u3002\u6b64\u5916\uff0cAutoMapper \u8fd8\u652f\u6301\u5217\u8868\u6620\u5c04\u548c\u9012\u5f52\u6620\u5c04\u3002\u56e0\u6b64\uff0c\u5982\u679c\u6211\u4eec\u6709\u4e00\u4e2a\u5fc5\u987b\u6620\u5c04\u7684\u5b9e\u4f53\uff0c\u4f8b\u5982 PersonEntity \u7c7b\u4e0a AddressEntity \u7c7b\u578b\u7684\u5c5e\u6027\uff0c\u5e76\u4e14\u76f8\u5e94\u7684\u914d\u7f6e\u6587\u4ef6\u53ef\u7528\uff0c\u5219\u8f6c\u6362\u5c06\u518d\u6b21\u81ea\u52a8\u6267\u884c\u3002<\/p>\n<p>The drawback of this approach is a performance overhead. AutoMapper works by dynamically executing mapping code at runtime, so it uses reflection under the hood. Profiles are created the first time they are used and then they are cached to speed up subsequent mappings. However, profiles are always applied dynamically, so there is a cost for the operation that is dependent on the complexity of the mapping code itself. We have only seen a basic example of AutoMapper. The library is very powerful and can manage quite complex mappings. However, we need to be careful not to abuse it \u2013 otherwise, we can negatively impact the performance of our application.<br \/>\n\u8fd9\u79cd\u65b9\u6cd5\u7684\u7f3a\u70b9\u662f\u6027\u80fd\u5f00\u9500\u3002AutoMapper \u7684\u5de5\u4f5c\u539f\u7406\u662f\u5728\u8fd0\u884c\u65f6\u52a8\u6001\u6267\u884c\u6620\u5c04\u4ee3\u7801\uff0c\u56e0\u6b64\u5b83\u5728\u540e\u53f0\u4f7f\u7528\u53cd\u5c04\u3002\u914d\u7f6e\u6587\u4ef6\u5728\u9996\u6b21\u4f7f\u7528\u65f6\u521b\u5efa\uff0c\u7136\u540e\u7f13\u5b58\u4ee5\u52a0\u5feb\u540e\u7eed\u6620\u5c04\u7684\u901f\u5ea6\u3002\u4f46\u662f\uff0c\u914d\u7f6e\u6587\u4ef6\u59cb\u7ec8\u662f\u52a8\u6001\u5e94\u7528\u7684\uff0c\u56e0\u6b64\u4f5c\u7684\u6210\u672c\u53d6\u51b3\u4e8e\u6620\u5c04\u4ee3\u7801\u672c\u8eab\u7684\u590d\u6742\u6027\u3002\u6211\u4eec\u53ea\u770b\u5230\u4e86 AutoMapper \u7684\u4e00\u4e2a\u57fa\u672c\u793a\u4f8b\u3002\u8be5\u5e93\u975e\u5e38\u5f3a\u5927\uff0c\u53ef\u4ee5\u7ba1\u7406\u76f8\u5f53\u590d\u6742\u7684\u6620\u5c04\u3002\u4f46\u662f\uff0c\u6211\u4eec\u9700\u8981\u5c0f\u5fc3\u4e0d\u8981\u6ee5\u7528\u5b83 - \u5426\u5219\uff0c\u6211\u4eec\u53ef\u80fd\u4f1a\u5bf9\u5e94\u7528\u7a0b\u5e8f\u7684\u6027\u80fd\u4ea7\u751f\u8d1f\u9762\u5f71\u54cd\u3002<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>Validation and mapping are two important features that we need to take into account when developing APIs to build more robust and maintainable applications. Minimal APIs do not provide any built-in way to perform these tasks, so it is important to know how we can add support for this kind of feature. We have seen that we can perform validations with data annotations or using FluentValidation and how to add validation information to Swagger. We have also talked about the significance of data mapping and shown how to either leverage manual mapping or the AutoMapper library, describing the pros and cons of each approach.<br \/>\n\u9a8c\u8bc1\u548c\u6620\u5c04\u662f\u6211\u4eec\u5728\u5f00\u53d1 API \u4ee5\u6784\u5efa\u66f4\u5065\u58ee\u4e14\u53ef\u7ef4\u62a4\u7684\u5e94\u7528\u7a0b\u5e8f\u65f6\u9700\u8981\u8003\u8651\u7684\u4e24\u4e2a\u91cd\u8981\u529f\u80fd\u3002Minimal API \u4e0d\u63d0\u4f9b\u4efb\u4f55\u5185\u7f6e\u65b9\u6cd5\u6765\u6267\u884c\u8fd9\u4e9b\u4efb\u52a1\uff0c\u56e0\u6b64\u4e86\u89e3\u5982\u4f55\u6dfb\u52a0\u5bf9\u6b64\u7c7b\u529f\u80fd\u7684\u652f\u6301\u975e\u5e38\u91cd\u8981\u3002\u6211\u4eec\u5df2\u7ecf\u770b\u5230\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u6570\u636e\u6ce8\u91ca\u6216\u4f7f\u7528 FluentValidation \u6267\u884c\u9a8c\u8bc1\uff0c\u4ee5\u53ca\u5982\u4f55\u5411 Swagger \u6dfb\u52a0\u9a8c\u8bc1\u4fe1\u606f\u3002\u6211\u4eec\u8fd8\u8ba8\u8bba\u4e86\u6570\u636e\u6620\u5c04\u7684\u91cd\u8981\u6027\uff0c\u5e76\u5c55\u793a\u4e86\u5982\u4f55\u5229\u7528\u624b\u52a8\u6620\u5c04\u6216 AutoMapper \u5e93\uff0c\u63cf\u8ff0\u4e86\u6bcf\u79cd\u65b9\u6cd5\u7684\u4f18\u7f3a\u70b9\u3002<\/p>\n<p>In the next chapter, we will talk about how to integrate minimal APIs with a data access layer, showing, for example, how to access a database using Entity Framework Core.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u5982\u4f55\u5c06\u6700\u5c0f API \u4e0e\u6570\u636e\u8bbf\u95ee\u5c42\u96c6\u6210\uff0c\u4f8b\u5982\uff0c\u5c55\u793a\u5982\u4f55\u4f7f\u7528 Entity Framework Core \u8bbf\u95ee\u6570\u636e\u5e93\u3002<\/p>\n<h1>7 Integration with the Data Access Layer<\/h1>\n<p>\u4e0e Data Access Layer \u96c6\u6210<\/p>\n<p>In this chapter, we will learn about some basic ways to add a data access layer to the minimal APIs in .NET 6.0. We will see how we can use some topics covered previously in the book to access data with Entity Framework (EF) and then with Dapper. These are two ways to access a database.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5411 .NET 6.0 \u4e2d\u7684\u6700\u5c0f API \u6dfb\u52a0\u6570\u636e\u8bbf\u95ee\u5c42\u7684\u4e00\u4e9b\u57fa\u672c\u65b9\u6cd5\u3002\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u4f7f\u7528\u672c\u4e66\u524d\u9762\u4ecb\u7ecd\u7684\u4e00\u4e9b\u4e3b\u9898\uff0c\u901a\u8fc7 Entity Framework \uff08EF\uff09 \u548c Dapper \u8bbf\u95ee\u6570\u636e\u3002\u8fd9\u662f\u8bbf\u95ee\u6570\u636e\u5e93\u7684\u4e24\u79cd\u65b9\u6cd5\u3002<\/p>\n<p>In this chapter, we will be covering the following topics:<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u4e3b\u9898\uff1a<\/p>\n<p>\u2022  Using Entity Framework<br \/>\n\u2022  Using Dapper<\/p>\n<p>By the end of this chapter, you will be able to use EF from scratch in a minimal API project, and use Dapper for the same goal. You will also be able to tell when one approach is better than the other in a project.<br \/>\n\u5728\u672c\u7ae0\u7ed3\u675f\u65f6\uff0c\u60a8\u5c06\u80fd\u591f\u5728\u6700\u5c0f\u7684 API \u9879\u76ee\u4e2d\u4ece\u5934\u5f00\u59cb\u4f7f\u7528 EF\uff0c\u5e76\u5c06 Dapper \u7528\u4e8e\u76f8\u540c\u7684\u76ee\u6807\u3002\u60a8\u8fd8\u53ef\u4ee5\u5224\u65ad\u5728\u9879\u76ee\u4e2d\u4f55\u65f6\u4e00\u79cd\u65b9\u6cd5\u4f18\u4e8e\u53e6\u4e00\u79cd\u65b9\u6cd5\u3002<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>To follow along with this chapter, you will need to create an ASP.NET Core 6.0 Web API application. You can use either of the following options:<br \/>\n\u8981\u6309\u7167\u672c\u7ae0\u7684\u5b66\u4e60\uff0c\u60a8\u9700\u8981\u521b\u5efa\u4e00\u4e2a ASP.NET Core 6.0 Web API \u5e94\u7528\u7a0b\u5e8f\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u4efb\u4e00\u9009\u9879\uff1a<\/p>\n<p>\u2022  Click on the New Project option in the File menu of Visual Studio 2022, then choose the ASP.NET Core Web API template, select a name and the working directory in the wizard, and be sure to uncheck the Use controllers option in the next step.<br \/>\n\u5355\u51fb Visual Studio 2022 \u7684\u201c\u6587\u4ef6\u201d\u83dc\u5355\u4e2d\u7684\u201c\u65b0\u5efa\u9879\u76ee\u201d\u9009\u9879\uff0c\u7136\u540e\u9009\u62e9 ASP.NET Core Web API \u6a21\u677f\uff0c\u5728\u5411\u5bfc\u4e2d\u9009\u62e9\u540d\u79f0\u548c\u5de5\u4f5c\u76ee\u5f55\uff0c\u5e76\u786e\u4fdd\u5728\u4e0b\u4e00\u6b65\u4e2d\u53d6\u6d88\u9009\u4e2d\u201c\u4f7f\u7528\u63a7\u5236\u5668\u201d\u9009\u9879\u3002<\/p>\n<p>\u2022  Open your console, shell, or Bash terminal, and change to your working directory. Use the following command to create a new Web API application: dotnet new webapi -minimal -o Chapter07<br \/>\n\u6253\u5f00\u60a8\u7684\u63a7\u5236\u53f0\u3001shell \u6216 Bash \u7ec8\u7aef\uff0c\u7136\u540e\u5207\u6362\u5230\u60a8\u7684\u5de5\u4f5c\u76ee\u5f55\u3002\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u521b\u5efa\u65b0\u7684 Web API \u5e94\u7528\u7a0b\u5e8f\uff1adotnet new webapi -minimal -o Chapter07<\/p>\n<p>Now, open the project in Visual Studio by double-clicking on the project file or, in Visual Studio Code, type the following command in the already open console:<br \/>\n\u73b0\u5728\uff0c\u901a\u8fc7\u53cc\u51fb\u9879\u76ee\u6587\u4ef6\u5728 Visual Studio \u4e2d\u6253\u5f00\u9879\u76ee\uff0c\u6216\u8005\u5728 Visual Studio Code \u4e2d\uff0c\u5728\u5df2\u6253\u5f00\u7684\u63a7\u5236\u53f0\u4e2d\u952e\u5165\u4ee5\u4e0b\u547d\u4ee4\uff1a<\/p>\n<pre><code>cd Chapter07\ncode.<\/code><\/pre>\n<p>Finally, you can safely remove all the code related to the WeatherForecast sample, as we don\u2019t need it for this chapter.<br \/>\n\u6700\u540e\uff0c\u60a8\u53ef\u4ee5\u5b89\u5168\u5730\u5220\u9664\u4e0e WeatherForecast \u793a\u4f8b\u76f8\u5173\u7684\u6240\u6709\u4ee3\u7801\uff0c\u56e0\u4e3a\u672c\u7ae0\u4e0d\u9700\u8981\u5b83\u3002<\/p>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter07\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter07<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter07\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter07<\/a>\u3002<\/p>\n<p>Using Entity Framework<br \/>\n\u4f7f\u7528Entity Framework<\/p>\n<p>We can absolutely say that if we are building an API, it is very likely that we will interact with data.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u80af\u5b9a\u5730\u8bf4\uff0c\u5982\u679c\u6211\u4eec\u6b63\u5728\u6784\u5efa\u4e00\u4e2a API\uff0c\u6211\u4eec\u5f88\u53ef\u80fd\u4f1a\u4e0e\u6570\u636e\u4ea4\u4e92\u3002<\/p>\n<p>In addition, this data most probably needs to be persisted after the application restarts or after other events, such as a new deployment of the application. There are many options for persisting data in .NET applications, but EF is the most user-friendly and common solution for a lot of scenarios.<br \/>\n\u6b64\u5916\uff0c\u8fd9\u4e9b\u6570\u636e\u5f88\u53ef\u80fd\u9700\u8981\u5728\u5e94\u7528\u7a0b\u5e8f\u91cd\u542f\u540e\u6216\u5176\u4ed6\u4e8b\u4ef6\uff08\u4f8b\u5982\u5e94\u7528\u7a0b\u5e8f\u7684\u65b0\u90e8\u7f72\uff09\u4e4b\u540e\u4fdd\u7559\u3002\u5728 .NET \u5e94\u7528\u7a0b\u5e8f\u4e2d\u4fdd\u5b58\u6570\u636e\u7684\u9009\u9879\u6709\u5f88\u591a\uff0c\u4f46 EF \u662f\u9002\u7528\u4e8e\u8bb8\u591a\u65b9\u6848\u7684\u6700\u7528\u6237\u53cb\u597d\u548c\u6700\u5e38\u89c1\u7684\u89e3\u51b3\u65b9\u6848\u3002<\/p>\n<p>Entity Framework Core (EF Core) is an extensible, open source, and cross-platform data access library for .NET applications. It enables developers to work with the database by using .NET objects directly and removes, in most cases, the need to know how to write the data access code directly in the database.<br \/>\nEntity Framework Core \uff08EF Core\uff09 \u662f\u4e00\u4e2a\u9002\u7528\u4e8e .NET \u5e94\u7528\u7a0b\u5e8f\u7684\u53ef\u6269\u5c55\u3001\u5f00\u6e90\u548c\u8de8\u5e73\u53f0\u6570\u636e\u8bbf\u95ee\u5e93\u3002\u5b83\u4f7f\u5f00\u53d1\u4eba\u5458\u80fd\u591f\u76f4\u63a5\u4f7f\u7528 .NET \u5bf9\u8c61\u6765\u5904\u7406\u6570\u636e\u5e93\uff0c\u5e76\u4e14\u5728\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u65e0\u9700\u77e5\u9053\u5982\u4f55\u76f4\u63a5\u5728\u6570\u636e\u5e93\u4e2d\u7f16\u5199\u6570\u636e\u8bbf\u95ee\u4ee3\u7801\u3002<\/p>\n<p>On top of this, EF Core supports a lot of databases, including SQLite, MySQL, Oracle, Microsoft SQL Server, and PostgreSQL.<br \/>\n\u6700\u91cd\u8981\u7684\u662f\uff0cEF Core \u652f\u6301\u8bb8\u591a\u6570\u636e\u5e93\uff0c\u5305\u62ec SQLite\u3001MySQL\u3001Oracle\u3001Microsoft SQL Server \u548c PostgreSQL\u3002<\/p>\n<p>In addition, it supports an in-memory database that helps to write tests for our applications or to make the development cycle easier because you don\u2019t need a real database up and running.<br \/>\n\u6b64\u5916\uff0c\u5b83\u8fd8\u652f\u6301\u5185\u5b58\u6570\u636e\u5e93\uff0c\u6709\u52a9\u4e8e\u4e3a\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u7f16\u5199\u6d4b\u8bd5\u6216\u7b80\u5316\u5f00\u53d1\u5468\u671f\uff0c\u56e0\u4e3a\u60a8\u4e0d\u9700\u8981\u542f\u52a8\u548c\u8fd0\u884c\u771f\u6b63\u7684\u6570\u636e\u5e93\u3002<\/p>\n<p>In the next section, we will see how to set up a project for using EF and its main features.<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u8bbe\u7f6e\u4f7f\u7528 EF \u7684\u9879\u76ee\u53ca\u5176\u4e3b\u8981\u529f\u80fd\u3002<\/p>\n<p>Setting up the project<br \/>\n\u8bbe\u7f6e\u9879\u76ee<\/p>\n<p>From the project root, create an Icecream.cs class and give it the following content:<br \/>\n\u4ece\u9879\u76ee\u6839\u76ee\u5f55\u4e2d\uff0c\u521b\u5efa\u4e00\u4e2a Icecream.cs \u7c7b\u5e76\u4e3a\u5176\u63d0\u4f9b\u4ee5\u4e0b\u5185\u5bb9\uff1a<\/p>\n<pre><code>namespace Chapter07.Models;\npublic class Icecream\n{\n    public int Id { get; set; }\n    public string? Name { get; set; }\n    public string? Description { get; set; }\n}<\/code><\/pre>\n<p>The Icecream class is an object that represents an ice cream in our project. This class should be called a data model, and we will use this object in the next sections of this chapter to map it to a database table.<br \/>\nIcecream \u7c7b\u662f\u8868\u793a\u6211\u4eec\u9879\u76ee\u4e2d\u7684Icecream\u7684\u5bf9\u8c61\u3002\u8fd9\u4e2a\u7c7b\u5e94\u8be5\u88ab\u79f0\u4e3a data model\uff0c\u6211\u4eec\u5c06\u5728\u672c\u7ae0\u7684\u540e\u9762\u90e8\u5206\u4f7f\u7528\u8fd9\u4e2a\u5bf9\u8c61\u6765 Map \u5b83\u5230\u4e00\u4e2a\u6570\u636e\u5e93\u8868\u3002<\/p>\n<p>Now it\u2019s time to add the EF Core NuGet reference to the project.<br \/>\n\u73b0\u5728\uff0c\u53ef\u4ee5\u5c06 EF Core NuGet \u5f15\u7528\u6dfb\u52a0\u5230\u9879\u76ee\u3002<\/p>\n<p>In order to do that, you can use one of the following methods:<br \/>\n\u4e3a\u6b64\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u65b9\u6cd5\u4e4b\u4e00\uff1a<\/p>\n<p>\u2022  In a new terminal window, enter the following code to add the EF Core InMemory package:<br \/>\n\u5728\u65b0\u7684\u7ec8\u7aef\u7a97\u53e3\u4e2d\uff0c\u8f93\u5165\u4ee5\u4e0b\u4ee3\u7801\u4ee5\u6dfb\u52a0 EF Core InMemory \u5305\uff1a<br \/>\ndotnet add package Microsoft.EntityFrameworkCore.InMemory<\/p>\n<p>\u2022  If you would like to use Visual Studio 2022 to add the reference, right-click on Dependencies and then select Manage NuGet Packages. Search for Microsoft.EntityFrameworkCore.InMemory and install the package.<br \/>\n\u5982\u679c\u8981\u4f7f\u7528 Visual Studio 2022 \u6dfb\u52a0\u5f15\u7528\uff0c\u8bf7\u53f3\u952e\u5355\u51fb \u201c\u4f9d\u8d56\u9879\u201d\uff0c\u7136\u540e\u9009\u62e9 \u201c\u7ba1\u7406 NuGet \u5305\u201d\u3002\u641c\u7d22 Microsoft.EntityFrameworkCore.InMemory \u5e76\u5b89\u88c5\u8be5\u5305\u3002<\/p>\n<p>In the next section, we will be adding EF Core to our project.<br \/>\n\u5728\u4e0b\u4e00\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06 EF Core \u6dfb\u52a0\u5230\u6211\u4eec\u7684\u9879\u76ee\u4e2d\u3002<\/p>\n<p>Adding EF Core to the project<br \/>\n\u5c06 EF Core \u6dfb\u52a0\u5230\u9879\u76ee<\/p>\n<p>In order to store the ice cream objects in the database, we need to set up EF Core in our project.<br \/>\n\u4e3a\u4e86\u5c06\u51b0\u6dc7\u6dcb\u5bf9\u8c61\u5b58\u50a8\u5728\u6570\u636e\u5e93\u4e2d\uff0c\u6211\u4eec\u9700\u8981\u5728\u9879\u76ee\u4e2d\u8bbe\u7f6e EF Core\u3002<\/p>\n<p>To set up an in-memory database, add the following code to the bottom of the Program.cs file:<br \/>\n\u8981\u8bbe\u7f6e\u5185\u5b58\u4e2d\u6570\u636e\u5e93\uff0c\u8bf7\u5c06\u4ee5\u4e0b\u4ee3\u7801\u6dfb\u52a0\u5230 Program.cs \u6587\u4ef6\u7684\u5e95\u90e8\uff1a<\/p>\n<pre><code>class IcecreamDb : DbContext\n{\n    public IcecreamDb(DbContextOptions options) :\n      base(options) { }\n    public DbSet&lt;Icecream&gt; Icecreams { get; set; } = null!;\n}<\/code><\/pre>\n<p>DbContext object represents a connection to the database, and it\u2019s used to save and query instances of entities in the database.<br \/>\nDbContext \u5bf9\u8c61\u8868\u793a\u4e0e\u6570\u636e\u5e93\u7684\u8fde\u63a5\uff0c\u7528\u4e8e\u4fdd\u5b58\u548c\u67e5\u8be2\u6570\u636e\u5e93\u4e2d\u7684\u5b9e\u4f53\u5b9e\u4f8b\u3002<\/p>\n<p>The DbSet represents the instances of the entities, and they will be converted into a real table in the database.<br \/>\nDbSet \u8868\u793a\u5b9e\u4f53\u7684\u5b9e\u4f8b\uff0c\u5b83\u4eec\u5c06\u8f6c\u6362\u4e3a\u6570\u636e\u5e93\u4e2d\u7684\u5b9e\u9645\u8868\u3002<\/p>\n<p>In this case, we will have just one table in the database, called Icecreams.<br \/>\n\u5728\u672c\u4f8b\u4e2d\uff0c\u6570\u636e\u5e93\u4e2d\u53ea\u6709\u4e00\u4e2a\u540d\u4e3a Icecreams \u7684\u8868\u3002<\/p>\n<p>In Program.cs, after the builder initialization, add the following code:<br \/>\n\u5728 Program.cs \u4e2d\uff0c\u5728\u751f\u6210\u5668\u521d\u59cb\u5316\u540e\uff0c\u6dfb\u52a0\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<pre><code>builder.Services.AddDbContext&lt;IcecreamDb&gt;(options =&gt; options.UseInMemoryDatabase(&quot;icecreams&quot;));<\/code><\/pre>\n<p>Now we are ready to add some API endpoints to start interacting with the database.<br \/>\n\u73b0\u5728\u6211\u4eec\u51c6\u5907\u6dfb\u52a0\u4e00\u4e9b API \u7aef\u70b9\u4ee5\u5f00\u59cb\u4e0e\u6570\u636e\u5e93\u4ea4\u4e92\u3002<\/p>\n<p>Adding endpoints to the project<br \/>\n\u5411\u9879\u76ee\u6dfb\u52a0\u7aef\u70b9<\/p>\n<p>Let\u2019s add the code to create a new item in the icecreams list. In Program.cs, add the following code before the app.Run() line of code:<br \/>\n\u8ba9\u6211\u4eec\u6dfb\u52a0\u4ee3\u7801\u4ee5\u5728 icecreams \u5217\u8868\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u9879\u76ee\u3002\u5728 Program.cs \u4e2d\uff0c\u5728app.Run()  \u4e4b\u524d\u6dfb\u52a0\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/icecreams&quot;, async (IcecreamDb db, Icecream icecream) =&gt;\n{\n    await db.Icecreams.AddAsync(icecream);\n    await db.SaveChangesAsync();\n    return Results.Created($&quot;\/icecreams\/{icecream.Id}&quot;,\n                           icecream);\n});<\/code><\/pre>\n<p>The first parameter of the MapPost function is the DbContext. By default, the minimal API architecture uses dependency injection to share the instances of the DbContext.<br \/>\nMapPost \u51fd\u6570\u7684\u7b2c\u4e00\u4e2a\u53c2\u6570\u662f DbContext\u3002\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u6700\u5c0f API \u4f53\u7cfb\u7ed3\u6784\u4f7f\u7528\u4f9d\u8d56\u9879\u6ce8\u5165\u6765\u5171\u4eab DbContext \u7684\u5b9e\u4f8b\u3002<\/p>\n<p>Dependency injection<br \/>\n\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165<\/p>\n<p>If you want to know more about dependency injection, go to Chapter 4, Dependency Injection in a Minimal API Project.<br \/>\n\u5982\u679c\u60a8\u60f3\u4e86\u89e3\u6709\u5173\u4f9d\u8d56\u6ce8\u5165\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u8f6c\u5230\u7b2c 4 \u7ae0 \u6700\u5c0f API \u9879\u76ee\u4e2d\u7684\u4f9d\u8d56\u6ce8\u5165\u3002<\/p>\n<p>In order to save an item into the database, we use the AddSync method directly from the entity that represents the object.<br \/>\n\u4e3a\u4e86\u5c06\u9879\u4fdd\u5b58\u5230\u6570\u636e\u5e93\u4e2d\uff0c\u6211\u4eec\u76f4\u63a5\u4ece\u8868\u793a\u5bf9\u8c61\u7684\u5b9e\u4f53\u4e2d\u4f7f\u7528 AddSync \u65b9\u6cd5\u3002<\/p>\n<p>To persist the new item in the database, we need to call the SaveChangesAsync() method, which is responsible for saving all the changes that happen to the database before the last call to SaveChangesAsync().<br \/>\n\u8981\u5728\u6570\u636e\u5e93\u4e2d\u4fdd\u7559\u65b0\u9879\uff0c\u6211\u4eec\u9700\u8981\u8c03\u7528 SaveChangesAsync\uff08\uff09 \u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u8d1f\u8d23\u4fdd\u5b58\u4e0a\u6b21\u8c03\u7528 SaveChangesAsync\uff08\uff09 \u4e4b\u524d\u5bf9\u6570\u636e\u5e93\u53d1\u751f\u7684\u6240\u6709\u66f4\u6539\u3002<\/p>\n<p>In a very similar way, we can add the endpoint to retrieve all the items in the icecreams database.<br \/>\n\u4ee5\u975e\u5e38\u76f8\u4f3c\u7684\u65b9\u5f0f\uff0c\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u7ec8\u7aef\u8282\u70b9\u6765\u68c0\u7d22 icecreams \u6570\u636e\u5e93\u4e2d\u7684\u6240\u6709\u9879\u76ee\u3002<\/p>\n<p>After the code to add an ice cream, we can add the following code:<br \/>\n\u5728\u6dfb\u52a0\u51b0\u6dc7\u6dcb\u7684\u4ee3\u7801\u4e4b\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/icecreams&quot;, async (IcecreamDb db) =&gt; await db.Icecreams.ToListAsync());<\/code><\/pre>\n<p>Also, in this case, the DbContext is available as a parameter and we can retrieve all the items in the database directly from the entities in the DbContext.<br \/>\n\u6b64\u5916\uff0c\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0cDbContext \u53ef\u7528\u4f5c\u53c2\u6570\uff0c\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u4ece DbContext \u4e2d\u7684\u5b9e\u4f53\u68c0\u7d22\u6570\u636e\u5e93\u4e2d\u7684\u6240\u6709\u9879\u3002<\/p>\n<p>With the ToListAsync() method, the application loads all the entities in the database and sends them back as the endpoint result.<br \/>\n\u4f7f\u7528 ToListAsync() \u65b9\u6cd5\uff0c\u5e94\u7528\u7a0b\u5e8f\u52a0\u8f7d\u6570\u636e\u5e93\u4e2d\u7684\u6240\u6709\u5b9e\u4f53\uff0c\u5e76\u5c06\u5b83\u4eec\u4f5c\u4e3a\u7ec8\u7aef\u8282\u70b9\u7ed3\u679c\u53d1\u9001\u56de\u53bb\u3002<\/p>\n<p>Make sure you have saved all your changes in the project and run the app.<br \/>\n\u786e\u4fdd\u60a8\u5df2\u4fdd\u5b58\u9879\u76ee\u4e2d\u7684\u6240\u6709\u66f4\u6539\u5e76\u8fd0\u884c\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>A new browser window will open, and you can navigate to the \/swagger URL:<br \/>\n\u5c06\u6253\u5f00\u4e00\u4e2a\u65b0\u7684\u6d4f\u89c8\u5668\u7a97\u53e3\uff0c\u60a8\u53ef\u4ee5\u5bfc\u822a\u5230 \/swagger URL\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0701.jpg\" ><\/p>\n<p>Figure 7.1 \u2013 Swagger browser window<br \/>\n\u56fe 7.1 \u2013 Swagger \u6d4f\u89c8\u5668\u7a97\u53e3<\/p>\n<p>Select the POST\/icecreams button, followed by Try it out.<br \/>\n\u9009\u62e9 POST\/icecreams \u6309\u94ae\uff0c\u7136\u540e\u9009\u62e9 Try it out\u3002<\/p>\n<p>Replace the request body content with the following JSON:<br \/>\n\u5c06\u8bf7\u6c42\u6b63\u6587\u5185\u5bb9\u66ff\u6362\u4e3a\u4ee5\u4e0b JSON\uff1a<\/p>\n<pre><code>{\n  &quot;id&quot;: 0,\n  &quot;name&quot;: &quot;icecream 1&quot;,\n  &quot;description&quot;: &quot;description 1&quot;\n}<\/code><\/pre>\n<p>Click on Execute:<br \/>\n\u5355\u51fb Execute\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0702.jpg\" ><\/p>\n<p>Figure 7.2 \u2013 Swagger response<br \/>\n\u56fe 7.2 \u2013 Swagger \u54cd\u5e94<\/p>\n<p>Now we have at least one item in the database, and we can try the other endpoint to retrieve all the items in the database.<br \/>\n\u73b0\u5728\uff0c\u6570\u636e\u5e93\u4e2d\u81f3\u5c11\u6709\u4e00\u4e2a\u9879\u76ee\uff0c\u6211\u4eec\u53ef\u4ee5\u5c1d\u8bd5\u4f7f\u7528\u53e6\u4e00\u4e2a\u7aef\u70b9\u6765\u68c0\u7d22\u6570\u636e\u5e93\u4e2d\u7684\u6240\u6709\u9879\u76ee\u3002<\/p>\n<p>Scroll down the page a little bit and select GET\/icecreams, followed by Try it out and then Execute.<br \/>\n\u5411\u4e0b\u6eda\u52a8\u9875\u9762\u5e76\u9009\u62e9 GET\/icecreams\uff0c\u7136\u540e\u9009\u62e9 Try it out\uff0c\u7136\u540e\u9009\u62e9 Execute\u3002<\/p>\n<p>You will see the list with one item under Response Body.<br \/>\n\u60a8\u5c06\u5728 Response Body \uff08\u54cd\u5e94\u6b63\u6587\uff09 \u4e0b\u770b\u5230\u5e26\u6709\u4e00\u4e2a\u9879\u76ee\u7684\u5217\u8868\u3002<\/p>\n<p>Let\u2019s see how to finalize this first demo by adding the other CRUD operations to our endpoints:<br \/>\n\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u901a\u8fc7\u5c06\u5176\u4ed6 CRUD\u4f5c\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u7aef\u70b9\u6765\u5b8c\u6210\u7b2c\u4e00\u4e2a\u6f14\u793a\uff1a<\/p>\n<ol>\n<li>To get an item by ID, add the following code under the app.MapGet route you created earlier:<br \/>\n\u8981\u6309 ID \u83b7\u53d6\u9879\u76ee\uff0c\u8bf7\u5728\u5e94\u7528\u7a0b\u5e8f\u4e0b\u6dfb\u52a0app.MapGet\u8def\u7531\u4ee3\u7801\uff1a<\/li>\n<\/ol>\n<pre><code>app.MapGet(&quot;\/icecreams\/{id}&quot;, async (IcecreamDb db, int id) =&gt; await db.Icecreams.FindAsync(id));<\/code><\/pre>\n<p>To check this out, you can launch the application again and use the Swagger UI as before.<br \/>\n\u8981\u68c0\u67e5\u8fd9\u4e00\u70b9\uff0c\u60a8\u53ef\u4ee5\u518d\u6b21\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\u5e76\u50cf\u4ee5\u524d\u4e00\u6837\u4f7f\u7528 Swagger UI\u3002<\/p>\n<ol start=\"2\">\n<li>\n<p>Next, add an item in the database by performing a post call (as in the previous section).<br \/>\n\u63a5\u4e0b\u6765\uff0c\u901a\u8fc7\u6267\u884c post \u8c03\u7528\u5728\u6570\u636e\u5e93\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u9879\u76ee\uff08\u5982\u4e0a\u4e00\u8282\u6240\u793a\uff09\u3002<\/p>\n<\/li>\n<li>\n<p>Click GET\/icecreams\/{id) followed by Try it out.<br \/>\n\u5355\u51fb GET\/icecreams\/{id\uff09 \u540e\u8ddf Try it out\u3002<\/p>\n<\/li>\n<li>\n<p>Insert the value 1 in the id parameter field and then click on Execute.<br \/>\n\u5728 id \u53c2\u6570\u5b57\u6bb5\u4e2d\u63d2\u5165\u503c 1\uff0c\u7136\u540e\u5355\u51fb Execute\u3002<\/p>\n<\/li>\n<li>\n<p>You will see the item in the Response Body section.<br \/>\n\u60a8\u5c06\u5728 Response Body \uff08\u54cd\u5e94\u6b63\u6587\uff09 \u90e8\u5206\u770b\u5230\u8be5\u9879\u76ee\u3002<\/p>\n<\/li>\n<li>\n<p>The following is an example of a response from the API:<br \/>\n\u4ee5\u4e0b\u662f\u6765\u81ea API \u7684\u54cd\u5e94\u793a\u4f8b\uff1a<\/p>\n<\/li>\n<\/ol>\n<pre><code>{\n  &quot;id&quot;: 1,\n  &quot;name&quot;: &quot;icecream 1&quot;,\n  &quot;description&quot;: &quot;description 1&quot;\n}<\/code><\/pre>\n<p>This is what the response looks like:<br \/>\n\u54cd\u5e94\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0703.jpg\"><\/p>\n<p>Figure 7.3 \u2013 Response result<br \/>\n\u56fe 7.3 \u2013 \u54cd\u5e94\u7ed3\u679c<\/p>\n<p>To update an item by ID, we can create a new MapPut endpoint with two parameters: the item with the entity values and the ID of the old entity in the database that we want to update.<br \/>\n\u8981\u6309 ID \u66f4\u65b0\u9879\u76ee\uff0c\u6211\u4eec\u53ef\u4ee5\u521b\u5efa\u4e00\u4e2a\u5177\u6709\u4e24\u4e2a\u53c2\u6570\u7684\u65b0 MapPut \u7ec8\u7aef\u8282\u70b9\uff1a\u5177\u6709\u5b9e\u4f53\u503c\u7684\u9879\u76ee\u548c\u6570\u636e\u5e93\u4e2d\u8981\u66f4\u65b0\u7684\u65e7\u5b9e\u4f53\u7684 ID\u3002<\/p>\n<p>The code should be like the following snippet:<br \/>\n\u4ee3\u7801\u5e94\u7c7b\u4f3c\u4e8e\u4ee5\u4e0b\u4ee3\u7801\u6bb5\uff1a<\/p>\n<pre><code>app.MapPut(&quot;\/icecreams\/{id}&quot;, async (IcecreamDb db, Icecream updateicecream, int id) =&gt;\n{\n    var icecream = await db.Icecreams.FindAsync(id);\n    if (icecream is null) return Results.NotFound();\n    icecream.Name = updateicecream.Name;\n    icecream.Description = updateicecream.Description;\n    await db.SaveChangesAsync();\n    return Results.NoContent();\n});<\/code><\/pre>\n<p>Just to be clear, first of all, we need to find the item in the database with the ID from the parameters. If we don\u2019t find an item in the database, it\u2019s a good practice to return a Not Found HTTP status to the caller.<br \/>\n\u9700\u8981\u660e\u786e\u7684\u662f\uff0c\u9996\u5148\uff0c\u6211\u4eec\u9700\u8981\u5728\u6570\u636e\u5e93\u4e2d\u627e\u5230\u5177\u6709\u53c2\u6570\u4e2d ID \u7684\u9879\u76ee\u3002\u5982\u679c\u6211\u4eec\u5728\u6570\u636e\u5e93\u4e2d\u627e\u4e0d\u5230\u9879\u76ee\uff0c\u6700\u597d\u5c06 Not Found HTTP \u72b6\u6001\u8fd4\u56de\u7ed9\u8c03\u7528\u8005\u3002<\/p>\n<p>If we find the entity in the database, we update the entity with the new values and we save all the changes in the database before sending back the HTTP status No Content.<br \/>\n\u5982\u679c\u6211\u4eec\u5728\u6570\u636e\u5e93\u4e2d\u627e\u5230\u5b9e\u4f53\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u65b0\u503c\u66f4\u65b0\u5b9e\u4f53\uff0c\u5e76\u5728\u53d1\u56de HTTP \u72b6\u6001 No Content \u4e4b\u524d\u4fdd\u5b58\u6570\u636e\u5e93\u4e2d\u7684\u6240\u6709\u66f4\u6539\u3002<\/p>\n<p>The last CRUD operation we need to perform is to delete an item from the database.<br \/>\n\u6211\u4eec\u9700\u8981\u6267\u884c\u7684\u6700\u540e\u4e00\u4e2a CRUD\u4f5c\u662f\u4ece\u6570\u636e\u5e93\u4e2d\u5220\u9664\u4e00\u4e2a\u9879\u76ee\u3002<\/p>\n<p>This operation is very similar to the update operation because, first of all, we need to find the item in the database and then we can try to perform the delete operation.<br \/>\n\u6b64\u64cd\u4f5c\u4e0e\u66f4\u65b0\u4f5c\u975e\u5e38\u76f8\u4f3c\uff0c\u56e0\u4e3a\u9996\u5148\uff0c\u6211\u4eec\u9700\u8981\u5728\u6570\u636e\u5e93\u4e2d\u627e\u5230\u8be5\u9879\u76ee\uff0c\u7136\u540e\u6211\u4eec\u53ef\u4ee5\u5c1d\u8bd5\u6267\u884c\u5220\u9664\u4f5c\u3002<\/p>\n<p>The following code snippet shows how to implement a delete operation with the right HTTP verb of the minimal API:<br \/>\n\u4ee5\u4e0b\u4ee3\u7801\u7247\u6bb5\u663e\u793a\u4e86\u5982\u4f55\u4f7f\u7528\u6700\u5c0f API \u7684\u6b63\u786e HTTP \u52a8\u8bcd\u5b9e\u65bd\u5220\u9664\u4f5c\uff1a<\/p>\n<pre><code>app.MapDelete(&quot;\/icecreams\/{id}&quot;, async (IcecreamDb db, int id) =&gt;\n{\n    var icecream = await db.Icecreams.FindAsync(id);\n    if (icecream is null)\n    {\n        return Results.NotFound();\n    }\n    db.Icecreams.Remove(icecream);\n    await db.SaveChangesAsync();\n    return Results.Ok();\n});<\/code><\/pre>\n<p>In this section, we have learned how to use EF in a minimal API project.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5b66\u4e60\u4e86\u5982\u4f55\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u4f7f\u7528 EF\u3002<\/p>\n<p>We saw how to add the NuGet packages to start working with EF, and how to implement the entire set of CRUD operations in a minimal API .NET 6 project.<br \/>\n\u6211\u4eec\u4e86\u89e3\u4e86\u5982\u4f55\u6dfb\u52a0 NuGet \u5305\u4ee5\u5f00\u59cb\u4f7f\u7528 EF\uff0c\u4ee5\u53ca\u5982\u4f55\u5728\u6700\u5c0f\u7684 API .NET 6 \u9879\u76ee\u4e2d\u5b9e\u73b0\u6574\u5957 CRUD\u4f5c\u3002<\/p>\n<p>In the next section, we will see how to implement the same project with the same logic but using Dapper as the primary library to access data.<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u4f7f\u7528\u76f8\u540c\u7684\u903b\u8f91\u5b9e\u73b0\u76f8\u540c\u7684\u9879\u76ee\uff0c\u4f46\u4f7f\u7528 Dapper \u4f5c\u4e3a\u4e3b\u5e93\u6765\u8bbf\u95ee\u6570\u636e\u3002<\/p>\n<p>Using Dapper<br \/>\n\u4f7f\u7528 Dapper<\/p>\n<p>Dapper is an Object-Relational Mapper (ORM) or, to be more precise, a micro ORM. With Dapper, we can write SQL statements directly in .NET projects like we can do in SQL Server (or another database). One of the best advantages of using Dapper in a project is the performance, because it doesn\u2019t translate queries from .NET objects and doesn\u2019t add any layers between the application and the library to access the database. It extends the IDbConnection object and provides a lot of methods to query the database. This means we have to write queries that are compatible with the database provider.<br \/>\nDapper \u662f\u4e00\u4e2a\u5bf9\u8c61\u5173\u7cfb\u6620\u5c04\u5668 \uff08ORM\uff09\uff0c\u6216\u8005\u66f4\u51c6\u786e\u5730\u8bf4\uff0c\u662f\u4e00\u4e2a\u5fae\u578b ORM\u3002\u4f7f\u7528 Dapper\uff0c\u6211\u4eec\u53ef\u4ee5\u76f4\u63a5\u5728 .NET \u9879\u76ee\u4e2d\u7f16\u5199 SQL \u8bed\u53e5\uff0c\u5c31\u50cf\u5728 SQL Server\uff08\u6216\u5176\u4ed6\u6570\u636e\u5e93\uff09\u4e2d\u4e00\u6837\u3002\u5728\u9879\u76ee\u4e2d\u4f7f\u7528 Dapper \u7684\u6700\u5927\u4f18\u52bf\u4e4b\u4e00\u662f\u6027\u80fd\uff0c\u56e0\u4e3a\u5b83\u4e0d\u4f1a\u8f6c\u6362\u6765\u81ea .NET \u5bf9\u8c61\u7684\u67e5\u8be2\uff0c\u4e5f\u4e0d\u4f1a\u5728\u5e94\u7528\u7a0b\u5e8f\u548c\u5e93\u4e4b\u95f4\u6dfb\u52a0\u4efb\u4f55\u5c42\u6765\u8bbf\u95ee\u6570\u636e\u5e93\u3002\u5b83\u6269\u5c55\u4e86 IDbConnection \u5bf9\u8c61\uff0c\u5e76\u63d0\u4f9b\u4e86\u8bb8\u591a\u67e5\u8be2\u6570\u636e\u5e93\u7684\u65b9\u6cd5\u3002\u8fd9\u610f\u5473\u7740\u6211\u4eec\u5fc5\u987b\u7f16\u5199\u4e0e\u6570\u636e\u5e93\u63d0\u4f9b\u7a0b\u5e8f\u517c\u5bb9\u7684\u67e5\u8be2\u3002<\/p>\n<p>It supports synchronous and asynchronous method executions. This is a list of the methods that Dapper adds to the IDbConnection interface:<br \/>\n\u5b83\u652f\u6301\u540c\u6b65\u548c\u5f02\u6b65\u65b9\u6cd5\u6267\u884c\u3002\u4ee5\u4e0b\u662f Dapper \u6dfb\u52a0\u5230 IDbConnection \u63a5\u53e3\u7684\u65b9\u6cd5\u5217\u8868\uff1a<\/p>\n<p>\u2022  Execute<br \/>\n\u2022  Query<br \/>\n\u2022  QueryFirst<br \/>\n\u2022  QueryFirstOrDefault<br \/>\n\u2022  QuerySingle<br \/>\n\u2022  QuerySingleOrDefault<br \/>\n\u2022  QueryMultiple<\/p>\n<p>As we mentioned, it provides an async version for all these methods. You can find the right methods by adding the Async keyword at the end of the method name.<br \/>\n\u6b63\u5982\u6211\u4eec\u6240\u63d0\u5230\u7684\uff0c\u5b83\u4e3a\u6240\u6709\u8fd9\u4e9b\u65b9\u6cd5\u63d0\u4f9b\u4e86\u4e00\u4e2a\u5f02\u6b65\u7248\u672c\u3002\u60a8\u53ef\u4ee5\u901a\u8fc7\u5728\u65b9\u6cd5\u540d\u79f0\u7684\u672b\u5c3e\u6dfb\u52a0 Async \u5173\u952e\u5b57\u6765\u67e5\u627e\u6b63\u786e\u7684\u65b9\u6cd5\u3002<\/p>\n<p>In the next section, we will see how to set up a project for using Dapper with a SQL Server LocalDB.<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u8bbe\u7f6e\u4e00\u4e2a\u9879\u76ee\uff0c\u4ee5\u4fbf\u5c06 Dapper \u4e0e SQL Server LocalDB \u7ed3\u5408\u4f7f\u7528\u3002<\/p>\n<p>Setting up the project<br \/>\n\u8bbe\u7f6e\u9879\u76ee<\/p>\n<p>The first thing we are going to do is to create a new database. You can use your SQL Server LocalDB instance installed with Visual Studio by default or another SQL Server instance in your environment.<br \/>\n\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u521b\u5efa\u4e00\u4e2a\u65b0\u6570\u636e\u5e93\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u9ed8\u8ba4\u968f Visual Studio \u4e00\u8d77\u5b89\u88c5\u7684 SQL Server LocalDB \u5b9e\u4f8b\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528\u73af\u5883\u4e2d\u7684\u5176\u4ed6 SQL Server \u5b9e\u4f8b\u3002<\/p>\n<p>You can execute the following script in your database to create one table and populate it with data:<br \/>\n\u60a8\u53ef\u4ee5\u5728\u6570\u636e\u5e93\u4e2d\u6267\u884c\u4ee5\u4e0b\u811a\u672c\u6765\u521b\u5efa\u4e00\u4e2a\u8868\u5e76\u4f7f\u7528\u6570\u636e\u586b\u5145\u5b83\uff1a<\/p>\n<pre><code>CREATE TABLE [dbo].[Icecreams](\n     [Id] [int] IDENTITY(1,1) NOT NULL,\n     [Name] [nvarchar](50) NOT NULL,\n     [Description] [nvarchar](255) NOT NULL)\nGO\nINSERT [dbo].[Icecreams] ([Name], [Description]) VALUES (&#039;Icecream 1&#039;,&#039;Description 1&#039;)\nINSERT [dbo].[Icecreams] ([Name], [Description]) VALUES (&#039;Icecream 2&#039;,&#039;Description 2&#039;)\nINSERT [dbo].[Icecreams] ([Name], [Description]) VALUES (&#039;Icecream 3&#039;,&#039;Description 3&#039;)<\/code><\/pre>\n<p>Once we have the database, we can install these NuGet packages with the following command in the Visual Studio terminal:<br \/>\n\u62e5\u6709\u6570\u636e\u5e93\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 Visual Studio \u7ec8\u7aef\u4e2d\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u5b89\u88c5\u8fd9\u4e9b NuGet \u5305\uff1a<\/p>\n<pre><code>Install-Package Dapper\nInstall-Package Microsoft.Data.SqlClient<\/code><\/pre>\n<p>Now we can continue to add the code to interact with the database. In this example, we are going to use a repository pattern.<br \/>\n\u73b0\u5728\u6211\u4eec\u53ef\u4ee5\u7ee7\u7eed\u6dfb\u52a0\u4ee3\u7801\u4ee5\u4e0e\u6570\u636e\u5e93\u4ea4\u4e92\u3002\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u5b58\u50a8\u5e93\u6a21\u5f0f\u3002<\/p>\n<p>Creating a repository pattern<br \/>\n\u521b\u5efa\u5b58\u50a8\u5e93\u6a21\u5f0f<\/p>\n<p>In this section, we are going to create a simple repository pattern, but we will try to make it as simple as possible so we can understand the main features of Dapper:<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a\u7b80\u5355\u7684\u5b58\u50a8\u5e93\u6a21\u5f0f\uff0c\u4f46\u6211\u4eec\u5c06\u5c1d\u8bd5\u4f7f\u5176\u5c3d\u53ef\u80fd\u7b80\u5355\uff0c\u4ee5\u4fbf\u6211\u4eec\u4e86\u89e3 Dapper \u7684\u4e3b\u8981\u529f\u80fd\uff1a<\/p>\n<ol>\n<li>\n<p>In the Program.cs file, add a simple class that represents our entity in the database:<br \/>\npublic class Icecream<br \/>\n\u5728 Program.cs \u6587\u4ef6\u4e2d\uff0c\u6dfb\u52a0\u4e00\u4e2a\u8868\u793a\u6570\u636e\u5e93\u4e2d\u5b9e\u4f53\u7684\u7b80\u5355\u7c7b\uff1a<\/p>\n<pre><code>{\npublic int Id { get; set; }\npublic string? Name { get; set; }\npublic string? Description { get; set; }\n}<\/code><\/pre>\n<\/li>\n<li>\n<p>After this, modify the appsettings.json file by adding the connection string at the end of the file:<br \/>\n\u5728\u6b64\u4e4b\u540e\uff0c\u901a\u8fc7\u5728\u6587\u4ef6\u672b\u5c3e\u6dfb\u52a0\u8fde\u63a5\u5b57\u7b26\u4e32\u6765\u4fee\u6539 appsettings.json \u6587\u4ef6\uff1a<\/p>\n<pre><code>&quot;ConnectionStrings&quot;: {\n&quot;SqlConnection&quot;:\n  &quot;Data Source=(localdb)\\\\MSSQLLocalDB;\n   Initial Catalog=Chapter07;\n   Integrated Security=True;\n   Connect Timeout=30;\n   Encrypt=False;\n   TrustServerCertificate=False;&quot;\n}<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>If you are using LocalDB, the connection string should be the right one for your environment as well.<br \/>\n\u5982\u679c\u60a8\u4f7f\u7528\u7684\u662f LocalDB\uff0c\u5219\u8fde\u63a5\u5b57\u7b26\u4e32\u4e5f\u5e94\u9002\u5408\u60a8\u7684\u73af\u5883\u3002<\/p>\n<ol start=\"3\">\n<li>\n<p>Create a new class in the root of the project called DapperContext and give it the following code:<br \/>\n\u5728\u9879\u76ee\u7684\u6839\u76ee\u5f55\u4e2d\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a DapperContext \u7684\u65b0\u7c7b\uff0c\u5e76\u4e3a\u5176\u63d0\u4f9b\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<pre><code>public class DapperContext\n{\nprivate readonly IConfiguration _configuration;\nprivate readonly string _connectionString;\npublic DapperContext(IConfiguration configuration)\n{\n    _configuration = configuration;\n    _connectionString = _configuration\n      .GetConnectionString(&quot;SqlConnection&quot;);\n}\n\npublic IDbConnection CreateConnection()\n    =&gt; new SqlConnection(_connectionString);\n}<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>We injected with dependency injection the IConfiguration interface to retrieve the connection string from the settings file.<br \/>\n\u6211\u4eec\u901a\u8fc7\u4f9d\u8d56\u9879\u6ce8\u5165\u6ce8\u5165 IConfiguration \u63a5\u53e3\u4ece\u8bbe\u7f6e\u6587\u4ef6\u4e2d\u68c0\u7d22\u8fde\u63a5\u5b57\u7b26\u4e32\u3002<\/p>\n<ol start=\"4\">\n<li>Now we are going to create the interface and the implementation of our repository. In order to do that, add the following code to the Program.cs file.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u63a5\u53e3\u548c\u5b58\u50a8\u5e93\u7684\u5b9e\u73b0\u3002\u4e3a\u6b64\uff0c\u8bf7\u5c06\u4ee5\u4e0b\u4ee3\u7801\u6dfb\u52a0\u5230 Program.cs \u6587\u4ef6\u4e2d\u3002<\/li>\n<\/ol>\n<pre><code>public interface IIcecreamsRepository\n{\n}\npublic class IcecreamsRepository : IIcecreamsRepository\n{\n    private readonly DapperContext _context;\n    public IcecreamsRepository(DapperContext context)\n    {\n        _context = context;\n    }\n}<\/code><\/pre>\n<p>In the next sections, we will be adding some code to the interface and to the implementation of the repository.<br \/>\n\u5728\u63a5\u4e0b\u6765\u7684\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06\u5411\u63a5\u53e3\u548c\u5b58\u50a8\u5e93\u7684\u5b9e\u73b0\u6dfb\u52a0\u4e00\u4e9b\u4ee3\u7801\u3002<\/p>\n<p>Finally, we can register the context, the interface, and its implementation as a service.<br \/>\n\u5728\u63a5\u4e0b\u6765\u7684\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06\u5411\u63a5\u53e3\u548c\u5b58\u50a8\u5e93\u7684\u5b9e\u73b0\u6dfb\u52a0\u4e00\u4e9b\u4ee3\u7801\u3002<\/p>\n<ol start=\"5\">\n<li>Let\u2019s put the following code after the builder initialization in the Program.cs file:<br \/>\n\u8ba9\u6211\u4eec\u5728 builder \u521d\u59cb\u5316\u540e\u5c06\u4ee5\u4e0b\u4ee3\u7801\u653e\u5165 Program.cs \u6587\u4ef6\u4e2d\uff1a<\/p>\n<pre><code>builder.Services.AddSingleton&lt;DapperContext&gt;();\nbuilder.Services.AddScoped&lt;IIcecreamsRepository, IcecreamsRepository&gt;();<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>Now we are ready to implement the first query.<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u51c6\u5907\u597d\u5b9e\u73b0\u7b2c\u4e00\u4e2a\u67e5\u8be2\u3002<\/p>\n<p>Using Dapper to query the database<br \/>\n\u4f7f\u7528 Dapper \u67e5\u8be2\u6570\u636e\u5e93<\/p>\n<p>First of all, let\u2019s modify the IIcecreamsRepository interface by adding a new method:<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u901a\u8fc7\u6dfb\u52a0\u65b0\u65b9\u6cd5\u6765\u4fee\u6539 IIcecreamsRepository \u63a5\u53e3\uff1a<\/p>\n<pre><code>public Task&lt;IEnumerable&lt;Icecream&gt;&gt; GetIcecreams();<\/code><\/pre>\n<p>Then, let\u2019s implement this method in the IcecreamsRepository class:<br \/>\n\u7136\u540e\uff0c\u8ba9\u6211\u4eec\u5728 IcecreamsRepository \u7c7b\u4e2d\u5b9e\u73b0\u6b64\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public async Task&lt;IEnumerable&lt;Icecream&gt;&gt; GetIcecreams()\n{\n    var query = &quot;SELECT * FROM Icecreams&quot;;\n    using (var connection = _context.CreateConnection())\n    {\n        var result = \n          await connection.QueryAsync&lt;Icecream&gt;(query);\n        return result.ToList();\n    }\n}<\/code><\/pre>\n<p>Let\u2019s try to understand all the steps in this method. We created a string called query, where we store the SQL query to fetch all the entities from the database.<br \/>\n\u8ba9\u6211\u4eec\u5c1d\u8bd5\u4e86\u89e3\u6b64\u65b9\u6cd5\u4e2d\u7684\u6240\u6709\u6b65\u9aa4\u3002\u6211\u4eec\u521b\u5efa\u4e86\u4e00\u4e2a\u540d\u4e3a query \u7684\u5b57\u7b26\u4e32\uff0c\u6211\u4eec\u5728\u5176\u4e2d\u5b58\u50a8 SQL \u67e5\u8be2\u4ee5\u4ece\u6570\u636e\u5e93\u4e2d\u83b7\u53d6\u6240\u6709\u5b9e\u4f53\u3002<\/p>\n<p>Then, inside the using statement, we used DapperContext to create the connection.<br \/>\n\u7136\u540e\uff0c\u5728 using \u8bed\u53e5\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528 DapperContext \u521b\u5efa\u8fde\u63a5\u3002<\/p>\n<p>Once the connection was created, we used it to call the QueryAsync method and passed the query as an argument.<br \/>\n\u521b\u5efa\u8fde\u63a5\u540e\uff0c\u6211\u4eec\u4f7f\u7528\u5b83\u6765\u8c03\u7528 QueryAsync \u65b9\u6cd5\u5e76\u5c06\u67e5\u8be2\u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u3002<\/p>\n<p>Dapper, when the results return from the database, converted them into <code>IEnumerable&lt;T&gt;<\/code> automatically.<br \/>\n\u5f53\u7ed3\u679c\u4ece\u6570\u636e\u5e93\u8fd4\u56de\u65f6\uff0cDapper \u4f1a\u81ea\u52a8\u5c06\u5b83\u4eec\u8f6c\u6362\u4e3a <code>IEnumerable&lt;T&gt;<\/code>\u3002<\/p>\n<p>The following is the final code of the interface and our first implementation:<br \/>\n\u4ee5\u4e0b\u662f\u63a5\u53e3\u7684\u6700\u7ec8\u4ee3\u7801\u548c\u6211\u4eec\u7684\u7b2c\u4e00\u4e2a\u5b9e\u73b0\uff1a<\/p>\n<pre><code>public interface IIcecreamsRepository\n{\n    public Task&lt;IEnumerable&lt;Icecream&gt;&gt; GetIcecreams();\n}\npublic class IcecreamsRepository : IIcecreamsRepository\n{\n    private readonly DapperContext _context;\n    public IcecreamsRepository(DapperContext context)\n    {\n        _context = context;\n    }\n    public async Task&lt;IEnumerable&lt;Icecream&gt;&gt; GetIcecreams()\n    {\n        var query = &quot;SELECT * FROM Icecreams&quot;;\n        using (var connection =\n              _context.CreateConnection())\n        {\n            var result = \n              await connection.QueryAsync&lt;Icecream&gt;(query);\n            return result.ToList();\n        }\n    }\n}<\/code><\/pre>\n<p>In the next section, we will see how to add a new entity to the database and how to use the ExecuteAsync method to run a query.<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u5411\u6570\u636e\u5e93\u6dfb\u52a0\u65b0\u5b9e\u4f53\uff0c\u4ee5\u53ca\u5982\u4f55\u4f7f\u7528 ExecuteAsync \u65b9\u6cd5\u8fd0\u884c\u67e5\u8be2\u3002<\/p>\n<p>Adding a new entity in the database with Dapper<br \/>\n\u4f7f\u7528 Dapper \u5728\u6570\u636e\u5e93\u4e2d\u6dfb\u52a0\u65b0\u5b9e\u4f53<\/p>\n<p>Now we are going to manage adding a new entity to the database for future implementations of the API post request.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5c06\u7ba1\u7406\u5411\u6570\u636e\u5e93\u6dfb\u52a0\u65b0\u5b9e\u4f53\uff0c\u4ee5\u4fbf\u5c06\u6765\u5b9e\u73b0 API post \u8bf7\u6c42\u3002<\/p>\n<p>Let\u2019s modify the interface by adding a new method called CreateIcecream with an input parameter of the Icecream type:<br \/>\n\u8ba9\u6211\u4eec\u901a\u8fc7\u6dfb\u52a0\u4e00\u4e2a\u540d\u4e3a CreateIcecream \u7684\u65b0\u65b9\u6cd5\u6765\u4fee\u6539\u63a5\u53e3\uff0c\u8be5\u65b9\u6cd5\u7684\u8f93\u5165\u53c2\u6570\u4e3a Icecream \u7c7b\u578b\uff1a<\/p>\n<pre><code>public Task CreateIcecream(Icecream icecream);<\/code><\/pre>\n<p>Now we must implement this method in the repository class:<br \/>\n\u73b0\u5728\u6211\u4eec\u5fc5\u987b\u5728 repository \u7c7b\u4e2d\u5b9e\u73b0\u6b64\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public async Task CreateIcecream(Icecream icecream)\n{\n    var query = &quot;INSERT INTO Icecreams (Name, Description)\n      VALUES (@Name, @Description)&quot;;\n    var parameters = new DynamicParameters();\n    parameters.Add(&quot;Name&quot;, icecream.Name, DbType.String);\n    parameters.Add(&quot;Description&quot;, icecream.Description,\n                    DbType.String);\n    using (var connection = _context.CreateConnection())\n    {\n        await connection.ExecuteAsync(query, parameters);\n    }\n}<\/code><\/pre>\n<p>Here, we create the query and a dynamic parameters object to pass all the values to the database.<br \/>\n\u5728\u8fd9\u91cc\uff0c\u6211\u4eec\u521b\u5efa\u67e5\u8be2\u548c\u52a8\u6001\u53c2\u6570\u5bf9\u8c61\uff0c\u4ee5\u5c06\u6240\u6709\u503c\u4f20\u9012\u7ed9\u6570\u636e\u5e93\u3002<\/p>\n<p>We populate the parameters with the values from the Icecream object in the method parameter.<br \/>\n\u6211\u4eec\u5728 method \u53c2\u6570\u4e2d\u4f7f\u7528 Icecream \u5bf9\u8c61\u7684\u503c\u586b\u5145\u53c2\u6570\u3002<\/p>\n<p>We create the connection with the Dapper context and then we use the ExecuteAsync method to execute the INSERT statement.<br \/>\n\u6211\u4eec\u4f7f\u7528 Dapper \u4e0a\u4e0b\u6587\u521b\u5efa\u8fde\u63a5\uff0c\u7136\u540e\u4f7f\u7528 ExecuteAsync \u65b9\u6cd5\u6267\u884c INSERT \u8bed\u53e5\u3002<\/p>\n<p>This method returns an integer value as a result, representing the number of affected rows in the database. In this case, we don\u2019t use this information, but you can return this value as the result of the method if you need it.<br \/>\n\u6b64\u65b9\u6cd5\u8fd4\u56de\u4e00\u4e2a\u6574\u6570\u503c\u4f5c\u4e3a\u7ed3\u679c\uff0c\u8be5\u503c\u8868\u793a\u6570\u636e\u5e93\u4e2d\u53d7\u5f71\u54cd\u7684\u884c\u6570\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u4e0d\u4f1a\u4f7f\u7528\u6b64\u4fe1\u606f\uff0c\u4f46\u5982\u679c\u9700\u8981\uff0c\u53ef\u4ee5\u5c06\u6b64\u503c\u4f5c\u4e3a\u65b9\u6cd5\u7684\u7ed3\u679c\u8fd4\u56de\u3002<\/p>\n<p>Implementing the repository in the endpoints<br \/>\n\u5728\u7aef\u70b9\u4e2d\u5b9e\u65bd\u5b58\u50a8\u5e93<\/p>\n<p>To add the final touch to our minimal API, we need to implement the two endpoints to manage all the methods in our repository pattern:<br \/>\n\u4e3a\u4e86\u5bf9\u6211\u4eec\u7684\u6700\u5c0f API \u8fdb\u884c\u6700\u540e\u7684\u6da6\u8272\uff0c\u6211\u4eec\u9700\u8981\u5b9e\u73b0\u4e24\u4e2a\u7aef\u70b9\u6765\u7ba1\u7406\u5b58\u50a8\u5e93\u6a21\u5f0f\u4e2d\u7684\u6240\u6709\u65b9\u6cd5\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/icecreams&quot;, async (IIcecreamsRepository repository, Icecream icecream) =&gt;\n{\n    await repository.CreateIcecream(icecream);\n    return Results.Ok();\n});\napp.MapGet(&quot;\/icecreams&quot;, async (IIcecreamsRepository repository) =&gt; await repository.GetIcecreams());<\/code><\/pre>\n<p>In both map methods, we pass the repository as a parameter because, as usual in the minimal API, the services are passed as parameters in the map methods.<br \/>\n\u5728\u8fd9\u4e24\u79cd map \u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u90fd\u5c06\u5b58\u50a8\u5e93\u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\uff0c\u56e0\u4e3a\u4e0e\u6700\u5c0f API \u4e00\u6837\uff0c\u670d\u52a1\u5728 map \u65b9\u6cd5\u4e2d\u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u3002<\/p>\n<p>This means that the repository is always available in all parts of the code.<br \/>\n\u8fd9\u610f\u5473\u7740\u5b58\u50a8\u5e93\u5728\u4ee3\u7801\u7684\u6240\u6709\u90e8\u5206\u4e2d\u59cb\u7ec8\u53ef\u7528\u3002<\/p>\n<p>In the MapGet endpoint, we use the repository to load all the entities from the implementation of the repository and we use the result as the result of the endpoint.<br \/>\n\u5728 MapGet \u7aef\u70b9\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528\u5b58\u50a8\u5e93\u52a0\u8f7d\u5b58\u50a8\u5e93\u5b9e\u73b0\u4e2d\u7684\u6240\u6709\u5b9e\u4f53\uff0c\u5e76\u5c06\u7ed3\u679c\u7528\u4f5c\u7aef\u70b9\u7684\u7ed3\u679c\u3002<\/p>\n<p>In the MapPost endpoint, in addition to the repository parameter, we accept also the Icecream entity from the body of the request and we use the same entity as a parameter to the CreateIcecream method of the repository.<br \/>\n\u5728 MapPost \u7ec8\u7aef\u8282\u70b9\u4e2d\uff0c\u9664\u4e86\u5b58\u50a8\u5e93\u53c2\u6570\u4e4b\u5916\uff0c\u6211\u4eec\u8fd8\u63a5\u53d7\u8bf7\u6c42\u6b63\u6587\u4e2d\u7684 Icecream \u5b9e\u4f53\uff0c\u5e76\u5c06\u540c\u4e00\u5b9e\u4f53\u7528\u4f5c\u5b58\u50a8\u5e93\u7684 CreateIcecream \u65b9\u6cd5\u7684\u53c2\u6570\u3002<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>In this chapter, we learned how to interact with a data access layer in a minimal API project with the two most common tools in a real-world scenario: EF and Dapper.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5b66\u4e60\u4e86\u5982\u4f55\u4f7f\u7528\u5b9e\u9645\u573a\u666f\u4e2d\u6700\u5e38\u7528\u7684\u4e24\u79cd\u5de5\u5177\uff08EF \u548c Dapper\uff09\u4e0e\u6700\u5c0f API \u9879\u76ee\u4e2d\u7684\u6570\u636e\u8bbf\u95ee\u5c42\u8fdb\u884c\u4ea4\u4e92\u3002<\/p>\n<p>For EF, we covered some basic features, such as setting up a project to use this ORM and how to perform some basic operations to implement a full CRUD API endpoint.<br \/>\n\u5bf9\u4e8e EF\uff0c\u6211\u4eec\u4ecb\u7ecd\u4e86\u4e00\u4e9b\u57fa\u672c\u529f\u80fd\uff0c\u4f8b\u5982\u8bbe\u7f6e\u9879\u76ee\u4ee5\u4f7f\u7528\u6b64 ORM\uff0c\u4ee5\u53ca\u5982\u4f55\u6267\u884c\u4e00\u4e9b\u57fa\u672c\u4f5c\u6765\u5b9e\u73b0\u5b8c\u6574\u7684 CRUD API \u7ec8\u7aef\u8282\u70b9\u3002<\/p>\n<p>We did basically the same thing with Dapper as well, starting from an empty project, adding Dapper, setting up the project for working with a SQL Server LocalDB, and implementing some basic interactions with the entities of the database.<br \/>\n\u6211\u4eec\u5bf9 Dapper \u4e5f\u505a\u4e86\u57fa\u672c\u76f8\u540c\u7684\u4f5c\uff0c\u4ece\u4e00\u4e2a\u7a7a\u9879\u76ee\u5f00\u59cb\uff0c\u6dfb\u52a0 Dapper\uff0c\u8bbe\u7f6e\u9879\u76ee\u4ee5\u4f7f\u7528 SQL Server LocalDB\uff0c\u5e76\u5b9e\u73b0\u4e0e\u6570\u636e\u5e93\u5b9e\u4f53\u7684\u4e00\u4e9b\u57fa\u672c\u4ea4\u4e92\u3002<\/p>\n<p>In the next chapter, we\u2019ll focus on authentication and authorization in a minimal API project. It\u2019s important, first of all, to protect your data in the database.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u91cd\u70b9\u4ecb\u7ecd\u6700\u5c0f API \u9879\u76ee\u4e2d\u7684\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u3002\u9996\u5148\uff0c\u4fdd\u62a4\u6570\u636e\u5e93\u4e2d\u7684\u6570\u636e\u5f88\u91cd\u8981\u3002<\/p>\n<h1>Part 3: Advanced Development and Microservices Concepts<\/h1>\n<p>\u7b2c 3 \u90e8\u5206\uff1a\u9ad8\u7ea7\u5f00\u53d1\u548c\u5fae\u670d\u52a1\u6982\u5ff5<\/p>\n<p>In this advanced section of the book, we want to show more scenarios that are typical in backend development. We will also go over the performance of this new framework and understand the scenarios in which it is really useful.<br \/>\n\u5728\u672c\u4e66\u7684\u8fd9\u4e2a\u9ad8\u7ea7\u90e8\u5206\uff0c\u6211\u4eec\u60f3\u5c55\u793a\u66f4\u591a\u540e\u7aef\u5f00\u53d1\u4e2d\u7684\u5178\u578b\u573a\u666f\u3002\u6211\u4eec\u8fd8\u5c06\u4ecb\u7ecd\u8fd9\u4e2a\u65b0\u6846\u67b6\u7684\u6027\u80fd\uff0c\u5e76\u4e86\u89e3\u5b83\u771f\u6b63\u6709\u7528\u7684\u573a\u666f\u3002<\/p>\n<p>We will cover the following chapters in this section:<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u7ae0\u8282\uff1a<\/p>\n<p>Chapter 8, Adding Authentication and Authorization<br \/>\n\u7b2c 8 \u7ae0 \u6dfb\u52a0\u9a8c\u8bc1\u548c\u6388\u6743<\/p>\n<p>Chapter 9, Leveraging Globalization and Localization<br \/>\n\u7b2c 9 \u7ae0 \u5229\u7528\u5168\u7403\u5316\u548c\u672c\u5730\u5316<\/p>\n<p>Chapter 10, Evaluating and Benchmarking the Performance of Minimal APIs<br \/>\n\u7b2c 10 \u7ae0 \u8bc4\u4f30\u6700\u5c0f API \u7684\u6027\u80fd\u5e76\u5bf9\u5176\u8fdb\u884c\u57fa\u51c6\u6d4b\u8bd5<\/p>\n<h1>8 Adding Authentication and Authorization<\/h1>\n<p>8 \u6dfb\u52a0\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743<\/p>\n<p>Any kind of application must deal with authentication and authorization. Often, these terms are used interchangeably, but they actually refer to different scenarios. In this chapter of the book, we will explain the difference between authentication and authorization and show how to add these features to a minimal API project.<br \/>\n\u4efb\u4f55\u7c7b\u578b\u7684\u5e94\u7528\u7a0b\u5e8f\u90fd\u5fc5\u987b\u5904\u7406\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u3002\u901a\u5e38\uff0c\u8fd9\u4e9b\u672f\u8bed\u53ef\u4ee5\u4e92\u6362\u4f7f\u7528\uff0c\u4f46\u5b83\u4eec\u5b9e\u9645\u4e0a\u6307\u7684\u662f\u4e0d\u540c\u7684\u573a\u666f\u3002\u5728\u672c\u4e66\u7684\u8fd9\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u89e3\u91ca\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u4e4b\u95f4\u7684\u533a\u522b\uff0c\u5e76\u5c55\u793a\u5982\u4f55\u5c06\u8fd9\u4e9b\u529f\u80fd\u6dfb\u52a0\u5230\u6700\u5c0f\u7684 API \u9879\u76ee\u4e2d\u3002<\/p>\n<p>Authentication can be performed in many different ways: using local accounts with external login providers, such as Microsoft, Google, Facebook, and Twitter; using Azure Active Directory and Azure B2C; and using authentication servers such as Identity Server and Okta. Moreover, we may have to deal with requirements such as two-factor authentication and refresh tokens. In this chapter, however, we will focus on the general aspects of authentication and authorization and see how to implement them in a minimal API project, in order to provide a general understanding of the topic. The information and samples that will be provided will show how to effectively work with authentication and authorization and how to customize their behaviors according to our requirements.<br \/>\n\u53ef\u4ee5\u901a\u8fc7\u591a\u79cd\u4e0d\u540c\u7684\u65b9\u5f0f\u6267\u884c\u8eab\u4efd\u9a8c\u8bc1\uff1a\u4f7f\u7528\u5916\u90e8\u767b\u5f55\u63d0\u4f9b\u7a0b\u5e8f\uff08\u5982 Microsoft\u3001Google\u3001Facebook \u548c Twitter\uff09\u7684\u672c\u5730\u5e10\u6237;\u4f7f\u7528 Azure Active Directory \u548c Azure B2C;\u4ee5\u53ca\u4f7f\u7528 Identity Server \u548c Okta \u7b49\u8eab\u4efd\u9a8c\u8bc1\u670d\u52a1\u5668\u3002\u6b64\u5916\uff0c\u6211\u4eec\u53ef\u80fd\u5fc5\u987b\u5904\u7406\u53cc\u91cd\u8eab\u4efd\u9a8c\u8bc1\u548c\u5237\u65b0\u4ee4\u724c\u7b49\u8981\u6c42\u3002\u4f46\u662f\uff0c\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u91cd\u70b9\u4ecb\u7ecd\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u7684\u4e00\u822c\u65b9\u9762\uff0c\u5e76\u4e86\u89e3\u5982\u4f55\u5728\u6700\u5c0f\u7684 API \u9879\u76ee\u4e2d\u5b9e\u73b0\u5b83\u4eec\uff0c\u4ee5\u4fbf\u5bf9\u8be5\u4e3b\u9898\u6709\u4e00\u4e2a\u5927\u81f4\u7684\u7406\u89e3\u3002\u5c06\u63d0\u4f9b\u7684\u4fe1\u606f\u548c\u793a\u4f8b\u5c06\u5c55\u793a\u5982\u4f55\u6709\u6548\u5730\u4f7f\u7528\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\uff0c\u4ee5\u53ca\u5982\u4f55\u6839\u636e\u6211\u4eec\u7684\u8981\u6c42\u81ea\u5b9a\u4e49\u5b83\u4eec\u7684\u884c\u4e3a\u3002<\/p>\n<p>In this chapter, we will be covering the following topics:<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u4e3b\u9898\uff1a<\/p>\n<p>\u2022  Introducing authentication and authorization<br \/>\n\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u7b80\u4ecb<\/p>\n<p>\u2022  Protecting a minimal API<br \/>\n\u4fdd\u62a4\u6700\u5c0f API<\/p>\n<p>\u2022  Handling authorization \u2013 roles and policies<br \/>\n\u5904\u7406\u6388\u6743 \u2013 \u89d2\u8272\u548c\u7b56\u7565<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>To follow the examples in this chapter, you will need to create an ASP.NET Core 6.0 Web API application. Refer to the Technical requirements section in Chapter 2, Exploring Minimal APIs and Their Advantages, for instructions on how to do so.<br \/>\n\u8981\u9075\u5faa\u672c\u7ae0\u4e2d\u7684\u793a\u4f8b\uff0c\u60a8\u9700\u8981\u521b\u5efa\u4e00\u4e2a ASP.NET Core 6.0 Web API \u5e94\u7528\u7a0b\u5e8f\u3002\u6709\u5173\u5982\u4f55\u6267\u884c\u6b64\u4f5c\u7684\u8bf4\u660e\uff0c\u8bf7\u53c2\u9605\u7b2c 2 \u7ae0 \u201c\u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf\u201d\u4e2d\u7684\u201c\u6280\u672f\u8981\u6c42\u201d\u90e8\u5206\u3002<\/p>\n<p>If you\u2019re using your console, shell, or Bash terminal to create the API, remember to change your working directory to the current chapter number: Chapter08.<br \/>\n\u5982\u679c\u60a8\u4f7f\u7528\u63a7\u5236\u53f0\u3001shell \u6216 Bash \u7ec8\u7aef\u521b\u5efa API\uff0c\u8bf7\u8bb0\u4f4f\u5c06\u5de5\u4f5c\u76ee\u5f55\u66f4\u6539\u4e3a\u5f53\u524d\u7ae0\u8282\u7f16\u53f7\uff1aChapter08\u3002<\/p>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter08\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter08<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter08\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter08<\/a>\u3002<\/p>\n<p>Introducing authentication and authorization<br \/>\n\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u7b80\u4ecb<\/p>\n<p>As said at the beginning, the terms authentication and authorization are often used interchangeably, but they represent different security functions. Authentication is the process of verifying that users are who they say they are, while authorization is the task of granting an authenticated user permission to do something. So, authorization must always follow authentication.<br \/>\n\u5982\u5f00\u5934\u6240\u8ff0\uff0c\u672f\u8bed authentication \u548c authorization \u7ecf\u5e38\u4e92\u6362\u4f7f\u7528\uff0c\u4f46\u5b83\u4eec\u4ee3\u8868\u4e0d\u540c\u7684\u5b89\u5168\u529f\u80fd\u3002\u8eab\u4efd\u9a8c\u8bc1\u662f\u9a8c\u8bc1\u7528\u6237\u662f\u5426\u662f\u4ed6\u4eec\u6240\u58f0\u79f0\u7684\u8eab\u4efd\u7684\u8fc7\u7a0b\uff0c\u800c\u6388\u6743\u662f\u6388\u4e88\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237\u6267\u884c\u67d0\u9879\u4f5c\u7684\u6743\u9650\u7684\u4efb\u52a1\u3002\u56e0\u6b64\uff0c\u6388\u6743\u5fc5\u987b\u59cb\u7ec8\u9075\u5faa\u8eab\u4efd\u9a8c\u8bc1\u3002<\/p>\n<p>Let\u2019s think about the security in an airport: first, you show your ID to authenticate your identity; then, at the gate, you present the boarding pass to be authorized to board the flight and get access to the plane.<br \/>\n\u8ba9\u6211\u4eec\u8003\u8651\u4e00\u4e0b\u673a\u573a\u7684\u5b89\u68c0\uff1a\u9996\u5148\uff0c\u60a8\u51fa\u793a\u60a8\u7684\u8eab\u4efd\u8bc1\u4ee5\u9a8c\u8bc1\u60a8\u7684\u8eab\u4efd;\u7136\u540e\uff0c\u5728\u767b\u673a\u53e3\uff0c\u60a8\u51fa\u793a\u767b\u673a\u724c\u4ee5\u83b7\u5f97\u767b\u673a\u548c\u767b\u673a\u6743\u3002<\/p>\n<p>Authentication and authorization in ASP.NET Core are handled by corresponding middleware and work in the same way in minimal APIs and controller-based projects. They allow the restriction of access to endpoints depending on user identity, roles, policies, and so on, as we\u2019ll see in detail in the following sections.<br \/>\nASP.NET Core \u4e2d\u7684\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u7531\u76f8\u5e94\u7684\u4e2d\u95f4\u4ef6\u5904\u7406\uff0c\u5e76\u4e14\u5728\u6700\u5c0f API \u548c\u57fa\u4e8e\u63a7\u5236\u5668\u7684\u9879\u76ee\u4e2d\u4ee5\u76f8\u540c\u7684\u65b9\u5f0f\u5de5\u4f5c\u3002\u5b83\u4eec\u5141\u8bb8\u6839\u636e\u7528\u6237\u8eab\u4efd\u3001\u89d2\u8272\u3001\u7b56\u7565\u7b49\u9650\u5236\u5bf9\u7ec8\u7aef\u8282\u70b9\u7684\u8bbf\u95ee\uff0c\u6211\u4eec\u5c06\u5728\u4ee5\u4e0b\u90e8\u5206\u4e2d\u8be6\u7ec6\u4ecb\u7ecd\u3002<\/p>\n<p>You can find a great overview of ASP.NET Core authentication and authorization in the official documentation available at <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication<\/a> and <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authorization\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authorization<\/a>.<br \/>\n\u60a8\u53ef\u4ee5\u5728 <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication<\/a> \u548c <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authorization\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authorization<\/a> \u4e0a\u63d0\u4f9b\u7684\u5b98\u65b9\u6587\u6863\u4e2d\u627e\u5230 ASP.NET Core \u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u7684\u7cbe\u5f69\u6982\u8ff0\u3002<\/p>\n<p>Protecting a minimal API<br \/>\n\u4fdd\u62a4\u6700\u5c0f API<\/p>\n<p>Protecting a minimal API means correctly setting up authentication and authorization. There are many types of authentication solutions that are adopted in modern applications. In web applications, we typically use cookies, while when dealing with web APIs, we use methods such as an API key, basic authentication, and JSON Web Token (JWT). JWTs are the most commonly used, and in the rest of the chapter, we\u2019ll focus on this solution.<br \/>\n\u4fdd\u62a4\u6700\u5c0f API \u610f\u5473\u7740\u6b63\u786e\u8bbe\u7f6e\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u3002\u73b0\u4ee3\u5e94\u7528\u7a0b\u5e8f\u4e2d\u91c7\u7528\u7684\u8eab\u4efd\u9a8c\u8bc1\u89e3\u51b3\u65b9\u6848\u6709\u591a\u79cd\u7c7b\u578b\u3002\u5728 Web \u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u6211\u4eec\u901a\u5e38\u4f7f\u7528 cookie\uff0c\u800c\u5728\u5904\u7406 Web API \u65f6\uff0c\u6211\u4eec\u4f7f\u7528 API \u5bc6\u94a5\u3001\u57fa\u672c\u8eab\u4efd\u9a8c\u8bc1\u548c JSON Web \u4ee4\u724c \uff08JWT\uff09 \u7b49\u65b9\u6cd5\u3002JWT \u662f\u6700\u5e38\u7528\u7684\uff0c\u5728\u672c\u7ae0\u7684\u5176\u4f59\u90e8\u5206\uff0c\u6211\u4eec\u5c06\u91cd\u70b9\u4ecb\u7ecd\u6b64\u89e3\u51b3\u65b9\u6848\u3002<\/p>\n<p>Note : A good starting point to understand what JWTs are and how they are used is available at <a href=\"https:\/\/jwt.io\/introduction\">https:\/\/jwt.io\/introduction<\/a>.<br \/>\n\u6ce8\u610f : \u4e86\u89e3 JWT \u662f\u4ec0\u4e48\u4ee5\u53ca\u5982\u4f55\u4f7f\u7528 JWT \u7684\u826f\u597d\u8d77\u70b9\u4f4d\u4e8e <a href=\"https:\/\/jwt.io\/introduction\">https:\/\/jwt.io\/introduction<\/a>\u3002<\/p>\n<p>To enable authentication and authorization based on JWT, the first thing to do is to add the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package to our project, using one of the following ways:<br \/>\n\u8981\u542f\u7528\u57fa\u4e8e JWT \u7684\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\uff0c\u9996\u5148\u8981\u505a\u7684\u662f\u4f7f\u7528\u4ee5\u4e0b\u65b9\u6cd5\u4e4b\u4e00\u5c06 Microsoft.AspNetCore.Authentication.JwtBearer NuGet \u5305\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u9879\u76ee\u4e2d\uff1a<\/p>\n<p>\u2022  Option 1: If you\u2019re using Visual Studio 2022, right-click on the project and choose the Manage NuGet Packages command to open Package Manager GUI, then search for Microsoft.AspNetCore.Authentication.JwtBearer and click on Install.<br \/>\n\u9009\u9879 1\uff1a\u5982\u679c\u60a8\u4f7f\u7528\u7684\u662f Visual Studio 2022\uff0c\u8bf7\u53f3\u952e\u5355\u51fb\u9879\u76ee\u5e76\u9009\u62e9\u201c\u7ba1\u7406 NuGet \u5305\u201d\u547d\u4ee4\u4ee5\u6253\u5f00\u5305\u7ba1\u7406\u5668 GUI\uff0c\u7136\u540e\u641c\u7d22 Microsoft.AspNetCore.Authentication.JwtBearer \u5e76\u5355\u51fb\u201c\u5b89\u88c5\u201d\u3002<\/p>\n<p>\u2022  Option 2: Open Package Manager Console if you\u2019re inside Visual Studio 2022, or open your console, shell, or Bash terminal, go to your project directory, and execute the following command:<br \/>\n\u9009\u9879 2\uff1a\u5982\u679c\u60a8\u5728 Visual Studio 2022 \u4e2d\uff0c\u8bf7\u6253\u5f00\u5305\u7ba1\u7406\u5668\u63a7\u5236\u53f0\uff0c\u6216\u8005\u6253\u5f00\u63a7\u5236\u53f0\u3001shell \u6216 Bash \u7ec8\u7aef\uff0c\u8f6c\u5230\u60a8\u7684\u9879\u76ee\u76ee\u5f55\uff0c\u7136\u540e\u6267\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a<br \/>\ndotnet add package Microsoft.AspNetCore.Authentication.JwtBearer<\/p>\n<p>Now, we need to add authentication and authorization services to the service provider, so that they are available through dependency injection:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u9700\u8981\u5411\u670d\u52a1\u63d0\u4f9b\u5546\u6dfb\u52a0\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u670d\u52a1\uff0c\u4ee5\u4fbf\u5b83\u4eec\u53ef\u4ee5\u901a\u8fc7\u4f9d\u8d56\u9879\u6ce8\u5165\u4f7f\u7528\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/...\nbuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();\nbuilder.Services.AddAuthorization();<\/code><\/pre>\n<p>This is the minimum code that is necessary to add JWT authentication and authorization support to an ASP.NET Core project. It isn\u2019t a real working solution yet, because it is missing the actual configuration, but it is enough to verify how endpoint protection works.<br \/>\n\u8fd9\u662f\u5411 ASP.NET Core \u9879\u76ee\u6dfb\u52a0 JWT \u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u652f\u6301\u6240\u9700\u7684\u6700\u5c11\u4ee3\u7801\u3002\u5b83\u8fd8\u4e0d\u662f\u4e00\u4e2a\u771f\u6b63\u7684\u6709\u6548\u89e3\u51b3\u65b9\u6848\uff0c\u56e0\u4e3a\u5b83\u7f3a\u5c11\u5b9e\u9645\u914d\u7f6e\uff0c\u4f46\u8db3\u4ee5\u9a8c\u8bc1 Endpoint Protection \u7684\u5de5\u4f5c\u539f\u7406\u3002<\/p>\n<p>In the AddAuthentication() method, we specify that we want to use the bearer authentication scheme. This is an HTTP authentication scheme that involves security tokens that are in fact called bearer tokens. These tokens must be sent in the Authorization HTTP header with the format Authorization: <code>Bearer &lt;token&gt;<\/code>. Then, we call AddJwtBearer() to tell ASP.NET Core that it must expect a bearer token in the JWT format. As we\u2019ll see later, the bearer token is an encoded string generated by the server in response to a login request. After that, we use AddAuthorization() to also add authorization services.<br \/>\n\u5728 AddAuthentication\uff08\uff09 \u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u6307\u5b9a\u8981\u4f7f\u7528\u4e0d\u8bb0\u540d\u8eab\u4efd\u9a8c\u8bc1\u65b9\u6848\u3002\u8fd9\u662f\u4e00\u79cd HTTP \u8eab\u4efd\u9a8c\u8bc1\u65b9\u6848\uff0c\u5b83\u6d89\u53ca\u5b9e\u9645\u4e0a\u79f0\u4e3a\u6301\u6709\u8005\u4ee4\u724c\u7684\u5b89\u5168\u4ee4\u724c\u3002\u8fd9\u4e9b\u4ee4\u724c\u5fc5\u987b\u5728 Authorization HTTP \u6807\u5934\u4e2d\u4ee5 Authorization\uff1a <code>Bearer &lt;token&gt;<\/code>\u683c\u5f0f\u53d1\u9001\u3002\u7136\u540e\uff0c\u6211\u4eec\u8c03\u7528 AddJwtBearer\uff08\uff09 \u6765\u544a\u8bc9 ASP.NET Core \u5b83\u5fc5\u987b\u9700\u8981 JWT \u683c\u5f0f\u7684\u4e0d\u8bb0\u540d\u4ee4\u724c\u3002\u6b63\u5982\u6211\u4eec\u7a0d\u540e\u5c06\u770b\u5230\u7684\uff0c\u6301\u6709\u8005\u4ee4\u724c\u662f\u670d\u52a1\u5668\u4e3a\u54cd\u5e94\u767b\u5f55\u8bf7\u6c42\u800c\u751f\u6210\u7684\u7f16\u7801\u5b57\u7b26\u4e32\u3002\u4e4b\u540e\uff0c\u6211\u4eec\u4f7f\u7528 AddAuthorization\uff08\uff09 \u4e5f\u6dfb\u52a0\u6388\u6743\u670d\u52a1\u3002<\/p>\n<p>Now, we need to insert authentication and authorization middleware in the pipeline so that ASP.NET Core will be instructed to check the token and apply all the authorization rules:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u9700\u8981\u5728\u7ba1\u9053\u4e2d\u63d2\u5165\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u4e2d\u95f4\u4ef6\uff0c\u4ee5\u4fbf\u6307\u793a ASP.NET Core \u68c0\u67e5\u4ee4\u724c\u5e76\u5e94\u7528\u6240\u6709\u6388\u6743\u89c4\u5219\uff1a<\/p>\n<pre><code>var app = builder.Build();\n\/\/..\napp.UseAuthentication();\napp.UseAuthorization();\n\/\/...\napp.Run();<\/code><\/pre>\n<p>Important Note : We have said that authorization must follow authentication. This means that the authentication middleware must come first; otherwise, the security will not work as expected.<br \/>\n\u91cd\u8981\u63d0\u793a : \u6211\u4eec\u5df2\u7ecf\u8bf4\u8fc7\uff0c\u6388\u6743\u5fc5\u987b\u5728\u8eab\u4efd\u9a8c\u8bc1\u4e4b\u540e\u8fdb\u884c\u3002\u8fd9\u610f\u5473\u7740\u8eab\u4efd\u9a8c\u8bc1\u4e2d\u95f4\u4ef6\u5fc5\u987b\u653e\u5728\u7b2c\u4e00\u4f4d;\u5426\u5219\uff0c\u5b89\u5168\u6027\u5c06\u65e0\u6cd5\u6309\u9884\u671f\u5de5\u4f5c\u3002<\/p>\n<p>Finally, we can protect our endpoints using the Authorize attribute or the RequireAuthorization() method:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 Authorize \u5c5e\u6027\u6216 RequireAuthorization\uff08\uff09 \u65b9\u6cd5\u4fdd\u62a4\u6211\u4eec\u7684\u7aef\u70b9\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/attribute-protected&quot;, [Authorize] () =&gt; &quot;This endpoint is protected using the Authorize attribute&quot;);\napp.MapGet(&quot;\/api\/method-protected&quot;, () =&gt; &quot;This endpoint is protected using the RequireAuthorization method&quot;)\n.RequireAuthorization();<\/code><\/pre>\n<p>Note : The ability to specify an attribute directly on a lambda expression (as in the first endpoint of the previous example) is a new feature of C# 10.<br \/>\n\u6ce8\u610f : \u76f4\u63a5\u5728 lambda \u8868\u8fbe\u5f0f\u4e0a\u6307\u5b9a\u5c5e\u6027\u7684\u529f\u80fd\uff08\u5982\u4e0a\u4e00\u4e2a\u793a\u4f8b\u7684\u7b2c\u4e00\u4e2a\u7ec8\u7ed3\u70b9\u6240\u793a\uff09\u662f C# 10 \u7684\u4e00\u9879\u65b0\u529f\u80fd\u3002<\/p>\n<p>If we now try to call each of these methods using Swagger, we\u2019ll get a 401 unauthorized response, which should look as follows:<br \/>\n\u5982\u679c\u6211\u4eec\u73b0\u5728\u5c1d\u8bd5\u4f7f\u7528 Swagger \u8c03\u7528\u8fd9\u4e9b\u65b9\u6cd5\u4e2d\u7684\u6bcf\u4e00\u4e2a\uff0c\u6211\u4eec\u5c06\u5f97\u5230\u4e00\u4e2a 401 \u672a\u6388\u6743\u7684\u54cd\u5e94\uff0c\u5b83\u5e94\u8be5\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0801.jpg\" > <\/p>\n<p>Figure 8.1 \u2013 Unauthorized response in Swagger<br \/>\n\u56fe 8.1 \u2013 Swagger \u4e2d\u672a\u7ecf\u6388\u6743\u7684\u54cd\u5e94<\/p>\n<p>Note that the message contains a header indicating that the expected authentication scheme is Bearer, as we have declared in the code.<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u8be5\u6d88\u606f\u5305\u542b\u4e00\u4e2a\u6807\u5934\uff0c\u6307\u793a\u9884\u671f\u7684\u8eab\u4efd\u9a8c\u8bc1\u65b9\u6848\u662f Bearer\uff0c\u6b63\u5982\u6211\u4eec\u5728\u4ee3\u7801\u4e2d\u58f0\u660e\u7684\u90a3\u6837\u3002<\/p>\n<p>So, now we know how to restrict access to our endpoints to authenticated users. But our work isn\u2019t finished: we need to generate a JWT bearer, validate it, and find a way to pass such a token to Swagger so that we can test our protected endpoints.<br \/>\n\u56e0\u6b64\uff0c\u73b0\u5728\u6211\u4eec\u77e5\u9053\u5982\u4f55\u5c06\u5bf9\u7ec8\u7aef\u8282\u70b9\u7684\u8bbf\u95ee\u9650\u5236\u4e3a\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237\u3002\u4f46\u6211\u4eec\u7684\u5de5\u4f5c\u8fd8\u6ca1\u6709\u5b8c\u6210\uff1a\u6211\u4eec\u9700\u8981\u751f\u6210\u4e00\u4e2a JWT bearer\uff0c\u9a8c\u8bc1\u5b83\uff0c\u5e76\u627e\u5230\u4e00\u79cd\u65b9\u6cd5\u5c06\u8fd9\u6837\u7684\u4ee4\u724c\u4f20\u9012\u7ed9 Swagger\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ef\u4ee5\u6d4b\u8bd5\u53d7\u4fdd\u62a4\u7684\u7aef\u70b9\u3002<\/p>\n<p>Generating a JWT bearer<br \/>\n\u751f\u6210 JWT \u6301\u6709\u8005<\/p>\n<p>We have said that a JWT bearer is generated by the server as a response to a login request. ASP.NET Core provides all the APIs we need to create it, so let\u2019s see how to perform this task.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u8bf4\u8fc7\uff0cJWT bearer \u662f\u7531\u670d\u52a1\u5668\u751f\u6210\u7684\uff0c\u4f5c\u4e3a\u5bf9\u767b\u5f55\u8bf7\u6c42\u7684\u54cd\u5e94\u3002ASP.NET Core \u63d0\u4f9b\u4e86\u521b\u5efa\u5b83\u6240\u9700\u7684\u6240\u6709 API\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u6267\u884c\u6b64\u4efb\u52a1\u3002<\/p>\n<p>The first thing to do is to define the login request endpoint to authenticate the user with their username and password:<br \/>\n\u9996\u5148\u8981\u505a\u7684\u662f\u5b9a\u4e49\u767b\u5f55\u8bf7\u6c42\u7aef\u70b9\uff0c\u4ee5\u4f7f\u7528\u7528\u6237\u7684\u7528\u6237\u540d\u548c\u5bc6\u7801\u5bf9\u7528\u6237\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/api\/auth\/login&quot;, (LoginRequest request) =&gt;\n{\n    if (request.Username == &quot;marco&quot; &amp;&amp; request.Password == \n        &quot;P@$$w0rd&quot;)\n    {\n        \/\/ Generate the JWT bearer...\n    }\n    return Results.BadRequest();\n});<\/code><\/pre>\n<p>For the sake of simplicity, in the preceding example, we have used hardcoded values, but in a real application, we\u2019d use, for example, ASP.NET Core Identity, the part of ASP.NET Core that is responsible for user management. More information on this topic is available in the official documentation at <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\/identity\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\/identity<\/a>.<br \/>\n\u4e3a\u7b80\u5355\u8d77\u89c1\uff0c\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528\u4e86\u786c\u7f16\u7801\u503c\uff0c\u4f46\u5728\u5b9e\u9645\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 ASP.NET Core Identity\uff0c\u8fd9\u662f Core \u4e2d\u8d1f\u8d23\u7528\u6237\u7ba1\u7406 ASP.NET \u90e8\u5206\u3002\u6709\u5173\u6b64\u4e3b\u9898\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\/identity\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\/identity<\/a> \u7684\u5b98\u65b9\u6587\u6863\u3002<\/p>\n<p>In a typical login workflow, if the credentials are invalid, we return a 400 Bad Request response to the client. If, instead, the username and password are correct, we can effectively generate a JWT bearer, using the classes available in ASP.NET Core:<br \/>\n\u5728\u5178\u578b\u7684\u767b\u5f55\u5de5\u4f5c\u6d41\u7a0b\u4e2d\uff0c\u5982\u679c\u51ed\u8bc1\u65e0\u6548\uff0c\u6211\u4eec\u4f1a\u5411\u5ba2\u6237\u7aef\u8fd4\u56de 400 Bad Request \u54cd\u5e94\u3002\u76f8\u53cd\uff0c\u5982\u679c\u7528\u6237\u540d\u548c\u5bc6\u7801\u6b63\u786e\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 ASP.NET Core \u4e2d\u53ef\u7528\u7684\u7c7b\u6709\u6548\u5730\u751f\u6210 JWT bearer\uff1a<\/p>\n<pre><code>var claims = new List&lt;Claim&gt;()\n{\n    new(ClaimTypes.Name, request.Username)\n};\nvar securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(&quot;mysecuritystring&quot;));\nvar credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);\nvar jwtSecurityToken = new JwtSecurityToken(\n    issuer: &quot;https:\/\/www.packtpub.com&quot;,\n    audience: &quot;Minimal APIs Client&quot;,\n    claims: claims, expires: DateTime.UtcNow.AddHours(1), \n      signingCredentials: credentials);\nvar accessToken = new JwtSecurityTokenHandler()\n  .WriteToken(jwtSecurityToken);\nreturn Results.Ok(new { AccessToken = accessToken });<\/code><\/pre>\n<p>JWT bearer creation involves many different concepts, but through the preceding code example, we\u2019ll focus on the basic ones. This kind of bearer contains information that allows verifying the user identity, along with other declarations that describe the properties of the user. These properties are called claims and are expressed as string key-value pairs. In the preceding code, we created a list with a single claim that contains the username. We can add as many claims as we need, and we can also have claims with the same name. In the next sections, we\u2019ll see how to use claims, for example, to enforce authorization.<br \/>\nJWT bearer \u521b\u5efa\u6d89\u53ca\u8bb8\u591a\u4e0d\u540c\u7684\u6982\u5ff5\uff0c\u4f46\u901a\u8fc7\u524d\u9762\u7684\u4ee3\u7801\u793a\u4f8b\uff0c\u6211\u4eec\u5c06\u91cd\u70b9\u4ecb\u7ecd\u57fa\u672c\u6982\u5ff5\u3002\u8fd9\u79cd\u7c7b\u578b\u7684 bearer \u5305\u542b\u5141\u8bb8\u9a8c\u8bc1\u7528\u6237\u8eab\u4efd\u7684\u4fe1\u606f\uff0c\u4ee5\u53ca\u63cf\u8ff0\u7528\u6237\u5c5e\u6027\u7684\u5176\u4ed6\u58f0\u660e\u3002\u8fd9\u4e9b\u5c5e\u6027\u79f0\u4e3a\u58f0\u660e\uff0c\u8868\u793a\u4e3a\u5b57\u7b26\u4e32\u952e\u503c\u5bf9\u3002\u5728\u524d\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u521b\u5efa\u4e86\u4e00\u4e2a\u5217\u8868\uff0c\u5176\u4e2d\u5305\u542b\u4e00\u4e2a\u5305\u542b\u7528\u6237\u540d\u7684\u58f0\u660e\u3002\u6211\u4eec\u53ef\u4ee5\u6839\u636e\u9700\u8981\u6dfb\u52a0\u4efb\u610f\u6570\u91cf\u7684\u58f0\u660e\uff0c\u4e5f\u53ef\u4ee5\u62e5\u6709\u5177\u6709\u76f8\u540c\u540d\u79f0\u7684\u58f0\u660e\u3002\u5728\u63a5\u4e0b\u6765\u7684\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u4f7f\u7528\u58f0\u660e\uff0c\u4f8b\u5982\uff0c\u5f3a\u5236\u5b9e\u65bd\u6388\u6743\u3002<\/p>\n<p>Next in the preceding code, we defined the credentials (SigningCredentials) to sign the JWT bearer. The signature depends on the actual token content and is used to check that the token hasn\u2019t been tampered with. In fact, if we change anything in the token, such as a claim value, the signature will consequentially change. As the key to sign the bearer is known only by the server, it is impossible for a third party to modify the token and sustain its validity. In the preceding code, we used SymmetricSecurityKey, which is never shared with clients.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u5728\u524d\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u5b9a\u4e49\u4e86\u51ed\u8bc1 \uff08SigningCredentials\uff09 \u6765\u5bf9 JWT \u6301\u6709\u8005\u8fdb\u884c\u7b7e\u540d\u3002\u7b7e\u540d\u53d6\u51b3\u4e8e\u5b9e\u9645\u7684 Token \u5185\u5bb9\uff0c\u7528\u4e8e\u68c0\u67e5 Token \u662f\u5426\u672a\u88ab\u7be1\u6539\u3002\u4e8b\u5b9e\u4e0a\uff0c\u5982\u679c\u6211\u4eec\u66f4\u6539 Token \u4e2d\u7684\u4efb\u4f55\u5185\u5bb9\uff0c\u4f8b\u5982\u58f0\u660e\u503c\uff0c\u7b7e\u540d\u4e5f\u4f1a\u968f\u4e4b\u66f4\u6539\u3002\u7531\u4e8e\u5bf9 bearer \u8fdb\u884c\u7b7e\u540d\u7684\u5bc6\u94a5\u53ea\u6709\u670d\u52a1\u5668\u77e5\u9053\uff0c\u56e0\u6b64\u7b2c\u4e09\u65b9\u65e0\u6cd5\u4fee\u6539 Token \u5e76\u7ef4\u6301\u5176\u6709\u6548\u6027\u3002\u5728\u4e0a\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528\u4e86 SymmetricSecurityKey\uff0c\u5b83\u6c38\u8fdc\u4e0d\u4f1a\u4e0e\u5ba2\u6237\u7aef\u5171\u4eab\u3002<\/p>\n<p>We used a short string to create the credentials, but the only requirement is that the key should be at least 32 bytes or 16 characters long. In .NET, strings are Unicode and therefore, each character takes 2 bytes. We also needed to set the algorithm that the credentials will use to sign the token. To this end, we have specified the Hash-Based Message Authentication Code (HMAC) and the hash function, SHA256, specifying the SecurityAlgorithms.HmacSha256 value. This algorithm is quite a common choice in these kinds of scenarios.<br \/>\n\u6211\u4eec\u4f7f\u7528\u4e86\u4e00\u4e2a\u77ed\u5b57\u7b26\u4e32\u6765\u521b\u5efa\u51ed\u8bc1\uff0c\u4f46\u552f\u4e00\u7684\u8981\u6c42\u662f\u5bc6\u94a5\u5e94\u81f3\u5c11\u4e3a 32 \u5b57\u8282\u6216 16 \u4e2a\u5b57\u7b26\u957f\u3002\u5728 .NET \u4e2d\uff0c\u5b57\u7b26\u4e32\u662f Unicode\uff0c\u56e0\u6b64\u6bcf\u4e2a\u5b57\u7b26\u5360\u7528 2 \u4e2a\u5b57\u8282\u3002\u6211\u4eec\u8fd8\u9700\u8981\u8bbe\u7f6e\u51ed\u8bc1\u5c06\u7528\u4e8e\u5bf9\u4ee4\u724c\u8fdb\u884c\u7b7e\u540d\u7684\u7b97\u6cd5\u3002\u4e3a\u6b64\uff0c\u6211\u4eec\u6307\u5b9a\u4e86\u57fa\u4e8e\u54c8\u5e0c\u7684\u6d88\u606f\u8eab\u4efd\u9a8c\u8bc1\u4ee3\u7801 \uff08HMAC\uff09 \u548c\u54c8\u5e0c\u51fd\u6570 SHA256\uff0c\u5e76\u6307\u5b9a\u4e86 SecurityAlgorithms.HmacSha256 \u503c\u3002\u5728\u8fd9\u7c7b\u573a\u666f\u4e2d\uff0c\u8fd9\u79cd\u7b97\u6cd5\u662f\u4e00\u4e2a\u975e\u5e38\u5e38\u89c1\u7684\u9009\u62e9\u3002<\/p>\n<p>Note : You can find more information about the HMAC and the SHA256 hash function at <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.security.cryptography.hmacsha256#remarks\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.security.cryptography.hmacsha256#remarks<\/a>.<br \/>\n\u6ce8\u610f : \u60a8\u53ef\u4ee5\u5728 <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.security.cryptography.hmacsha256#remarks\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.security.cryptography.hmacsha256#remarks<\/a> \u4e2d\u627e\u5230\u6709\u5173 HMAC \u548c SHA256 \u54c8\u5e0c\u51fd\u6570\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>By this point in the preceding code, we finally have all the information to create the token, so we can instantiate a JwtSecurityToken object. This class can use many parameters to build the token, but for the sake of simplicity, we have specified only the minimum set for a working example:<br \/>\n\u5728\u524d\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u5230\u8fd9\u4e00\u70b9\u65f6\uff0c\u6211\u4eec\u7ec8\u4e8e\u62e5\u6709\u4e86\u521b\u5efa\u4ee4\u724c\u7684\u6240\u6709\u4fe1\u606f\uff0c\u56e0\u6b64\u6211\u4eec\u53ef\u4ee5\u5b9e\u4f8b\u5316 JwtSecurityToken \u5bf9\u8c61\u3002\u8fd9\u4e2a\u7c7b\u53ef\u4ee5\u4f7f\u7528\u8bb8\u591a\u53c2\u6570\u6765\u6784\u5efa\u4ee4\u724c\uff0c\u4f46\u4e3a\u4e86\u7b80\u5355\u8d77\u89c1\uff0c\u6211\u4eec\u53ea\u4e3a\u5de5\u4f5c\u793a\u4f8b\u6307\u5b9a\u4e86\u6700\u5c0f\u96c6\uff1a<\/p>\n<p>Issuer: A string (typically a URI) that identifies the name of the entity that is creating the token<br \/>\n\u9881\u53d1\u8005\uff1a\u4e00\u4e2a\u5b57\u7b26\u4e32\uff08\u901a\u5e38\u662f URI\uff09\uff0c\u7528\u4e8e\u6807\u8bc6\u521b\u5efa\u4ee4\u724c\u7684\u5b9e\u4f53\u7684\u540d\u79f0<\/p>\n<p>Audience: The recipient that the JWT is intended for, that is, who can consume the token<br \/>\n\u53d7\u4f17\uff1aJWT \u7684\u76ee\u6807\u63a5\u6536\u8005\uff0c\u5373\u53ef\u4ee5\u4f7f\u7528\u4ee4\u724c\u7684\u7528\u6237<\/p>\n<p>The list of claims<br \/>\n\u7d22\u8d54\u5217\u8868<\/p>\n<p>The expiration time of the token (in UTC)<br \/>\nToken \u7684\u8fc7\u671f\u65f6\u95f4\uff08UTC \u5355\u4f4d\uff09<\/p>\n<p>The signing credentials<br \/>\n\u7b7e\u540d\u51ed\u8bc1<\/p>\n<p>Tip In the preceding code example, values used to build the token are hardcoded, but in a real-life application, we should place them in an external source, for example, in the appsettings.json configuration file.<br \/>\n\u63d0\u793a : \u5728\u524d\u9762\u7684\u4ee3\u7801\u793a\u4f8b\u4e2d\uff0c\u7528\u4e8e\u6784\u5efa\u4ee4\u724c\u7684\u503c\u662f\u786c\u7f16\u7801\u7684\uff0c\u4f46\u5728\u5b9e\u9645\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u6211\u4eec\u5e94\u8be5\u5c06\u5b83\u4eec\u653e\u5728\u5916\u90e8\u6e90\u4e2d\uff0c\u4f8b\u5982\uff0c\u5728 appsettings.json \u914d\u7f6e\u6587\u4ef6\u4e2d\u3002<\/p>\n<p>You can find further information on creating a token at <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.identitymodel.tokens.jwt.jwtsecuritytoken\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.identitymodel.tokens.jwt.jwtsecuritytoken<\/a>.<br \/>\n\u60a8\u53ef\u4ee5\u5728 <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.identitymodel.tokens.jwt.jwtsecuritytoken\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.identitymodel.tokens.jwt.jwtsecuritytoken<\/a> \u4e2d\u627e\u5230\u6709\u5173\u521b\u5efa\u4ee4\u724c\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>After all the preceding steps, we could create JwtSecurityTokenHandler, which is responsible for actually generating the bearer token and returning it to the caller with a 200 OK response.<br \/>\n\u5b8c\u6210\u4e0a\u8ff0\u6240\u6709\u6b65\u9aa4\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u521b\u5efa JwtSecurityTokenHandler\uff0c\u5b83\u8d1f\u8d23\u5b9e\u9645\u751f\u6210\u4e0d\u8bb0\u540d\u4ee4\u724c\u5e76\u5c06\u5176\u8fd4\u56de\u7ed9\u8c03\u7528\u65b9\uff0c\u5e76\u7ed9\u51fa 200 OK \u54cd\u5e94\u3002<\/p>\n<p>So, now we can try the login endpoint in Swagger. After inserting the correct username and password and clicking the Execute button, we will get the following response:<br \/>\n\u6240\u4ee5\uff0c\u73b0\u5728\u6211\u4eec\u53ef\u4ee5\u5c1d\u8bd5 Swagger \u4e2d\u7684\u767b\u5f55\u7aef\u70b9\u3002\u5728\u63d2\u5165\u6b63\u786e\u7684\u7528\u6237\u540d\u548c\u5bc6\u7801\u5e76\u5355\u51fb Execute \u6309\u94ae\u540e\uff0c\u6211\u4eec\u5c06\u5f97\u5230\u4ee5\u4e0b\u54cd\u5e94\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0802.jpg\" ><\/p>\n<p>Figure 8.2 \u2013 The JWT bearer as a result of the login request in Swagger<br \/>\n\u56fe 8.2 \u2013 Swagger \u4e2d\u767b\u5f55\u8bf7\u6c42\u7684\u7ed3\u679c JWT \u6301\u6709\u8005<\/p>\n<p>We can copy the token value and insert it in the URL of the site <a href=\"https:\/\/jwt.ms\">https:\/\/jwt.ms<\/a> to see what it contains. We\u2019ll get something like this:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u590d\u5236 token \u503c\u5e76\u5c06\u5176\u63d2\u5165\u5230\u7ad9\u70b9\u7684 URL \u4e2d <a href=\"https:\/\/jwt.ms\">https:\/\/jwt.ms<\/a> \u4ee5\u67e5\u770b\u5b83\u5305\u542b\u7684\u5185\u5bb9\u3002\u6211\u4eec\u5c06\u5f97\u5230\u5982\u4e0b\u7ed3\u679c\uff1a<\/p>\n<pre><code>{\n  &quot;alg&quot;: &quot;HS256&quot;,\n  &quot;typ&quot;: &quot;JWT&quot;\n}.{\n  &quot;http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/name&quot;: &quot;marco&quot;,\n  &quot;exp&quot;: 1644431527,\n  &quot;iss&quot;: &quot;https:\/\/www.packtpub.com&quot;,\n  &quot;aud&quot;: &quot;Minimal APIs Client&quot;\n}.[Signature]<\/code><\/pre>\n<p>In particular, we see the claims that have been configured:<br \/>\n\u5177\u4f53\u800c\u8a00\uff0c\u6211\u4eec\u4f1a\u770b\u5230\u5df2\u914d\u7f6e\u7684\u58f0\u660e\uff1a<\/p>\n<p>\u2022  name: The name of the logged user<br \/>\nname\uff1a\u5df2\u767b\u5f55\u7528\u6237\u7684\u540d\u79f0<\/p>\n<p>\u2022  exp: The token expiration time, expressed in Unix epoch<br \/>\nexp\uff1aToken \u8fc7\u671f\u65f6\u95f4\uff0c\u4ee5 Unix \u7eaa\u5143\u8868\u793a<\/p>\n<p>\u2022  iss: The issuer of the token<br \/>\niss\uff1a\u4ee4\u724c\u7684\u53d1\u884c\u8005<\/p>\n<p>\u2022  aud: The audience (receiver) of the token<br \/>\naud\uff1a\u4ee4\u724c\u7684\u53d7\u4f17\uff08\u63a5\u6536\u8005\uff09<\/p>\n<p>This is the raw view, but we can switch to the Claims tab to see the decoded list of all the claims, with a description of their meaning, where available.<br \/>\n\u8fd9\u662f\u539f\u59cb\u89c6\u56fe\uff0c\u4f46\u6211\u4eec\u53ef\u4ee5\u5207\u6362\u5230 Claims \u9009\u9879\u5361\uff0c\u67e5\u770b\u6240\u6709\u58f0\u660e\u7684\u89e3\u7801\u5217\u8868\uff0c\u4ee5\u53ca\u5176\u542b\u4e49\u7684\u63cf\u8ff0\uff08\u5982\u679c\u53ef\u7528\uff09\u3002<\/p>\n<p>There is one important point that requires attention: by default, the JWT bearer isn\u2019t encrypted (it\u2019s just a Base64-encoded string), so everyone can read its content. Token security does not depend on the inability to be decoded, but on the fact that it is signed. Even if the token\u2019s content is clear, it is impossible to modify it because in this case, the signature (which uses a key that is known only by the server) will become invalid.<br \/>\n\u6709\u4e00\u70b9\u9700\u8981\u6ce8\u610f\uff1a\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cJWT bearer \u672a\u52a0\u5bc6\uff08\u5b83\u53ea\u662f\u4e00\u4e2a Base64 \u7f16\u7801\u7684\u5b57\u7b26\u4e32\uff09\uff0c\u56e0\u6b64\u6bcf\u4e2a\u4eba\u90fd\u53ef\u4ee5\u8bfb\u53d6\u5176\u5185\u5bb9\u3002\u4ee4\u724c\u5b89\u5168\u6027\u4e0d\u53d6\u51b3\u4e8e\u65e0\u6cd5\u89e3\u7801\uff0c\u800c\u662f\u53d6\u51b3\u4e8e\u5b83\u662f\u5426\u5df2\u7b7e\u540d\u3002\u5373\u4f7f Token \u7684\u5185\u5bb9\u5f88\u6e05\u695a\uff0c\u4e5f\u65e0\u6cd5\u4fee\u6539\u5b83\uff0c\u56e0\u4e3a\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u7b7e\u540d\uff08\u4f7f\u7528\u53ea\u6709\u670d\u52a1\u5668\u77e5\u9053\u7684\u5bc6\u94a5\uff09\u5c06\u5931\u6548\u3002<\/p>\n<p>So, it\u2019s important not to insert sensitive data in the token; claims such as usernames, user IDs, and roles are usually fine, but, for example, we should not insert information related to privacy. To give a deliberately exaggerated example, we mustn\u2019t insert a credit card number in the token! In any case, keep in mind that even Microsoft for Azure Active Directory uses JWT, with no encryption, so we can trust this security system.<br \/>\n\u56e0\u6b64\uff0c\u4e0d\u8981\u5728\u4ee4\u724c\u4e2d\u63d2\u5165\u654f\u611f\u6570\u636e\u975e\u5e38\u91cd\u8981;\u7528\u6237\u540d\u3001\u7528\u6237 ID \u548c\u89d2\u8272\u7b49\u58f0\u660e\u901a\u5e38\u6ca1\u95ee\u9898\uff0c\u4f46\u4f8b\u5982\uff0c\u6211\u4eec\u4e0d\u5e94\u63d2\u5165\u4e0e\u9690\u79c1\u76f8\u5173\u7684\u4fe1\u606f\u3002\u4e3e\u4e00\u4e2a\u6545\u610f\u5938\u5927\u7684\u4f8b\u5b50\uff0c\u6211\u4eec\u4e0d\u80fd\u5728\u4ee4\u724c\u4e2d\u63d2\u5165\u4fe1\u7528\u5361\u53f7\uff01\u65e0\u8bba\u5982\u4f55\uff0c\u8bf7\u8bb0\u4f4f\uff0c\u5373\u4f7f\u662f Microsoft for Azure Active Directory \u4e5f\u4f7f\u7528 JWT\uff0c\u6ca1\u6709\u52a0\u5bc6\uff0c\u56e0\u6b64\u6211\u4eec\u53ef\u4ee5\u4fe1\u4efb\u8fd9\u4e2a\u5b89\u5168\u7cfb\u7edf\u3002<\/p>\n<p>In conclusion, we have described how to obtain a valid JWT. The next steps are to pass the token to our protected endpoints and instruct our minimal API on how to validate it.<br \/>\n\u603b\u4e4b\uff0c\u6211\u4eec\u5df2\u7ecf\u63cf\u8ff0\u4e86\u5982\u4f55\u83b7\u53d6\u6709\u6548\u7684 JWT\u3002\u63a5\u4e0b\u6765\u7684\u6b65\u9aa4\u662f\u5c06\u4ee4\u724c\u4f20\u9012\u7ed9\u6211\u4eec\u53d7\u4fdd\u62a4\u7684\u7ec8\u7aef\u8282\u70b9\uff0c\u5e76\u6307\u793a\u6211\u4eec\u7684\u6700\u5c0f API \u5982\u4f55\u9a8c\u8bc1\u5b83\u3002<\/p>\n<p>Validating a JWT bearer<br \/>\n\u9a8c\u8bc1 JWT \u6301\u6709\u8005<\/p>\n<p>After creating the JWT bearer, we need to pass it in every HTTP request, inside the Authorization HTTP header, so that ASP.NET Core can verify its validity and allow us to invoke the protected endpoints. So, we have to complete the AddJwtBearer() method invocation that we showed earlier with the description of the rules to validate the bearer:<br \/>\n\u521b\u5efa JWT \u4e0d\u8bb0\u540d\u540e\uff0c\u6211\u4eec\u9700\u8981\u5728 Authorization HTTP \u6807\u5934\u5185\u7684\u6bcf\u4e2a HTTP \u8bf7\u6c42\u4e2d\u4f20\u9012\u5b83\uff0c\u4ee5\u4fbf ASP.NET Core \u53ef\u4ee5\u9a8c\u8bc1\u5176\u6709\u6548\u6027\u5e76\u5141\u8bb8\u6211\u4eec\u8c03\u7528\u53d7\u4fdd\u62a4\u7684\u7aef\u70b9\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u5fc5\u987b\u5b8c\u6210\u4e4b\u524d\u5c55\u793a\u7684 AddJwtBearer\uff08\uff09 \u65b9\u6cd5\u8c03\u7528\uff0c\u5176\u4e2d\u5305\u542b\u9a8c\u8bc1 bearer \u7684\u89c4\u5219\u8bf4\u660e\uff1a<\/p>\n<pre><code>builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)\n.AddJwtBearer(options =&gt;\n{\n    options.TokenValidationParameters = new TokenValidationParameters\n    {\n        ValidateIssuerSigningKey = true,\n        IssuerSigningKey = new SymmetricSecurityKey(\n          Encoding.UTF8.GetBytes(&quot;mysecuritystring&quot;)),\n        ValidIssuer = &quot;https:\/\/www.packtpub.com&quot;,\n        ValidAudience = &quot;Minimal APIs Client&quot;\n    };\n});<\/code><\/pre>\n<p>In the preceding code, we added a lambda expression with which we defined the TokenValidationParameter object that contains the token validation rules. First of all, we checked the issuer signing key, that is, the signature of the token, as shown in the Generating a JWT bearer section, to verify that the JWT has not been tampered with. The security string that has been used to sign the token is required to perform this check, so we specify the same value (mysecuritystring) that we inserted during the login request.<br \/>\n\u5728\u524d\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u6dfb\u52a0\u4e86\u4e00\u4e2a lambda \u8868\u8fbe\u5f0f\uff0c\u6211\u4eec\u7528\u8be5\u8868\u8fbe\u5f0f\u5b9a\u4e49\u4e86\u5305\u542b\u4ee4\u724c\u9a8c\u8bc1\u89c4\u5219\u7684 TokenValidationParameter \u5bf9\u8c61\u3002\u9996\u5148\uff0c\u6211\u4eec\u68c0\u67e5\u4e86\u9881\u53d1\u8005\u7684\u7b7e\u540d\u5bc6\u94a5\uff0c\u5373 Token \u7684\u7b7e\u540d\uff0c\u5982 \u751f\u6210 JWT bearer \u90e8\u5206\u6240\u793a\uff0c\u4ee5\u9a8c\u8bc1 JWT \u662f\u5426\u672a\u88ab\u7be1\u6539\u3002\u6267\u884c\u6b64\u68c0\u67e5\u9700\u8981\u7528\u4e8e\u5bf9\u4ee4\u724c\u8fdb\u884c\u7b7e\u540d\u7684\u5b89\u5168\u5b57\u7b26\u4e32\uff0c\u56e0\u6b64\u6211\u4eec\u6307\u5b9a\u4e86\u5728\u767b\u5f55\u8bf7\u6c42\u671f\u95f4\u63d2\u5165\u7684\u76f8\u540c\u503c \uff08mysecuritystring\uff09\u3002<\/p>\n<p>Then, we specify what valid values for the issuer and the audience of the token are. If the token has been emitted from a different issuer, or was intended for another audience, the validation fails. This is an important security check; we should be sure that the bearer has been issued by someone we expected to issue it and for the audience we want.<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u6307\u5b9a\u4ee4\u724c\u7684\u9881\u53d1\u8005\u548c\u53d7\u4f17\u7684\u6709\u6548\u503c\u3002\u5982\u679c\u4ee4\u724c\u662f\u4ece\u5176\u4ed6\u9881\u53d1\u8005\u53d1\u51fa\u7684\uff0c\u6216\u8005\u662f\u9488\u5bf9\u5176\u4ed6\u53d7\u4f17\u7684\uff0c\u5219\u9a8c\u8bc1\u5c06\u5931\u8d25\u3002\u8fd9\u662f\u4e00\u9879\u91cd\u8981\u7684\u5b89\u5168\u68c0\u67e5;\u6211\u4eec\u5e94\u8be5\u786e\u4fdd Bearer \u662f\u7531\u6211\u4eec\u9884\u671f\u4f1a\u9881\u53d1\u5b83\u7684\u4eba\u7b7e\u53d1\u7684\uff0c\u5e76\u4e14\u662f\u9488\u5bf9\u6211\u4eec\u60f3\u8981\u7684\u53d7\u4f17\u3002<\/p>\n<p>Tip : As already pointed out, we should place the information used to work with the token in an external source, so that we can reference the correct values during token generation and validation, avoiding hardcoding them or writing their values twice.<br \/>\n\u63d0\u793a : \u5982\u524d\u6240\u8ff0\uff0c\u6211\u4eec\u5e94\u8be5\u5c06\u7528\u4e8e\u5904\u7406\u4ee4\u724c\u7684\u4fe1\u606f\u653e\u5728\u5916\u90e8\u6e90\u4e2d\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ef\u4ee5\u5728\u4ee4\u724c\u751f\u6210\u548c\u9a8c\u8bc1\u671f\u95f4\u5f15\u7528\u6b63\u786e\u7684\u503c\uff0c\u907f\u514d\u5bf9\u5b83\u4eec\u8fdb\u884c\u786c\u7f16\u7801\u6216\u91cd\u590d\u5199\u5165\u5b83\u4eec\u7684\u503c\u3002<\/p>\n<p>We don\u2019t need to specify that we also want to validate the token expiration because this check is automatically enabled. A clock skew is applied when validating the time to compensate for slight differences in clock time or to handle delays between the client request and the instant at which it is processed by the server. The default value is 5 minutes, which means that an expired token is considered valid for a 5-minute timeframe after its actual expiration. We can reduce the clock skew, or disable it, using the ClockSkew property of the TokenValidationParameter class.<br \/>\n\u6211\u4eec\u4e0d\u9700\u8981\u6307\u5b9a\u6211\u4eec\u8fd8\u8981\u9a8c\u8bc1\u4ee4\u724c\u8fc7\u671f\uff0c\u56e0\u4e3a\u6b64\u68c0\u67e5\u662f\u81ea\u52a8\u542f\u7528\u7684\u3002\u5728\u9a8c\u8bc1\u65f6\u95f4\u65f6\u5e94\u7528 clock skew \u4ee5\u8865\u507f clock time \u7684\u5fae\u5c0f\u5dee\u5f02\u6216\u5904\u7406 Client \u7aef\u8bf7\u6c42\u4e0e\u670d\u52a1\u5668\u5904\u7406\u8bf7\u6c42\u7684\u65f6\u523b\u4e4b\u95f4\u7684\u5ef6\u8fdf\u3002\u9ed8\u8ba4\u503c\u4e3a 5 \u5206\u949f\uff0c\u8fd9\u610f\u5473\u7740\u8fc7\u671f\u7684\u4ee4\u724c\u5728\u5b9e\u9645\u8fc7\u671f\u540e\u7684 5 \u5206\u949f\u5185\u88ab\u89c6\u4e3a\u6709\u6548\u3002\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 TokenValidationParameter \u7c7b\u7684 ClockSkew \u5c5e\u6027\u6765\u51cf\u5c11\u6216\u7981\u7528\u65f6\u949f\u504f\u5dee\u3002<\/p>\n<p>Now, the minimal API has all the information to check the bearer token validity. In order to test whether everything works as expected, we need a way to tell Swagger how to send the token within a request, as we\u2019ll see in the next section.<br \/>\n\u73b0\u5728\uff0c\u6700\u5c0f API \u62e5\u6709\u68c0\u67e5\u6301\u6709\u8005\u4ee4\u724c\u6709\u6548\u6027\u7684\u6240\u6709\u4fe1\u606f\u3002\u4e3a\u4e86\u6d4b\u8bd5\u4e00\u5207\u662f\u5426\u6309\u9884\u671f\u5de5\u4f5c\uff0c\u6211\u4eec\u9700\u8981\u4e00\u79cd\u65b9\u6cd5\u6765\u544a\u8bc9 Swagger \u5982\u4f55\u5728\u8bf7\u6c42\u4e2d\u53d1\u9001\u4ee4\u724c\uff0c\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u8282\u4e2d\u770b\u5230\u3002<\/p>\n<p>Adding JWT support to Swagger<br \/>\n\u5411 Swagger \u6dfb\u52a0 JWT \u652f\u6301<\/p>\n<p>We have said that the bearer token is sent in the Authorization HTTP header of a request. If we want to use Swagger to verify the authentication system and test our protected endpoints, we need to update the configuration so that it will be able to include this header in the requests.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u8bf4\u8fc7\uff0c\u6301\u6709\u8005\u4ee4\u724c\u662f\u5728\u8bf7\u6c42\u7684 Authorization HTTP \u6807\u5934\u4e2d\u53d1\u9001\u7684\u3002\u5982\u679c\u6211\u4eec\u60f3\u4f7f\u7528 Swagger \u6765\u9a8c\u8bc1\u8eab\u4efd\u9a8c\u8bc1\u7cfb\u7edf\u5e76\u6d4b\u8bd5\u53d7\u4fdd\u62a4\u7684\u7aef\u70b9\uff0c\u6211\u4eec\u9700\u8981\u66f4\u65b0\u914d\u7f6e\uff0c\u4ee5\u4fbf\u5b83\u80fd\u591f\u5728\u8bf7\u6c42\u4e2d\u5305\u542b\u6b64\u6807\u5934\u3002<\/p>\n<p>To perform this task, it is necessary to add a bit of code to the AddSwaggerGen() method:<br \/>\n\u8981\u6267\u884c\u6b64\u4efb\u52a1\uff0c\u5fc5\u987b\u5411 AddSwaggerGen\uff08\uff09 \u65b9\u6cd5\u6dfb\u52a0\u4e00\u4e9b\u4ee3\u7801\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/...\nbuilder.Services.AddSwaggerGen(options =&gt;\n{\n    options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme\n    {\n        Type = SecuritySchemeType.ApiKey,\n        In = ParameterLocation.Header,\n        Name = HeaderNames.Authorization,\n        Description = &quot;Insert the token with the &#039;Bearer &#039; \n                       prefix&quot;\n    });\n    options.AddSecurityRequirement(new\n      OpenApiSecurityRequirement\n    {\n        {\n            new OpenApiSecurityScheme\n            {\n                Reference = new OpenApiReference\n                {\n                    Type = ReferenceType.SecurityScheme,\n                    Id = \n                     JwtBearerDefaults.AuthenticationScheme\n                }\n            },\n            Array.Empty&lt;string&gt;()\n        }\n    });\n});<\/code><\/pre>\n<p>In the preceding code, we defined how Swagger handles authentication. Using the AddSecurityDefinition() method, we described how our API is protected; we used an API key, which is the bearer token, in the header with the name Authorization. Then, with AddSecurityRequirement(), we specified that we have a security requirement for our endpoints, which means that the security information must be sent for every request.<br \/>\n\u5728\u4e0a\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u5b9a\u4e49\u4e86 Swagger \u5982\u4f55\u5904\u7406\u8eab\u4efd\u9a8c\u8bc1\u3002\u4f7f\u7528 AddSecurityDefinition\uff08\uff09 \u65b9\u6cd5\uff0c\u6211\u4eec\u63cf\u8ff0\u4e86\u5982\u4f55\u4fdd\u62a4\u6211\u4eec\u7684 API;\u6211\u4eec\u5728\u6807\u5934\u4e2d\u4f7f\u7528\u4e86\u540d\u4e3a Authorization \u7684 API \u5bc6\u94a5\uff0c\u5373\u4e0d\u8bb0\u540d\u4ee4\u724c\u3002\u7136\u540e\uff0c\u4f7f\u7528 AddSecurityRequirement\uff08\uff09\uff0c\u6211\u4eec\u6307\u5b9a\u4e86\u7aef\u70b9\u7684\u5b89\u5168\u8981\u6c42\uff0c\u8fd9\u610f\u5473\u7740\u5fc5\u987b\u4e3a\u6bcf\u4e2a\u8bf7\u6c42\u53d1\u9001\u5b89\u5168\u4fe1\u606f\u3002<\/p>\n<p>After adding the preceding code, if we now run our application, the Swagger UI will contain something new.<br \/>\n\u6dfb\u52a0\u4e0a\u8ff0\u4ee3\u7801\u540e\uff0c\u5982\u679c\u6211\u4eec\u73b0\u5728\u8fd0\u884c\u5e94\u7528\u7a0b\u5e8f\uff0cSwagger UI \u5c06\u5305\u542b\u4e00\u4e9b\u65b0\u5185\u5bb9\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0803.jpg\" ><\/p>\n<p>Figure 8.3 \u2013 Swagger showing the authentication features<br \/>\n\u56fe 8.3 \u2013 Swagger \u663e\u793a\u8eab\u4efd\u9a8c\u8bc1\u529f\u80fd<\/p>\n<p>Upon clicking the Authorize button or any of the padlock icons at the right of the endpoints, the following window will show up, allowing us to insert the bearer token:<br \/>\n\u5355\u51fb Authorize \u6309\u94ae\u6216\u7aef\u70b9\u53f3\u4fa7\u7684\u4efb\u4f55\u6302\u9501\u56fe\u6807\u540e\uff0c\u5c06\u663e\u793a\u4ee5\u4e0b\u7a97\u53e3\uff0c\u5141\u8bb8\u6211\u4eec\u63d2\u5165\u4e0d\u8bb0\u540d\u4ee4\u724c\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0804.jpg\" > <\/p>\n<p>Figure 8.4 \u2013 The window that allows setting the bearer token<br \/>\n\u56fe 8.4 \u2013 \u5141\u8bb8\u8bbe\u7f6e bearer token \u7684\u7a97\u53e3<\/p>\n<p>The last thing to do is to insert the token in the Value textbox and confirm by clicking on Authorize. From now on, the specified bearer will be sent along with every request made with Swagger.<br \/>\n\u6700\u540e\u8981\u505a\u7684\u662f\u5c06\u4ee4\u724c\u63d2\u5165 Value \u6587\u672c\u6846\u4e2d\uff0c\u7136\u540e\u5355\u51fb Authorize \u8fdb\u884c\u786e\u8ba4\u3002\u4ece\u73b0\u5728\u5f00\u59cb\uff0c\u6307\u5b9a\u7684 bearer \u5c06\u4e0e\u4f7f\u7528 Swagger \u53d1\u51fa\u7684\u6bcf\u4e2a\u8bf7\u6c42\u4e00\u8d77\u53d1\u9001\u3002<\/p>\n<p>We have finally completed all the required steps to add authentication support to minimal APIs. Now, it\u2019s time to verify that everything works as expected. In the next section, we\u2019ll perform some tests.<br \/>\n\u6211\u4eec\u7ec8\u4e8e\u5b8c\u6210\u4e86\u5411\u6700\u5c0f API \u6dfb\u52a0\u8eab\u4efd\u9a8c\u8bc1\u652f\u6301\u6240\u9700\u7684\u6240\u6709\u6b65\u9aa4\u3002\u73b0\u5728\uff0c\u662f\u65f6\u5019\u9a8c\u8bc1\u4e00\u5207\u662f\u5426\u6309\u9884\u671f\u5de5\u4f5c\u4e86\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u6267\u884c\u4e00\u4e9b\u6d4b\u8bd5\u3002<\/p>\n<p>Testing authentication<br \/>\n\u6d4b\u8bd5\u8eab\u4efd\u9a8c\u8bc1<\/p>\n<p>As described in the previous sections, if we call one of the protected endpoints, we get a 401 Unauthorized response. To verify that token authentication works, let\u2019s call the login endpoint to get a token. After that, click on the Authorize button in Swagger and insert the obtained token, remembering the Bearer<space> prefix. Now, we\u2019ll get a 200 OK response, meaning that we are able to correctly invoke the endpoints that require authentication. We can also try changing a single character in the token to again get the 401 Unauthorized response, because in this case, the signature will not be the expected one, as described before. In the same way, if the token is formally valid but has expired, we will obtain a 401 response.<br \/>\n\u5982\u524d\u9762\u90e8\u5206\u6240\u8ff0\uff0c\u5982\u679c\u6211\u4eec\u8c03\u7528\u5176\u4e2d\u4e00\u4e2a\u53d7\u4fdd\u62a4\u7684\u7ec8\u7aef\u8282\u70b9\uff0c\u5219\u4f1a\u6536\u5230 401 Unauthorized \u54cd\u5e94\u3002\u8981\u9a8c\u8bc1\u4ee4\u724c\u8eab\u4efd\u9a8c\u8bc1\u662f\u5426\u6709\u6548\uff0c\u8ba9\u6211\u4eec\u8c03\u7528\u767b\u5f55\u7ec8\u7aef\u8282\u70b9\u4ee5\u83b7\u53d6\u4ee4\u724c\u3002\u4e4b\u540e\uff0c\u70b9\u51fb Swagger \u4e2d\u7684 Authorize \u6309\u94ae\u5e76\u63d2\u5165\u83b7\u53d6\u7684\u4ee4\u724c\uff0c\u8bb0\u4f4f Bearer<space> \u524d\u7f00\u3002\u73b0\u5728\uff0c\u6211\u4eec\u5c06\u6536\u5230 200 OK \u54cd\u5e94\uff0c\u8fd9\u610f\u5473\u7740\u6211\u4eec\u80fd\u591f\u6b63\u786e\u8c03\u7528\u9700\u8981\u8eab\u4efd\u9a8c\u8bc1\u7684\u7ec8\u7aef\u8282\u70b9\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u5c1d\u8bd5\u66f4\u6539\u4ee4\u724c\u4e2d\u7684\u5355\u4e2a\u5b57\u7b26\u4ee5\u518d\u6b21\u83b7\u5f97 401 Unauthorized \u54cd\u5e94\uff0c\u56e0\u4e3a\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u7b7e\u540d\u5c06\u4e0d\u662f\u9884\u671f\u7684\u7b7e\u540d\uff0c\u5982\u524d\u6240\u8ff0\u3002\u540c\u7406\uff0c\u5982\u679c Token \u6b63\u5f0f\u6709\u6548\u4f46\u5df2\u8fc7\u671f\uff0c\u6211\u4eec\u5c06\u83b7\u5f97 401 \u54cd\u5e94\u3002<\/p>\n<p>As we have defined endpoints that can be reached only by authenticated users, a common requirement is to access user information within the corresponding route handlers. In Chapter 2, Exploring Minimal APIs and Their Advantages, we showed that minimal APIs provide a special binding that directly provides a ClaimsPrincipal object representing the logged user:<br \/>\n\u7531\u4e8e\u6211\u4eec\u5df2\u7ecf\u5b9a\u4e49\u4e86\u53ea\u6709\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237\u624d\u80fd\u8bbf\u95ee\u7684\u7aef\u70b9\uff0c\u56e0\u6b64\u4e00\u4e2a\u5e38\u89c1\u7684\u8981\u6c42\u662f\u8bbf\u95ee\u76f8\u5e94\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u7528\u6237\u4fe1\u606f\u3002\u5728\u7b2c 2 \u7ae0 \u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf\u4e2d\uff0c\u6211\u4eec\u5c55\u793a\u4e86\u6700\u5c0f API \u63d0\u4f9b\u4e86\u4e00\u4e2a\u7279\u6b8a\u7684\u7ed1\u5b9a\uff0c\u8be5\u7ed1\u5b9a\u76f4\u63a5\u63d0\u4f9b\u8868\u793a\u5df2\u8bb0\u5f55\u7528\u6237\u7684 ClaimsPrincipal \u5bf9\u8c61\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/me&quot;, [Authorize] (ClaimsPrincipal user) =&gt; $&quot;Logged username: {user.Identity.Name}&quot;);<\/code><\/pre>\n<p>The user parameter of the route handler is automatically filled with user information. In this example, we just get the name, which in turn is read from the token claims, but the object exposes many properties that allow us to work with authentication data. We can refer to the official documentation at <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.security.claims.claimsprincipal.identity\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.security.claims.claimsprincipal.identity<\/a> for further details.<br \/>\n\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u7684 user \u53c2\u6570\u4f1a\u81ea\u52a8\u586b\u5145\u7528\u6237\u4fe1\u606f\u3002\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u53ea\u83b7\u53d6 name\uff0c\u800c name \u53c8\u662f\u4ece token \u58f0\u660e\u4e2d\u8bfb\u53d6\u7684\uff0c\u4f46\u8be5\u5bf9\u8c61\u516c\u5f00\u4e86\u8bb8\u591a\u5141\u8bb8\u6211\u4eec\u5904\u7406\u8eab\u4efd\u9a8c\u8bc1\u6570\u636e\u7684\u5c5e\u6027\u3002\u6709\u5173\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.security.claims.claimsprincipal.identity\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.security.claims.claimsprincipal.identity<\/a> \u4e0a\u7684\u5b98\u65b9\u6587\u6863\u3002<\/p>\n<p>This ends our overview of authentication. In the next section, we\u2019ll see how to handle authorization.<br \/>\n\u6211\u4eec\u5bf9\u8eab\u4efd\u9a8c\u8bc1\u7684\u6982\u8ff0\u5230\u6b64\u7ed3\u675f\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u5904\u7406\u6388\u6743\u3002<\/p>\n<p>Handling authorization \u2013 roles and policies<br \/>\n\u5904\u7406\u6388\u6743 \u2013 \u89d2\u8272\u548c\u7b56\u7565<\/p>\n<p>Right after the authentication, there is the authorization step, which grants an authenticated user permission to do something. Minimal APIs provide the same authorization features as controller-based projects, based on the concepts of roles and policies.<br \/>\n\u5728\u8eab\u4efd\u9a8c\u8bc1\u4e4b\u540e\uff0c\u7acb\u5373\u6267\u884c\u6388\u6743\u6b65\u9aa4\uff0c\u8be5\u6b65\u9aa4\u6388\u4e88\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237\u6267\u884c\u67d0\u4e9b\u4f5c\u7684\u6743\u9650\u3002Minimal API \u57fa\u4e8e\u89d2\u8272\u548c\u7b56\u7565\u7684\u6982\u5ff5\uff0c\u63d0\u4f9b\u4e0e\u57fa\u4e8e\u63a7\u5236\u5668\u7684\u9879\u76ee\u76f8\u540c\u7684\u6388\u6743\u529f\u80fd\u3002<\/p>\n<p>When an identity is created, it may belong to one or more roles. For example, a user can belong to the Administrator role, while another can be part of two roles: User and Stakeholder. Typically, each user can perform only the operations that are allowed by their roles. Roles are just claims that are inserted in the JWT bearer upon authentication. As we\u2019ll see in a moment, ASP.NET Core provides built-in support to verify whether a user belongs to a role.<br \/>\n\u521b\u5efa\u8eab\u4efd\u65f6\uff0c\u5b83\u53ef\u80fd\u5c5e\u4e8e\u4e00\u4e2a\u6216\u591a\u4e2a\u89d2\u8272\u3002\u4f8b\u5982\uff0c\u4e00\u4e2a\u7528\u6237\u53ef\u4ee5\u5c5e\u4e8e Administrator \u89d2\u8272\uff0c\u800c\u53e6\u4e00\u4e2a\u7528\u6237\u53ef\u4ee5\u5c5e\u4e8e\u4e24\u4e2a\u89d2\u8272\uff1aUser \u548c Slikeholder\u3002\u901a\u5e38\uff0c\u6bcf\u4e2a\u7528\u6237\u53ea\u80fd\u6267\u884c\u5176\u89d2\u8272\u5141\u8bb8\u7684\u4f5c\u3002\u89d2\u8272\u53ea\u662f\u5728\u8eab\u4efd\u9a8c\u8bc1\u65f6\u63d2\u5165\u5230 JWT \u6301\u6709\u8005\u4e2d\u7684\u58f0\u660e\u3002\u6b63\u5982\u6211\u4eec\u7a0d\u540e\u5c06\u770b\u5230\u7684\uff0cASP.NET Core \u63d0\u4f9b\u4e86\u5185\u7f6e\u652f\u6301\u6765\u9a8c\u8bc1\u7528\u6237\u662f\u5426\u5c5e\u4e8e\u67d0\u4e2a\u89d2\u8272\u3002<\/p>\n<p>While role-based authorization covers many scenarios, there are cases in which this kind of security isn\u2019t enough because we need to apply more specific rules to check whether the user has the right to perform some activities. In such a situation, we can create custom policies that allow us to specify more detailed authorization requirements and even completely define the authorization logic based on our algorithms.<br \/>\n\u867d\u7136\u57fa\u4e8e\u89d2\u8272\u7684\u6388\u6743\u6db5\u76d6\u4e86\u8bb8\u591a\u573a\u666f\uff0c\u4f46\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u8fd9\u79cd\u5b89\u5168\u6027\u662f\u4e0d\u591f\u7684\uff0c\u56e0\u4e3a\u6211\u4eec\u9700\u8981\u5e94\u7528\u66f4\u5177\u4f53\u7684\u89c4\u5219\u6765\u68c0\u67e5\u7528\u6237\u662f\u5426\u6709\u6743\u6267\u884c\u67d0\u4e9b\u6d3b\u52a8\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u53ef\u4ee5\u521b\u5efa\u81ea\u5b9a\u4e49\u7b56\u7565\uff0c\u5141\u8bb8\u6211\u4eec\u6307\u5b9a\u66f4\u8be6\u7ec6\u7684\u6388\u6743\u8981\u6c42\uff0c\u751a\u81f3\u6839\u636e\u6211\u4eec\u7684\u7b97\u6cd5\u5b8c\u5168\u5b9a\u4e49\u6388\u6743\u903b\u8f91\u3002<\/p>\n<p>In the next sections, we\u2019ll see how to manage both role-based and policy-based authorization in our APIs, so that we can cover all our requirements, that is, allowing access to certain endpoints only to users with specific roles or claims, or based on our custom logic.<br \/>\n\u5728\u63a5\u4e0b\u6765\u7684\u90e8\u5206\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u5728 API \u4e2d\u7ba1\u7406\u57fa\u4e8e\u89d2\u8272\u548c\u57fa\u4e8e\u7b56\u7565\u7684\u6388\u6743\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ef\u4ee5\u6ee1\u8db3\u6240\u6709\u8981\u6c42\uff0c\u5373\u4ec5\u5141\u8bb8\u5177\u6709\u7279\u5b9a\u89d2\u8272\u6216\u58f0\u660e\u7684\u7528\u6237\u8bbf\u95ee\u67d0\u4e9b\u7ec8\u7aef\u8282\u70b9\uff0c\u6216\u8005\u5141\u8bb8\u57fa\u4e8e\u6211\u4eec\u7684\u81ea\u5b9a\u4e49\u903b\u8f91\u8bbf\u95ee\u67d0\u4e9b\u7ec8\u7aef\u8282\u70b9\u3002<\/p>\n<p>Handling role-based authorization<br \/>\n\u5904\u7406\u57fa\u4e8e\u89d2\u8272\u7684\u6388\u6743<\/p>\n<p>As already introduced, roles are claims. This means that they must be inserted in the JWT bearer token upon authentication, just like any other claims:<br \/>\n\u5982\u524d\u6240\u8ff0\uff0c\u89d2\u8272\u662f\u58f0\u660e\u3002\u8fd9\u610f\u5473\u7740\uff0c\u5728\u8eab\u4efd\u9a8c\u8bc1\u65f6\uff0c\u5fc5\u987b\u5c06\u5b83\u4eec\u63d2\u5165\u5230 JWT \u4e0d\u8bb0\u540d\u4ee4\u724c\u4e2d\uff0c\u5c31\u50cf\u4efb\u4f55\u5176\u4ed6\u58f0\u660e\u4e00\u6837\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/api\/auth\/login&quot;, (LoginRequest request) =&gt;\n{\n    if (request.Username == &quot;marco&quot; &amp;&amp; request.Password == \n        &quot;P@$$w0rd&quot;)\n    {\n        var claims = new List&lt;Claim&gt;()\n        {\n            new(ClaimTypes.Name, request.Username),\n            new(ClaimTypes.Role, &quot;Administrator&quot;),\n            new(ClaimTypes.Role, &quot;User&quot;)\n        };\n\n    \/\/...\n}<\/code><\/pre>\n<p>In this example, we statically add two claims with name ClaimTypes.Role: Administrator and User. As said in the previous sections, in a real-world application, these values typically come from a complete user management system built, for example, with ASP.NET Core Identity.<br \/>\n\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u9759\u6001\u6dfb\u52a0\u4e24\u4e2a\u540d\u79f0\u4e3a ClaimTypes.Role \u7684\u58f0\u660e\uff1aAdministrator \u548c User\u3002\u5982\u524d\u51e0\u8282\u6240\u8ff0\uff0c\u5728\u5b9e\u9645\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u8fd9\u4e9b\u503c\u901a\u5e38\u6765\u81ea\u4e00\u4e2a\u5b8c\u6574\u7684\u7528\u6237\u7ba1\u7406\u7cfb\u7edf\uff0c\u4f8b\u5982\uff0c\u4f7f\u7528 ASP.NET Core Identity \u6784\u5efa\u3002<\/p>\n<p>As in all the other claims, roles are inserted in the JWT bearer. If now we try to invoke the login endpoint, we\u2019ll notice that the token is longer because it contains a lot of information, which we can verify using the <a href=\"https:\/\/jwt.ms\">https:\/\/jwt.ms<\/a> site again, as follows:<br \/>\n\u4e0e\u6240\u6709\u5176\u4ed6\u58f0\u660e\u4e00\u6837\uff0c\u89d2\u8272\u4e5f\u63d2\u5165\u5230 JWT \u6301\u6709\u8005\u4e2d\u3002\u5982\u679c\u73b0\u5728\u6211\u4eec\u5c1d\u8bd5\u8c03\u7528\u767b\u5f55\u7aef\u70b9\uff0c\u6211\u4eec\u4f1a\u6ce8\u610f\u5230\u4ee4\u724c\u66f4\u957f\uff0c\u56e0\u4e3a\u5b83\u5305\u542b\u5927\u91cf\u4fe1\u606f\uff0c\u6211\u4eec\u53ef\u4ee5\u518d\u6b21\u4f7f\u7528 <a href=\"https:\/\/jwt.ms\">https:\/\/jwt.ms<\/a> \u7ad9\u70b9\u9a8c\u8bc1\u8fd9\u4e9b\u4fe1\u606f\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>{\n  &quot;alg&quot;: &quot;HS256&quot;,\n  &quot;typ&quot;: &quot;JWT&quot;\n}.{\n  &quot;http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/name&quot;: &quot;marco&quot;,\n  &quot;http:\/\/schemas.microsoft.com\/ws\/2008\/06\/identity\/claims\/role&quot;: [\n    &quot;Administrator&quot;,\n    &quot;User&quot;\n  ],\n  &quot;exp&quot;: 1644755166,\n  &quot;iss&quot;: &quot;https:\/\/www.packtpub.com&quot;,\n  &quot;aud&quot;: &quot;Minimal APIs Client&quot;\n}.[Signature]<\/code><\/pre>\n<p>In order to restrict access to a particular endpoint only for users that belong to a given role, we need to specify this role as an argument in the Authorize attribute or the RequireAuthorization() method:<br \/>\n\u4e3a\u4e86\u9650\u5236\u4ec5\u5c5e\u4e8e\u7ed9\u5b9a\u89d2\u8272\u7684\u7528\u6237\u8bbf\u95ee\u7279\u5b9a\u7aef\u70b9\uff0c\u6211\u4eec\u9700\u8981\u5c06\u6b64\u89d2\u8272\u6307\u5b9a\u4e3a Authorize \u5c5e\u6027\u6216 RequireAuthorization\uff08\uff09 \u65b9\u6cd5\u4e2d\u7684\u53c2\u6570\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/admin-attribute-protected&quot;, [Authorize(Roles = &quot;Administrator&quot;)] () =&gt; { });\napp.MapGet(&quot;\/api\/admin-method-protected&quot;, () =&gt; { })\n.RequireAuthorization(new AuthorizeAttribute { Roles = &quot;Administrator&quot; });<\/code><\/pre>\n<p>In this way, only users who are assigned the Administrator role can access the endpoints. We can also specify more roles, separating them with a comma: the user will be authorized if they have at least one of the specified roles.<br \/>\n\u8fd9\u6837\uff0c\u53ea\u6709\u5206\u914d\u4e86 Administrator \u89d2\u8272\u7684\u7528\u6237\u624d\u80fd\u8bbf\u95ee\u7ec8\u7aef\u8282\u70b9\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u6307\u5b9a\u66f4\u591a\u89d2\u8272\uff0c\u7528\u9017\u53f7\u5206\u9694\uff1a\u5982\u679c\u7528\u6237\u81f3\u5c11\u62e5\u6709\u4e00\u4e2a\u6307\u5b9a\u7684\u89d2\u8272\uff0c\u5219\u7528\u6237\u5c06\u88ab\u6388\u6743\u3002<\/p>\n<p>Important Note : Role names are case sensitive.<br \/>\n\u91cd\u8981\u63d0\u793a : \u89d2\u8272\u540d\u79f0\u533a\u5206\u5927\u5c0f\u5199\u3002<\/p>\n<p>Now suppose we have the following endpoint:<br \/>\n\u73b0\u5728\u5047\u8bbe\u6211\u4eec\u6709\u4ee5\u4e0b\u7aef\u70b9\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/stackeholder-protected&quot;, [Authorize(Roles = &quot;Stakeholder&quot;)] () =&gt; { });<\/code><\/pre>\n<p>This method can only be consumed by a user who is assigned the Stakeholder role. However, in our example, this role isn\u2019t assigned. So, if we use the previous bearer token and try to invoke this endpoint, of course, we\u2019ll get an error. But in this case, it won\u2019t be 401 Unauthorized, but rather 403 Forbidden. We see this behavior because the user is actually authenticated (meaning the token is valid, so no 401 error), but they don\u2019t have the authorization to execute the method, so access is forbidden. In other words, authentication errors and authorization errors lead to different HTTP status codes.<br \/>\n\u6b64\u65b9\u6cd5\u53ea\u80fd\u7531\u5206\u914d\u4e86 Stakeholder \u89d2\u8272\u7684\u7528\u6237\u4f7f\u7528\u3002\u4f46\u662f\uff0c\u5728\u6211\u4eec\u7684\u793a\u4f8b\u4e2d\uff0c\u672a\u5206\u914d\u6b64\u89d2\u8272\u3002\u56e0\u6b64\uff0c\u5982\u679c\u6211\u4eec\u4f7f\u7528\u4ee5\u524d\u7684 bearer token \u5e76\u5c1d\u8bd5\u8c03\u7528\u6b64 endpoint\uff0c\u6211\u4eec\u5f53\u7136\u4f1a\u6536\u5230\u9519\u8bef\u3002\u4f46\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u5b83\u4e0d\u4f1a\u662f 401 Unauthorized\uff0c\u800c\u662f 403 Forbidden\u3002\u6211\u4eec\u770b\u5230\u8fd9\u79cd\u884c\u4e3a\u662f\u56e0\u4e3a\u7528\u6237\u5b9e\u9645\u4e0a\u5df2\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\uff08\u610f\u5473\u7740\u4ee4\u724c\u6709\u6548\uff0c\u56e0\u6b64\u6ca1\u6709 401 \u9519\u8bef\uff09\uff0c\u4f46\u4ed6\u4eec\u6ca1\u6709\u6267\u884c\u8be5\u65b9\u6cd5\u7684\u6388\u6743\uff0c\u56e0\u6b64\u7981\u6b62\u8bbf\u95ee\u3002\u6362\u53e5\u8bdd\u8bf4\uff0c\u8eab\u4efd\u9a8c\u8bc1\u9519\u8bef\u548c\u6388\u6743\u9519\u8bef\u4f1a\u5bfc\u81f4\u4e0d\u540c\u7684 HTTP \u72b6\u6001\u4ee3\u7801\u3002<\/p>\n<p>There is another important scenario that involves roles. Sometimes, we don\u2019t need to restrict endpoint access at all but need to adapt the behavior of the handler according to the specific user role, such as when retrieving only a certain type of information. In this case, we can use the IsInRole() method, which is available on the ClaimsPrincipal object:<br \/>\n\u8fd8\u6709\u53e6\u4e00\u4e2a\u6d89\u53ca\u89d2\u8272\u7684\u91cd\u8981\u65b9\u6848\u3002\u6709\u65f6\uff0c\u6211\u4eec\u6839\u672c\u4e0d\u9700\u8981\u9650\u5236\u7aef\u70b9\u8bbf\u95ee\uff0c\u4f46\u9700\u8981\u6839\u636e\u7279\u5b9a\u7684\u7528\u6237\u89d2\u8272\u6765\u8c03\u6574\u5904\u7406\u7a0b\u5e8f\u7684\u884c\u4e3a\uff0c\u4f8b\u5982\u5f53\u53ea\u68c0\u7d22\u67d0\u79cd\u7c7b\u578b\u7684\u4fe1\u606f\u65f6\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 IsInRole\uff08\uff09 \u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u5728 ClaimsPrincipal \u5bf9\u8c61\u4e0a\u53ef\u7528\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/role-check&quot;, [Authorize] (ClaimsPrincipal user) =&gt;\n{\n    if (user.IsInRole(&quot;Administrator&quot;))\n    {\n        return &quot;User is an Administrator&quot;;\n    }\n    return &quot;This is a normal user&quot;;\n});<\/code><\/pre>\n<p>In this endpoint, we only use the Authorize attribute to check whether the user is authenticated or not. Then, in the route handler, we check whether the user has the Administrator role. If yes, we just return a message, but we can imagine that administrators can retrieve all the available information, while normal users get only a subset, based on the values of the information itself.<br \/>\n\u5728\u6b64\u7ec8\u7aef\u8282\u70b9\u4e2d\uff0c\u6211\u4eec\u53ea\u4f7f\u7528 Authorize \u5c5e\u6027\u6765\u68c0\u67e5\u7528\u6237\u662f\u5426\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u3002\u7136\u540e\uff0c\u5728\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u4e2d\uff0c\u6211\u4eec\u68c0\u67e5\u7528\u6237\u662f\u5426\u5177\u6709 Administrator \u89d2\u8272\u3002\u5982\u679c\u662f\uff0c\u6211\u4eec\u53ea\u8fd4\u56de\u4e00\u6761\u6d88\u606f\uff0c\u4f46\u6211\u4eec\u53ef\u4ee5\u60f3\u8c61\u7ba1\u7406\u5458\u53ef\u4ee5\u68c0\u7d22\u6240\u6709\u53ef\u7528\u4fe1\u606f\uff0c\u800c\u666e\u901a\u7528\u6237\u53ea\u80fd\u6839\u636e\u4fe1\u606f\u672c\u8eab\u7684\u503c\u83b7\u5f97\u4e00\u4e2a\u5b50\u96c6\u3002<\/p>\n<p>As we have seen, with role-based authorization, we can perform different types of authorization checks in our endpoints, to cover many scenarios. However, this approach cannot handle all situations. If roles aren\u2019t enough, we need to use authorization based on policies, which we will discuss in the next section.<br \/>\n\u6b63\u5982\u6211\u4eec\u6240\u770b\u5230\u7684\uff0c\u901a\u8fc7\u57fa\u4e8e\u89d2\u8272\u7684\u6388\u6743\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u7aef\u70b9\u4e2d\u6267\u884c\u4e0d\u540c\u7c7b\u578b\u7684\u6388\u6743\u68c0\u67e5\uff0c\u4ee5\u6db5\u76d6\u8bb8\u591a\u573a\u666f\u3002\u4f46\u662f\uff0c\u6b64\u65b9\u6cd5\u65e0\u6cd5\u5904\u7406\u6240\u6709\u60c5\u51b5\u3002\u5982\u679c\u89d2\u8272\u8fd8\u4e0d\u591f\uff0c\u6211\u4eec\u9700\u8981\u4f7f\u7528\u57fa\u4e8e\u7b56\u7565\u7684\u6388\u6743\uff0c\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u8282\u4e2d\u8ba8\u8bba\u3002<\/p>\n<p>Applying policy-based authorization<br \/>\n\u5e94\u7528\u57fa\u4e8e\u7b56\u7565\u7684\u6388\u6743<br \/>\nPolicies are a more general way to define authorization rules. Role-based authorization can be considered a specific policy authorization that involves a roles check. We typically use policies when we need to handle more complex scenarios.<br \/>\n\u7b56\u7565\u662f\u5b9a\u4e49\u6388\u6743\u89c4\u5219\u7684\u66f4\u901a\u7528\u65b9\u6cd5\u3002\u57fa\u4e8e\u89d2\u8272\u7684\u6388\u6743\u53ef\u88ab\u89c6\u4e3a\u6d89\u53ca\u89d2\u8272\u68c0\u67e5\u7684\u7279\u5b9a\u7b56\u7565\u6388\u6743\u3002\u5f53\u6211\u4eec\u9700\u8981\u5904\u7406\u66f4\u590d\u6742\u7684\u573a\u666f\u65f6\uff0c\u6211\u4eec\u901a\u5e38\u4f1a\u4f7f\u7528\u7b56\u7565\u3002<\/p>\n<p>This kind of authorization requires two steps:<br \/>\n\u8fd9\u79cd\u6388\u6743\u9700\u8981\u4e24\u4e2a\u6b65\u9aa4\uff1a<\/p>\n<ol>\n<li>Defining a policy with a rule set<br \/>\n\u4f7f\u7528\u89c4\u5219\u96c6\u5b9a\u4e49\u7b56\u7565<\/li>\n<li>Applying a certain policy on the endpoints<br \/>\n\u5728\u7aef\u70b9\u4e0a\u5e94\u7528\u7279\u5b9a\u7b56\u7565<\/li>\n<\/ol>\n<p>Policies are added in the context of the AddAuthorization() method, which we saw in the previous section, Protecting a minimal API. Each policy has a unique name, which is used to later reference it, and a set of rules, which are typically described in a fluent manner.<br \/>\n\u7b56\u7565\u662f\u5728 AddAuthorization\uff08\uff09 \u65b9\u6cd5\u7684\u4e0a\u4e0b\u6587\u4e2d\u6dfb\u52a0\u7684\uff0c\u6211\u4eec\u5728\u4e0a\u4e00\u8282 \u4fdd\u62a4\u6700\u5c0f API \u4e2d\u770b\u5230\u4e86\u3002\u6bcf\u4e2a\u7b56\u7565\u90fd\u6709\u4e00\u4e2a\u552f\u4e00\u7684\u540d\u79f0\uff08\u7528\u4e8e\u4ee5\u540e\u5f15\u7528\u5b83\uff09\u548c\u4e00\u7ec4\u89c4\u5219\uff0c\u8fd9\u4e9b\u89c4\u5219\u901a\u5e38\u4ee5\u6d41\u7545\u7684\u65b9\u5f0f\u8fdb\u884c\u63cf\u8ff0\u3002<\/p>\n<p>We can use policies when role authorization is not enough. Suppose that the bearer token also contains the ID of the tenant to which the user belongs:<br \/>\n\u5f53\u89d2\u8272\u6388\u6743\u4e0d\u8db3\u65f6\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u7b56\u7565\u3002\u5047\u8bbe bearer token \u8fd8\u5305\u542b\u7528\u6237\u6240\u5c5e\u79df\u6237\u7684 ID\uff1a<\/p>\n<pre><code>var claims = new List&lt;Claim&gt;()\n{\n    \/\/ ...\n    new(&quot;tenant-id&quot;, &quot;42&quot;)\n};<\/code><\/pre>\n<p>Again, in a real-world scenario, this value could come from a database that stores the properties of the user. Suppose that we want to only allow users who belong to a particular tenant to reach an endpoint. As tenant-id is a custom claim, ASP.NET Core doesn\u2019t know how to use it to enforce authorization. So, we can\u2019t use the solutions shown earlier. We need to define a custom policy with the corresponding rule:<br \/>\n\u540c\u6837\uff0c\u5728\u5b9e\u9645\u65b9\u6848\u4e2d\uff0c\u6b64\u503c\u53ef\u80fd\u6765\u81ea\u5b58\u50a8\u7528\u6237\u5c5e\u6027\u7684\u6570\u636e\u5e93\u3002\u5047\u8bbe\u6211\u4eec\u53ea\u60f3\u5141\u8bb8\u5c5e\u4e8e\u7279\u5b9a\u79df\u6237\u7684\u7528\u6237\u8bbf\u95ee\u7ec8\u7aef\u8282\u70b9\u3002\u7531\u4e8e tenant-id \u662f\u4e00\u4e2a\u81ea\u5b9a\u4e49\u58f0\u660e\uff0c\u56e0\u6b64 ASP.NET Core \u4e0d\u77e5\u9053\u5982\u4f55\u4f7f\u7528\u5b83\u6765\u5f3a\u5236\u5b9e\u65bd\u6388\u6743\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u4e0d\u80fd\u4f7f\u7528\u524d\u9762\u663e\u793a\u7684\u89e3\u51b3\u65b9\u6848\u3002\u6211\u4eec\u9700\u8981\u5b9a\u4e49\u4e00\u4e2a\u5e26\u6709\u76f8\u5e94\u89c4\u5219\u7684\u81ea\u5b9a\u4e49\u7b56\u7565\uff1a<\/p>\n<pre><code>builder.Services.AddAuthorization(options =&gt;\n{\n    options.AddPolicy(&quot;Tenant42&quot;, policy =&gt;\n    {\n        policy.RequireClaim(&quot;tenant-id&quot;, &quot;42&quot;);\n    });\n});<\/code><\/pre>\n<p>In the preceding code, we created a policy named Tenant42, which requires that the token contains the tenant-id claim with the value 42. The policy variable is an instance of AuthorizationPolicyBuilder and exposes methods that allow us to fluently specify the authorization rules; we can specify that a policy requires certain users, roles, and claims to be satisfied. We can also chain multiple requirements in the same policy, writing, for example, something such as policy.RequireRole(\u201cAdministrator\u201d).RequireClaim(\u201ctenant-id\u201d). The full list of methods is available on the documentation page at <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.aspnetcore.authorization.authorizationpolicybuilder\">https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.aspnetcore.authorization.authorizationpolicybuilder<\/a>.<br \/>\n\u5728\u4e0a\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u521b\u5efa\u4e86\u4e00\u4e2a\u540d\u4e3a Tenant42 \u7684\u7b56\u7565\uff0c\u8be5\u7b56\u7565\u8981\u6c42\u4ee4\u724c\u5305\u542b\u503c\u4e3a 42 \u7684 tenant-id \u58f0\u660e\u3002policy \u53d8\u91cf\u662f AuthorizationPolicyBuilder \u7684\u4e00\u4e2a\u5b9e\u4f8b\uff0c\u5b83\u516c\u5f00\u4e86\u5141\u8bb8\u6211\u4eec\u6d41\u7545\u5730\u6307\u5b9a\u6388\u6743\u89c4\u5219\u7684\u65b9\u6cd5;\u6211\u4eec\u53ef\u4ee5\u6307\u5b9a\u7b56\u7565\u8981\u6c42\u6ee1\u8db3\u67d0\u4e9b\u7528\u6237\u3001\u89d2\u8272\u548c\u58f0\u660e\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u5c06\u591a\u4e2a\u9700\u6c42\u94fe\u63a5\u5728\u540c\u4e00\u4e2a\u7b56\u7565\u4e2d\uff0c\u4f8b\u5982\uff0c\u7f16\u5199\u8bf8\u5982 policy \u4e4b\u7c7b\u7684\u5185\u5bb9\u3002RequireRole\uff08\u201c\u7ba1\u7406\u5458\u201d\uff09\u3002RequireClaim\uff08\u201ctenant-id\u201d\uff09\u7684\u5b8c\u6574\u7684\u65b9\u6cd5\u5217\u8868\u53ef\u5728 <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.aspnetcore.authorization.authorizationpolicybuilder\">https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.aspnetcore.authorization.authorizationpolicybuilder<\/a> \u7684\u6587\u6863\u9875\u9762\u4e0a\u627e\u5230\u3002<\/p>\n<p>Then, in the method we want to protect, we have to specify the policy name, as usual with the Authorize attribute or the RequireAuthorization() method:<br \/>\n\u7136\u540e\uff0c\u5728\u6211\u4eec\u60f3\u8981\u4fdd\u62a4\u7684\u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u5fc5\u987b\u6307\u5b9a\u7b56\u7565\u540d\u79f0\uff0c\u5c31\u50cf\u901a\u5e38\u4f7f\u7528 Authorize \u5c5e\u6027\u6216 RequireAuthorization\uff08\uff09 \u65b9\u6cd5\u4e00\u6837\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/policy-attribute-protected&quot;, [Authorize(Policy = &quot;Tenant42&quot;)] () =&gt; { });\napp.MapGet(&quot;\/api\/policy-method-protected&quot;, () =&gt; { })\n.RequireAuthorization(&quot;Tenant42&quot;);<\/code><\/pre>\n<p>If we try to execute these preceding endpoints with a token that doesn\u2019t have the tenant-id claim, or its value isn\u2019t 42, we get a 403 Forbidden result, as happened with the role check.<br \/>\n\u5982\u679c\u6211\u4eec\u5c1d\u8bd5\u4f7f\u7528\u6ca1\u6709 tenant-id \u58f0\u660e\u6216\u5176\u503c\u4e0d\u662f 42 \u7684\u4ee4\u724c\u6267\u884c\u8fd9\u4e9b\u524d\u9762\u7684\u7ec8\u7ed3\u70b9\uff0c\u5219\u4f1a\u6536\u5230 403 Forbidden \u7ed3\u679c\uff0c\u5c31\u50cf\u89d2\u8272\u68c0\u67e5\u4e00\u6837\u3002<\/p>\n<p>There are scenarios in which declaring a list of allowed roles and claims isn\u2019t enough: for example, we would need to perform more complex checks or verify authorization based on dynamic parameters. In these cases, we can use the so-called policy requirements, which comprise a collection of authorization rules for which we can provide custom verification logic.<br \/>\n\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u58f0\u660e\u5141\u8bb8\u7684\u89d2\u8272\u548c\u58f0\u660e\u5217\u8868\u662f\u4e0d\u591f\u7684\uff1a\u4f8b\u5982\uff0c\u6211\u4eec\u9700\u8981\u6267\u884c\u66f4\u590d\u6742\u7684\u68c0\u67e5\u6216\u6839\u636e\u52a8\u6001\u53c2\u6570\u9a8c\u8bc1\u6388\u6743\u3002\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u6240\u8c13\u7684\u7b56\u7565\u8981\u6c42\uff0c\u5b83\u5305\u542b\u4e00\u7ec4\u6388\u6743\u89c4\u5219\uff0c\u6211\u4eec\u53ef\u4ee5\u4e3a\u5176\u63d0\u4f9b\u81ea\u5b9a\u4e49\u9a8c\u8bc1\u903b\u8f91\u3002<\/p>\n<p>To adopt this solution, we need two objects:<br \/>\n\u8981\u91c7\u7528\u6b64\u89e3\u51b3\u65b9\u6848\uff0c\u6211\u4eec\u9700\u8981\u4e24\u4e2a\u5bf9\u8c61\uff1a<\/p>\n<p>\u2022  A requirement class that implements the IAuthorizationRequirement interface and defines the requirement we want to manage<br \/>\n\u5b9e\u73b0 IAuthorizationRequirement \u63a5\u53e3\u5e76\u5b9a\u4e49\u6211\u4eec\u8981\u7ba1\u7406\u7684\u8981\u6c42\u7684\u8981\u6c42\u7c7b<\/p>\n<p>\u2022  A handler class that inherits from AuthorizationHandler and contains the logic to verify the requirement<br \/>\n\u4e00\u4e2a\u4ece AuthorizationHandler \u7ee7\u627f\u5e76\u5305\u542b\u9a8c\u8bc1\u8981\u6c42\u7684\u903b\u8f91\u7684\u5904\u7406\u7a0b\u5e8f\u7c7b<\/p>\n<p>Let\u2019s suppose we don\u2019t want users who don\u2019t belong to the Administrator role to access certain endpoints during a maintenance time window. This is a perfectly valid authorization rule, but we cannot afford it using the solutions we have seen up to now. The rule involves a condition that considers the current time, so the policy cannot be statically defined.<br \/>\n\u5047\u8bbe\u6211\u4eec\u4e0d\u5e0c\u671b\u4e0d\u5c5e\u4e8e Administrator \u89d2\u8272\u7684\u7528\u6237\u5728\u7ef4\u62a4\u65f6\u6bb5\u5185\u8bbf\u95ee\u67d0\u4e9b\u7ec8\u7aef\u8282\u70b9\u3002\u8fd9\u662f\u4e00\u4e2a\u5b8c\u5168\u6709\u6548\u7684\u6388\u6743\u89c4\u5219\uff0c\u4f46\u4f7f\u7528\u6211\u4eec\u76ee\u524d\u770b\u5230\u7684\u89e3\u51b3\u65b9\u6848\uff0c\u6211\u4eec\u65e0\u6cd5\u627f\u53d7\u5b83\u3002\u8be5\u89c4\u5219\u6d89\u53ca\u8003\u8651\u5f53\u524d\u65f6\u95f4\u7684\u6761\u4ef6\uff0c\u56e0\u6b64\u4e0d\u80fd\u9759\u6001\u5b9a\u4e49\u7b56\u7565\u3002<\/p>\n<p>So, we start by creating a custom requirement:<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u9996\u5148\u521b\u5efa\u81ea\u5b9a\u4e49\u9700\u6c42\uff1a<\/p>\n<pre><code>public class MaintenanceTimeRequirement : IAuthorizationRequirement\n{\n    public TimeOnly StartTime { get; init; }\n    public TimeOnly EndTime { get; init; }\n}<\/code><\/pre>\n<p>The requirement contains the start and end times of the maintenance window. During this interval, we only want administrators to be able to operate.<br \/>\n\u8be5\u8981\u6c42\u5305\u542b\u7ef4\u62a4\u65f6\u6bb5\u7684\u5f00\u59cb\u548c\u7ed3\u675f\u65f6\u95f4\u3002\u5728\u6b64\u95f4\u9694\u671f\u95f4\uff0c\u6211\u4eec\u53ea\u5e0c\u671b\u7ba1\u7406\u5458\u80fd\u591f\u8fdb\u884c\u4f5c\u3002<\/p>\n<p>Note : TimeOnly is a new data type that has been introduced with C# 10 and allows us to store only only the time of the day (and not the date). More information is available at <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.timeonly\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.timeonly<\/a>.<br \/>\n\u6ce8\u610f : TimeOnly \u662f C# 10 \u4e2d\u5f15\u5165\u7684\u4e00\u79cd\u65b0\u6570\u636e\u7c7b\u578b\uff0c\u5b83\u5141\u8bb8\u6211\u4eec\u53ea\u5b58\u50a8\u4e00\u5929\u4e2d\u7684\u65f6\u95f4\uff08\u800c\u4e0d\u662f\u65e5\u671f\uff09\u3002\u6709\u5173\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u8bbf\u95ee <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.timeonly\">https:\/\/docs.microsoft.com\/dotnet\/api\/system.timeonly<\/a>\u3002<\/p>\n<p>Note that the IAuthorizationRequirement interface is just a placeholder. It doesn\u2019t contain any method or property to be implemented; it serves only to identify that the class is a requirement. In other words, if we don\u2019t need any additional information for the requirement, we can create a class that implements IAuthorizationRequirement but actually has no content at all.<br \/>\n\u8bf7\u6ce8\u610f\uff0cIAuthorizationRequirement \u63a5\u53e3\u53ea\u662f\u4e00\u4e2a\u5360\u4f4d\u7b26\u3002\u5b83\u4e0d\u5305\u542b\u4efb\u4f55\u8981\u5b9e\u73b0\u7684\u65b9\u6cd5\u6216\u5c5e\u6027;\u5b83\u4ec5\u7528\u4e8e\u6807\u8bc6\u8be5\u7c7b\u662f\u5fc5\u9700\u7684\u3002\u6362\u53e5\u8bdd\u8bf4\uff0c\u5982\u679c\u6211\u4eec\u4e0d\u9700\u8981\u8981\u6c42\u7684\u4efb\u4f55\u5176\u4ed6\u4fe1\u606f\uff0c\u6211\u4eec\u53ef\u4ee5\u521b\u5efa\u4e00\u4e2a\u5b9e\u73b0 IAuthorizationRequirement \u4f46\u5b9e\u9645\u4e0a\u6839\u672c\u6ca1\u6709\u5185\u5bb9\u7684\u7c7b\u3002<\/p>\n<p>This requirement must be enforced, so it is necessary to create the corresponding handler:<br \/>\n\u5fc5\u987b\u5f3a\u5236\u6267\u884c\u6b64\u8981\u6c42\uff0c\u56e0\u6b64\u5fc5\u987b\u521b\u5efa\u76f8\u5e94\u7684\u5904\u7406\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>public class MaintenanceTimeAuthorizationHandler\n    : AuthorizationHandler&lt;MaintenanceTimeRequirement&gt;\n{\n    protected override Task HandleRequirementAsync(\n        AuthorizationHandlerContext context,\n        MaintenanceTimeRequirement requirement)\n    {\n        var isAuthorized = true;\n        if (!context.User.IsInRole(&quot;Administrator&quot;))\n        {\n            var time = TimeOnly.FromDateTime(DateTime.Now);\n            if (time &gt;= requirement.StartTime &amp;&amp; time &lt;\n                requirement.EndTime)\n            {\n                isAuthorized = false;\n            }\n        }\n        if (isAuthorized)\n        {\n            context.Succeed(requirement);\n        }\n        return Task.CompletedTask;\n    }\n}<\/code><\/pre>\n<p>Our handler inherits from <code>AuthorizationHandler&lt;MaintenanceTimeRequirement&gt;<\/code>, so we need to override the <code>HandleRequirementAsync()<\/code> method to verify the requirement, using the AuthorizationHandlerContext parameter, which contains a reference to the current user. As said at the beginning, if the user is not assigned the Administrator role, we check whether the current time falls in the maintenance window. If so, the user doesn\u2019t have the right to access.<br \/>\n\u6211\u4eec\u7684\u5904\u7406\u7a0b\u5e8f\u7ee7\u627f\u81ea <code>AuthorizationHandler&lt;MaintenanceTimeRequirement&gt;<\/code>\uff0c\u56e0\u6b64\u6211\u4eec\u9700\u8981\u4f7f\u7528 AuthorizationHandlerContext \u53c2\u6570\uff08\u5305\u542b\u5bf9\u5f53\u524d\u7528\u6237\u7684\u5f15\u7528\uff09\u91cd\u5199 <code>HandleRequirementAsync()<\/code> \u65b9\u6cd5\u6765\u9a8c\u8bc1\u9700\u6c42\u3002\u5982\u5f00\u5934\u6240\u8ff0\uff0c\u5982\u679c\u672a\u4e3a\u7528\u6237\u5206\u914d Administrator \u89d2\u8272\uff0c\u6211\u4eec\u5c06\u68c0\u67e5\u5f53\u524d\u65f6\u95f4\u662f\u5426\u5728\u7ef4\u62a4\u65f6\u6bb5\u5185\u3002\u5982\u679c\u662f\u8fd9\u6837\uff0c\u5219\u7528\u6237\u65e0\u6743\u8bbf\u95ee\u3002<\/p>\n<p>At the end, if the isAuthorized variable is true, it means that the authorization can be granted, so we call the Succeed() method on the context object, passing the requirement that we want to validate. Otherwise, we don\u2019t invoke any method on the context, meaning that the requirement hasn\u2019t been verified.<br \/>\n\u6700\u540e\uff0c\u5982\u679c isAuthorized \u53d8\u91cf\u4e3a true\uff0c\u5219\u8868\u793a\u53ef\u4ee5\u6388\u4e88\u6388\u6743\uff0c\u56e0\u6b64\u6211\u4eec\u5728\u4e0a\u4e0b\u6587\u5bf9\u8c61\u4e0a\u8c03\u7528 Succeed\uff08\uff09 \u65b9\u6cd5\uff0c\u4f20\u9012\u6211\u4eec\u8981\u9a8c\u8bc1\u7684\u8981\u6c42\u3002\u5426\u5219\uff0c\u6211\u4eec\u4e0d\u4f1a\u5728\u4e0a\u4e0b\u6587\u4e2d\u8c03\u7528\u4efb\u4f55\u65b9\u6cd5\uff0c\u8fd9\u610f\u5473\u7740\u9700\u6c42\u5c1a\u672a\u7ecf\u8fc7\u9a8c\u8bc1\u3002<\/p>\n<p>We haven\u2019t yet finished implementing the custom policy. We still have to define the policy and register the handler in the service provider:<br \/>\n\u6211\u4eec\u5c1a\u672a\u5b8c\u6210\u81ea\u5b9a\u4e49\u7b56\u7565\u7684\u5b9e\u65bd\u3002\u6211\u4eec\u4ecd\u7136\u9700\u8981\u5b9a\u4e49\u7b56\u7565\u5e76\u5728\u670d\u52a1\u63d0\u4f9b\u8005\u4e2d\u6ce8\u518c\u5904\u7406\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>builder.Services.AddAuthorization(options =&gt;\n{\n    options.AddPolicy(&quot;TimedAccessPolicy&quot;, policy =&gt;\n    {\n        policy.Requirements.Add(new\n          MaintenanceTimeRequirement\n        {\n            StartTime = new TimeOnly(0, 0, 0),\n            EndTime = new TimeOnly(4, 0, 0)\n        });\n    });\n});\nbuilder.Services.AddScoped&lt;IAuthorizationHandler, MaintenanceTimeAuthorizationHandler&gt;();<\/code><\/pre>\n<p>In the preceding code, we defined a maintenance time window from midnight till 4:00 in the morning. Then, we registered the handler as an implementation of the IAuthorizationHandler interface, which in turn is implemented by the AuthorizationHandler class.<br \/>\n\u5728\u4e0a\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u5b9a\u4e49\u4e86\u4ece\u5348\u591c\u5230\u51cc\u6668 4\uff1a00 \u7684\u7ef4\u62a4\u65f6\u95f4\u7a97\u53e3\u3002\u7136\u540e\uff0c\u6211\u4eec\u5c06\u5904\u7406\u7a0b\u5e8f\u6ce8\u518c\u4e3a IAuthorizationHandler \u63a5\u53e3\u7684\u5b9e\u73b0\uff0c\u800c\u8be5\u63a5\u53e3\u53c8\u7531 AuthorizationHandler \u7c7b\u5b9e\u73b0\u3002<\/p>\n<p>Now that we have everything in place, we can apply the policy to our endpoints:<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u51c6\u5907\u597d\u4e86\u4e00\u5207\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u7b56\u7565\u5e94\u7528\u4e8e\u6211\u4eec\u7684\u7aef\u70b9\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/api\/custom-policy-protected&quot;, [Authorize(Policy = &quot;TimedAccessPolicy&quot;)] () =&gt; { });<\/code><\/pre>\n<p>When we try to reach this endpoint, ASP.NET Core will check the corresponding policy, find that it contains a requirement, and scan all the registrations of the IAuhorizationHandler interface to see whether there is one that is able to handle the requirement. Then, the handler will be invoked, and the result will be used to determine whether the user has the right to access the route. If the policy isn\u2019t verified, we\u2019ll get a 403 Forbidden response.<br \/>\n\u5f53\u6211\u4eec\u5c1d\u8bd5\u8bbf\u95ee\u6b64\u7ec8\u7aef\u8282\u70b9\u65f6\uff0cASP.NET Core \u5c06\u68c0\u67e5\u76f8\u5e94\u7684\u7b56\u7565\uff0c\u53d1\u73b0\u5b83\u5305\u542b\u9700\u6c42\uff0c\u5e76\u626b\u63cf IAuhorizationHandler \u63a5\u53e3\u7684\u6240\u6709\u6ce8\u518c\uff0c\u4ee5\u67e5\u770b\u662f\u5426\u6709\u80fd\u591f\u5904\u7406\u8be5\u8981\u6c42\u7684\u63a5\u53e3\u3002\u7136\u540e\uff0c\u5c06\u8c03\u7528\u5904\u7406\u7a0b\u5e8f\uff0c\u7ed3\u679c\u5c06\u7528\u4e8e\u786e\u5b9a\u7528\u6237\u662f\u5426\u6709\u6743\u8bbf\u95ee\u8def\u7531\u3002\u5982\u679c\u7b56\u7565\u672a\u7ecf\u8fc7\u9a8c\u8bc1\uff0c\u6211\u4eec\u5c06\u6536\u5230 403 Forbidden \u54cd\u5e94\u3002<\/p>\n<p>We have shown how powerful policies are, but there is more. We can also use them to define global rules that are automatically applied to all endpoints, using the concepts of default and fallback policies, as we\u2019ll see in the next section.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u5c55\u793a\u4e86\u653f\u7b56\u7684\u5f3a\u5927\u4e4b\u5904\uff0c\u4f46\u8fd8\u6709\u66f4\u591a\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u4f7f\u7528 default \u548c fallback \u7b56\u7565\u7684\u6982\u5ff5\uff0c\u4f7f\u7528\u5b83\u4eec\u6765\u5b9a\u4e49\u81ea\u52a8\u5e94\u7528\u4e8e\u6240\u6709\u7aef\u70b9\u7684\u5168\u5c40\u89c4\u5219\uff0c\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u8282\u4e2d\u770b\u5230\u3002<\/p>\n<p>Using default and fallback policies<br \/>\n\u4f7f\u7528 default \u548c fallback \u7b56\u7565<\/p>\n<p>Default and fallback policies are useful when we want to define global rules that must be automatically applied. In fact, when we use the Authorize attribute or the RequireAuthorization() method, without any other parameter, we implicitly refer to the default policy defined by ASP.NET Core, which is set to require an authenticated user.<br \/>\n\u5f53\u6211\u4eec\u60f3\u8981\u5b9a\u4e49\u5fc5\u987b\u81ea\u52a8\u5e94\u7528\u7684\u5168\u5c40\u89c4\u5219\u65f6\uff0cDefault \u548c fallback \u7b56\u7565\u975e\u5e38\u6709\u7528\u3002\u4e8b\u5b9e\u4e0a\uff0c\u5f53\u6211\u4eec\u4f7f\u7528 Authorize \u5c5e\u6027\u6216 RequireAuthorization\uff08\uff09 \u65b9\u6cd5\u65f6\uff0c\u5982\u679c\u6ca1\u6709\u4efb\u4f55\u5176\u4ed6\u53c2\u6570\uff0c\u6211\u4eec\u9690\u5f0f\u5f15\u7528\u4e86 ASP.NET Core \u5b9a\u4e49\u7684\u9ed8\u8ba4\u7b56\u7565\uff0c\u8be5\u7b56\u7565\u8bbe\u7f6e\u4e3a\u9700\u8981\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237\u3002<\/p>\n<p>If we want to use different conditions by default, we just need to redefine the DefaultPolicy property, which is available in the context of the AddAuthorization() method:<br \/>\n\u5982\u679c\u6211\u4eec\u60f3\u9ed8\u8ba4\u4f7f\u7528\u4e0d\u540c\u7684\u6761\u4ef6\uff0c\u6211\u4eec\u53ea\u9700\u8981\u91cd\u65b0\u5b9a\u4e49 DefaultPolicy \u5c5e\u6027\uff0c\u8be5\u5c5e\u6027\u5728 AddAuthorization\uff08\uff09 \u65b9\u6cd5\u7684\u4e0a\u4e0b\u6587\u4e2d\u53ef\u7528\uff1a<\/p>\n<pre><code>builder.Services.AddAuthorization(options =&gt;\n{\n    var policy = new AuthorizationPolicyBuilder()\n      .RequireAuthenticatedUser()\n        .RequireClaim(&quot;tenant-id&quot;).Build();\n    options.DefaultPolicy = policy;    \n});<\/code><\/pre>\n<p>We use AuthorizationPolicyBuilder to define all the security requirements, then we set it as a default policy. In this way, even if we don\u2019t specify a custom policy in the Authorize attribute or the RequireAuthorization() method, the system will always verify whether the user is authenticated, and the bearer contains the tenant-id claim. Of course, we can override this default behavior by just specifying roles or policy names in the authorization attribute or method.<br \/>\n\u6211\u4eec\u4f7f\u7528 AuthorizationPolicyBuilder \u5b9a\u4e49\u6240\u6709\u5b89\u5168\u8981\u6c42\uff0c\u7136\u540e\u5c06\u5176\u8bbe\u7f6e\u4e3a\u9ed8\u8ba4\u7b56\u7565\u3002\u8fd9\u6837\uff0c\u5373\u4f7f\u6211\u4eec\u6ca1\u6709\u5728 Authorize \u5c5e\u6027\u6216 RequireAuthorization\uff08\uff09 \u65b9\u6cd5\u4e2d\u6307\u5b9a\u81ea\u5b9a\u4e49\u7b56\u7565\uff0c\u7cfb\u7edf\u4e5f\u5c06\u59cb\u7ec8\u9a8c\u8bc1\u7528\u6237\u662f\u5426\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\uff0c\u5e76\u4e14\u6301\u6709\u8005\u5305\u542b tenant-id \u58f0\u660e\u3002\u5f53\u7136\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u5728 authorization \u5c5e\u6027\u6216\u65b9\u6cd5\u4e2d\u6307\u5b9a\u89d2\u8272\u6216\u7b56\u7565\u540d\u79f0\u6765\u8986\u76d6\u6b64\u9ed8\u8ba4\u884c\u4e3a\u3002<\/p>\n<p>A fallback policy, on the other hand, is the policy that is applied when there is no authorization information on the endpoints. It is useful, for example, when we want all our endpoints to be automatically protected, even if we forget to specify the Authorize attribute or just don\u2019t want to repeat the attribute for each handler. Let us try and understand this using the following code:<br \/>\n\u53e6\u4e00\u65b9\u9762\uff0c\u56de\u9000\u7b56\u7565\u662f\u5728\u7ec8\u7aef\u8282\u70b9\u4e0a\u6ca1\u6709\u6388\u6743\u4fe1\u606f\u65f6\u5e94\u7528\u7684\u7b56\u7565\u3002\u4f8b\u5982\uff0c\u5f53\u6211\u4eec\u5e0c\u671b\u81ea\u52a8\u4fdd\u62a4\u6240\u6709\u7aef\u70b9\u65f6\uff0c\u5373\u4f7f\u6211\u4eec\u5fd8\u8bb0\u6307\u5b9a Authorize \u5c5e\u6027\u6216\u53ea\u662f\u4e0d\u60f3\u4e3a\u6bcf\u4e2a\u5904\u7406\u7a0b\u5e8f\u91cd\u590d\u8be5\u5c5e\u6027\uff0c\u5b83\u4e5f\u5f88\u6709\u7528\u3002\u8ba9\u6211\u4eec\u5c1d\u8bd5\u4f7f\u7528\u4ee5\u4e0b\u4ee3\u7801\u6765\u7406\u89e3\u8fd9\u4e00\u70b9\uff1a<\/p>\n<pre><code>builder.Services.AddAuthorization(options =&gt;\n{\n    options.FallbackPolicy = options.DefaultPolicy;\n});<\/code><\/pre>\n<p>In the preceding code, FallbackPolicy becomes equal to DefaultPolicy. We have said that the default policy requires that the user be authenticated, so the result of this code is that now, all the endpoints automatically need authentication, even if we don\u2019t explicitly protect them.<br \/>\n\u5728\u4e0a\u9762\u7684\u4ee3\u7801\u4e2d\uff0cFallbackPolicy \u7b49\u4e8e DefaultPolicy\u3002\u6211\u4eec\u5df2\u7ecf\u8bf4\u8fc7\uff0c\u9ed8\u8ba4\u7b56\u7565\u8981\u6c42\u5bf9\u7528\u6237\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1\uff0c\u56e0\u6b64\u6b64\u4ee3\u7801\u7684\u7ed3\u679c\u662f\uff0c\u73b0\u5728\uff0c\u6240\u6709\u7aef\u70b9\u90fd\u81ea\u52a8\u9700\u8981\u8eab\u4efd\u9a8c\u8bc1\uff0c\u5373\u4f7f\u6211\u4eec\u6ca1\u6709\u660e\u786e\u4fdd\u62a4\u5b83\u4eec\u3002<\/p>\n<p>This is a typical solution to adopt when most of our endpoints have restricted access. We don\u2019t need to specify the Authorize attribute or use the RequireAuthorization() method anymore. In other words, now all our endpoints are protected by default.<br \/>\n\u5f53\u6211\u4eec\u7684\u5927\u591a\u6570\u7aef\u70b9\u90fd\u9650\u5236\u8bbf\u95ee\u65f6\uff0c\u8fd9\u662f\u4e00\u79cd\u5178\u578b\u7684\u89e3\u51b3\u65b9\u6848\u3002\u6211\u4eec\u4e0d\u518d\u9700\u8981\u6307\u5b9a Authorize \u5c5e\u6027\u6216\u4f7f\u7528 RequireAuthorization\uff08\uff09 \u65b9\u6cd5\u3002\u6362\u53e5\u8bdd\u8bf4\uff0c\u73b0\u5728\u6211\u4eec\u6240\u6709\u7684\u7aef\u70b9\u90fd\u9ed8\u8ba4\u53d7\u5230\u4fdd\u62a4\u3002<\/p>\n<p>If we decide to use this approach, but a bunch of endpoints need public access, such as the login endpoint, which everyone should be able to invoke, we can use the AllowAnonymous attribute or the AllowAnonymous() method:<br \/>\n\u5982\u679c\u6211\u4eec\u51b3\u5b9a\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff0c\u4f46\u6709\u5927\u91cf\u7aef\u70b9\u9700\u8981\u516c\u5171\u8bbf\u95ee\uff0c\u4f8b\u5982\u6bcf\u4e2a\u4eba\u90fd\u5e94\u8be5\u80fd\u591f\u8c03\u7528\u7684\u767b\u5f55\u7aef\u70b9\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 AllowAnonymous \u5c5e\u6027\u6216 AllowAnonymous\uff08\uff09 \u65b9\u6cd5\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/api\/auth\/login&quot;, [AllowAnonymous] (LoginRequest request) =&gt; { });\n\/\/ OR\napp.MapPost(&quot;\/api\/auth\/login&quot;, (LoginRequest request) =&gt; { })\n.AllowAnonymous();<\/code><\/pre>\n<p>As the name implies, the preceding code will bypass all authorization checks for the endpoint, including the default and fallback authorization policies.<br \/>\n\u987e\u540d\u601d\u4e49\uff0c\u524d\u9762\u7684\u4ee3\u7801\u5c06\u7ed5\u8fc7\u7ec8\u7aef\u8282\u70b9\u7684\u6240\u6709\u6388\u6743\u68c0\u67e5\uff0c\u5305\u62ec\u9ed8\u8ba4\u548c\u56de\u9000\u6388\u6743\u7b56\u7565\u3002<\/p>\n<p>To deepen our knowledge of policy-based authentication, we can refer to the official documentation at <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authorization\/policies\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authorization\/policies<\/a>.<br \/>\n\u4e3a\u4e86\u52a0\u6df1\u6211\u4eec\u5bf9\u57fa\u4e8e\u7b56\u7565\u7684\u8eab\u4efd\u9a8c\u8bc1\u7684\u4e86\u89e3\uff0c\u6211\u4eec\u53ef\u4ee5\u53c2\u8003 <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authorization\/policies\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authorization\/policies<\/a> \u7684\u5b98\u65b9\u6587\u6863\u3002<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>Knowing how authentication and authorization work in minimal APIs is fundamental to developing secure applications. Using JWT bearer authentication roles and policies, we can even define complex authorization scenarios, with the ability to use both standard and custom rules.<br \/>\n\u4e86\u89e3\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u5728\u6700\u5c0f API \u4e2d\u7684\u5de5\u4f5c\u539f\u7406\u662f\u5f00\u53d1\u5b89\u5168\u5e94\u7528\u7a0b\u5e8f\u7684\u57fa\u7840\u3002\u4f7f\u7528 JWT \u4e0d\u8bb0\u540d\u8eab\u4efd\u9a8c\u8bc1\u89d2\u8272\u548c\u7b56\u7565\uff0c\u6211\u4eec\u751a\u81f3\u53ef\u4ee5\u5b9a\u4e49\u590d\u6742\u7684\u6388\u6743\u573a\u666f\uff0c\u5e76\u80fd\u591f\u4f7f\u7528\u6807\u51c6\u548c\u81ea\u5b9a\u4e49\u89c4\u5219\u3002<\/p>\n<p>In this chapter, we have introduced basic concepts to make a service secure, but there is much more to talk about, especially regarding ASP.NET Core Identity: an API that supports login functionality and allows managing users, passwords, profile data, roles, claims, and more. We can look further into this topic by checking out the official documentation, which is available at <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\/identity\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\/identity<\/a>.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u4ecb\u7ecd\u4e86\u786e\u4fdd\u670d\u52a1\u5b89\u5168\u7684\u57fa\u672c\u6982\u5ff5\uff0c\u4f46\u8fd8\u6709\u66f4\u591a\u5185\u5bb9\u8981\u8ba8\u8bba\uff0c\u5c24\u5176\u662f\u5173\u4e8e ASP.NET \u6838\u5fc3\u8eab\u4efd\uff1a\u4e00\u4e2a\u652f\u6301\u767b\u5f55\u529f\u80fd\u5e76\u5141\u8bb8\u7ba1\u7406\u7528\u6237\u3001\u5bc6\u7801\u3001\u914d\u7f6e\u6587\u4ef6\u6570\u636e\u3001\u89d2\u8272\u3001\u58f0\u660e\u7b49\u7684 API\u3002\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u67e5\u770b\u5b98\u65b9\u6587\u6863\u6765\u8fdb\u4e00\u6b65\u4e86\u89e3\u8fd9\u4e2a\u4e3b\u9898\uff0c\u8be5\u6587\u6863\u53ef\u5728 <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\/identity\">https:\/\/docs.microsoft.com\/aspnet\/core\/security\/authentication\/identity<\/a> \u4e0a\u83b7\u5f97\u3002<\/p>\n<p>In the next chapter, we will see how to add multilanguage support to our minimal APIs and how to correctly handle applications that work with different date formats, time zones, and so on.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u4e3a\u6211\u4eec\u7684\u6700\u5c0f API \u6dfb\u52a0\u591a\u8bed\u8a00\u652f\u6301\uff0c\u4ee5\u53ca\u5982\u4f55\u6b63\u786e\u5904\u7406\u4f7f\u7528\u4e0d\u540c\u65e5\u671f\u683c\u5f0f\u3001\u65f6\u533a\u7b49\u7684\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<h1>9 Leveraging Globalization and Localization<\/h1>\n<p>9 \u5229\u7528\u5168\u7403\u5316\u548c\u672c\u5730\u5316<\/p>\n<p>When developing an application, it is important to think about multi-language support; a multilingual application allows for a wider audience reach. This is also true for web APIs: messages returned by endpoints (for example, validation errors) should be localized, and the service should be able to handle different cultures and deal with time zones. In this chapter of the book, we will talk about globalization and localization, and we will explain what features are available in minimal APIs to work with these concepts. The information and samples that will be provided will guide us when adding multi-language support to our services and correctly handling all the related behaviors so that we will be able to develop global applications.<br \/>\n\u5728\u5f00\u53d1\u5e94\u7528\u7a0b\u5e8f\u65f6\uff0c\u8003\u8651\u591a\u8bed\u8a00\u652f\u6301\u975e\u5e38\u91cd\u8981;\u591a\u8bed\u8a00\u5e94\u7528\u7a0b\u5e8f\u5141\u8bb8\u66f4\u5e7f\u6cdb\u7684\u53d7\u4f17\u8303\u56f4\u3002Web API \u4e5f\u662f\u5982\u6b64\uff1a\u7aef\u70b9\u8fd4\u56de\u7684\u6d88\u606f\uff08\u4f8b\u5982\uff0c\u9a8c\u8bc1\u9519\u8bef\uff09\u5e94\u8be5\u672c\u5730\u5316\uff0c\u5e76\u4e14\u670d\u52a1\u5e94\u8be5\u80fd\u591f\u5904\u7406\u4e0d\u540c\u7684\u533a\u57df\u6027\u5e76\u5904\u7406\u65f6\u533a\u3002\u5728\u672c\u4e66\u7684\u8fd9\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u5168\u7403\u5316\u548c\u672c\u5730\u5316\uff0c\u5e76\u5c06\u89e3\u91ca\u6700\u5c0f API \u4e2d\u6709\u54ea\u4e9b\u529f\u80fd\u53ef\u7528\u4e8e\u5904\u7406\u8fd9\u4e9b\u6982\u5ff5\u3002\u5c06\u63d0\u4f9b\u7684\u4fe1\u606f\u548c\u793a\u4f8b\u5c06\u6307\u5bfc\u6211\u4eec\u5411\u6211\u4eec\u7684\u670d\u52a1\u6dfb\u52a0\u591a\u8bed\u8a00\u652f\u6301\u5e76\u6b63\u786e\u5904\u7406\u6240\u6709\u76f8\u5173\u884c\u4e3a\uff0c\u4ee5\u4fbf\u6211\u4eec\u80fd\u591f\u5f00\u53d1\u5168\u7403\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>In this chapter, we will be covering the following topics:<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4ee5\u4e0b\u4e3b\u9898\uff1a<\/p>\n<p>\u2022  Introducing globalization and localization<br \/>\n\u5168\u7403\u5316\u548c\u672c\u5730\u5316\u7b80\u4ecb<\/p>\n<p>\u2022  Localizing a minimal API application<br \/>\n\u672c\u5730\u5316\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f<\/p>\n<p>\u2022  Using resource files<br \/>\n\u4f7f\u7528\u8d44\u6e90\u6587\u4ef6<\/p>\n<p>\u2022  Integrating localization in validation frameworks<br \/>\n\u5c06\u672c\u5730\u5316\u96c6\u6210\u5230\u9a8c\u8bc1\u6846\u67b6\u4e2d<\/p>\n<p>\u2022  Adding UTC support to a globalized minimal API<br \/>\n\u5411\u5168\u7403\u5316\u7684\u6700\u5c0f API \u6dfb\u52a0 UTC \u652f\u6301<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>To follow the descriptions in this chapter, you will need to create an ASP.NET Core 6.0 Web API application. Refer to the Technical requirements section in Chapter 1, Introduction to Minimal APIs, for instructions on how to do so.<br \/>\n\u8981\u6309\u7167\u672c\u7ae0\u4e2d\u7684\u63cf\u8ff0\u8fdb\u884c\u4f5c\uff0c\u60a8\u9700\u8981\u521b\u5efa\u4e00\u4e2a ASP.NET Core 6.0 Web API \u5e94\u7528\u7a0b\u5e8f\u3002\u6709\u5173\u5982\u4f55\u6267\u884c\u6b64\u4f5c\u7684\u8bf4\u660e\uff0c\u8bf7\u53c2\u9605\u7b2c 1 \u7ae0 \u6700\u5c0f API \u7b80\u4ecb\u4e2d\u7684\u6280\u672f\u8981\u6c42\u90e8\u5206\u3002<\/p>\n<p>If you\u2019re using your console, shell, or Bash terminal to create the API, remember to change your working directory to the current chapter number (Chapter09).<br \/>\n\u5982\u679c\u60a8\u4f7f\u7528\u63a7\u5236\u53f0\u3001shell \u6216 Bash \u7ec8\u7aef\u521b\u5efa API\uff0c\u8bf7\u8bb0\u4f4f\u5c06\u5de5\u4f5c\u76ee\u5f55\u66f4\u6539\u4e3a\u5f53\u524d\u7ae0\u8282\u7f16\u53f7 \uff08Chapter09\uff09\u3002<\/p>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter09\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter09<\/a>.<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter09\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter09<\/a>\u3002<\/p>\n<p>Introducing globalization and localization<br \/>\n\u5168\u7403\u5316\u548c\u672c\u5730\u5316\u7b80\u4ecb<\/p>\n<p>When thinking about internationalization, we must deal with globalization and localization, two terms that seem to refer to the same concepts but actually involve different areas. Globalization is the task of designing applications that can manage and support different cultures. Localization is the process of adapting an application to a particular culture, for example, by providing translated resources for each culture that will be supported.<br \/>\n\u5728\u8003\u8651\u56fd\u9645\u5316\u65f6\uff0c\u6211\u4eec\u5fc5\u987b\u5904\u7406\u5168\u7403\u5316\u548c\u672c\u5730\u5316\uff0c\u8fd9\u4e24\u4e2a\u672f\u8bed\u4f3c\u4e4e\u6307\u7684\u662f\u76f8\u540c\u7684\u6982\u5ff5\uff0c\u4f46\u5b9e\u9645\u4e0a\u6d89\u53ca\u4e0d\u540c\u7684\u9886\u57df\u3002\u5168\u7403\u5316\u7684\u4efb\u52a1\u662f\u8bbe\u8ba1\u80fd\u591f\u7ba1\u7406\u548c\u652f\u6301\u4e0d\u540c\u533a\u57df\u6027\u7684\u5e94\u7528\u7a0b\u5e8f\u3002\u672c\u5730\u5316\u662f\u4f7f\u5e94\u7528\u7a0b\u5e8f\u9002\u5e94\u7279\u5b9a\u533a\u57df\u6027\u7684\u8fc7\u7a0b\uff0c\u4f8b\u5982\uff0c\u4e3a\u5c06\u8981\u652f\u6301\u7684\u6bcf\u79cd\u533a\u57df\u6027\u63d0\u4f9b\u7ffb\u8bd1\u8d44\u6e90\u3002<\/p>\n<p>Note : The terms internationalization, globalization, and localization are often abbreviated to I18N, G11N, and L10N, respectively.<br \/>\n\u6ce8\u610f : \u672f\u8bed\u56fd\u9645\u5316\u3001\u5168\u7403\u5316\u548c\u672c\u5730\u5316\u901a\u5e38\u5206\u522b\u7f29\u5199\u4e3a I18N\u3001G11N \u548c L10N\u3002<\/p>\n<p>As with all the other features that we have already introduced in the previous chapters, globalization and localization can be handled by the corresponding middleware and services that ASP.NET Core provides and work in the same way in minimal APIs and controller-based projects.<br \/>\n\u4e0e\u6211\u4eec\u5728\u524d\u51e0\u7ae0\u4e2d\u4ecb\u7ecd\u7684\u6240\u6709\u5176\u4ed6\u529f\u80fd\u4e00\u6837\uff0c\u5168\u7403\u5316\u548c\u672c\u5730\u5316\u53ef\u4ee5\u7531 ASP.NET Core \u63d0\u4f9b\u7684\u76f8\u5e94\u4e2d\u95f4\u4ef6\u548c\u670d\u52a1\u5904\u7406\uff0c\u5e76\u4e14\u5728\u6700\u5c0f\u7684 API \u548c\u57fa\u4e8e\u63a7\u5236\u5668\u7684\u9879\u76ee\u4e2d\u4ee5\u76f8\u540c\u7684\u65b9\u5f0f\u5de5\u4f5c\u3002<\/p>\n<p>You can find a great introduction to globalization and localization in the official documentation available at <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/globalization\">https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/globalization<\/a> and <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/localization\">https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/localization<\/a>, respectively. In the rest of the chapter, we will focus on how to add support for these features in a minimal API project; in this way, we\u2019ll introduce some important concepts and explain how to leverage globalization and localization in ASP.NET Core.<br \/>\n\u60a8\u53ef\u4ee5\u5206\u522b\u5728 <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/globalization\">https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/globalization<\/a> \u548c <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/localization\">https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/localization<\/a> \u4e0a\u63d0\u4f9b\u7684\u5b98\u65b9\u6587\u6863\u4e2d\u627e\u5230\u6709\u5173\u5168\u7403\u5316\u548c\u672c\u5730\u5316\u7684\u7cbe\u5f69\u4ecb\u7ecd\u3002\u5728\u672c\u7ae0\u7684\u5176\u4f59\u90e8\u5206\uff0c\u6211\u4eec\u5c06\u91cd\u70b9\u4ecb\u7ecd\u5982\u4f55\u5728\u6700\u5c0f API \u9879\u76ee\u4e2d\u6dfb\u52a0\u5bf9\u8fd9\u4e9b\u529f\u80fd\u7684\u652f\u6301;\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u4e00\u4e9b\u91cd\u8981\u7684\u6982\u5ff5\uff0c\u5e76\u89e3\u91ca\u5982\u4f55\u5728 ASP.NET Core \u4e2d\u5229\u7528\u5168\u7403\u5316\u548c\u672c\u5730\u5316\u3002<\/p>\n<p>Localizing a minimal API application<br \/>\n\u672c\u5730\u5316\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f<\/p>\n<p>To enable localization within a minimal API application, let us go through the following steps:<br \/>\n\u8981\u5728\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f\u4e2d\u542f\u7528\u672c\u5730\u5316\uff0c\u8ba9\u6211\u4eec\u6267\u884c\u4ee5\u4e0b\u6b65\u9aa4\uff1a<\/p>\n<ol>\n<li>The first step to making an application localizable is to specify the supported cultures by setting the corresponding options, as follows:<br \/>\n\u4f7f\u5e94\u7528\u7a0b\u5e8f\u53ef\u672c\u5730\u5316\u7684\u7b2c\u4e00\u6b65\u662f\u901a\u8fc7\u8bbe\u7f6e\u76f8\u5e94\u7684\u9009\u9879\u6765\u6307\u5b9a\u53d7\u652f\u6301\u7684\u533a\u57df\u6027\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/...\nvar supportedCultures = new CultureInfo[] { new(&quot;en&quot;), new(&quot;it&quot;), new(&quot;fr&quot;) };\nbuilder.Services.Configure&lt;RequestLocalizationOptions&gt;(options =&gt;\n{\noptions.SupportedCultures = supportedCultures;\noptions.SupportedUICultures = supportedCultures;\noptions.DefaultRequestCulture = new\nRequestCulture(supportedCultures.First());\n});<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>In our example, we want to support three cultures \u2013 English, Italian, and French \u2013 so, we create an array of CultureInfo objects.<br \/>\n\u5728\u6211\u4eec\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5e0c\u671b\u652f\u6301\u4e09\u79cd\u533a\u57df\u6027 \u2013 \u82f1\u8bed\u3001\u610f\u5927\u5229\u8bed\u548c\u6cd5\u8bed \u2013 \u56e0\u6b64\uff0c\u6211\u4eec\u521b\u5efa\u4e86\u4e00\u4e2a CultureInfo \u5bf9\u8c61\u6570\u7ec4\u3002<\/p>\n<p>We\u2019re defining neutral cultures, that is, cultures that have a language but are not associated with a country or region. We could also use specific cultures, such as en-US or en-GB, to represent the cultures of a particular region: for example, en-US would refer to the English culture prevalent in the United States, while en-GB would refer to the English culture prevalent in the United Kingdom. This difference is important because, depending on the scenario, we may need to use country-specific information to correctly implement localization. For example, if we want to show a date, we have to know that the date format in the United States is M\/d\/yyyy, while in the United Kingdom, it is dd\/MM\/yyyy. So, in this case, it becomes fundamental to work with specific cultures. We also use specific cultures if we need to support language differences across cultures. For example, a particular word may have different spellings depending on the country (e.g., color in the US versus colour in the UK). That said, for our scenario of minimal APIs, working with neutral cultures is just fine.<br \/>\n\u6211\u4eec\u5b9a\u4e49\u7684\u975e\u7279\u5b9a\u533a\u57df\u6027\uff0c\u5373\u5177\u6709\u67d0\u79cd\u8bed\u8a00\u4f46\u4e0e\u56fd\u5bb6\u6216\u5730\u533a\u65e0\u5173\u7684\u533a\u57df\u6027\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u4f7f\u7528\u7279\u5b9a\u533a\u57df\u6027\uff08\u5982 en-US \u6216 en-GB\uff09\u6765\u8868\u793a\u7279\u5b9a\u533a\u57df\u7684\u533a\u57df\u6027\uff1a\u4f8b\u5982\uff0cen-US \u8868\u793a\u7f8e\u56fd\u6d41\u884c\u7684\u82f1\u8bed\u533a\u57df\u6027\uff0c\u800c en-GB \u8868\u793a\u82f1\u56fd\u6d41\u884c\u7684\u82f1\u8bed\u533a\u57df\u6027\u3002\u8fd9\u79cd\u5dee\u5f02\u5f88\u91cd\u8981\uff0c\u56e0\u4e3a\u6839\u636e\u5177\u4f53\u60c5\u51b5\uff0c\u6211\u4eec\u53ef\u80fd\u9700\u8981\u4f7f\u7528\u7279\u5b9a\u4e8e\u56fd\u5bb6\/\u5730\u533a\u7684\u4fe1\u606f\u6765\u6b63\u786e\u5b9e\u65bd\u672c\u5730\u5316\u3002\u4f8b\u5982\uff0c\u5982\u679c\u6211\u4eec\u60f3\u663e\u793a\u4e00\u4e2a\u65e5\u671f\uff0c\u6211\u4eec\u5fc5\u987b\u77e5\u9053\u7f8e\u56fd\u7684\u65e5\u671f\u683c\u5f0f\u662f M\/d\/yyyy\uff0c\u800c\u5728\u82f1\u56fd\u662f dd\/MM\/yyyy\u3002\u56e0\u6b64\uff0c\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u4e0e\u7279\u5b9a\u6587\u5316\u5408\u4f5c\u53d8\u5f97\u81f3\u5173\u91cd\u8981\u3002\u5982\u679c\u6211\u4eec\u9700\u8981\u652f\u6301\u8de8\u6587\u5316\u7684\u8bed\u8a00\u5dee\u5f02\uff0c\u6211\u4eec\u4e5f\u4f1a\u4f7f\u7528\u7279\u5b9a\u533a\u57df\u6027\u3002\u4f8b\u5982\uff0c\u6839\u636e\u56fd\u5bb6\/\u5730\u533a\uff0c\u7279\u5b9a\u5355\u8bcd\u53ef\u80fd\u5177\u6709\u4e0d\u540c\u7684\u62fc\u5199\uff08\u4f8b\u5982\uff0c\u7f8e\u56fd\u7684 color \u4e0e\u82f1\u56fd\u7684 colour\uff09\u3002\u4e5f\u5c31\u662f\u8bf4\uff0c\u5bf9\u4e8e\u6211\u4eec\u7684\u6700\u5c0f API \u65b9\u6848\uff0c\u4f7f\u7528\u975e\u7279\u5b9a\u533a\u57df\u6027\u5c31\u5f88\u597d\u4e86\u3002<\/p>\n<ol start=\"2\">\n<li>Next, we configure RequestLocalizationOptions, setting the cultures and specifying the default one to use if no information about the culture is provided. We specify both the supported cultures and the supported UI cultures:<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u914d\u7f6e RequestLocalizationOptions\uff0c\u8bbe\u7f6e\u533a\u57df\u6027\u5e76\u6307\u5b9a\u5728\u672a\u63d0\u4f9b\u6709\u5173\u533a\u57df\u6027\u7684\u4fe1\u606f\u65f6\u8981\u4f7f\u7528\u7684\u9ed8\u8ba4\u533a\u57df\u6027\u3002\u6211\u4eec\u6307\u5b9a\u4e86\u53d7\u652f\u6301\u7684\u533a\u57df\u6027\u548c\u53d7\u652f\u6301\u7684 UI \u533a\u57df\u6027\uff1a<\/li>\n<\/ol>\n<p>\u2022  The supported cultures control the output of culture-dependent functions, such as date, time, and number format.<br \/>\n\u652f\u6301\u7684\u533a\u57df\u6027\u63a7\u5236\u4f9d\u8d56\u4e8e\u533a\u57df\u6027\u7684\u51fd\u6570\uff08\u5982\u65e5\u671f\u3001\u65f6\u95f4\u548c\u6570\u5b57\u683c\u5f0f\uff09\u7684\u8f93\u51fa\u3002<\/p>\n<p>\u2022  The supported UI cultures are used to choose which translated strings (from .resx files) are searched for. We will talk about .resx files later in this chapter.<br \/>\n\u652f\u6301\u7684 UI \u533a\u57df\u6027\u7528\u4e8e\u9009\u62e9\u8981\u641c\u7d22\u7684\u5df2\u7ffb\u8bd1\u5b57\u7b26\u4e32\uff08\u4ece .resx \u6587\u4ef6\uff09\u3002\u6211\u4eec\u5c06\u5728\u672c\u7ae0\u540e\u9762\u8ba8\u8bba .resx \u6587\u4ef6\u3002<\/p>\n<p>In a typical application, cultures and UI cultures are set to the same values, but of course, we can use different options if needed.<br \/>\n\u5728\u5178\u578b\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u533a\u57df\u6027\u548c UI \u533a\u57df\u6027\u8bbe\u7f6e\u4e3a\u76f8\u540c\u7684\u503c\uff0c\u4f46\u5f53\u7136\uff0c\u5982\u679c\u9700\u8981\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u4e0d\u540c\u7684\u9009\u9879\u3002<\/p>\n<ol start=\"3\">\n<li>Now that we have configured our service to support globalization, we need to add the localization middleware to the ASP.NET Core pipeline so it will be able to automatically set the culture of the request. Let us do so using the following code:<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u5c06\u670d\u52a1\u914d\u7f6e\u4e3a\u652f\u6301\u5168\u7403\u5316\uff0c\u6211\u4eec\u9700\u8981\u5c06\u672c\u5730\u5316\u4e2d\u95f4\u4ef6\u6dfb\u52a0\u5230 ASP.NET Core \u7ba1\u9053\u4e2d\uff0c\u4ee5\u4fbf\u5b83\u80fd\u591f\u81ea\u52a8\u8bbe\u7f6e\u8bf7\u6c42\u7684\u533a\u57df\u6027\u3002\u8ba9\u6211\u4eec\u4f7f\u7528\u4ee5\u4e0b\u4ee3\u7801\u6765\u505a\u5230\u8fd9\u4e00\u70b9\uff1a<\/p>\n<pre><code>var app = builder.Build();\n\/\/...\napp.UseRequestLocalization();\n\/\/...\napp.Run();<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>In the preceding code, with UseRequestLocalization(), we\u2019re adding RequestLocalizationMiddleware to the ASP.NET Core pipeline to set the current culture of each request. This task is performed using a list of RequestCultureProvider that can read information about the culture from various sources. Default providers comprise the following:<br \/>\n\u5728\u524d\u9762\u7684\u4ee3\u7801\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528 UseRequestLocalization\uff08\uff09 \u5c06 RequestLocalizationMiddleware \u6dfb\u52a0\u5230 ASP.NET Core \u7ba1\u9053\uff0c\u4ee5\u8bbe\u7f6e\u6bcf\u4e2a\u8bf7\u6c42\u7684\u5f53\u524d\u533a\u57df\u6027\u3002\u6b64\u4efb\u52a1\u662f\u4f7f\u7528 RequestCultureProvider \u5217\u8868\u6267\u884c\u7684\uff0c\u8be5\u5217\u8868\u53ef\u4ee5\u4ece\u5404\u79cd\u6e90\u8bfb\u53d6\u6709\u5173\u533a\u57df\u6027\u7684\u4fe1\u606f\u3002\u9ed8\u8ba4\u63d0\u4f9b\u7a0b\u5e8f\u5305\u62ec\u4ee5\u4e0b\u5185\u5bb9\uff1a<\/p>\n<p>\u2022  QueryStringRequestCultureProvider: Searches for the culture and ui-culture query string parameters<br \/>\n\u2022  QueryStringRequestCultureProvider\uff1a\u641c\u7d22 culture \u548c ui-culture \u67e5\u8be2\u5b57\u7b26\u4e32\u53c2\u6570<\/p>\n<p>\u2022  CookieRequestCultureProvider: Uses the ASP.NET Core cookie<br \/>\nCookieRequestCultureProvider\uff1a\u4f7f\u7528 ASP.NET Core Cookie<\/p>\n<p>AcceptLanguageHeaderRequestProvider: Reads the requested culture from the Accept-Language HTTP header<br \/>\nAcceptLanguageHeaderRequestProvider\uff1a\u4ece Accept-Language HTTP \u6807\u5934\u4e2d\u8bfb\u53d6\u8bf7\u6c42\u7684\u533a\u57df\u6027<\/p>\n<p>For each request, the system will try to use these providers in this exact order, until it finds the first one that can determine the culture. If the culture cannot be set, the one specified in the DefaultRequestCulture property of RequestLocalizationOptions will be used.<br \/>\n\u5bf9\u4e8e\u6bcf\u4e2a\u8bf7\u6c42\uff0c\u7cfb\u7edf\u5c06\u5c1d\u8bd5\u6309\u6b64\u786e\u5207\u987a\u5e8f\u4f7f\u7528\u8fd9\u4e9b\u63d0\u4f9b\u7a0b\u5e8f\uff0c\u76f4\u5230\u627e\u5230\u53ef\u4ee5\u786e\u5b9a\u533a\u57df\u6027\u7684\u7b2c\u4e00\u4e2a\u63d0\u4f9b\u7a0b\u5e8f\u3002\u5982\u679c\u65e0\u6cd5\u8bbe\u7f6e\u533a\u57df\u6027\uff0c\u5219\u5c06\u4f7f\u7528 RequestLocalizationOptions \u7684 DefaultRequestCulture \u5c5e\u6027\u4e2d\u6307\u5b9a\u7684\u533a\u57df\u6027\u3002<\/p>\n<p>If necessary, it is also possible to change the order of the request culture providers or even define a custom provider to implement our own logic to determine the culture. More information on this topic is available at :<br \/>\n\u5982\u6709\u5fc5\u8981\uff0c\u8fd8\u53ef\u4ee5\u66f4\u6539\u8bf7\u6c42\u6587\u5316\u63d0\u4f9b\u8005\u7684\u987a\u5e8f\uff0c\u751a\u81f3\u5b9a\u4e49\u81ea\u5b9a\u4e49\u63d0\u4f9b\u8005\u6765\u5b9e\u73b0\u6211\u4eec\u81ea\u5df1\u7684\u903b\u8f91\u6765\u786e\u5b9a\u6587\u5316\u3002\u6709\u5173\u6b64\u4e3b\u9898\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u8bbf\u95ee:<br \/>\n<a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/localization#use-a-custom-provider\">https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/localization#use-a-custom-provider<\/a>.<\/p>\n<p>Important note : The localization middleware must be inserted before any other middleware that might use the request culture.<br \/>\n\u91cd\u8981\u63d0\u793a : \u672c\u5730\u5316\u4e2d\u95f4\u4ef6\u5fc5\u987b\u63d2\u5165\u5230\u53ef\u80fd\u4f7f\u7528\u8bf7\u6c42\u533a\u57df\u6027\u7684\u4efb\u4f55\u5176\u4ed6\u4e2d\u95f4\u4ef6\u4e4b\u524d\u3002<\/p>\n<p>In the case of web APIs, whether using controller-based or minimal APIs, we usually set the request culture through the Accept-Language HTTP header. In the following section, we will see how to extend Swagger with the ability to add this header when trying to invoke methods.<br \/>\n\u5bf9\u4e8e Web API\uff0c\u65e0\u8bba\u662f\u4f7f\u7528\u57fa\u4e8e\u63a7\u5236\u5668\u7684 API \u8fd8\u662f\u6700\u5c0f\u7684 API\uff0c\u6211\u4eec\u901a\u5e38\u901a\u8fc7 Accept-Language HTTP \u6807\u5934\u6765\u8bbe\u7f6e\u8bf7\u6c42\u6587\u5316\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u770b\u5230\u5982\u4f55\u6269\u5c55 Swagger\uff0c\u4f7f\u5176\u80fd\u591f\u5728\u5c1d\u8bd5\u8c03\u7528\u65b9\u6cd5\u65f6\u6dfb\u52a0\u6b64\u6807\u5934\u3002<\/p>\n<p>Adding globalization support to Swagger<br \/>\n\u5411 Swagger \u6dfb\u52a0\u5168\u7403\u5316\u652f\u6301<\/p>\n<p>We want Swagger to provide us with a way to specify the Accept-Language HTTP header for each request so that we can test our globalized endpoints. Technically speaking, this means adding an operation filter to Swagger that will be able to automatically insert the language header, using the following code:<br \/>\n\u6211\u4eec\u5e0c\u671b Swagger \u4e3a\u6211\u4eec\u63d0\u4f9b\u4e00\u79cd\u65b9\u6cd5\u6765\u4e3a\u6bcf\u4e2a\u8bf7\u6c42\u6307\u5b9a Accept-Language HTTP \u6807\u5934\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ef\u4ee5\u6d4b\u8bd5\u6211\u4eec\u7684\u5168\u7403\u5316\u7aef\u70b9\u3002\u4ece\u6280\u672f\u4e0a\u8bb2\uff0c\u8fd9\u610f\u5473\u7740\u5411 Swagger \u6dfb\u52a0\u4e00\u4e2a\u4f5c\u8fc7\u6ee4\u5668\uff0c\u8be5\u8fc7\u6ee4\u5668\u5c06\u80fd\u591f\u4f7f\u7528\u4ee5\u4e0b\u4ee3\u7801\u81ea\u52a8\u63d2\u5165\u8bed\u8a00\u6807\u5934\uff1a<\/p>\n<pre><code>public class AcceptLanguageHeaderOperationFilter : IOperationFilter\n{\n     private readonly List&lt;IOpenApiAny&gt;? \n     supportedLanguages;\n     public AcceptLanguageHeaderOperationFilter \n     (IOptions&lt;RequestLocalizationOptions&gt; \n     requestLocalizationOptions)\n     {\n           supportedLanguages = \n           requestLocalizationOptions.Value.\n           SupportedCultures?.Select(c =&gt; \n           newOpenApiString(c.TwoLetterISOLanguageName)).\n           Cast&lt;IOpenApiAny&gt;().           ToList();\n     }\n     public void Apply(OpenApiOperation operation, \n     OperationFilterContext context)\n     {\n           if (supportedLanguages?.Any() ?? false)\n           {\n                 operation.Parameters ??= new \n                 List&lt;OpenApiParameter&gt;();\n                 operation.Parameters.Add(new \n                 OpenApiParameter\n                 {\n                       Name = HeaderNames.AcceptLanguage,\n                       In = ParameterLocation.Header,\n                       Required = false,\n                       Schema = new OpenApiSchema\n                       {\n                             Type = &quot;string&quot;,\n                             Enum = supportedLanguages,\n                             Default = supportedLanguages.\n                             First()\n                       }\n                 });\n           }\n     }\n}<\/code><\/pre>\n<p>In the preceding code, AcceptLanguageHeaderOperationFilter takes the RequestLocalizationOptions object via dependency injection that we have defined at startup and extracts the supported languages in the format that Swagger expects from it. Then, in the Apply() method, we add a new OpenApiParameter that corresponds to the Accept-Language header. In particular, with the Schema.Enum property, we provide the list of supported languages using the values we have extracted in the constructor. This method is invoked for every operation (that is, every endpoint), meaning that the parameter will be automatically added to each of them.<br \/>\n\u5728\u524d\u9762\u7684\u4ee3\u7801\u4e2d\uff0cAcceptLanguageHeaderOperationFilter \u901a\u8fc7\u6211\u4eec\u5728\u542f\u52a8\u65f6\u5b9a\u4e49\u7684\u4f9d\u8d56\u9879\u6ce8\u5165\u83b7\u53d6 RequestLocalizationOptions \u5bf9\u8c61\uff0c\u5e76\u4ee5 Swagger \u671f\u671b\u7684\u683c\u5f0f\u63d0\u53d6\u652f\u6301\u7684\u8bed\u8a00\u3002\u7136\u540e\uff0c\u5728 Apply\uff08\uff09 \u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u6dfb\u52a0\u4e00\u4e2a\u5bf9\u5e94\u4e8e Accept-Language \u6807\u5934\u7684\u65b0 OpenApiParameter\u3002\u5177\u4f53\u800c\u8a00\uff0c\u5bf9\u4e8e Schema.Enum \u5c5e\u6027\uff0c\u6211\u4eec\u4f7f\u7528\u5728\u6784\u9020\u51fd\u6570\u4e2d\u63d0\u53d6\u7684\u503c\u63d0\u4f9b\u652f\u6301\u7684\u8bed\u8a00\u5217\u8868\u3002\u6bcf\u4e2a\u4f5c\uff08\u5373\u6bcf\u4e2a\u7aef\u70b9\uff09\u90fd\u4f1a\u8c03\u7528\u6b64\u65b9\u6cd5\uff0c\u8fd9\u610f\u5473\u7740\u53c2\u6570\u5c06\u81ea\u52a8\u6dfb\u52a0\u5230\u6bcf\u4e2a\u4f5c\u4e2d\u3002<\/p>\n<p>Now, we need to add the new filter to Swagger:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u9700\u8981\u5c06\u65b0\u8fc7\u6ee4\u5668\u6dfb\u52a0\u5230 Swagger\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/...\nbuilder.Services.AddSwaggerGen(options =&gt;\n{\n     options.OperationFilter&lt;AcceptLanguageHeaderOperation\n     Filter&gt;();\n});<\/code><\/pre>\n<p>As we did with the preceding code, for every operation, Swagger will execute the filter, which in turn will add a parameter to specify the language of the request.<br \/>\n\u6b63\u5982\u6211\u4eec\u5bf9\u524d\u9762\u7684\u4ee3\u7801\u6240\u505a\u7684\u90a3\u6837\uff0c\u5bf9\u4e8e\u6bcf\u4e2a\u4f5c\uff0cSwagger \u5c06\u6267\u884c\u8fc7\u6ee4\u5668\uff0c\u800c\u8fc7\u6ee4\u5668\u53c8\u4f1a\u6dfb\u52a0\u4e00\u4e2a\u53c2\u6570\u6765\u6307\u5b9a\u8bf7\u6c42\u7684\u8bed\u8a00\u3002<\/p>\n<p>So, let\u2019s suppose we have the following endpoint:<br \/>\n\u56e0\u6b64\uff0c\u5047\u8bbe\u6211\u4eec\u6709\u4ee5\u4e0b\u7aef\u70b9\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/culture&quot;, () =&gt; Thread.CurrentThread.CurrentCulture.DisplayName);<\/code><\/pre>\n<p>In the preceding handler, we just return the culture of the thread. This method takes no parameter; however, after adding the preceding filter, the Swagger UI will show the following:<br \/>\n\u5728\u524d\u9762\u7684\u5904\u7406\u7a0b\u5e8f\u4e2d\uff0c\u6211\u4eec\u53ea\u8fd4\u56de\u7ebf\u7a0b\u7684\u533a\u57df\u6027\u3002\u6b64\u65b9\u6cd5\u4e0d\u5e26\u53c2\u6570;\u4f46\u662f\uff0c\u5728\u6dfb\u52a0\u4e0a\u8ff0\u7b5b\u9009\u5668\u540e\uff0cSwagger UI \u5c06\u663e\u793a\u4ee5\u4e0b\u5185\u5bb9\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0901.jpg\" ><\/p>\n<p>Figure 9.1 \u2013 The Accept-Language header added to Swagger<br \/>\n\u56fe 9.1 \u2013 \u6dfb\u52a0\u5230 Swagger \u7684 Accept-Language \u6807\u5934<\/p>\n<p>The operation filter has added a new parameter to the endpoint, allowing us to select the language from a dropdown. We can click the Try it out button to choose a value from the list and then click Execute to invoke the endpoint:<br \/>\n\u4f5c\u7b5b\u9009\u5668\u5df2\u5411\u7ec8\u7aef\u8282\u70b9\u6dfb\u52a0\u4e86\u4e00\u4e2a\u65b0\u53c2\u6570\uff0c\u5141\u8bb8\u6211\u4eec\u4ece\u4e0b\u62c9\u5217\u8868\u4e2d\u9009\u62e9\u8bed\u8a00\u3002\u6211\u4eec\u53ef\u4ee5\u5355\u51fb Try it out \u6309\u94ae\u4ece\u5217\u8868\u4e2d\u9009\u62e9\u4e00\u4e2a\u503c\uff0c\u7136\u540e\u5355\u51fb Execute \u4ee5\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0902.jpg\"><\/p>\n<p>Figure 9.2 \u2013 The result of the execution with the Accept-Language HTTP header<br \/>\n\u56fe 9.2 \u2013 \u4f7f\u7528 Accept-Language HTTP \u6807\u5934\u6267\u884c\u7684\u7ed3\u679c<\/p>\n<p>This is the result of selecting it as a language request: Swagger has added the Accept-Language HTTP header, which, in turn, has been used by ASP.NET Core to set the current culture. Then, in the end, we get and return the culture display name in the route handler.<br \/>\n\u8fd9\u662f\u9009\u62e9\u5b83\u4f5c\u4e3a\u8bed\u8a00\u8bf7\u6c42\u7684\u7ed3\u679c\uff1aSwagger \u6dfb\u52a0\u4e86 Accept-Language HTTP \u6807\u5934\uff0c\u800c ASP.NET Core \u53c8\u4f7f\u7528\u8be5\u6807\u5934\u6765\u8bbe\u7f6e\u5f53\u524d\u533a\u57df\u6027\u3002\u7136\u540e\uff0c\u6700\u540e\uff0c\u6211\u4eec\u5728\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u4e2d\u83b7\u53d6\u5e76\u8fd4\u56de\u533a\u57df\u6027\u663e\u793a\u540d\u79f0\u3002<\/p>\n<p>This example shows us that we have correctly added globalization support to our minimal API. In the next section, we\u2019ll go further and work with localization, starting by providing translated resources to callers based on the corresponding languages.<br \/>\n\u6b64\u793a\u4f8b\u5411\u6211\u4eec\u5c55\u793a\u4e86\u6211\u4eec\u5df2\u6b63\u786e\u5730\u5c06\u5168\u7403\u5316\u652f\u6301\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u6700\u5c0f API \u4e2d\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u8fdb\u4e00\u6b65\u8ba8\u8bba\u672c\u5730\u5316\uff0c\u9996\u5148\u6839\u636e\u76f8\u5e94\u7684\u8bed\u8a00\u5411\u8c03\u7528\u8005\u63d0\u4f9b\u7ffb\u8bd1\u540e\u7684\u8d44\u6e90\u3002<\/p>\n<p>Using resource files<br \/>\n\u4f7f\u7528\u8d44\u6e90\u6587\u4ef6<\/p>\n<p>Our minimal API now supports globalization, so it can switch cultures based on the request. This means that we can provide localized messages to callers, for example, when communicating validation errors. This feature is based on the so-called resource files (.resx), a particular kind of XML file that contains key-value string pairs representing messages that must be localized.<br \/>\n\u6211\u4eec\u7684\u6700\u5c0f API \u73b0\u5728\u652f\u6301\u5168\u7403\u5316\uff0c\u56e0\u6b64\u5b83\u53ef\u4ee5\u6839\u636e\u8bf7\u6c42\u5207\u6362\u533a\u57df\u6027\u3002\u8fd9\u610f\u5473\u7740\u6211\u4eec\u53ef\u4ee5\u5411\u8c03\u7528\u8005\u63d0\u4f9b\u672c\u5730\u5316\u6d88\u606f\uff0c\u4f8b\u5982\uff0c\u5728\u4f20\u8fbe\u9a8c\u8bc1\u9519\u8bef\u65f6\u3002\u6b64\u529f\u80fd\u57fa\u4e8e\u6240\u8c13\u7684\u8d44\u6e90\u6587\u4ef6 \uff08.resx\uff09\uff0c\u8fd9\u662f\u4e00\u79cd\u7279\u6b8a\u7c7b\u578b\u7684 XML \u6587\u4ef6\uff0c\u5176\u4e2d\u5305\u542b\u8868\u793a\u5fc5\u987b\u672c\u5730\u5316\u7684\u6d88\u606f\u7684\u952e\u503c\u5b57\u7b26\u4e32\u5bf9\u3002<\/p>\n<p>Note : These resource files are exactly the same as they have been since the early versions of .NET.<br \/>\n\u6ce8\u610f : \u8fd9\u4e9b\u8d44\u6e90\u6587\u4ef6\u4e0e\u81ea .NET \u65e9\u671f\u7248\u672c\u4ee5\u6765\u5b8c\u5168\u76f8\u540c\u3002<\/p>\n<p>Creating and working with resource files<br \/>\n\u521b\u5efa\u548c\u4f7f\u7528\u8d44\u6e90\u6587\u4ef6<\/p>\n<p>With resource files, we can easily separate strings from code and group them by culture. Typically, resource files are put in a folder called Resources. To create a file of this kind using Visual Studio, let us go through the following steps:<br \/>\n\u4f7f\u7528\u8d44\u6e90\u6587\u4ef6\uff0c\u6211\u4eec\u53ef\u4ee5\u8f7b\u677e\u5730\u5c06\u5b57\u7b26\u4e32\u4e0e\u4ee3\u7801\u5206\u79bb\uff0c\u5e76\u6309\u533a\u57df\u6027\u5bf9\u5b83\u4eec\u8fdb\u884c\u5206\u7ec4\u3002\u901a\u5e38\uff0c\u8d44\u6e90\u6587\u4ef6\u653e\u5728\u540d\u4e3a Resources \u7684\u6587\u4ef6\u5939\u4e2d\u3002\u8981\u4f7f\u7528 Visual Studio \u521b\u5efa\u6b64\u7c7b\u6587\u4ef6\uff0c\u8ba9\u6211\u4eec\u6267\u884c\u4ee5\u4e0b\u6b65\u9aa4\uff1a<\/p>\n<p>Important note : Unfortunately, Visual Studio Code does not provide support for handling .resx files. More information about this topic is available at <a href=\"https:\/\/github.com\/dotnet\/AspNetCore.Docs\/issues\/2501\">https:\/\/github.com\/dotnet\/AspNetCore.Docs\/issues\/2501<\/a>.<br \/>\n\u91cd\u8981\u63d0\u793a : \u9057\u61be\u7684\u662f\uff0cVisual Studio Code \u4e0d\u652f\u6301\u5904\u7406 .resx \u6587\u4ef6\u3002\u6709\u5173\u6b64\u4e3b\u9898\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u8bbf\u95ee <a href=\"https:\/\/github.com\/dotnet\/AspNetCore.Docs\/issues\/2501\">https:\/\/github.com\/dotnet\/AspNetCore.Docs\/issues\/2501<\/a>\u3002<\/p>\n<ol>\n<li>\n<p>Right-click on the folder in Solution Explorer and then choose Add | New Item.<br \/>\n\u53f3\u952e\u5355\u51fb\u201c\u89e3\u51b3\u65b9\u6848\u8d44\u6e90\u7ba1\u7406\u5668\u201d\u4e2d\u7684\u6587\u4ef6\u5939\uff0c\u7136\u540e\u9009\u62e9\u201c\u6dfb\u52a0\u201d|\u201d\u65b0\u5efa\u9879\u76ee\u3002<\/p>\n<\/li>\n<li>\n<p>In the Add New Item dialog window, search for Resources, select the corresponding template, and name the file, for example, Messages.resx:<br \/>\n\u5728 Add New Item \u5bf9\u8bdd\u6846\u7a97\u53e3\u4e2d\uff0c\u641c\u7d22 Resources\uff0c\u9009\u62e9\u76f8\u5e94\u7684\u6a21\u677f\uff0c\u7136\u540e\u5c06\u6587\u4ef6\u547d\u540d\u4e3a Messages.resx\uff1a<\/p>\n<\/li>\n<\/ol>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0903.jpg\"><\/p>\n<p>Figure 9.3 \u2013 Adding a resource file to the project<br \/>\n\u56fe 9.3 \u2013 \u5c06\u8d44\u6e90\u6587\u4ef6\u6dfb\u52a0\u5230\u9879\u76ee\u4e2d<\/p>\n<p>The new file will immediately open in the Visual Studio editor.<br \/>\n\u65b0\u6587\u4ef6\u5c06\u7acb\u5373\u5728 Visual Studio \u7f16\u8f91\u5668\u4e2d\u6253\u5f00\u3002<\/p>\n<ol start=\"3\">\n<li>The first thing to do in the new file is to select Internal or Public (based on the code visibility we want to achieve) from the Access Modifier option so that Visual Studio will create a C# file that exposes the properties to access the resources:<br \/>\n\u5728\u65b0\u6587\u4ef6\u4e2d\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u4ece Access Modifier \u9009\u9879\u4e2d\u9009\u62e9 Internal \u6216 Public \uff08\u57fa\u4e8e\u6211\u4eec\u60f3\u8981\u5b9e\u73b0\u7684\u4ee3\u7801\u53ef\u89c1\u6027\uff09\uff0c\u4ee5\u4fbf Visual Studio \u521b\u5efa\u4e00\u4e2a C# \u6587\u4ef6\uff0c\u8be5\u6587\u4ef6\u516c\u5f00\u5c5e\u6027\u4ee5\u8bbf\u95ee\u8d44\u6e90\uff1a<\/li>\n<\/ol>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/0904.jpg\"><\/p>\n<p>Figure 9.4 \u2013 Changing the Access Modifier of the resource file<br \/>\n\u56fe 9.4 \u2013 \u66f4\u6539\u8d44\u6e90\u6587\u4ef6\u7684\u8bbf\u95ee\u4fee\u9970\u7b26<\/p>\n<p>As soon as we change this value, Visual Studio will add a Messages.Designer.cs file to the project and automatically create properties that correspond to the strings we insert in the resource file.<br \/>\n\u4e00\u65e6\u6211\u4eec\u66f4\u6539\u4e86\u6b64\u503c\uff0cVisual Studio \u5c31\u4f1a\u5c06 Messages.Designer.cs \u6587\u4ef6\u6dfb\u52a0\u5230\u9879\u76ee\u4e2d\uff0c\u5e76\u81ea\u52a8\u521b\u5efa\u4e0e\u6211\u4eec\u63d2\u5165\u5230\u8d44\u6e90\u6587\u4ef6\u4e2d\u7684\u5b57\u7b26\u4e32\u76f8\u5bf9\u5e94\u7684\u5c5e\u6027\u3002<\/p>\n<p>Resource files must follow a precise naming convention. The file that contains default culture messages can have any name (such as Messages.resx, as in our example), but the other .resx files that provide the corresponding translations must have the same name, with the specification of the culture (neutral or specific) to which they refer. So, we have Messages.resx, which will store default (English) messages.<br \/>\n\u8d44\u6e90\u6587\u4ef6\u5fc5\u987b\u9075\u5faa\u7cbe\u786e\u7684\u547d\u540d\u7ea6\u5b9a\u3002\u5305\u542b\u9ed8\u8ba4\u533a\u57df\u6027\u6d88\u606f\u7684\u6587\u4ef6\u53ef\u4ee5\u5177\u6709\u4efb\u4f55\u540d\u79f0\uff08\u5982 Messages.resx\uff0c\u5982\u672c\u4f8b\u4e2d\u6240\u793a\uff09\uff0c\u4f46\u63d0\u4f9b\u76f8\u5e94\u7ffb\u8bd1\u7684\u5176\u4ed6 .resx \u6587\u4ef6\u5fc5\u987b\u5177\u6709\u76f8\u540c\u7684\u540d\u79f0\uff0c\u5e76\u5177\u6709\u5b83\u4eec\u6240\u5f15\u7528\u7684\u533a\u57df\u6027\uff08\u975e\u7279\u5b9a\u6216\u7279\u5b9a\uff09\u7684\u89c4\u8303\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u6709 Messages.resx\uff0c\u5b83\u5c06\u5b58\u50a8\u9ed8\u8ba4\uff08\u82f1\u6587\uff09\u6d88\u606f\u3002<\/p>\n<ol start=\"4\">\n<li>Since we also want to localize our messages in Italian, we need to create another file with the name Messages.it.resx.<br \/>\n\u7531\u4e8e\u6211\u4eec\u8fd8\u5e0c\u671b\u5c06\u6d88\u606f\u672c\u5730\u5316\u4e3a Italian\uff0c\u56e0\u6b64\u9700\u8981\u521b\u5efa\u53e6\u4e00\u4e2a\u540d\u4e3a Messages.it.resx \u7684\u6587\u4ef6\u3002<\/li>\n<\/ol>\n<p>Note : We don\u2019t create a resource file for French culture on purpose because this way, we\u2019ll see how APS.NET Core looks up the localized messages in practice.<br \/>\n\u6ce8\u610f : \u6211\u4eec\u4e0d\u4f1a\u6545\u610f\u4e3a\u6cd5\u56fd\u6587\u5316\u521b\u5efa\u8d44\u6e90\u6587\u4ef6\uff0c\u56e0\u4e3a\u8fd9\u6837\uff0c\u6211\u4eec\u5c06\u770b\u5230 APS.NET Core \u5728\u5b9e\u8df5\u4e2d\u5982\u4f55\u67e5\u627e\u672c\u5730\u5316\u7684\u6d88\u606f\u3002<\/p>\n<ol start=\"5\">\n<li>Now, we can start experimenting with resource files. Let\u2019s open the Messages.resx file and set Name to HelloWorld and Value to Hello World!.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u5f00\u59cb\u8bd5\u9a8c\u8d44\u6e90\u6587\u4ef6\u3002\u8ba9\u6211\u4eec\u6253\u5f00 Messages.resx \u6587\u4ef6\uff0c\u5e76\u5c06 Name \u8bbe\u7f6e\u4e3a HelloWorld\uff0c\u5c06 Value \u8bbe\u7f6e\u4e3a Hello World\uff01\u3002<\/li>\n<\/ol>\n<p>In this way, Visual Studio will add a static HelloWorld property in the Messages autogenerated class that allows us to access values based on the current culture.<br \/>\n\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0cVisual Studio \u5c06\u5728 Messages \u81ea\u52a8\u751f\u6210\u7684\u7c7b\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u9759\u6001 HelloWorld \u5c5e\u6027\uff0c\u8be5\u5c5e\u6027\u5141\u8bb8\u6211\u4eec\u8bbf\u95ee\u57fa\u4e8e\u5f53\u524d\u533a\u57df\u6027\u7684\u503c\u3002<\/p>\n<ol start=\"6\">\n<li>\n<p>To demonstrate this behavior, also open the Messages.it.resx file and add an item with the same Name, HelloWorld, but now set Value to the translation Ciao mondo!.<br \/>\n\u4e3a\u4e86\u6f14\u793a\u6b64\u884c\u4e3a\uff0c\u8fd8\u8bf7\u6253\u5f00 Messages.it.resx \u6587\u4ef6\u5e76\u6dfb\u52a0\u5177\u6709\u76f8\u540c\u540d\u79f0\u7684\u9879 HelloWorld\uff0c\u4f46\u73b0\u5728\u5c06 Value \u8bbe\u7f6e\u4e3a\u7ffb\u8bd1 Ciao mondo\uff01\u3002<\/p>\n<\/li>\n<li>\n<p>Finally, we can add a new endpoint to showcase the usage of the resource files:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u65b0\u7684\u7aef\u70b9\u6765\u5c55\u793a\u8d44\u6e90\u6587\u4ef6\u7684\u4f7f\u7528\u60c5\u51b5\uff1a<\/p>\n<\/li>\n<\/ol>\n<pre><code>\/\/ using Chapter09.Resources;\napp.MapGet(&quot;\/helloworld&quot;, () =&gt; Messages.HelloWorld);<\/code><\/pre>\n<p>In the preceding route handler, we simply access the static Mesasges.HelloWorld property that, as discussed before, has been automatically created while editing the Messages.resx file.<br \/>\n\u5728\u524d\u9762\u7684\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u4e2d\uff0c\u6211\u4eec\u53ea\u9700\u8bbf\u95ee\u9759\u6001 Mesasges.HelloWorld \u5c5e\u6027\uff0c\u5982\u524d\u6240\u8ff0\uff0c\u8be5\u5c5e\u6027\u662f\u5728\u7f16\u8f91 Messages.resx \u6587\u4ef6\u65f6\u81ea\u52a8\u521b\u5efa\u7684\u3002<\/p>\n<p>If we now run the minimal API and try to execute this endpoint, we\u2019ll get the following responses based on the request language that we select in Swagger:<br \/>\n\u5982\u679c\u6211\u4eec\u73b0\u5728\u8fd0\u884c\u6700\u5c0f API \u5e76\u5c1d\u8bd5\u6267\u884c\u6b64\u7ec8\u7aef\u8282\u70b9\uff0c\u6211\u4eec\u5c06\u6839\u636e\u6211\u4eec\u5728 Swagger \u4e2d\u9009\u62e9\u7684\u8bf7\u6c42\u8bed\u8a00\u83b7\u5f97\u4ee5\u4e0b\u54cd\u5e94\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/09T01.jpg\"><\/p>\n<p>Table 9.1 \u2013 Responses based on the request language<br \/>\n\u8868 9.1 \u2013 \u57fa\u4e8e\u8bf7\u6c42\u8bed\u8a00\u7684\u54cd\u5e94<\/p>\n<p>When accessing a property such as HelloWorld, the autogenerated Messages class internally uses ResourceManager to look up the corresponding localized string. First of all, it looks for a resource file whose name contains the requested culture. If it is not found, it reverts to the parent culture of that culture. This means that, if the requested culture is specific, ResourceManager searches for the neutral culture. If no resource file is still found, then the default one is used.<br \/>\n\u5f53\u8bbf\u95ee\u8bf8\u5982 HelloWorld \u4e4b\u7c7b\u7684\u5c5e\u6027\u65f6\uff0c\u81ea\u52a8\u751f\u6210\u7684 Messages \u7c7b\u5728\u5185\u90e8\u4f7f\u7528 ResourceManager \u6765\u67e5\u627e\u76f8\u5e94\u7684\u672c\u5730\u5316\u5b57\u7b26\u4e32\u3002\u9996\u5148\uff0c\u5b83\u67e5\u627e\u5176\u540d\u79f0\u5305\u542b\u6240\u8bf7\u6c42\u533a\u57df\u6027\u7684\u8d44\u6e90\u6587\u4ef6\u3002\u5982\u679c\u672a\u627e\u5230\uff0c\u5b83\u5c06\u8fd8\u539f\u4e3a\u8be5\u533a\u57df\u6027\u7684\u7236\u533a\u57df\u6027\u3002\u8fd9\u610f\u5473\u7740\uff0c\u5982\u679c\u8bf7\u6c42\u7684\u533a\u57df\u6027\u662f\u7279\u5b9a\u7684\uff0c\u5219 ResourceManager \u4f1a\u641c\u7d22\u975e\u7279\u5b9a\u533a\u57df\u6027\u3002\u5982\u679c\u4ecd\u672a\u627e\u5230\u8d44\u6e90\u6587\u4ef6\uff0c\u5219\u4f7f\u7528\u9ed8\u8ba4\u8d44\u6e90\u6587\u4ef6\u3002<\/p>\n<p>In our case, using Swagger, we can select only English, Italian, or French as a neutral culture. But what happens if a client sends other values? We can have situations such as the following:<br \/>\n\u5728\u6211\u4eec\u7684\u793a\u4f8b\u4e2d\uff0c\u4f7f\u7528 Swagger\uff0c\u6211\u4eec\u53ea\u80fd\u9009\u62e9\u82f1\u8bed\u3001\u610f\u5927\u5229\u8bed\u6216\u6cd5\u8bed\u4f5c\u4e3a\u975e\u7279\u5b9a\u533a\u57df\u6027\u3002\u4f46\u662f\uff0c\u5982\u679c\u5ba2\u6237\u7aef\u53d1\u9001\u5176\u4ed6\u503c\uff0c\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\u5462\uff1f\u6211\u4eec\u53ef\u80fd\u4f1a\u9047\u5230\u4ee5\u4e0b\u60c5\u51b5\uff1a<\/p>\n<p>\u2022  The request culture is it-IT: the system searches for Messages.it-IT.resx and then finds and uses Messages.it.resx.<br \/>\n\u8bf7\u6c42\u533a\u57df\u6027\u662f it-IT\uff1a\u7cfb\u7edf\u641c\u7d22 Messages.it-IT.resx\uff0c\u7136\u540e\u67e5\u627e\u5e76\u4f7f\u7528 Messages.it.resx\u3002<\/p>\n<p>\u2022  The request culture is fr-FR: the system searches for Messages.fr-FR.resx, then Messages.fr.resx, and (because neither are available) finally uses the default, Messages.resx.<br \/>\n\u8bf7\u6c42\u533a\u57df\u6027\u662f fr-FR\uff1a\u7cfb\u7edf\u641c\u7d22 Messages.fr-FR.resx\uff0c\u7136\u540e\u641c\u7d22 Messages.fr.resx\uff0c\u6700\u540e\uff08\u56e0\u4e3a\u4e24\u8005\u90fd\u4e0d\u53ef\u7528\uff09\u4f7f\u7528\u9ed8\u8ba4\u7684 Messages.resx\u3002<\/p>\n<p>\u2022  The request culture is de (German): because this isn\u2019t a supported culture at all, the default request culture will be automatically selected, so strings will be searched for in the Messages.resx file.<br \/>\n\u8bf7\u6c42\u533a\u57df\u6027\u4e3a de \uff08\u5fb7\u8bed\uff09 \uff1a\u7531\u4e8e\u8fd9\u6839\u672c\u4e0d\u662f\u53d7\u652f\u6301\u7684\u533a\u57df\u6027\uff0c\u56e0\u6b64\u5c06\u81ea\u52a8\u9009\u62e9\u9ed8\u8ba4\u8bf7\u6c42\u533a\u57df\u6027\uff0c\u56e0\u6b64\u5c06\u5728 Messages.resx \u6587\u4ef6\u4e2d\u641c\u7d22\u5b57\u7b26\u4e32\u3002<\/p>\n<p>Note : If a localized resource file exists, but it doesn\u2019t contain the specified key, then the value of the default file will be used.<br \/>\n\u6ce8\u610f : \u5982\u679c\u672c\u5730\u5316\u8d44\u6e90\u6587\u4ef6\u5b58\u5728\uff0c\u4f46\u4e0d\u5305\u542b\u6307\u5b9a\u7684\u952e\uff0c\u5219\u5c06\u4f7f\u7528\u9ed8\u8ba4\u6587\u4ef6\u7684\u503c\u3002<\/p>\n<p>Formatting localized messages using resource files<br \/>\n\u4f7f\u7528\u8d44\u6e90\u6587\u4ef6\u8bbe\u7f6e\u672c\u5730\u5316\u6d88\u606f\u7684\u683c\u5f0f<\/p>\n<p>We can also use resource files to format localized messages. For example, we can add the following strings to the resource files of the project:<br \/>\n\u6211\u4eec\u8fd8\u53ef\u4ee5\u4f7f\u7528 resource \u6587\u4ef6\u6765\u683c\u5f0f\u5316\u672c\u5730\u5316\u7684\u6d88\u606f\u3002\u4f8b\u5982\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u4ee5\u4e0b\u5b57\u7b26\u4e32\u6dfb\u52a0\u5230\u9879\u76ee\u7684\u8d44\u6e90\u6587\u4ef6\u4e2d\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/09T02.jpg\"><\/p>\n<p>Table 9.2 \u2013 A custom localized message<br \/>\n\u8868 9.2 \u2013 \u81ea\u5b9a\u4e49\u672c\u5730\u5316\u6d88\u606f<\/p>\n<p>Now, let\u2019s define this endpoint:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u5b9a\u4e49\u8fd9\u4e2a\u7aef\u70b9\uff1a<\/p>\n<pre><code>\/\/ using Chapter09.Resources;\napp.MapGet(&quot;\/hello&quot;, (string name) =&gt;\n{\n     var message = string.Format(Messages.GreetingMessage, \n     name);\n     return message;\n});<\/code><\/pre>\n<p>As in the preceding code example, we get a string from a resource file according to the culture of the request. But, in this case, the message contains a placeholder, so we can use it to create a custom localized message using the name that is passed to the route handler. If we try to execute the endpoint, we will get results such as these:<br \/>\n\u4e0e\u524d\u9762\u7684\u4ee3\u7801\u793a\u4f8b\u4e00\u6837\uff0c\u6211\u4eec\u6839\u636e\u8bf7\u6c42\u7684\u533a\u57df\u6027\u4ece\u8d44\u6e90\u6587\u4ef6\u4e2d\u83b7\u53d6\u5b57\u7b26\u4e32\u3002\u4f46\u662f\uff0c\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6d88\u606f\u5305\u542b\u4e00\u4e2a\u5360\u4f4d\u7b26\uff0c\u56e0\u6b64\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u5b83\u6765\u4f7f\u7528\u4f20\u9012\u7ed9\u8def\u7531\u5904\u7406\u7a0b\u5e8f\u7684\u540d\u79f0\u521b\u5efa\u81ea\u5b9a\u4e49\u672c\u5730\u5316\u6d88\u606f\u3002\u5982\u679c\u6211\u4eec\u5c1d\u8bd5\u6267\u884c\u7aef\u70b9\uff0c\u6211\u4eec\u5c06\u5f97\u5230\u5982\u4e0b\u7ed3\u679c\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/09T03.jpg\"><\/p>\n<p>Table 9.3 \u2013 Responses with custom localized messages based on the request language<br \/>\n\u8868 9.3 \u2013 \u4f7f\u7528\u57fa\u4e8e\u8bf7\u6c42\u8bed\u8a00\u7684\u81ea\u5b9a\u4e49\u672c\u5730\u5316\u6d88\u606f\u7684\u54cd\u5e94<\/p>\n<p>The possibility to create localized messages with placeholders that are replaced at runtime using different values is a key point for creating truly localizable services.<br \/>\n\u521b\u5efa\u5e26\u6709\u5360\u4f4d\u7b26\u7684\u672c\u5730\u5316\u6d88\u606f\u7684\u53ef\u80fd\u6027\uff0c\u8fd9\u4e9b\u5360\u4f4d\u7b26\u5728\u8fd0\u884c\u65f6\u4f7f\u7528\u4e0d\u540c\u7684\u503c\u66ff\u6362\uff0c\u8fd9\u662f\u521b\u5efa\u771f\u6b63\u53ef\u672c\u5730\u5316\u670d\u52a1\u7684\u5173\u952e\u70b9\u3002<\/p>\n<p>In the beginning, we said that a typical use case of localization in web APIs is when we need to provide localized error messages upon validation. In the next section, we\u2019ll see how to add this feature to our minimal API.<br \/>\n\u4e00\u5f00\u59cb\uff0c\u6211\u4eec\u8bf4\u8fc7 Web API \u4e2d\u672c\u5730\u5316\u7684\u4e00\u4e2a\u5178\u578b\u7528\u4f8b\u662f\u6211\u4eec\u9700\u8981\u5728\u9a8c\u8bc1\u65f6\u63d0\u4f9b\u672c\u5730\u5316\u7684\u9519\u8bef\u6d88\u606f\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u5c06\u6b64\u529f\u80fd\u6dfb\u52a0\u5230\u6211\u4eec\u7684\u6700\u5c0f API \u4e2d\u3002<\/p>\n<p>Integrating localization in validation frameworks<br \/>\n\u5c06\u672c\u5730\u5316\u96c6\u6210\u5230\u9a8c\u8bc1\u6846\u67b6\u4e2d<\/p>\n<p>In Chapter 6, Exploring Validation and Mapping, we talked about how to integrate validation into a minimal API project. We learned how to use the MiniValidation library, rather than FluentValidation, to validate our models and provide validation messages to the callers. We also said that FluentValidation already provides translations for standard error messages.<br \/>\n\u5728 \u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04 \u4e2d\uff0c\u6211\u4eec\u8ba8\u8bba\u4e86\u5982\u4f55\u5c06\u9a8c\u8bc1\u96c6\u6210\u5230\u4e00\u4e2a\u6700\u5c0f\u7684 API \u9879\u76ee\u4e2d\u3002\u6211\u4eec\u5b66\u4e60\u4e86\u5982\u4f55\u4f7f\u7528 MiniValidation \u5e93\uff08\u800c\u4e0d\u662f FluentValidation\uff09\u6765\u9a8c\u8bc1\u6211\u4eec\u7684\u6a21\u578b\u5e76\u5411\u8c03\u7528\u8005\u63d0\u4f9b\u9a8c\u8bc1\u6d88\u606f\u3002\u6211\u4eec\u8fd8\u8bf4\u8fc7\uff0cFluentValidation \u5df2\u7ecf\u4e3a\u6807\u51c6\u9519\u8bef\u6d88\u606f\u63d0\u4f9b\u4e86\u7ffb\u8bd1\u3002<\/p>\n<p>However, with both libraries, we can leverage the localization support we have just added to our project to support localized and custom validation messages.<br \/>\n\u4f46\u662f\uff0c\u5bf9\u4e8e\u8fd9\u4e24\u4e2a\u5e93\uff0c\u6211\u4eec\u53ef\u4ee5\u5229\u7528\u521a\u521a\u6dfb\u52a0\u5230\u9879\u76ee\u4e2d\u7684\u672c\u5730\u5316\u652f\u6301\u6765\u652f\u6301\u672c\u5730\u5316\u548c\u81ea\u5b9a\u4e49\u9a8c\u8bc1\u6d88\u606f\u3002<\/p>\n<p>Localizing validation messages with MiniValidation<br \/>\n\u4f7f\u7528 MiniValidation \u672c\u5730\u5316\u9a8c\u8bc1\u6d88\u606f<\/p>\n<p>Using the MiniValidation library, we can use validation based on Data Annotations with minimal APIs. Refer to Chapter 6, Exploring Validation and Mapping, for instructions on how to add this library to the project.<br \/>\n\u4f7f\u7528 MiniValidation \u5e93\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u57fa\u4e8e\u6570\u636e\u6ce8\u91ca\u7684\u9a8c\u8bc1\u548c\u6700\u5c11\u7684 API\u3002\u6709\u5173\u5982\u4f55\u5c06\u6b64\u5e93\u6dfb\u52a0\u5230\u9879\u76ee\u4e2d\u7684\u8bf4\u660e\uff0c\u8bf7\u53c2\u9605\u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04\u3002<\/p>\n<p>Then, recreate the same Person class:<br \/>\n\u7136\u540e\uff0c\u91cd\u65b0\u521b\u5efa\u76f8\u540c\u7684 Person \u7c7b\uff1a<\/p>\n<pre><code>public class Person\n{\n     [Required]\n     [MaxLength(30)]\n     public string FirstName { get; set; }\n     [Required]\n     [MaxLength(30)]\n     public string LastName { get; set; }\n     [EmailAddress]\n     [StringLength(100, MinimumLength = 6)]\n     public string Email { get; set; }\n}<\/code><\/pre>\n<p>Every validation attribute allows us to specify an error message, which can be a static string or a reference to a resource file. Let\u2019s see how to correctly handle the localization for the Required attribute. Add the following values in resource files:<br \/>\n\u6bcf\u4e2a validation \u5c5e\u6027\u90fd\u5141\u8bb8\u6211\u4eec\u6307\u5b9a\u4e00\u6761\u9519\u8bef\u6d88\u606f\uff0c\u5b83\u53ef\u4ee5\u662f\u9759\u6001\u5b57\u7b26\u4e32\u6216\u5bf9\u8d44\u6e90\u6587\u4ef6\u7684\u5f15\u7528\u3002\u8ba9\u6211\u4eec\u770b\u770b\u5982\u4f55\u6b63\u786e\u5904\u7406 Required \u5c5e\u6027\u7684\u672c\u5730\u5316\u3002\u5728\u8d44\u6e90\u6587\u4ef6\u4e2d\u6dfb\u52a0\u4ee5\u4e0b\u503c\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/09T04.jpg\"><\/p>\n<p>Table 9.4 \u2013 Localized validation error messages used by Data Annotations<br \/>\n\u8868 9.4 \u2013 \u6570\u636e\u6ce8\u91ca\u4f7f\u7528\u7684\u672c\u5730\u5316\u9a8c\u8bc1\u9519\u8bef\u6d88\u606f<\/p>\n<p>We want it so that when a required validation rule fails, the localized message that corresponds to FieldRequiredAnnotation is returned. Moreover, this message contains a placeholder, because we want to use it for every required field, so we also need the translation of property names.<br \/>\n\u6211\u4eec\u5e0c\u671b\uff0c\u5f53\u5fc5\u9700\u7684\u9a8c\u8bc1\u89c4\u5219\u5931\u8d25\u65f6\uff0c\u5c06\u8fd4\u56de\u4e0e FieldRequiredAnnotation \u5bf9\u5e94\u7684\u672c\u5730\u5316\u6d88\u606f\u3002\u6b64\u5916\uff0c\u6b64\u6d88\u606f\u5305\u542b\u4e00\u4e2a\u5360\u4f4d\u7b26\uff0c\u56e0\u4e3a\u6211\u4eec\u5e0c\u671b\u5c06\u5176\u7528\u4e8e\u6bcf\u4e2a\u5fc5\u586b\u5b57\u6bb5\uff0c\u56e0\u6b64\u6211\u4eec\u8fd8\u9700\u8981\u5c5e\u6027\u540d\u79f0\u7684\u7ffb\u8bd1\u3002<\/p>\n<p>With these resources, we can update the Person class with the following declarations:<br \/>\n\u6709\u4e86\u8fd9\u4e9b\u8d44\u6e90\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u58f0\u660e\u66f4\u65b0 Person \u7c7b\uff1a<\/p>\n<pre><code>public class Person\n{\n     [Display(Name = &quot;FirstName&quot;, ResourceType = \n      typeof(Messages))]\n     [Required(ErrorMessageResourceName = \n     &quot;FieldRequiredAnnotation&quot;,\n      ErrorMessageResourceType = typeof(Messages))]\n     public string FirstName { get; set; }\n     \/\/...\n}<\/code><\/pre>\n<p>Each validation attribute, such as Required (as used in this example), exposes properties that allow us to specify the name of the resource to use and the type of class that contains the corresponding definition. Keep in mind that the name is a simple string, with no check at compile time, so if we write an incorrect value, we\u2019ll only get an error at runtime.<br \/>\n\u6bcf\u4e2a\u9a8c\u8bc1\u5c5e\u6027\uff08\u5982 Required\uff08\u5982\u672c\u4f8b\u4e2d\u6240\u793a\uff09\uff09\u90fd\u516c\u5f00\u4e86\u5141\u8bb8\u6211\u4eec\u6307\u5b9a\u8981\u4f7f\u7528\u7684\u8d44\u6e90\u7684\u540d\u79f0\u4ee5\u53ca\u5305\u542b\u76f8\u5e94\u5b9a\u4e49\u7684\u7c7b\u7c7b\u578b\u7684\u5c5e\u6027\u3002\u8bf7\u8bb0\u4f4f\uff0c\u540d\u79f0\u662f\u4e00\u4e2a\u7b80\u5355\u7684\u5b57\u7b26\u4e32\uff0c\u5728\u7f16\u8bd1\u65f6\u6ca1\u6709\u68c0\u67e5\uff0c\u56e0\u6b64\u5982\u679c\u6211\u4eec\u5199\u5165\u4e86\u4e0d\u6b63\u786e\u7684\u503c\uff0c\u6211\u4eec\u53ea\u4f1a\u5728\u8fd0\u884c\u65f6\u6536\u5230\u9519\u8bef\u3002<\/p>\n<p>Next, we can use the Display attribute to also specify the name of the field that must be inserted in the validation message.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u8fd8\u53ef\u4ee5\u4f7f\u7528 Display \u5c5e\u6027\u6765\u6307\u5b9a\u5fc5\u987b\u63d2\u5165\u5230\u9a8c\u8bc1\u6d88\u606f\u4e2d\u7684\u5b57\u6bb5\u7684\u540d\u79f0\u3002<\/p>\n<p>Note : You can find the complete declaration of the Person class with localized data annotations on the GitHub repository at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/blob\/main\/Chapter09\/Program.cs#L97\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/blob\/main\/Chapter09\/Program.cs#L97<\/a>.<br \/>\n\u6ce8\u610f : \u60a8\u53ef\u4ee5\u5728 GitHub \u5b58\u50a8\u5e93\u7684 <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/blob\/main\/Chapter09\/Program.cs#L97\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/blob\/main\/Chapter09\/Program.cs#L97<\/a> \u4e0a\u627e\u5230\u5e26\u6709\u672c\u5730\u5316\u6570\u636e\u6ce8\u91ca\u7684 Person \u7c7b\u7684\u5b8c\u6574\u58f0\u660e\u3002<\/p>\n<p>Now we can re-add the validation code shown in Chapter 6, Exploring Validation and Mapping. The difference is that now the validation messages will be localized:<br \/>\n\u73b0\u5728\u6211\u4eec\u53ef\u4ee5\u91cd\u65b0\u6dfb\u52a0\u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04 \u4e2d\u6240\u793a\u7684\u9a8c\u8bc1\u4ee3\u7801\u3002\u4e0d\u540c\u4e4b\u5904\u5728\u4e8e\uff0c\u73b0\u5728\u9a8c\u8bc1\u6d88\u606f\u5c06\u88ab\u672c\u5730\u5316\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/people&quot;, (Person person) =&gt;\n{\n     var isValid = MiniValidator.TryValidate(person, out \n     var errors);\n     if (!isValid)\n     {\n           return Results.ValidationProblem(errors, title: \n           Messages.ValidationErrors);\n     }\n     return Results.NoContent();\n});<\/code><\/pre>\n<p>In the preceding code, the messages contained in the errors dictionary that is returned by the MiniValidator.TryValidate() method will be localized according to the request culture, as described in the previous sections. We also specify the title parameter in the Results.ValidationProblem() invocation because we want to localize this value too (otherwise, it will always be the default One or more validation errors occurred).<br \/>\n\u5728\u4e0a\u9762\u7684\u4ee3\u7801\u4e2d\uff0cMiniValidator.TryValidate\uff08\uff09 \u65b9\u6cd5\u8fd4\u56de\u7684 errors \u5b57\u5178\u4e2d\u5305\u542b\u7684\u6d88\u606f\u5c06\u6839\u636e\u8bf7\u6c42\u533a\u57df\u6027\u8fdb\u884c\u672c\u5730\u5316\uff0c\u5982\u524d\u9762\u7684\u90e8\u5206\u6240\u8ff0\u3002\u6211\u4eec\u8fd8\u5728 Results.ValidationProblem\uff08\uff09 \u8c03\u7528\u4e2d\u6307\u5b9a\u4e86 title \u53c2\u6570\uff0c\u56e0\u4e3a\u6211\u4eec\u4e5f\u5e0c\u671b\u672c\u5730\u5316\u6b64\u503c\uff08\u5426\u5219\uff0c\u5b83\u5c06\u59cb\u7ec8\u4e3a\u9ed8\u8ba4\u7684 One or more validation errors occurred\uff09\u3002<\/p>\n<p>If instead of data annotations, we prefer using FluentValidation, we know that it supports localization of standard error messages by default from Chapter 6, Exploring Validation and Mapping. However, with this library, we can also provide our translations. In the next section, we\u2019ll talk about implementing this solution.<br \/>\n\u5982\u679c\u6211\u4eec\u66f4\u559c\u6b22\u4f7f\u7528 FluentValidation \u800c\u4e0d\u662f\u6570\u636e\u6ce8\u91ca\uff0c\u90a3\u4e48\u6211\u4eec\u77e5\u9053\u5b83\u9ed8\u8ba4\u652f\u6301\u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04 \u4e2d\u7684\u6807\u51c6\u9519\u8bef\u6d88\u606f\u7684\u672c\u5730\u5316\u3002\u4f46\u662f\uff0c\u6709\u4e86\u8fd9\u4e2a\u5e93\uff0c\u6211\u4eec\u4e5f\u53ef\u4ee5\u63d0\u4f9b\u6211\u4eec\u7684\u7ffb\u8bd1\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u5982\u4f55\u5b9e\u73b0\u6b64\u89e3\u51b3\u65b9\u6848\u3002<\/p>\n<p>Localizing validation messages with FluentValidation<br \/>\n\u4f7f\u7528 FluentValidation \u672c\u5730\u5316\u9a8c\u8bc1\u6d88\u606f<\/p>\n<p>With FluentValidation, we can totally decouple the validation rules from our models. As said before, refer to Chapter 6, Exploring Validation and Mapping, for instructions on how to add this library to the project and how to configure it.<br \/>\n\u4f7f\u7528 FluentValidation\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u9a8c\u8bc1\u89c4\u5219\u4e0e\u6211\u4eec\u7684\u6a21\u578b\u5b8c\u5168\u89e3\u8026\u3002\u5982\u524d\u6240\u8ff0\uff0c\u8bf7\u53c2\u9605 \u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04 \uff0c\u4ee5\u83b7\u53d6\u6709\u5173\u5982\u4f55\u5c06\u6b64\u5e93\u6dfb\u52a0\u5230\u9879\u76ee\u4ee5\u53ca\u5982\u4f55\u914d\u7f6e\u5b83\u7684\u8bf4\u660e\u3002<\/p>\n<p>Next, let us recreate the PersonValidator class:<br \/>\n\u63a5\u4e0b\u6765\uff0c\u8ba9\u6211\u4eec\u91cd\u65b0\u521b\u5efa PersonValidator \u7c7b\uff1a<\/p>\n<pre><code>public class PersonValidator : AbstractValidator&lt;Person&gt;\n{\n     public PersonValidator()\n     {\n           RuleFor(p =&gt; p.FirstName).NotEmpty().\n           MaximumLength(30);\n           RuleFor(p =&gt; p.LastName).NotEmpty().\n           MaximumLength(30);\n           RuleFor(p =&gt; p.Email).EmailAddress().Length(6, \n           100);\n     }\n}<\/code><\/pre>\n<p>In the case that we haven\u2019t specified any messages, the default ones will be used. Let\u2019s add the following resource to customize the NotEmpty validation rule:<br \/>\n\u5982\u679c\u6211\u4eec\u6ca1\u6709\u6307\u5b9a\u4efb\u4f55\u6d88\u606f\uff0c\u5219\u5c06\u4f7f\u7528\u9ed8\u8ba4\u6d88\u606f\u3002\u8ba9\u6211\u4eec\u6dfb\u52a0\u4ee5\u4e0b\u8d44\u6e90\u6765\u81ea\u5b9a\u4e49 NotEmpty \u9a8c\u8bc1\u89c4\u5219\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/09T05.jpg\"><\/p>\n<p>Table 9.5 \u2013 The localized validation error messages used by FluentValidation<br \/>\n\u8868 9.5 \u2013 FluentValidation \u4f7f\u7528\u7684\u672c\u5730\u5316\u9a8c\u8bc1\u9519\u8bef\u6d88\u606f<\/p>\n<p>Note that, in this case, we also have a placeholder that will be replaced by the property name. However, different from data annotations, FluentValidation uses a placeholder with a name to better identify its meaning.<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u5728\u672c\u4f8b\u4e2d\uff0c\u6211\u4eec\u8fd8\u6709\u4e00\u4e2a\u5360\u4f4d\u7b26\uff0c\u8be5\u5360\u4f4d\u7b26\u5c06\u66ff\u6362\u4e3a\u5c5e\u6027\u540d\u79f0\u3002\u4f46\u662f\uff0c\u4e0e\u6570\u636e\u6ce8\u91ca\u4e0d\u540c\uff0cFluentValidation \u4f7f\u7528\u5e26\u6709\u540d\u79f0\u7684\u5360\u4f4d\u7b26\u6765\u66f4\u597d\u5730\u8bc6\u522b\u5176\u542b\u4e49\u3002<\/p>\n<p>Now, we can add this message in the validator, for example, for the FirstName property:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u9a8c\u8bc1\u5668\u4e2d\u6dfb\u52a0\u4ee5\u4e0b\u6d88\u606f\uff0c\u4f8b\u5982\uff0c\u5bf9\u4e8e FirstName \u5c5e\u6027\uff1a<\/p>\n<pre><code>RuleFor(p =&gt; p.FirstName).NotEmpty().\n     WithMessage(Messages.NotEmptyMessage).\n     WithName(Messages.FirstName);<\/code><\/pre>\n<p>We use WithMessage() to specify the message that must be used when the preceding rule fails, following which we add the WithName() invocation to overwrite the default property name used for the {PropertyName} placeholder of the message.<br \/>\n\u6211\u4eec\u4f7f\u7528 WithMessage\uff08\uff09 \u6307\u5b9a\u5728\u524d\u9762\u7684\u89c4\u5219\u5931\u8d25\u65f6\u5fc5\u987b\u4f7f\u7528\u7684\u6d88\u606f\uff0c\u7136\u540e\u6211\u4eec\u6dfb\u52a0 WithName\uff08\uff09 \u8c03\u7528\u4ee5\u8986\u76d6\u7528\u4e8e\u6d88\u606f\u7684 {PropertyName} \u5360\u4f4d\u7b26\u7684\u9ed8\u8ba4\u5c5e\u6027\u540d\u79f0\u3002<\/p>\n<p>Note : You can find the complete implementation of the PersonValidator class with localized messages in the GitHub repository at <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/blob\/main\/Chapter09\/Program.cs#L129\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/blob\/main\/Chapter09\/Program.cs#L129<\/a>.<br \/>\n\u6ce8\u610f : \u60a8\u53ef\u4ee5\u5728 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230 PersonValidator \u7c7b\u7684\u5b8c\u6574\u5b9e\u73b0\u4ee5\u53ca\u672c\u5730\u5316\u6d88\u606f\uff0c\u7f51\u5740\u4e3a <a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/blob\/main\/Chapter09\/Program.cs#L129\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/blob\/main\/Chapter09\/Program.cs#L129<\/a>\u3002<\/p>\n<p>Finally, we can leverage the localized validator in our endpoint, as we did in Chapter 6, Exploring Validation and Mapping:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u7aef\u70b9\u4e2d\u5229\u7528\u672c\u5730\u5316\u7684\u9a8c\u8bc1\u5668\uff0c\u5c31\u50cf\u6211\u4eec\u5728\u7b2c 6 \u7ae0 \u63a2\u7d22\u9a8c\u8bc1\u548c\u6620\u5c04\u4e2d\u6240\u505a\u7684\u90a3\u6837\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/people&quot;, async (Person person, IValidator&lt;Person&gt; validator) =&gt;\n{\n     var validationResult = await validator.\n     ValidateAsync(person);\n     if (!validationResult.IsValid)\n     {\n           var errors = validationResult.ToDictionary();\n           return Results.ValidationProblem(errors, title: \n           Messages.ValidationErrors);\n     }\n     return Results.NoContent();\n});<\/code><\/pre>\n<p>As in the case of data annotations, the validationResult variable will contain localized error messages that we return to the caller using the Results.ValidationProblem() method (again, with the definition of the title property).<br \/>\n\u4e0e\u6570\u636e\u6ce8\u91ca\u4e00\u6837\uff0cvalidationResult \u53d8\u91cf\u5c06\u5305\u542b\u672c\u5730\u5316\u7684\u9519\u8bef\u6d88\u606f\uff0c\u6211\u4eec\u4f7f\u7528 Results.ValidationProblem\uff08\uff09 \u65b9\u6cd5\uff08\u540c\u6837\uff0c\u4f7f\u7528 title \u5c5e\u6027\u7684\u5b9a\u4e49\uff09\u5c06\u8fd9\u4e9b\u9519\u8bef\u6d88\u606f\u8fd4\u56de\u7ed9\u8c03\u7528\u8005\u3002<\/p>\n<p>Tip : In our example, we have seen how to explicitly assign translations for each property using the WithMessage() method. FluentValidation also provides a way to replace all (or some) of its default messages. You can find more information in the official documentation at <a href=\"https:\/\/docs.fluentvalidation.net\/en\/latest\/localization.xhtml#default-messages\">https:\/\/docs.fluentvalidation.net\/en\/latest\/localization.xhtml#default-messages<\/a>.<br \/>\n\u63d0\u793a : \u5728\u6211\u4eec\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5df2\u7ecf\u770b\u5230\u4e86\u5982\u4f55\u4f7f\u7528 WithMessage\uff08\uff09 \u65b9\u6cd5\u4e3a\u6bcf\u4e2a\u5c5e\u6027\u663e\u5f0f\u5206\u914d\u7ffb\u8bd1\u3002FluentValidation \u8fd8\u63d0\u4f9b\u4e86\u4e00\u79cd\u66ff\u6362\u5176\u6240\u6709\uff08\u6216\u90e8\u5206\uff09\u9ed8\u8ba4\u6d88\u606f\u7684\u65b9\u6cd5\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"https:\/\/docs.fluentvalidation.net\/en\/latest\/localization.xhtml#default-messages\">https:\/\/docs.fluentvalidation.net\/en\/latest\/localization.xhtml#default-messages<\/a> \u7684\u5b98\u65b9\u6587\u6863\u4e2d\u627e\u5230\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>This ends our overview of localization using resource files. Next, we\u2019ll talk about an important topic when dealing with services that are meant to be used worldwide: the correct handling of different time zones.<br \/>\n\u6211\u4eec\u5bf9\u4f7f\u7528\u8d44\u6e90\u6587\u4ef6\u7684\u672c\u5730\u5316\u7684\u6982\u8ff0\u5230\u6b64\u7ed3\u675f\u3002\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u5728\u5904\u7406\u65e8\u5728\u5728\u5168\u7403\u8303\u56f4\u5185\u4f7f\u7528\u7684\u670d\u52a1\u65f6\u7684\u4e00\u4e2a\u91cd\u8981\u8bdd\u9898\uff1a\u6b63\u786e\u5904\u7406\u4e0d\u540c\u7684\u65f6\u533a\u3002<\/p>\n<p>Adding UTC support to a globalized minimal API<br \/>\n\u5411\u5168\u7403\u5316\u7684\u6700\u5c0f API \u6dfb\u52a0 UTC \u652f\u6301<\/p>\n<p>So far, we have added globalization and localization support to our minimal API because we want it to be used by the widest audience possible, irrespective of culture. But, if we think about being accessible to a worldwide audience, we should consider several aspects related to globalization. Globalization does not only pertain to language support; there are important factors we need to consider, for example, geographic locations, as well as time zones.<br \/>\n\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u6211\u4eec\u5df2\u7ecf\u5728\u6211\u4eec\u7684\u6700\u5c0f API \u4e2d\u6dfb\u52a0\u4e86\u5168\u7403\u5316\u548c\u672c\u5730\u5316\u652f\u6301\uff0c\u56e0\u4e3a\u6211\u4eec\u5e0c\u671b\u5b83\u88ab\u5c3d\u53ef\u80fd\u5e7f\u6cdb\u7684\u53d7\u4f17\u4f7f\u7528\uff0c\u800c\u4e0d\u53d7\u6587\u5316\u5f71\u54cd\u3002\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u8003\u8651\u8ba9\u5168\u4e16\u754c\u7684\u53d7\u4f17\u90fd\u80fd\u63a5\u89e6\u5230\uff0c\u6211\u4eec\u5e94\u8be5\u8003\u8651\u4e0e\u5168\u7403\u5316\u76f8\u5173\u7684\u51e0\u4e2a\u65b9\u9762\u3002\u5168\u7403\u5316\u4e0d\u4ec5\u4e0e\u8bed\u8a00\u652f\u6301\u6709\u5173;\u6211\u4eec\u9700\u8981\u8003\u8651\u4e00\u4e9b\u91cd\u8981\u56e0\u7d20\uff0c\u4f8b\u5982\u5730\u7406\u4f4d\u7f6e\u548c\u65f6\u533a\u3002<\/p>\n<p>So, for example, we can have our minimal API running in Italy, which follows Central European Time (CET) (GMT+1), while our clients can use browsers that execute a single-page application, rather than mobile apps, all over the world. We could also have a database server that contains our data, and this could be in another time zone. Moreover, at a certain point, it may be necessary to provide better support for worldwide users, so we\u2019ll have to move our service to another location, which could have a new time zone. In conclusion, our system could deal with data in different time zones, and, potentially, the same services could switch time zones during their lives.<br \/>\n\u56e0\u6b64\uff0c\u4f8b\u5982\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u610f\u5927\u5229\u8fd0\u884c\u6211\u4eec\u7684\u6700\u5c0f API\uff0c\u5b83\u9075\u5faa\u4e2d\u6b27\u65f6\u95f4 \uff08CET\uff09 \uff08GMT+1\uff09\uff0c\u800c\u6211\u4eec\u7684\u5ba2\u6237\u53ef\u4ee5\u4f7f\u7528\u6267\u884c\u5355\u9875\u5e94\u7528\u7a0b\u5e8f\u7684\u6d4f\u89c8\u5668\uff0c\u800c\u4e0d\u662f\u4e16\u754c\u5404\u5730\u7684\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u6709\u4e00\u4e2a\u5305\u542b\u6211\u4eec\u6570\u636e\u7684\u6570\u636e\u5e93\u670d\u52a1\u5668\uff0c\u5b83\u53ef\u4ee5\u5728\u53e6\u4e00\u4e2a\u65f6\u533a\u3002\u6b64\u5916\uff0c\u5728\u67d0\u4e2a\u65f6\u5019\uff0c\u53ef\u80fd\u9700\u8981\u4e3a\u5168\u7403\u7528\u6237\u63d0\u4f9b\u66f4\u597d\u7684\u652f\u6301\uff0c\u56e0\u6b64\u6211\u4eec\u5c06\u4e0d\u5f97\u4e0d\u5c06\u6211\u4eec\u7684\u670d\u52a1\u8f6c\u79fb\u5230\u53e6\u4e00\u4e2a\u4f4d\u7f6e\uff0c\u8be5\u4f4d\u7f6e\u53ef\u80fd\u5177\u6709\u65b0\u7684\u65f6\u533a\u3002\u603b\u4e4b\uff0c\u6211\u4eec\u7684\u7cfb\u7edf\u53ef\u4ee5\u5904\u7406\u4e0d\u540c\u65f6\u533a\u7684\u6570\u636e\uff0c\u5e76\u4e14\u76f8\u540c\u7684\u670d\u52a1\u5728\u5176\u751f\u547d\u5468\u671f\u4e2d\u53ef\u80fd\u4f1a\u5207\u6362\u65f6\u533a\u3002<\/p>\n<p>In these situations, the ideal solution is working with DateTimeOffset, a data type that includes time zones and that JsonSerializer fully supports, preserving time zone information during serialization and deserialization. If we could always use it, we\u2019d automatically solve any problem related to globalization, because converting a DateTimeOffset value to a different time zone is straightforward. However, there are cases in which we can\u2019t handle the DateTimeOffset type, for example:<br \/>\n\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c\u7406\u60f3\u7684\u89e3\u51b3\u65b9\u6848\u662f\u4f7f\u7528 DateTimeOffset\uff0c\u8fd9\u662f\u4e00\u79cd\u5305\u542b\u65f6\u533a\u7684\u6570\u636e\u7c7b\u578b\uff0c\u5e76\u4e14 JsonSerializer \u5b8c\u5168\u652f\u6301\uff0c\u5728\u5e8f\u5217\u5316\u548c\u53cd\u5e8f\u5217\u5316\u671f\u95f4\u4fdd\u7559\u65f6\u533a\u4fe1\u606f\u3002\u5982\u679c\u6211\u4eec\u59cb\u7ec8\u53ef\u4ee5\u4f7f\u7528\u5b83\uff0c\u6211\u4eec\u5c31\u4f1a\u81ea\u52a8\u89e3\u51b3\u4e0e\u5168\u7403\u5316\u76f8\u5173\u7684\u4efb\u4f55\u95ee\u9898\uff0c\u56e0\u4e3a\u5c06 DateTimeOffset \u503c\u8f6c\u6362\u4e3a\u4e0d\u540c\u7684\u65f6\u533a\u975e\u5e38\u7b80\u5355\u3002\u4f46\u662f\uff0c\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u65e0\u6cd5\u5904\u7406 DateTimeOffset \u7c7b\u578b\uff0c\u4f8b\u5982\uff1a<\/p>\n<p>\u2022  When we\u2019re working on a legacy system that relies on DateTime everywhere, updating the code to use DateTimeOffset isn\u2019t an option because it requires too many changes and breaks the compatibility with the old data.<br \/>\n\u5f53\u6211\u4eec\u5728\u65e0\u5904\u4e0d\u5728\u90fd\u4f9d\u8d56 DateTime \u7684\u65e7\u7cfb\u7edf\u4e0a\u5de5\u4f5c\u65f6\uff0c\u66f4\u65b0\u4ee3\u7801\u4ee5\u4f7f\u7528 DateTimeOffset \u4e0d\u662f\u4e00\u4e2a\u9009\u9879\uff0c\u56e0\u4e3a\u5b83\u9700\u8981\u592a\u591a\u66f4\u6539\u5e76\u7834\u574f\u4e0e\u65e7\u6570\u636e\u7684\u517c\u5bb9\u6027\u3002<\/p>\n<p>\u2022  We have a database server such as MySQL that doesn\u2019t have a column type for storing DateTimeOffset directly, so handling it requires extra effort, for example, using two separate columns, increasing the complexity of the domain.<br \/>\n\u6211\u4eec\u6709\u4e00\u4e2a\u6570\u636e\u5e93\u670d\u52a1\u5668\uff0c\u4f8b\u5982 MySQL\uff0c\u5b83\u6ca1\u6709\u7528\u4e8e\u76f4\u63a5\u5b58\u50a8 DateTimeOffset \u7684\u5217\u7c7b\u578b\uff0c\u56e0\u6b64\u5904\u7406\u5b83\u9700\u8981\u989d\u5916\u7684\u5de5\u4f5c\uff0c\u4f8b\u5982\uff0c\u4f7f\u7528\u4e24\u4e2a\u5355\u72ec\u7684\u5217\uff0c\u8fd9\u589e\u52a0\u4e86\u57df\u7684\u590d\u6742\u6027\u3002<\/p>\n<p>\u2022  In some cases, we simply aren\u2019t interested in sending, receiving, and saving time zones \u2013 we just want to handle time in a \u201cuniversal\u201d way.<br \/>\n\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u53ea\u662f\u5bf9\u53d1\u9001\u3001\u63a5\u6536\u548c\u4fdd\u5b58\u65f6\u533a\u4e0d\u611f\u5174\u8da3\u2014\u2014\u6211\u4eec\u53ea\u60f3\u4ee5 \u201c\u901a\u7528\u201d \u7684\u65b9\u5f0f\u5904\u7406\u65f6\u95f4\u3002<\/p>\n<p>So, in all the scenarios where we can\u2019t or don\u2019t want to use the DateTimeOffset data type, one of the best and simplest ways to deal with different time zones is to handle all dates using Coordinated Universal Time (UTC): the service must assume that the dates it receives are in the UTC format and, on the other hand, all the dates returned by the API must be in UTC.<br \/>\n\u56e0\u6b64\uff0c\u5728\u6211\u4eec\u4e0d\u80fd\u6216\u4e0d\u60f3\u4f7f\u7528 DateTimeOffset \u6570\u636e\u7c7b\u578b\u7684\u6240\u6709\u60c5\u51b5\u4e0b\uff0c\u5904\u7406\u4e0d\u540c\u65f6\u533a\u7684\u6700\u4f73\u548c\u6700\u7b80\u5355\u7684\u65b9\u6cd5\u4e4b\u4e00\u662f\u4f7f\u7528\u534f\u8c03\u4e16\u754c\u65f6 \uff08UTC\uff09 \u5904\u7406\u6240\u6709\u65e5\u671f\uff1a\u670d\u52a1\u5fc5\u987b\u5047\u5b9a\u5b83\u6536\u5230\u7684\u65e5\u671f\u662f UTC \u683c\u5f0f\uff0c\u53e6\u4e00\u65b9\u9762\uff0c API \u8fd4\u56de\u7684\u6240\u6709\u65e5\u671f\u90fd\u5fc5\u987b\u91c7\u7528 UTC \u683c\u5f0f\u3002<\/p>\n<p>Of course, we must handle this behavior in a centralized way; we don\u2019t want to have to remember to apply the conversion to and from the UTC format every time we receive or send a date. The well-known JSON.NET library provides an option to specify how to treat the time value when working with a DateTime property, allowing it to automatically handle all dates as UTC and convert them to that format if they represent a local time. However, the current version of Microsoft JsonSerializer used in minimal APIs doesn\u2019t include such a feature. From Chapter 2, Exploring Minimal APIs and Their Advantages, we know that we cannot change the default JSON serializer in minimal APIs, but we can overcome this lack of UTC support by creating a simple JsonConverter:<br \/>\n\u5f53\u7136\uff0c\u6211\u4eec\u5fc5\u987b\u4ee5\u96c6\u4e2d\u7684\u65b9\u5f0f\u5904\u7406\u8fd9\u79cd\u884c\u4e3a;\u6211\u4eec\u4e0d\u60f3\u8bb0\u4f4f\u5728\u6bcf\u6b21\u63a5\u6536\u6216\u53d1\u9001\u65e5\u671f\u65f6\u90fd\u8981\u5e94\u7528\u4e0e UTC \u683c\u5f0f\u4e4b\u95f4\u7684\u8f6c\u6362\u3002\u4f17\u6240\u5468\u77e5\u7684 JSON.NET \u5e93\u63d0\u4f9b\u4e86\u4e00\u4e2a\u9009\u9879\uff0c\u7528\u4e8e\u6307\u5b9a\u5728\u4f7f\u7528 DateTime \u5c5e\u6027\u65f6\u5982\u4f55\u5904\u7406\u65f6\u95f4\u503c\uff0c\u4ece\u800c\u5141\u8bb8\u5b83\u81ea\u52a8\u5c06\u6240\u6709\u65e5\u671f\u4f5c\u4e3a UTC \u5904\u7406\uff0c\u5e76\u5728\u5b83\u4eec\u8868\u793a\u672c\u5730\u65f6\u95f4\u65f6\u5c06\u5176\u8f6c\u6362\u4e3a\u8be5\u683c\u5f0f\u3002\u4f46\u662f\uff0c\u6700\u5c0f API \u4e2d\u4f7f\u7528\u7684 Microsoft JsonSerializer \u7684\u5f53\u524d\u7248\u672c\u4e0d\u5305\u542b\u6b64\u7c7b\u529f\u80fd\u3002\u4ece\u7b2c 2 \u7ae0 \u63a2\u7d22\u6700\u5c0f API \u53ca\u5176\u4f18\u52bf\u4e2d\uff0c\u6211\u4eec\u77e5\u9053\u6211\u4eec\u65e0\u6cd5\u5728\u6700\u5c0f API \u4e2d\u66f4\u6539\u9ed8\u8ba4\u7684 JSON \u5e8f\u5217\u5316\u5668\uff0c\u4f46\u662f\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u521b\u5efa\u4e00\u4e2a\u7b80\u5355\u7684 JsonConverter \u6765\u514b\u670d\u7f3a\u4e4f UTC \u652f\u6301\u7684\u95ee\u9898\uff1a<\/p>\n<pre><code>public class UtcDateTimeConverter : JsonConverter&lt;DateTime&gt;\n{\n     public override DateTime Read(ref Utf8JsonReader \n     reader, Type typeToConvert, JsonSerializerOptions  \n     options)\n     =&gt; reader.GetDateTime().ToUniversalTime();\n     public override void Write(Utf8JsonWriter writer, \n     DateTime value, JsonSerializerOptions options)\n     =&gt; writer.WriteStringValue((value.Kind == \n     DateTimeKind.Local ? value.ToUniversalTime() : value)\n     .ToString(&quot;yyyy&#039;-&#039;MM&#039;-&#039;dd&#039;T&#039;HH&#039;:&#039;mm&#039;:&#039;ss&#039;.&#039;\n     fffffff&#039;Z&#039;&quot;));\n}<\/code><\/pre>\n<p>With this converter, we tell JsonSerializer how to treat DateTime properties:<br \/>\n\u901a\u8fc7\u8fd9\u4e2a\u8f6c\u6362\u5668\uff0c\u6211\u4eec\u544a\u8bc9 JsonSerializer \u5982\u4f55\u5904\u7406 DateTime \u5c5e\u6027\uff1a<\/p>\n<p>\u2022  When DateTime is read from JSON, the value is converted to UTC using the ToUniversalTime() method.<br \/>\n\u4ece JSON \u4e2d\u8bfb\u53d6 DateTime \u65f6\uff0c\u5c06\u4f7f\u7528 ToUniversalTime\uff08\uff09 \u65b9\u6cd5\u5c06\u8be5\u503c\u8f6c\u6362\u4e3a UTC\u3002<\/p>\n<p>\u2022  When DateTime must be written to JSON, if it represents a local time (DateTimeKind.Local), it is converted to UTC before serialization \u2013 then, it is serialized using the Z suffix, which indicates that the time is UTC.<br \/>\n\u5f53\u5fc5\u987b\u5c06 DateTime \u5199\u5165 JSON \u65f6\uff0c\u5982\u679c\u5b83\u8868\u793a\u672c\u5730\u65f6\u95f4 \uff08DateTimeKind.Local\uff09\uff0c\u5219\u4f1a\u5728\u5e8f\u5217\u5316\u4e4b\u524d\u5c06\u5176\u8f6c\u6362\u4e3a UTC \u2013 \u7136\u540e\uff0c\u5b83\u5c06\u4f7f\u7528 Z \u540e\u7f00\u8fdb\u884c\u5e8f\u5217\u5316\uff0c\u8fd9\u8868\u793a\u65f6\u95f4\u4e3a UTC\u3002<\/p>\n<p>Now, before using this converter, let\u2019s add the following endpoint definition:<br \/>\n\u73b0\u5728\uff0c\u5728\u4f7f\u7528\u6b64\u8f6c\u6362\u5668\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u6dfb\u52a0\u4ee5\u4e0b\u7aef\u70b9\u5b9a\u4e49\uff1a<\/p>\n<pre><code>app.MapPost(&quot;\/date&quot;, (DateInput date) =&gt;\n{\n     return Results.Ok(new\n     {\n           Input = date.Value,\n           DateKind = date.Value.Kind.ToString(),\n           ServerDate = DateTime.Now\n     });\n});\npublic record DateInput(DateTime Value);<\/code><\/pre>\n<p>Let\u2019s try to call it, for example, with a date formatted as 2022-03-06T16:42:37-05:00. We\u2019ll obtain something similar to the following:<br \/>\n\u4f8b\u5982\uff0c\u8ba9\u6211\u4eec\u5c1d\u8bd5\u4f7f\u7528\u683c\u5f0f\u4e3a 2022-03-06T16\uff1a42\uff1a37-05\uff1a00 \u7684\u65e5\u671f\u6765\u8c03\u7528\u5b83\u3002\u6211\u4eec\u5c06\u83b7\u5f97\u7c7b\u4f3c\u4e8e\u4ee5\u4e0b\u5185\u5bb9\u7684\u5185\u5bb9\uff1a<\/p>\n<pre><code>{\n  &quot;input&quot;: &quot;2022-03-06T22:42:37+01:00&quot;,\n  &quot;dateKind&quot;: &quot;Local&quot;,\n  &quot;serverDate&quot;: &quot;2022-03-07T18:33:17.0288535+01:00&quot;\n}<\/code><\/pre>\n<p>The input date, containing a time zone, has automatically been converted to the local time of the server (in this case, the server is running in Italy, as stated at the beginning), as also demonstrated by the dateKind field. Moreover, serverDate contains a date that is relative to the server time zone.<br \/>\n\u5305\u542b\u65f6\u533a\u7684\u8f93\u5165\u65e5\u671f\u5df2\u81ea\u52a8\u8f6c\u6362\u4e3a\u670d\u52a1\u5668\u7684\u672c\u5730\u65f6\u95f4\uff08\u5728\u672c\u4f8b\u4e2d\uff0c\u670d\u52a1\u5668\u5728\u610f\u5927\u5229\u8fd0\u884c\uff0c\u5982\u5f00\u5934\u6240\u8ff0\uff09\uff0cdateKind \u5b57\u6bb5\u4e5f\u6f14\u793a\u4e86\u8be5\u65e5\u671f\u3002\u6b64\u5916\uff0c serverDate \u5305\u542b\u76f8\u5bf9\u4e8e\u670d\u52a1\u5668\u65f6\u533a\u7684\u65e5\u671f\u3002<\/p>\n<p>Now, let\u2019s add UtcDateTimeConverter to JsonSerializer:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u5c06 UtcDateTimeConverter \u6dfb\u52a0\u5230 JsonSerializer \u4e2d\uff1a<\/p>\n<pre><code>var builder = WebApplication.CreateBuilder(args);\n\/\/...\nbuilder.Services.Configure&lt;Microsoft.AspNetCore.Http.Json.\nJsonOptions&gt;(options =&gt;\n{\n     options.SerializerOptions.Converters.Add(new \n     UtcDateTimeConverter());\n});<\/code><\/pre>\n<p>With this configuration, every DateTime property will be processed using our custom converters. Now, execute the endpoint again, using the same input as before. This time, the result will be as follows:<br \/>\n\u4f7f\u7528\u6b64\u914d\u7f6e\uff0c\u6bcf\u4e2a DateTime \u5c5e\u6027\u90fd\u5c06\u4f7f\u7528\u6211\u4eec\u7684\u81ea\u5b9a\u4e49\u8f6c\u6362\u5668\u8fdb\u884c\u5904\u7406\u3002\u73b0\u5728\uff0c\u4f7f\u7528\u4e0e\u4e4b\u524d\u76f8\u540c\u7684\u8f93\u5165\u518d\u6b21\u6267\u884c\u7ec8\u7aef\u8282\u70b9\u3002\u8fd9\u4e00\u6b21\uff0c\u7ed3\u679c\u5c06\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>{\n  &quot;input&quot;: &quot;2022-03-06T21:42:37.0000000Z&quot;,\n  &quot;dateKind&quot;: &quot;Utc&quot;,\n  &quot;serverDate&quot;: &quot;2022-03-06T17:40:08.1472051Z&quot;\n}<\/code><\/pre>\n<p>The input is the same, but our UtcDateTimeConverter has now converted the date to UTC and, on the other hand, has serialized the server date as UTC; now, our API, in a centralized way, can automatically handle all dates as UTC, no matter its time zone or the time zones of the callers.<br \/>\n\u8f93\u5165\u662f\u76f8\u540c\u7684\uff0c\u4f46\u662f\u6211\u4eec\u7684 UtcDateTimeConverter \u73b0\u5728\u5df2\u7ecf\u5c06\u65e5\u671f\u8f6c\u6362\u4e3a UTC\uff0c\u53e6\u4e00\u65b9\u9762\uff0c\u5df2\u5c06\u670d\u52a1\u5668\u65e5\u671f\u5e8f\u5217\u5316\u4e3a UTC;\u73b0\u5728\uff0c\u6211\u4eec\u7684 API \u4ee5\u96c6\u4e2d\u7684\u65b9\u5f0f\u81ea\u52a8\u5c06\u6240\u6709\u65e5\u671f\u5904\u7406\u4e3a UTC\uff0c\u65e0\u8bba\u5176\u65f6\u533a\u6216\u8c03\u7528\u8005\u7684\u65f6\u533a\u5982\u4f55\u3002<\/p>\n<p>Finally, there are two other points to make all the systems correctly work with UTC:<br \/>\n\u6700\u540e\uff0c\u8fd8\u6709\u53e6\u5916\u4e24\u70b9\u53ef\u4ee5\u4f7f\u6240\u6709\u7cfb\u7edf\u6b63\u786e\u5730\u4f7f\u7528 UTC\uff1a<\/p>\n<p>\u2022  When we need to retrieve the current date in the code, we always have to use DateTime.UtcNow instead of DateTime.Now<br \/>\n\u5f53\u6211\u4eec\u9700\u8981\u5728\u4ee3\u7801\u4e2d\u68c0\u7d22\u5f53\u524d\u65e5\u671f\u65f6\uff0c\u6211\u4eec\u59cb\u7ec8\u5fc5\u987b\u4f7f\u7528 DateTime.UtcNow \u800c\u4e0d\u662f DateTime.Now<\/p>\n<p>\u2022  Client applications must know that they will receive the date in UTC format and act accordingly, for example, invoking the ToLocalTime() method<br \/>\n\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u5fc5\u987b\u77e5\u9053\u5b83\u4eec\u5c06\u6536\u5230 UTC \u683c\u5f0f\u7684\u65e5\u671f\u5e76\u91c7\u53d6\u76f8\u5e94\u7684\u63aa\u65bd\uff0c\u4f8b\u5982\uff0c\u8c03\u7528 ToLocalTime\uff08\uff09 \u65b9\u6cd5<\/p>\n<p>In this way, the minimal API is truly globalized and can work with any time zone; without having to worry about explicit conversion, all times input or output will be always in UTC, so it will be much easier to handle them.<br \/>\n\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u6700\u5c0f\u7684 API \u662f\u771f\u6b63\u5168\u7403\u5316\u7684\uff0c\u5e76\u4e14\u53ef\u4ee5\u5728\u4efb\u4f55\u65f6\u533a\u5de5\u4f5c;\u65e0\u9700\u62c5\u5fc3\u663e\u5f0f\u8f6c\u6362\uff0c\u6240\u6709\u65f6\u95f4 input \u6216 output \u90fd\u5c06\u59cb\u7ec8\u4e3a UTC\uff0c\u56e0\u6b64\u5904\u7406\u5b83\u4eec\u4f1a\u5bb9\u6613\u5f97\u591a\u3002<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>Developing minimal APIs with globalization and localization support in mind is fundamental in an interconnected world. ASP.NET Core includes all the features needed to create services that can react to the culture of the user and provide translations based on the request language: the usage of localization middleware, resource files, and custom validation messages allows the creation of services that can support virtually every culture. We have also talked about the globalization-related problems that could arise when working with different time zones and shown how to solve it using the centralized UTC date time format so that our APIs can seamlessly work irrespective of the geographic location and time zone of clients.<br \/>\n\u5728\u8003\u8651\u5168\u7403\u5316\u548c\u672c\u5730\u5316\u652f\u6301\u7684\u60c5\u51b5\u4e0b\u5f00\u53d1\u6700\u5c11\u7684 API \u662f\u4e92\u8054\u4e16\u754c\u7684\u57fa\u7840\u3002ASP.NET Core \u5305\u62ec\u521b\u5efa\u670d\u52a1\u6240\u9700\u7684\u6240\u6709\u529f\u80fd\uff0c\u8fd9\u4e9b\u670d\u52a1\u53ef\u4ee5\u54cd\u5e94\u7528\u6237\u6587\u5316\u5e76\u6839\u636e\u8bf7\u6c42\u8bed\u8a00\u63d0\u4f9b\u7ffb\u8bd1\uff1a\u4f7f\u7528\u672c\u5730\u5316\u4e2d\u95f4\u4ef6\u3001\u8d44\u6e90\u6587\u4ef6\u548c\u81ea\u5b9a\u4e49\u9a8c\u8bc1\u6d88\u606f\uff0c\u53ef\u4ee5\u521b\u5efa\u51e0\u4e4e\u53ef\u4ee5\u652f\u6301\u6240\u6709\u6587\u5316\u7684\u670d\u52a1\u3002\u6211\u4eec\u8fd8\u8ba8\u8bba\u4e86\u4f7f\u7528\u4e0d\u540c\u65f6\u533a\u65f6\u53ef\u80fd\u51fa\u73b0\u7684\u5168\u7403\u5316\u76f8\u5173\u95ee\u9898\uff0c\u5e76\u5c55\u793a\u4e86\u5982\u4f55\u4f7f\u7528\u96c6\u4e2d\u5f0f UTC \u65e5\u671f\u65f6\u95f4\u683c\u5f0f\u6765\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\uff0c\u4ee5\u4fbf\u6211\u4eec\u7684 API \u53ef\u4ee5\u65e0\u7f1d\u5de5\u4f5c\uff0c\u800c\u4e0d\u53d7\u5ba2\u6237\u7684\u5730\u7406\u4f4d\u7f6e\u548c\u65f6\u533a\u7684\u5f71\u54cd\u3002<\/p>\n<p>In Chapter 10, Evaluating and Benchmarking the Performance of Minimal APIs, we will talk about why minimal APIs were created and analyze the performance benefits of using minimal APIs over the classic controller-based approach.<br \/>\n\u5728\u7b2c 10 \u7ae0 \u8bc4\u4f30\u6700\u5c0f API \u7684\u6027\u80fd\u5e76\u5bf9\u5176\u8fdb\u884c\u57fa\u51c6\u6d4b\u8bd5\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba\u521b\u5efa\u6700\u5c0f API \u7684\u539f\u56e0\uff0c\u5e76\u5206\u6790\u4f7f\u7528\u6700\u5c0f API \u76f8\u5bf9\u4e8e\u57fa\u4e8e\u63a7\u5236\u5668\u7684\u7ecf\u5178\u65b9\u6cd5\u7684\u6027\u80fd\u4f18\u52bf\u3002<\/p>\n<h1>10 Evaluating and Benchmarking the Performance of Minimal APIs<\/h1>\n<p>\u8bc4\u4f30\u6700\u5c0f API \u7684\u6027\u80fd\u5e76\u5bf9\u5176\u8fdb\u884c\u57fa\u51c6\u6d4b\u8bd5<\/p>\n<p>The purpose of this chapter is to understand one of the motivations for which the minimal APIs framework was created.<br \/>\n\u672c\u7ae0\u7684\u76ee\u7684\u662f\u4e86\u89e3\u521b\u5efa\u6700\u5c0f API \u6846\u67b6\u7684\u52a8\u673a\u4e4b\u4e00\u3002<\/p>\n<p>This chapter will provide some obvious data and examples of how you can measure the performance of an ASP.NET 6 application using the traditional approach as well as how you can measure the performance of an ASP.NET application using the minimal API approach.<br \/>\n\u672c\u7ae0\u5c06\u63d0\u4f9b\u4e00\u4e9b\u660e\u663e\u7684\u6570\u636e\u548c\u793a\u4f8b\uff0c\u8bf4\u660e\u5982\u4f55\u4f7f\u7528\u4f20\u7edf\u65b9\u6cd5\u6d4b\u91cf ASP.NET 6 \u5e94\u7528\u7a0b\u5e8f\u7684\u6027\u80fd\uff0c\u4ee5\u53ca\u5982\u4f55\u4f7f\u7528\u6700\u5c0f API \u65b9\u6cd5\u6d4b\u91cf ASP.NET \u5e94\u7528\u7a0b\u5e8f\u7684\u6027\u80fd\u3002<\/p>\n<p>Performance is key to any functioning application; however, very often it takes a back seat.<br \/>\n\u6027\u80fd\u662f\u4efb\u4f55\u6b63\u5e38\u8fd0\u884c\u7684\u5e94\u7528\u7a0b\u5e8f\u7684\u5173\u952e;\u7136\u800c\uff0c\u5b83\u7ecf\u5e38\u9000\u5c45\u4e8c\u7ebf\u3002<\/p>\n<p>A performant and scalable application depends not only on our code but also on the development stack. Today, we have moved on from the .NET full framework and .NET Core to .NET and can start to appreciate the performance that the new .NET has achieved, version after version \u2013 not only with the introduction of new features and the clarity of the framework but also primarily because the framework has been completely rewritten and improved with many features that have made it fast and very competitive compared to other languages.<br \/>\n\u9ad8\u6027\u80fd\u548c\u53ef\u6269\u5c55\u7684\u5e94\u7528\u7a0b\u5e8f\u4e0d\u4ec5\u53d6\u51b3\u4e8e\u6211\u4eec\u7684\u4ee3\u7801\uff0c\u8fd8\u53d6\u51b3\u4e8e\u5f00\u53d1\u5806\u6808\u3002\u4eca\u5929\uff0c\u6211\u4eec\u5df2\u7ecf\u4ece .NET \u5b8c\u6574\u6846\u67b6\u548c .NET Core \u8f6c\u5411 .NET\uff0c\u5e76\u4e14\u53ef\u4ee5\u5f00\u59cb\u6b23\u8d4f\u65b0 .NET \u6240\u5b9e\u73b0\u7684\u6027\u80fd\uff0c\u4e00\u4e2a\u7248\u672c\u53c8\u4e00\u4e2a\u7248\u672c - \u4e0d\u4ec5\u5f15\u5165\u4e86\u65b0\u529f\u80fd\u548c\u6846\u67b6\u7684\u6e05\u6670\u5ea6\uff0c\u800c\u4e14\u4e3b\u8981\u662f\u56e0\u4e3a\u8be5\u6846\u67b6\u5df2\u88ab\u5b8c\u5168\u91cd\u5199\u548c\u6539\u8fdb\uff0c\u5177\u6709\u8bb8\u591a\u529f\u80fd\uff0c\u4e0e\u5176\u4ed6\u8bed\u8a00\u76f8\u6bd4\uff0c\u8fd9\u4e9b\u529f\u80fd\u4f7f\u5176\u901f\u5ea6\u66f4\u5feb\u4e14\u975e\u5e38\u6709\u7ade\u4e89\u529b\u3002<\/p>\n<p>In this chapter, we will evaluate the performance of the minimal API by comparing its code with identical code that has been developed traditionally. We\u2019ll understand how to evaluate the performance of a web application, taking advantage of the BenchmarkDotNet framework, which can be useful in other application scenarios.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u901a\u8fc7\u5c06\u6700\u5c0f API \u7684\u4ee3\u7801\u4e0e\u4f20\u7edf\u5f00\u53d1\u7684\u76f8\u540c\u4ee3\u7801\u8fdb\u884c\u6bd4\u8f83\u6765\u8bc4\u4f30\u6700\u5c0f API \u7684\u6027\u80fd\u3002\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u5229\u7528 BenchmarkDotNet \u6846\u67b6\u8bc4\u4f30 Web \u5e94\u7528\u7a0b\u5e8f\u7684\u6027\u80fd\uff0c\u8be5\u6846\u67b6\u5728\u5176\u4ed6\u5e94\u7528\u7a0b\u5e8f\u573a\u666f\u4e2d\u53ef\u80fd\u5f88\u6709\u7528\u3002<\/p>\n<p>With minimal APIs, we have a new simplified framework that helps improve performance by leaving out some components that we take for granted with ASP.NET.<br \/>\n\u901a\u8fc7\u6700\u5c11\u7684 API\uff0c\u6211\u4eec\u6709\u4e00\u4e2a\u65b0\u7684\u7b80\u5316\u6846\u67b6\uff0c\u5b83\u901a\u8fc7\u7701\u7565\u4e00\u4e9b\u6211\u4eec\u8ba4\u4e3a\u7406\u6240\u5f53\u7136\u7684\u7ec4\u4ef6\u6765\u5e2e\u52a9\u63d0\u9ad8\u6027\u80fd ASP.NET\u3002<\/p>\n<p>The themes we will touch on in this chapter are as follows:<br \/>\n\u6211\u4eec\u5c06\u5728\u672c\u7ae0\u4e2d\u8ba8\u8bba\u7684\u4e3b\u9898\u5982\u4e0b\uff1a<\/p>\n<p>\u2022  Improvements with minimal APIs<br \/>\n\u4f7f\u7528\u6700\u5c11\u7684 API \u8fdb\u884c\u6539\u8fdb<\/p>\n<p>\u2022  Exploring performance with load tests<br \/>\n\u901a\u8fc7\u8d1f\u8f7d\u6d4b\u8bd5\u63a2\u7d22\u6027\u80fd<\/p>\n<p>\u2022  Benchmarking minimal APIs with BenchmarkDotNet<br \/>\n\u4f7f\u7528 BenchmarkDotNet \u5bf9\u6700\u5c0f API \u8fdb\u884c\u57fa\u51c6\u6d4b\u8bd5<\/p>\n<p>Technical requirements<br \/>\n\u6280\u672f\u8981\u6c42<\/p>\n<p>Many systems can help us test the performance of a framework.<br \/>\n\u8bb8\u591a\u7cfb\u7edf\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u6d4b\u8bd5\u6846\u67b6\u7684\u6027\u80fd\u3002<\/p>\n<p>We can measure how many requests per second one application can handle compared to another, assuming equal application load. In this case, we are talking about load testing.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u6d4b\u91cf\u4e00\u4e2a\u5e94\u7528\u7a0b\u5e8f\u6bcf\u79d2\u53ef\u4ee5\u5904\u7406\u591a\u5c11\u4e2a\u8bf7\u6c42\uff0c\u5047\u8bbe\u5e94\u7528\u7a0b\u5e8f\u8d1f\u8f7d\u76f8\u540c\u3002\u5728\u672c\u4f8b\u4e2d\uff0c\u6211\u4eec\u8c08\u8bba\u7684\u662f\u8d1f\u8f7d\u6d4b\u8bd5\u3002<\/p>\n<p>To put the minimal APIs on the test bench, we need to install k6, the framework we will use for conducting our tests.<br \/>\n\u8981\u5c06\u6700\u5c0f\u7684 API \u653e\u5728\u6d4b\u8bd5\u53f0\u4e0a\uff0c\u6211\u4eec\u9700\u8981\u5b89\u88c5 k6\uff0c\u6211\u4eec\u5c06\u7528\u4e8e\u6267\u884c\u6d4b\u8bd5\u7684\u6846\u67b6\u3002<\/p>\n<p>We will launch load testing on a Windows machine with only .NET applications running.<br \/>\n\u6211\u4eec\u5c06\u5728\u4ec5\u8fd0\u884c .NET \u5e94\u7528\u7a0b\u5e8f\u7684 Windows \u8ba1\u7b97\u673a\u4e0a\u542f\u52a8\u8d1f\u8f7d\u6d4b\u8bd5\u3002<\/p>\n<p>To install k6, you can do either one of the following:<br \/>\n\u8981\u5b89\u88c5 k6\uff0c\u60a8\u53ef\u4ee5\u6267\u884c\u4ee5\u4e0b\u4efb\u4e00\u64cd\u4f5c\uff1a<\/p>\n<p>\u2022  If you\u2019re using the Chocolatey package manager (<a href=\"https:\/\/chocolatey.org\/\">https:\/\/chocolatey.org\/<\/a>), you can install the unofficial k6 package with the following command:<br \/>\n\u5982\u679c\u60a8\u4f7f\u7528\u7684\u662f Chocolatey \u5305\u7ba1\u7406\u5668 \uff08<a href=\"https:\/\/chocolatey.org\/\uff09\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u5b89\u88c5\u975e\u5b98\u65b9\u7684\">https:\/\/chocolatey.org\/\uff09\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u5b89\u88c5\u975e\u5b98\u65b9\u7684<\/a> k6 \u5305\uff1a<\/p>\n<pre><code>choco install k6<\/code><\/pre>\n<p>\u2022  If you\u2019re using Windows Package Manager (<a href=\"https:\/\/github.com\/microsoft\/winget-cli\">https:\/\/github.com\/microsoft\/winget-cli<\/a>), you can install the official package from the k6 manifests with this command:<br \/>\n\u5982\u679c\u60a8\u4f7f\u7528\u7684\u662f Windows Package Manager \uff08<a href=\"https:\/\/github.com\/microsoft\/winget-cli\uff09\uff0c\u5219\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u4ece\">https:\/\/github.com\/microsoft\/winget-cli\uff09\uff0c\u5219\u53ef\u4ee5\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u4ece<\/a> k6 \u6e05\u5355\u5b89\u88c5\u5b98\u65b9\u8f6f\u4ef6\u5305\uff1a<\/p>\n<pre><code>winget install k6<\/code><\/pre>\n<p>\u2022  You can also test your application published on the internet with Docker:<br \/>\n\u60a8\u8fd8\u53ef\u4ee5\u4f7f\u7528 Docker \u6d4b\u8bd5\u5728 Internet \u4e0a\u53d1\u5e03\u7684\u5e94\u7528\u7a0b\u5e8f\uff1a<\/p>\n<pre><code>docker pull loadimpact\/k6<\/code><\/pre>\n<p>\u2022  Or as we did, we installed k6 on the Windows machine and launched everything from the command line. You can download k6 from this link: <a href=\"https:\/\/dl.k6.io\/msi\/k6-latest-amd64.msi\">https:\/\/dl.k6.io\/msi\/k6-latest-amd64.msi<\/a>.<br \/>\n\u6216\u8005\uff0c\u6211\u4eec\u5728 Windows \u8ba1\u7b97\u673a\u4e0a\u5b89\u88c5\u4e86 k6 \u5e76\u4ece\u547d\u4ee4\u884c\u542f\u52a8\u6240\u6709\u5185\u5bb9\u3002\u60a8\u53ef\u4ee5\u4ece\u4ee5\u4e0b\u94fe\u63a5\u4e0b\u8f7d k6\uff1a<a href=\"https:\/\/dl.k6.io\/msi\/k6-latest-amd64.msi\">https:\/\/dl.k6.io\/msi\/k6-latest-amd64.msi<\/a>\u3002<\/p>\n<p>In the final part of the chapter, we\u2019ll measure the duration of the HTTP method for making calls to the API.<br \/>\n\u5728\u672c\u7ae0\u7684\u6700\u540e\u4e00\u90e8\u5206\uff0c\u6211\u4eec\u5c06\u6d4b\u91cf HTTP \u65b9\u6cd5\u8c03\u7528 API \u7684\u6301\u7eed\u65f6\u95f4\u3002<\/p>\n<p>We\u2019ll stand at the end of the system as if the API were a black box and measure the reaction time. BenchmarkDotNet is the tool we\u2019ll be using \u2013 to include it in our project, we need to reference its NuGet package:<br \/>\n\u6211\u4eec\u5c06\u7ad9\u5728\u7cfb\u7edf\u7684\u672b\u7aef\uff0c\u5c31\u597d\u50cf API \u662f\u4e00\u4e2a\u9ed1\u5323\u5b50\u4e00\u6837\uff0c\u5e76\u6d4b\u91cf\u53cd\u5e94\u65f6\u95f4\u3002BenchmarkDotNet \u662f\u6211\u4eec\u5c06\u8981\u4f7f\u7528\u7684\u5de5\u5177 - \u8981\u5c06\u5176\u5305\u542b\u5728\u6211\u4eec\u7684\u9879\u76ee\u4e2d\uff0c\u6211\u4eec\u9700\u8981\u5f15\u7528\u5176 NuGet \u5305\uff1a<\/p>\n<pre><code>dotnet add package BenchmarkDotNet<\/code><\/pre>\n<p>All the code samples in this chapter can be found in the GitHub repository for this book at the following link:<br \/>\n\u672c\u7ae0\u4e2d\u7684\u6240\u6709\u4ee3\u7801\u793a\u4f8b\u90fd\u53ef\u4ee5\u5728\u672c\u4e66\u7684 GitHub \u5b58\u50a8\u5e93\u4e2d\u627e\u5230\uff0c\u94fe\u63a5\u5982\u4e0b\uff1a<br \/>\n<a href=\"https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter10\">https:\/\/github.com\/PacktPublishing\/Minimal-APIs-in-ASP.NET-Core-6\/tree\/main\/Chapter10<\/a><\/p>\n<p>Improvements with minimal APIs<br \/>\n\u4f7f\u7528\u6700\u5c11\u7684 API \u8fdb\u884c\u6539\u8fdb<\/p>\n<p>Minimal APIs were designed not only to improve the performance of APIs but also for better code convenience and similarity to other languages to bring developers from other platforms closer. Performance has increased both from the point of view of the .NET framework, as each version has incredible improvements, as well as from the point of view of the simplification of the application pipeline. Let\u2019s see in detail what has not been ported and what improves the performance of this framework.<br \/>\nMinimal API \u7684\u8bbe\u8ba1\u4e0d\u4ec5\u662f\u4e3a\u4e86\u63d0\u9ad8 API \u7684\u6027\u80fd\uff0c\u4e5f\u662f\u4e3a\u4e86\u66f4\u597d\u7684\u4ee3\u7801\u4fbf\u5229\u6027\u548c\u4e0e\u5176\u4ed6\u8bed\u8a00\u7684\u76f8\u4f3c\u6027\uff0c\u4ece\u800c\u62c9\u8fd1\u6765\u81ea\u5176\u4ed6\u5e73\u53f0\u7684\u5f00\u53d1\u4eba\u5458\u7684\u8ddd\u79bb\u3002\u4ece .NET Framework \u7684\u89d2\u5ea6\u6765\u770b\uff0c\u6027\u80fd\u90fd\u6709\u6240\u63d0\u9ad8\uff0c\u56e0\u4e3a\u6bcf\u4e2a\u7248\u672c\u90fd\u6709\u4ee4\u4eba\u96be\u4ee5\u7f6e\u4fe1\u7684\u6539\u8fdb\uff0c\u800c\u4e14\u4ece\u5e94\u7528\u7a0b\u5e8f\u7ba1\u9053\u7684\u7b80\u5316\u7684\u89d2\u5ea6\u6765\u770b\u4e5f\u662f\u5982\u6b64\u3002\u8ba9\u6211\u4eec\u8be6\u7ec6\u770b\u770b\u54ea\u4e9b\u5185\u5bb9\u5c1a\u672a\u79fb\u690d\uff0c\u54ea\u4e9b\u5185\u5bb9\u63d0\u9ad8\u4e86\u6b64\u6846\u67b6\u7684\u6027\u80fd\u3002<\/p>\n<p>The minimal APIs execution pipeline omits the following features, which makes the framework lighter:<br \/>\n\u6700\u5c0f API \u6267\u884c\u7ba1\u9053\u7701\u7565\u4e86\u4ee5\u4e0b\u529f\u80fd\uff0c\u8fd9\u4f7f\u5f97\u6846\u67b6\u66f4\u8f7b\u91cf\u7ea7\uff1a<\/p>\n<p>\u2022  Filters, such as IAsyncAuthorizationFilter, IAsyncActionFilter, IAsyncExceptionFilter, IAsyncResultFilter, and IasyncResourceFilter<br \/>\n\u2022  Model binding<br \/>\n\u2022  Binding for forms, such as IFormFile<br \/>\n\u2022  Built-in validation<br \/>\n\u2022  Formatters<br \/>\n\u2022  Content negotiations<br \/>\n\u2022  Some middleware<br \/>\n\u2022  View rendering<br \/>\n\u2022  JsonPatch<br \/>\n\u2022  OData<br \/>\n\u2022  API versioning<\/p>\n<p>Performance Improvements in .NET 6<br \/>\n.NET 6 \u4e2d\u7684\u6027\u80fd\u6539\u8fdb<\/p>\n<p>Version after version, .NET improves its performance. In the latest version of the framework, improvements made over previous versions have been reported. Here\u2019s where you can find a complete summary of what\u2019s new in .NET 6:<br \/>\n\u4e00\u4e2a\u53c8\u4e00\u4e2a\u7248\u672c\uff0c.NET \u63d0\u9ad8\u4e86\u5176\u6027\u80fd\u3002\u5728\u6700\u65b0\u7248\u672c\u7684\u6846\u67b6\u4e2d\uff0c\u62a5\u544a\u4e86\u5bf9\u4ee5\u524d\u7248\u672c\u6240\u505a\u7684\u6539\u8fdb\u3002\u60a8\u53ef\u4ee5\u5728\u6b64\u5904\u627e\u5230 .NET 6 \u4e2d\u65b0\u589e\u529f\u80fd\u7684\u5b8c\u6574\u6458\u8981\uff1a<br \/>\n<a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/performance-improvements-in-net-6\/\">https:\/\/devblogs.microsoft.com\/dotnet\/performance-improvements-in-net-6\/<\/a><\/p>\n<p>Exploring performance with load tests<br \/>\n\u901a\u8fc7\u8d1f\u8f7d\u6d4b\u8bd5\u63a2\u7d22\u6027\u80fd<\/p>\n<p>How to estimate the performance of minimal APIs? There are many points of view to consider and in this chapter, we will try to address them from the point of view of the load they can support. We decided to adopt a tool \u2013 k6 \u2013 that performs load tests on a web application and tells us how many requests per second can a minimal API handle.<br \/>\n\u5982\u4f55\u4f30\u7b97\u6700\u5c0f API \u7684\u6027\u80fd\uff1f\u6709\u8bb8\u591a\u89c2\u70b9\u9700\u8981\u8003\u8651\uff0c\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u5c1d\u8bd5\u4ece\u5b83\u4eec\u53ef\u4ee5\u652f\u6301\u7684\u8d1f\u8f7d\u7684\u89d2\u5ea6\u6765\u89e3\u51b3\u8fd9\u4e9b\u95ee\u9898\u3002\u6211\u4eec\u51b3\u5b9a\u91c7\u7528\u4e00\u79cd\u5de5\u5177 k6\uff0c\u5b83\u5728 Web \u5e94\u7528\u7a0b\u5e8f\u4e0a\u6267\u884c\u8d1f\u8f7d\u6d4b\u8bd5\uff0c\u5e76\u544a\u8bc9\u6211\u4eec\u6700\u5c0f API \u6bcf\u79d2\u53ef\u4ee5\u5904\u7406\u591a\u5c11\u4e2a\u8bf7\u6c42\u3002<\/p>\n<p>As described by its creators, k6 is an open source load testing tool that makes performance testing easy and productive for engineering teams. The tool is free, developer-centric, and extensible. Using k6, you can test the reliability and performance of your systems and catch performance regressions and problems earlier. This tool will help you to build resilient and performant applications that scale.<br \/>\n\u6b63\u5982\u5176\u521b\u5efa\u8005\u6240\u63cf\u8ff0\u7684\u90a3\u6837\uff0ck6 \u662f\u4e00\u79cd\u5f00\u6e90\u8d1f\u8f7d\u6d4b\u8bd5\u5de5\u5177\uff0c\u5b83\u4f7f\u5de5\u7a0b\u56e2\u961f\u7684\u6027\u80fd\u6d4b\u8bd5\u53d8\u5f97\u7b80\u5355\u800c\u9ad8\u6548\u3002\u8be5\u5de5\u5177\u662f\u514d\u8d39\u7684\u3001\u4ee5\u5f00\u53d1\u4eba\u5458\u4e3a\u4e2d\u5fc3\u4e14\u53ef\u6269\u5c55\u7684\u3002\u4f7f\u7528 k6\uff0c\u60a8\u53ef\u4ee5\u6d4b\u8bd5\u7cfb\u7edf\u7684\u53ef\u9760\u6027\u548c\u6027\u80fd\uff0c\u5e76\u66f4\u65e9\u5730\u6355\u83b7\u6027\u80fd\u56de\u5f52\u548c\u95ee\u9898\u3002\u6b64\u5de5\u5177\u5c06\u5e2e\u52a9\u60a8\u6784\u5efa\u53ef\u6269\u5c55\u7684\u5f39\u6027\u548c\u9ad8\u6027\u80fd\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>In our case, we would like to use the tool for performance evaluation and not for load testing. Many parameters should be considered during load testing, but we will only focus on the http_reqs index, which indicates how many requests have been handled correctly by the system.<br \/>\n\u5728\u6211\u4eec\u7684\u4f8b\u5b50\u4e2d\uff0c\u6211\u4eec\u5e0c\u671b\u4f7f\u7528\u8be5\u5de5\u5177\u8fdb\u884c\u6027\u80fd\u8bc4\u4f30\uff0c\u800c\u4e0d\u662f\u8fdb\u884c\u8d1f\u8f7d\u6d4b\u8bd5\u3002\u5728\u8d1f\u8f7d\u6d4b\u8bd5\u671f\u95f4\u5e94\u8003\u8651\u8bb8\u591a\u53c2\u6570\uff0c\u4f46\u6211\u4eec\u53ea\u5173\u6ce8 http_reqs \u6307\u6570\uff0c\u5b83\u8868\u793a\u7cfb\u7edf\u6b63\u786e\u5904\u7406\u4e86\u591a\u5c11\u4e2a\u8bf7\u6c42\u3002<\/p>\n<p>We agree with the creators of k6 about the purpose of our test, namely performance and synthetic monitoring.<br \/>\n\u6211\u4eec\u540c\u610f k6 \u7684\u521b\u5efa\u8005\u5173\u4e8e\u6211\u4eec\u6d4b\u8bd5\u7684\u76ee\u7684\uff0c\u5373\u6027\u80fd\u548c\u7efc\u5408\u76d1\u63a7\u3002<\/p>\n<p>Use cases<br \/>\n\u4f7f\u7528\u6848\u4f8b<\/p>\n<p>k6 users are typically developers, QA engineers, SDETs, and SREs. They use k6 for testing the performance and reliability of APIs, microservices, and websites. Common k6 use cases include the following:<br \/>\nk6 \u7528\u6237\u901a\u5e38\u662f\u5f00\u53d1\u4eba\u5458\u3001QA \u5de5\u7a0b\u5e08\u3001SDET \u548c SRE\u3002\u4ed6\u4eec\u4f7f\u7528 k6 \u6765\u6d4b\u8bd5 API\u3001\u5fae\u670d\u52a1\u548c\u7f51\u7ad9\u7684\u6027\u80fd\u548c\u53ef\u9760\u6027\u3002\u5e38\u89c1\u7684 k6 \u4f7f\u7528\u6848\u4f8b\u5305\u62ec\uff1a<\/p>\n<p>\u2022  Load testing: k6 is optimized for minimal resource consumption and designed for running high load tests (spike, stress, and soak tests).<br \/>\n\u8d1f\u8f7d\u6d4b\u8bd5\uff1ak6 \u9488\u5bf9\u6700\u5c0f\u8d44\u6e90\u6d88\u8017\u8fdb\u884c\u4e86\u4f18\u5316\uff0c\u4e13\u4e3a\u8fd0\u884c\u9ad8\u8d1f\u8f7d\u6d4b\u8bd5\uff08\u5cf0\u503c\u3001\u538b\u529b\u548c\u6d78\u6ce1\u6d4b\u8bd5\uff09\u800c\u8bbe\u8ba1\u3002<\/p>\n<p>\u2022  Performance and synthetic monitoring: With k6, you can run tests with a small load to continuously validate the performance and availability of your production environment.<br \/>\n\u6027\u80fd\u548c\u7efc\u5408\u76d1\u63a7\uff1a\u4f7f\u7528 k6\uff0c\u60a8\u53ef\u4ee5\u8fd0\u884c\u5c0f\u8d1f\u8f7d\u6d4b\u8bd5\uff0c\u4ee5\u6301\u7eed\u9a8c\u8bc1\u751f\u4ea7\u73af\u5883\u7684\u6027\u80fd\u548c\u53ef\u7528\u6027\u3002<\/p>\n<p>\u2022  Chaos and reliability testing: k6 provides an extensible architecture. You can use k6 to simulate traffic as part of your chaos experiments or trigger them from your k6 tests.<br \/>\n\u6df7\u6c8c\u548c\u53ef\u9760\u6027\u6d4b\u8bd5\uff1ak6 \u63d0\u4f9b\u53ef\u6269\u5c55\u7684\u67b6\u6784\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528 k6 \u5728\u6df7\u6c8c\u5b9e\u9a8c\u4e2d\u6a21\u62df\u6d41\u91cf\uff0c\u4e5f\u53ef\u4ee5\u4ece k6 \u6d4b\u8bd5\u4e2d\u89e6\u53d1\u6d41\u91cf\u3002<\/p>\n<p>However, we have to make several assumptions if we want to evaluate the application from the point of view just described. When a load test is performed, it is usually much more complex than the ones we will perform in this section. When an application is bombarded with requests, not all of them will be successful. We can say that the test passed successfully if a very small percentage of the responses failed. In particular, we usually consider 95 or 98 percentiles of outcomes as the statistic on which to derive the test numbers.<br \/>\n\u4f46\u662f\uff0c\u5982\u679c\u6211\u4eec\u60f3\u4ece\u521a\u624d\u63cf\u8ff0\u7684\u89d2\u5ea6\u8bc4\u4f30\u5e94\u7528\u7a0b\u5e8f\uff0c\u6211\u4eec\u5fc5\u987b\u505a\u51fa\u51e0\u4e2a\u5047\u8bbe\u3002\u6267\u884c\u8d1f\u8f7d\u6d4b\u8bd5\u65f6\uff0c\u5b83\u901a\u5e38\u6bd4\u6211\u4eec\u5c06\u5728\u672c\u8282\u4e2d\u6267\u884c\u7684\u8981\u590d\u6742\u5f97\u591a\u3002\u5f53\u5e94\u7528\u7a0b\u5e8f\u88ab\u8bf7\u6c42\u8f70\u70b8\u65f6\uff0c\u5e76\u975e\u6240\u6709\u8bf7\u6c42\u90fd\u4f1a\u6210\u529f\u3002\u5982\u679c\u6781\u5c0f\u6bd4\u4f8b\u7684\u54cd\u5e94\u5931\u8d25\uff0c\u6211\u4eec\u53ef\u4ee5\u8bf4\u6d4b\u8bd5\u6210\u529f\u901a\u8fc7\u3002\u7279\u522b\u662f\uff0c\u6211\u4eec\u901a\u5e38\u5c06 95 \u6216 98 \u4e2a\u767e\u5206\u4f4d\u6570\u7684\u7ed3\u679c\u89c6\u4e3a\u5f97\u51fa\u6d4b\u8bd5\u6570\u5b57\u7684\u7edf\u8ba1\u6570\u636e\u3002<\/p>\n<p>With this background, we can perform stepwise load testing as follows: in ramp up, the system will be concerned with running the virtual user (VU) load from 0 to 50 for about 15 seconds. Then, we will keep the number of users stable for 60 seconds, and finally, ramp down the load to zero virtual users for another 15 seconds.<br \/>\n\u5728\u6b64\u80cc\u666f\u4e0b\uff0c\u6211\u4eec\u53ef\u4ee5\u6309\u5982\u4e0b\u65b9\u5f0f\u6267\u884c\u9010\u6b65\u8d1f\u8f7d\u6d4b\u8bd5\uff1a\u5728\u52a0\u901f\u8fc7\u7a0b\u4e2d\uff0c\u7cfb\u7edf\u5c06\u5173\u6ce8\u4ece 0 \u5230 50 \u7684\u865a\u62df\u7528\u6237 \uff08VU\uff09 \u8d1f\u8f7d\u8fd0\u884c\u7ea6 15 \u79d2\u3002\u7136\u540e\uff0c\u6211\u4eec\u5c06\u4fdd\u6301\u7528\u6237\u6570\u91cf\u7a33\u5b9a 60 \u79d2\uff0c\u6700\u540e\uff0c\u5c06\u8d1f\u8f7d\u964d\u4f4e\u5230\u96f6\u865a\u62df\u7528\u6237\uff0c\u518d\u6301\u7eed 15 \u79d2\u3002<\/p>\n<p>Each newly written stage of the test is expressed in the JavaScript file in the stages section. Testing is therefore conducted under a simple empirical evaluation.<br \/>\n\u6d4b\u8bd5\u7684\u6bcf\u4e2a\u65b0\u7f16\u5199\u9636\u6bb5\u90fd\u8868\u793a\u5728 JavaScript \u6587\u4ef6\u7684 stages \u90e8\u5206\u4e2d\u3002\u56e0\u6b64\uff0c\u6d4b\u8bd5\u662f\u5728\u7b80\u5355\u7684\u5b9e\u8bc1\u8bc4\u4f30\u4e0b\u8fdb\u884c\u7684\u3002<\/p>\n<p>First, we create three types of responses, both for the ASP.NET Web API and minimal API:<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u4e3a ASP.NET Web API \u548c\u6700\u5c0f API \u521b\u5efa\u4e09\u79cd\u7c7b\u578b\u7684\u54cd\u5e94\uff1a<\/p>\n<p>\u2022  Plain-text.<br \/>\n\u2022  Very small JSON data against a call \u2013 the data is static and always the same.<br \/>\n\u9488\u5bf9\u8c03\u7528\u7684\u975e\u5e38\u5c0f\u7684 JSON \u6570\u636e \u2013 \u6570\u636e\u662f\u9759\u6001\u7684\uff0c\u5e76\u4e14\u59cb\u7ec8\u76f8\u540c\u3002<\/p>\n<p>\u2022  In the third response, we send JSON data with an HTTP POST method to the API. For the Web API, we check the validation of the object, and for the minimal API, since there is no validation, we return the object as received.<br \/>\n\u5728\u7b2c\u4e09\u4e2a\u54cd\u5e94\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528 HTTP POST \u65b9\u6cd5\u5c06 JSON \u6570\u636e\u53d1\u9001\u5230 API\u3002\u5bf9\u4e8e Web API\uff0c\u6211\u4eec\u68c0\u67e5\u5bf9\u8c61\u7684\u9a8c\u8bc1\uff0c\u5bf9\u4e8e\u6700\u5c0f\u7684 API\uff0c\u7531\u4e8e\u6ca1\u6709\u9a8c\u8bc1\uff0c\u6211\u4eec\u8fd4\u56de\u63a5\u6536\u7684\u5bf9\u8c61\u3002<\/p>\n<p>The following code will be used to compare the performance between the minimal API and the traditional approach:<br \/>\n\u4ee5\u4e0b\u4ee3\u7801\u5c06\u7528\u4e8e\u6bd4\u8f83\u6700\u5c0f API \u548c\u4f20\u7edf\u65b9\u6cd5\u4e4b\u95f4\u7684\u6027\u80fd\uff1a<\/p>\n<p>Minimal API<br \/>\n\u6700\u5c0f API<\/p>\n<pre><code>app.MapGet(&quot;text-plain&quot;,() =&gt; Results.Content(&quot;response&quot;))\n.WithName(&quot;GetTextPlain&quot;);\napp.MapPost(&quot;validations&quot;,(ValidationData validation) =&gt; Results.Ok(validation)).WithName(&quot;PostValidationData&quot;);\napp.MapGet(&quot;jsons&quot;, () =&gt;\n     {\n           var response = new[]\n           {\n                new PersonData { Name = &quot;Andrea&quot;, Surname = \n                &quot;Tosato&quot;, BirthDate = new DateTime\n                (2022, 01, 01) },\n                new PersonData { Name = &quot;Emanuele&quot;, \n                Surname = &quot;Bartolesi&quot;, BirthDate = new \n                DateTime(2022, 01, 01) },\n                new PersonData { Name = &quot;Marco&quot;, Surname = \n                &quot;Minerva&quot;, BirthDate = new DateTime\n                (2022, 01, 01) }\n           };\n           return Results.Ok(response);\n     })\n.WithName(&quot;GetJsonData&quot;);<\/code><\/pre>\n<p>Traditional Approach<br \/>\n\u4f20\u7edf\u65b9\u6cd5<\/p>\n<p>For the traditional approach, three distinct controllers have been designed as shown here:<br \/>\n\u5bf9\u4e8e\u4f20\u7edf\u65b9\u6cd5\uff0c\u8bbe\u8ba1\u4e86\u4e09\u4e2a\u4e0d\u540c\u7684\u63a7\u5236\u5668\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>[Route(&quot;text-plain&quot;)]\n     [ApiController]\n     public class TextPlainController : ControllerBase\n     {\n           [HttpGet]\n           public IActionResult Get()\n           {\n                 return Content(&quot;response&quot;);\n           }\n     }\n[Route(&quot;validations&quot;)]\n     [ApiController]\n     public class ValidationsController : ControllerBase\n     {\n           [HttpPost]\n           public IActionResult Post(ValidationData data)\n           {\n                 return Ok(data);\n           }\n     }\n     public class ValidationData\n     {\n           [Required]\n           public int Id { get; set; }\n           [Required]\n           [StringLength(100)]\n           public string Description { get; set; }\n     }\n[Route(&quot;jsons&quot;)]\n[ApiController]\npublic class JsonsController : ControllerBase\n{\n     [HttpGet]\n     public IActionResult Get()\n     {\n           var response = new[]\n           {\n              new PersonData { Name = &quot;Andrea&quot;, Surname = \n              &quot;Tosato&quot;, BirthDate = new \n              DateTime(2022, 01, 01) },\n              new PersonData { Name = &quot;Emanuele&quot;, Surname = \n              &quot;Bartolesi&quot;, BirthDate = new \n              DateTime(2022, 01, 01) },\n              new PersonData { Name = &quot;Marco&quot;, Surname = \n              &quot;Minerva&quot;, BirthDate = new \n              DateTime(2022, 01, 01) }\n            };\n            return Ok(response);\n     }\n}\n     public class PersonData\n     {\n           public string Name { get; set; }\n           public string Surname { get; set; }\n           public DateTime BirthDate { get; set; }\n     }<\/code><\/pre>\n<p>In the next section, we will define an options object, where we are going to define the execution ramp described here. We define all clauses to consider the test satisfied. As the last step, we write the real test, which does nothing but call the HTTP endpoint using GET or POST, depending on the test.<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u5b9a\u4e49\u4e00\u4e2a options \u5bf9\u8c61\uff0c\u6211\u4eec\u5c06\u5728\u5176\u4e2d\u5b9a\u4e49\u6b64\u5904\u63cf\u8ff0\u7684\u6267\u884c\u659c\u5761\u3002\u6211\u4eec\u5b9a\u4e49\u6240\u6709\u5b50\u53e5\u4ee5\u8ba4\u4e3a\u6ee1\u8db3\u6d4b\u8bd5\u3002\u4f5c\u4e3a\u6700\u540e\u4e00\u6b65\uff0c\u6211\u4eec\u7f16\u5199\u771f\u6b63\u7684\u6d4b\u8bd5\uff0c\u5b83\u53ea\u4f7f\u7528 GET \u6216 POST \u8c03\u7528 HTTP \u7ec8\u7aef\u8282\u70b9\uff0c\u5177\u4f53\u53d6\u51b3\u4e8e\u6d4b\u8bd5\u3002<\/p>\n<p>Writing k6 tests<br \/>\n\u7f16\u5199 k6 \u6d4b\u8bd5<\/p>\n<p>Let\u2019s create a test for each case scenario that we described in the previous section:<br \/>\n\u8ba9\u6211\u4eec\u4e3a\u4e0a\u4e00\u8282\u4e2d\u63cf\u8ff0\u7684\u6bcf\u4e2a case \u573a\u666f\u521b\u5efa\u4e00\u4e2a\u6d4b\u8bd5\uff1a<\/p>\n<pre><code>import http from &quot;k6\/http&quot;;\nimport { check } from &quot;k6&quot;;\nexport let options = {\n     summaryTrendStats: [&quot;avg&quot;, &quot;p(95)&quot;],\n     stages: [\n           \/\/ Linearly ramp up from 1 to 50 VUs during 10 \n              seconds\n              { target: 50, duration: &quot;10s&quot; },\n           \/\/ Hold at 50 VUs for the next 1 minute\n              { target: 50, duration: &quot;1m&quot; },\n           \/\/ Linearly ramp down from 50 to 0 VUs over the \n              last 15 seconds\n              { target: 0, duration: &quot;15s&quot; }\n     ],\n     thresholds: {\n           \/\/ We want the 95th percentile of all HTTP \n              request durations to be less than 500ms\n              &quot;http_req_duration&quot;: [&quot;p(95)&lt;500&quot;],\n           \/\/ Thresholds based on the custom metric we \n              defined and use to track application failures\n              &quot;check_failure_rate&quot;: [\n          \/\/ Global failure rate should be less than 1%\n             &quot;rate&lt;0.01&quot;,\n          \/\/ Abort the test early if it climbs over 5%\n             { threshold: &quot;rate&lt;=0.05&quot;, abortOnFail: true },\n           ],\n     },\n};\nexport default function () {\n    \/\/ execute http get call\n    let response = http.get(&quot;http:\/\/localhost:7060\/jsons&quot;);\n    \/\/ check() returns false if any of the specified \n       conditions fail\n    check(response, {\n           &quot;status is 200&quot;: (r) =&gt; r.status === 200,\n    });\n}<\/code><\/pre>\n<p>In the preceding JavaScript file, we wrote the test using k6 syntax. We have defined the options, such as the evaluation threshold of the test, the parameters to be measured, and the stages that the test should simulate. Once we have defined the options of the test, we just have to write the code to call the APIs that interest us \u2013 in our case, we have defined three tests to call the three endpoints that we want to evaluate.<br \/>\n\u5728\u4e0a\u9762\u7684 JavaScript \u6587\u4ef6\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528 k6 \u8bed\u6cd5\u7f16\u5199\u4e86\u6d4b\u8bd5\u3002\u6211\u4eec\u5df2\u7ecf\u5b9a\u4e49\u4e86\u9009\u9879\uff0c\u4f8b\u5982\u6d4b\u8bd5\u7684\u8bc4\u4f30\u9608\u503c\u3001\u8981\u6d4b\u91cf\u7684\u53c2\u6570\u4ee5\u53ca\u6d4b\u8bd5\u5e94\u6a21\u62df\u7684\u9636\u6bb5\u3002\u5b9a\u4e49\u6d4b\u8bd5\u9009\u9879\u540e\uff0c\u6211\u4eec\u53ea\u9700\u7f16\u5199\u4ee3\u7801\u6765\u8c03\u7528\u6211\u4eec\u611f\u5174\u8da3\u7684 API \u2013 \u5728\u6211\u4eec\u7684\u4f8b\u5b50\u4e2d\uff0c\u6211\u4eec\u5df2\u7ecf\u5b9a\u4e49\u4e86\u4e09\u4e2a\u6d4b\u8bd5\u6765\u8c03\u7528\u6211\u4eec\u60f3\u8981\u8bc4\u4f30\u7684\u4e09\u4e2a\u7aef\u70b9\u3002<\/p>\n<p>Running a k6 performance test<br \/>\n\u8fd0\u884c k6 \u6027\u80fd\u6d4b\u8bd5<\/p>\n<p>Now that we have written the code to test the performance, let\u2019s run the test and generate the statistics of the tests.<br \/>\n\u73b0\u5728\u6211\u4eec\u5df2\u7ecf\u7f16\u5199\u4e86\u4ee3\u7801\u6765\u6d4b\u8bd5\u6027\u80fd\uff0c\u8ba9\u6211\u4eec\u8fd0\u884c\u6d4b\u8bd5\u5e76\u751f\u6210\u6d4b\u8bd5\u7684\u7edf\u8ba1\u4fe1\u606f\u3002<\/p>\n<p>We will report all the general statistics of the collected tests:<br \/>\n\u6211\u4eec\u5c06\u62a5\u544a\u6240\u6536\u96c6\u6d4b\u8bd5\u7684\u6240\u6709\u4e00\u822c\u7edf\u8ba1\u6570\u636e\uff1a<\/p>\n<ol>\n<li>\n<p>First, we need to start the web applications to run the load test. Let\u2019s start with both the ASP.NET Web API application and the minimal API application. We expose the URLs, both the HTTPS and HTTP protocols.<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u9700\u8981\u542f\u52a8 Web \u5e94\u7528\u7a0b\u5e8f\u4ee5\u8fd0\u884c\u8d1f\u8f7d\u6d4b\u8bd5\u3002\u8ba9\u6211\u4eec\u4ece ASP.NET Web API \u5e94\u7528\u7a0b\u5e8f\u548c\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f\u5f00\u59cb\u3002\u6211\u4eec\u516c\u5f00 URL\uff0c\u5305\u62ec HTTPS \u548c HTTP \u534f\u8bae\u3002<\/p>\n<\/li>\n<li>\n<p>Move the shell to the root folder and run the following two commands in two different shells:<br \/>\n\u5c06 shell \u79fb\u52a8\u5230\u6839\u6587\u4ef6\u5939\uff0c\u5e76\u5728\u4e24\u4e2a\u4e0d\u540c\u7684 shell \u4e2d\u8fd0\u884c\u4ee5\u4e0b\u4e24\u4e2a\u547d\u4ee4\uff1a<\/p>\n<pre><code>dotnet .\\MinimalAPI.Sample\\bin\\Release\\net6.0\\MinimalAPI.Sample.dll --urls=https:\/\/localhost:7059\/;http:\/\/localhost:7060\/\ndotnet .\\ControllerAPI.Sample\\bin\\Release\\net6.0\\ControllerAPI.Sample.dll --urls=&quot;https:\/\/localhost:7149\/;http:\/\/localhost:7150\/&quot;<\/code><\/pre>\n<\/li>\n<li>\n<p>Now, we just have to run the three test files for each project.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ea\u9700\u8981\u4e3a\u6bcf\u4e2a\u9879\u76ee\u8fd0\u884c\u4e09\u4e2a\u6d4b\u8bd5\u6587\u4ef6\u3002<\/p>\n<\/li>\n<\/ol>\n<p>\u2022  This one is for the controller-based Web API:<br \/>\n\u6b64 API \u9002\u7528\u4e8e\u57fa\u4e8e\u63a7\u5236\u5668\u7684 Web API\uff1a<br \/>\nk6 run .\\K6\\Controllers\\json.js --summary-export=.\\K6\\results\\controller-json.json<\/p>\n<p>\u2022  This one is for the minimal API:<br \/>\n\u6b64 API \u9002\u7528\u4e8e\u6700\u5c0f API\uff1a<br \/>\nk6 run .\\K6\\Minimal\\json.js --summary-export=.\\K6\\results\\minimal-json.json<\/p>\n<p>Here are the results.<br \/>\n\u4ee5\u4e0b\u662f\u7ed3\u679c\u3002<\/p>\n<p>For the test in traditional development mode with a plain-text content type, the number of requests served per second is 1,547:<br \/>\n\u5bf9\u4e8e\u7eaf\u6587\u672c\u5185\u5bb9\u7c7b\u578b\u7684\u4f20\u7edf\u5f00\u53d1\u6a21\u5f0f\u4e0b\u7684\u6d4b\u8bd5\uff0c\u6bcf\u79d2\u63d0\u4f9b\u7684\u8bf7\u6c42\u6570\u4e3a 1547\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/1001.jpg\" ><\/p>\n<p>Figure 10.1 \u2013 The load test for a controller-based API and plain text<br \/>\n\u56fe 10.1 \u2013 \u57fa\u4e8e\u63a7\u5236\u5668\u7684 API \u548c\u7eaf\u6587\u672c\u7684\u8d1f\u8f7d\u6d4b\u8bd5<\/p>\n<p>For the test in traditional development mode with a json content type, the number of requests served per second is 1,614:<br \/>\n\u5bf9\u4e8e\u4f20\u7edf\u5f00\u53d1\u6a21\u5f0f\u4e0b\u7684 json \u5185\u5bb9\u7c7b\u578b\u7684\u6d4b\u8bd5\uff0c\u6bcf\u79d2\u63d0\u4f9b\u7684\u8bf7\u6c42\u6570\u4e3a 1614\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/1002.jpg\" ><\/p>\n<p>Figure 10.2 \u2013 The load test for a controller-based API and JSON result<br \/>\n\u56fe 10.2 \u2013 \u57fa\u4e8e\u63a7\u5236\u5668\u7684 API \u548c JSON \u7ed3\u679c\u7684\u8d1f\u8f7d\u6d4b\u8bd5<\/p>\n<p>For the test in traditional development mode with a json content type and model validation, the number of requests served per second is 1,602:<br \/>\n\u5bf9\u4e8e\u4f20\u7edf\u5f00\u53d1\u6a21\u5f0f\u4e0b\u7684 json \u5185\u5bb9\u7c7b\u578b\u548c\u6a21\u578b\u9a8c\u8bc1\u7684\u6d4b\u8bd5\uff0c\u6bcf\u79d2\u63d0\u4f9b\u7684\u8bf7\u6c42\u6570\u4e3a 1602\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/1003.jpg\" > <\/p>\n<p>Figure 10.3 \u2013 The load test for a controller-based API and validation payload<br \/>\n\u56fe 10.3 \u2013 \u57fa\u4e8e\u63a7\u5236\u5668\u7684 API \u548c\u9a8c\u8bc1\u6709\u6548\u8d1f\u8f7d\u7684\u8d1f\u8f7d\u6d4b\u8bd5<\/p>\n<p>For the test in minimal API development mode with a plain-text content type, the number of requests served per second is 2,285:<br \/>\n\u5bf9\u4e8e\u5728\u7eaf\u6587\u672c\u5185\u5bb9\u7c7b\u578b\u7684\u6700\u5c0f API \u5f00\u53d1\u6a21\u5f0f\u4e0b\u7684\u6d4b\u8bd5\uff0c\u6bcf\u79d2\u63d0\u4f9b\u7684\u8bf7\u6c42\u6570\u4e3a 2285\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/1004.jpg\" > <\/p>\n<p>Figure 10.4 \u2013 The load test for a minimal API and plain text<br \/>\n\u56fe 10.4 \u2013 \u6700\u5c0f API \u548c\u7eaf\u6587\u672c\u7684\u8d1f\u8f7d\u6d4b\u8bd5<\/p>\n<p>For the test in minimal API development mode with a json content type, the number of requests served per second is 2,030:<br \/>\n\u5bf9\u4e8e\u5728 json \u5185\u5bb9\u7c7b\u578b\u7684\u6700\u5c0f API \u5f00\u53d1\u6a21\u5f0f\u4e0b\u7684\u6d4b\u8bd5\uff0c\u6bcf\u79d2\u63d0\u4f9b\u7684\u8bf7\u6c42\u6570\u4e3a 2030\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/1005.jpg\" > <\/p>\n<p>Figure 10.5 \u2013 The load test for a minimal API and JSON result<br \/>\n\u56fe 10.5 \u2013 \u6700\u5c0f API \u548c JSON \u7ed3\u679c\u7684\u8d1f\u8f7d\u6d4b\u8bd5<\/p>\n<p>For the test in minimal API development mode with a json content type with model validation, the number of requests served per second is 2,070:<br \/>\n\u5bf9\u4e8e\u5728\u6700\u5c0f API \u5f00\u53d1\u6a21\u5f0f\u4e0b\u4f7f\u7528\u5177\u6709\u6a21\u578b\u9a8c\u8bc1\u7684 json \u5185\u5bb9\u7c7b\u578b\u7684\u6d4b\u8bd5\uff0c\u6bcf\u79d2\u63d0\u4f9b\u7684\u8bf7\u6c42\u6570\u4e3a 2070\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/1006.jpg\" > <\/p>\n<p>Figure 10.6 \u2013 The load test for a minimal API and no validation payload<br \/>\n\u56fe 10.6 \u2013 \u6700\u5c0f API \u4e14\u65e0\u9a8c\u8bc1\u6709\u6548\u8d1f\u8f7d\u7684\u8d1f\u8f7d\u6d4b\u8bd5<\/p>\n<p>In the following image, we show a comparison of the three tested functionalities, reporting the number of requests served with the same functionality:<br \/>\n\u5728\u4e0b\u56fe\u4e2d\uff0c\u6211\u4eec\u663e\u793a\u4e86\u4e09\u4e2a\u6d4b\u8bd5\u529f\u80fd\u7684\u6bd4\u8f83\uff0c\u62a5\u544a\u4e86\u4f7f\u7528\u76f8\u540c\u529f\u80fd\u63d0\u4f9b\u7684\u8bf7\u6c42\u6570\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/1007.jpg\" > <\/p>\n<p>Figure 10.7 \u2013 The performance results<\/p>\n<p>As we might have expected, minimal APIs are much faster than controller-based web APIs.<br \/>\n\u6b63\u5982\u6211\u4eec\u6240\u6599\uff0c\u6700\u5c0f\u7684 API \u6bd4\u57fa\u4e8e\u63a7\u5236\u5668\u7684 Web API \u5feb\u5f97\u591a\u3002<\/p>\n<p>The difference is approximately 30%, and that\u2019s no small feat.<br \/>\n\u5dee\u5f02\u7ea6\u4e3a 30%\uff0c\u8fd9\u53ef\u4e0d\u662f\u4e00\u4ef6\u5c0f\u4e8b\u3002<\/p>\n<p>Obviously, as previously mentioned, minimal APIs have features missing in order to optimize performance, the most striking being data validation.<br \/>\n\u663e\u7136\uff0c\u5982\u524d\u6240\u8ff0\uff0c\u4e3a\u4e86\u4f18\u5316\u6027\u80fd\uff0c\u6700\u5c0f\u7684 API \u7f3a\u5c11\u4e00\u4e9b\u529f\u80fd\uff0c\u6700\u5f15\u4eba\u6ce8\u76ee\u7684\u662f\u6570\u636e\u9a8c\u8bc1\u3002<\/p>\n<p>In the example, the payload is very small, and the differences are not very noticeable.<br \/>\n\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u6709\u6548\u8d1f\u8f7d\u975e\u5e38\u5c0f\uff0c\u5dee\u5f02\u4e0d\u662f\u5f88\u660e\u663e\u3002<\/p>\n<p>As the payload and validation rules grow, the difference in speed between the two frameworks will only increase.<br \/>\n\u968f\u7740\u6709\u6548\u8d1f\u8f7d\u548c\u9a8c\u8bc1\u89c4\u5219\u7684\u589e\u957f\uff0c\u4e24\u4e2a\u6846\u67b6\u4e4b\u95f4\u7684\u901f\u5ea6\u5dee\u5f02\u53ea\u4f1a\u589e\u52a0\u3002<\/p>\n<p>We have seen how to measure performance with a load testing tool and then evaluate how many requests it can serve per second with the same number of machines and users connected.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u4e86\u89e3\u4e86\u5982\u4f55\u4f7f\u7528\u8d1f\u8f7d\u6d4b\u8bd5\u5de5\u5177\u6d4b\u91cf\u6027\u80fd\uff0c\u7136\u540e\u8bc4\u4f30\u5728\u8fde\u63a5\u76f8\u540c\u6570\u91cf\u7684\u673a\u5668\u548c\u7528\u6237\u7684\u60c5\u51b5\u4e0b\uff0c\u5b83\u6bcf\u79d2\u53ef\u4ee5\u5904\u7406\u591a\u5c11\u4e2a\u8bf7\u6c42\u3002<\/p>\n<p>We can also use other tools to understand how minimal APIs have had a strong positive impact on performance.<br \/>\n\u6211\u4eec\u8fd8\u53ef\u4ee5\u4f7f\u7528\u5176\u4ed6\u5de5\u5177\u6765\u4e86\u89e3\u6700\u5c11\u7684 API \u5982\u4f55\u5bf9\u6027\u80fd\u4ea7\u751f\u5f3a\u5927\u7684\u79ef\u6781\u5f71\u54cd\u3002<\/p>\n<p>Benchmarking minimal APIs with BenchmarkDotNet<br \/>\n\u4f7f\u7528 BenchmarkDotNet \u5bf9\u6700\u5c0f API \u8fdb\u884c\u57fa\u51c6\u6d4b\u8bd5<\/p>\n<p>BenchmarkDotNet is a framework that allows you to measure written code and compare performance between libraries written in different versions or compiled with different .NET frameworks.<br \/>\nBenchmarkDotNet \u662f\u4e00\u4e2a\u6846\u67b6\uff0c\u53ef\u7528\u4e8e\u6d4b\u91cf\u7f16\u5199\u7684\u4ee3\u7801\uff0c\u5e76\u6bd4\u8f83\u4ee5\u4e0d\u540c\u7248\u672c\u7f16\u5199\u6216\u4f7f\u7528\u4e0d\u540c .NET \u6846\u67b6\u7f16\u8bd1\u7684\u5e93\u4e4b\u95f4\u7684\u6027\u80fd\u3002<\/p>\n<p>This tool is used for calculating the time taken for the execution of a task, the memory used, and many other parameters.<br \/>\n\u6b64\u5de5\u5177\u7528\u4e8e\u8ba1\u7b97\u6267\u884c\u4efb\u52a1\u6240\u82b1\u8d39\u7684\u65f6\u95f4\u3001\u4f7f\u7528\u7684\u5185\u5b58\u548c\u8bb8\u591a\u5176\u4ed6\u53c2\u6570\u3002<\/p>\n<p>Our case is a very simple scenario. We want to compare the response times of two applications written to the same version of the .NET Framework.<br \/>\n\u6211\u4eec\u7684\u60c5\u51b5\u975e\u5e38\u7b80\u5355\u3002\u6211\u4eec\u60f3\u8981\u6bd4\u8f83\u5199\u5165\u540c\u4e00\u7248\u672c\u7684 .NET Framework \u7684\u4e24\u4e2a\u5e94\u7528\u7a0b\u5e8f\u7684\u54cd\u5e94\u65f6\u95f4\u3002<\/p>\n<p>How do we perform this comparison? We take an HttpClient object and start calling the methods that we have also defined for the load testing case.<br \/>\n\u6211\u4eec\u5982\u4f55\u8fdb\u884c\u8fd9\u79cd\u6bd4\u8f83\uff1f\u6211\u4eec\u83b7\u53d6\u4e00\u4e2a HttpClient \u5bf9\u8c61\uff0c\u5e76\u5f00\u59cb\u8c03\u7528\u6211\u4eec\u4e5f\u4e3a\u8d1f\u8f7d\u6d4b\u8bd5\u6848\u4f8b\u5b9a\u4e49\u7684\u65b9\u6cd5\u3002<\/p>\n<p>We will therefore obtain a comparison between two methods that exploit the same HttpClient object and recall methods with the same functionality, but one is written with the ASP.NET Web API and the traditional controllers, while the other is written using minimal APIs.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u5c06\u6bd4\u8f83\u4e24\u79cd\u5229\u7528\u76f8\u540c HttpClient \u5bf9\u8c61\u548c\u8c03\u7528\u5177\u6709\u76f8\u540c\u529f\u80fd\u7684\u65b9\u6cd5\uff0c\u4f46\u4e00\u79cd\u662f\u4f7f\u7528 ASP.NET Web API \u548c\u4f20\u7edf\u63a7\u5236\u5668\u7f16\u5199\u7684\uff0c\u800c\u53e6\u4e00\u79cd\u662f\u4f7f\u7528\u6700\u5c11\u7684 API \u7f16\u5199\u7684\u3002<\/p>\n<p>BenchmarkDotNet helps you to transform methods into benchmarks, track their performance, and share reproducible measurement experiments.<br \/>\nBenchmarkDotNet \u53ef\u5e2e\u52a9\u60a8\u5c06\u65b9\u6cd5\u8f6c\u6362\u4e3a\u57fa\u51c6\u6d4b\u8bd5\uff0c\u8ddf\u8e2a\u5176\u6027\u80fd\uff0c\u5e76\u5171\u4eab\u53ef\u91cd\u73b0\u7684\u6d4b\u91cf\u5b9e\u9a8c\u3002<\/p>\n<p>Under the hood, it performs a lot of magic that guarantees reliable and precise results thanks to the perfolizer statistical engine. BenchmarkDotNet protects you from popular benchmarking mistakes and warns you if something is wrong with your benchmark design or obtained measurements. The library has been adopted by over 6,800 projects, including .NET Runtime, and is supported by the .NET Foundation (<a href=\"https:\/\/benchmarkdotnet.org\/\">https:\/\/benchmarkdotnet.org\/<\/a>).<br \/>\n\u5728\u5f15\u64ce\u76d6\u4e0b\uff0c\u5b83\u6267\u884c\u4e86\u5f88\u591a\u9b54\u529b\uff0c\u7531\u4e8e perfolizer \u7edf\u8ba1\u5f15\u64ce\uff0c\u4fdd\u8bc1\u4e86\u53ef\u9760\u548c\u7cbe\u786e\u7684\u7ed3\u679c\u3002BenchmarkDotNet \u53ef\u4fdd\u62a4\u60a8\u514d\u53d7\u5e38\u89c1\u7684\u57fa\u51c6\u6d4b\u8bd5\u9519\u8bef\u7684\u5f71\u54cd\uff0c\u5e76\u5728\u57fa\u51c6\u6d4b\u8bd5\u8bbe\u8ba1\u6216\u83b7\u5f97\u7684\u6d4b\u91cf\u503c\u51fa\u73b0\u95ee\u9898\u65f6\u5411\u60a8\u53d1\u51fa\u8b66\u544a\u3002\u8be5\u5e93\u5df2\u88ab 6,800 \u591a\u4e2a\u9879\u76ee\u91c7\u7528\uff0c\u5305\u62ec .NET Runtime\uff0c\u5e76\u5f97\u5230 .NET Foundation \uff08<a href=\"https:\/\/benchmarkdotnet.org\/\">https:\/\/benchmarkdotnet.org\/<\/a>\uff09 \u7684\u652f\u6301\u3002<\/p>\n<p>Running BenchmarkDotNet<br \/>\n\u8fd0\u884c BenchmarkDotNet<\/p>\n<p>We will write a class that represents all the methods for calling the APIs of the two web applications. Let\u2019s make the most of the startup feature and prepare the objects we will send via POST. The function marked as [GlobalSetup] is not computed during runtime, and this helps us calculate exactly how long it takes between the call and the response from the web application:<br \/>\n\u6211\u4eec\u5c06\u7f16\u5199\u4e00\u4e2a\u7c7b\uff0c\u8be5\u7c7b\u8868\u793a\u7528\u4e8e\u8c03\u7528\u4e24\u4e2a Web \u5e94\u7528\u7a0b\u5e8f\u7684 API \u7684\u6240\u6709\u65b9\u6cd5\u3002\u8ba9\u6211\u4eec\u5145\u5206\u5229\u7528\u542f\u52a8\u529f\u80fd\u5e76\u51c6\u5907\u5c06\u901a\u8fc7 POST \u53d1\u9001\u7684\u5bf9\u8c61\u3002\u6807\u8bb0\u4e3a [GlobalSetup] \u7684\u51fd\u6570\u5728\u8fd0\u884c\u65f6\u4e0d\u4f1a\u8ba1\u7b97\uff0c\u8fd9\u6709\u52a9\u4e8e\u6211\u4eec\u51c6\u786e\u8ba1\u7b97\u8c03\u7528\u548c Web \u5e94\u7528\u7a0b\u5e8f\u7684\u54cd\u5e94\u4e4b\u95f4\u9700\u8981\u591a\u957f\u65f6\u95f4\uff1a<\/p>\n<ol>\n<li>Register all the classes in Program.cs that implement BenchmarkDotNet:<br \/>\n\u5728 Program.cs \u4e2d\u6ce8\u518c\u6240\u6709\u5b9e\u73b0 BenchmarkDotNet \u7684\u7c7b\uff1a<\/p>\n<pre><code>BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>In the preceding snippet, we have registered the current assembly that implements all the functions that will be needed to be evaluated in the performance calculation. The methods marked with [Benchmark] will be executed over and over again to establish the average execution time.<br \/>\n\u5728\u524d\u9762\u7684\u4ee3\u7801\u6bb5\u4e2d\uff0c\u6211\u4eec\u6ce8\u518c\u4e86\u5f53\u524d\u7a0b\u5e8f\u96c6\uff0c\u8be5\u7a0b\u5e8f\u96c6\u5b9e\u73b0\u4e86\u5728\u6027\u80fd\u8ba1\u7b97\u4e2d\u9700\u8981\u8bc4\u4f30\u7684\u6240\u6709\u51fd\u6570\u3002\u6807\u6709 [Benchmark] \u7684\u65b9\u6cd5\u5c06\u4e00\u904d\u53c8\u4e00\u904d\u5730\u6267\u884c\uff0c\u4ee5\u786e\u5b9a\u5e73\u5747\u6267\u884c\u65f6\u95f4\u3002<\/p>\n<ol start=\"2\">\n<li>\n<p>The application must be compiled on release and possibly within the production environment:<br \/>\n\u5e94\u7528\u7a0b\u5e8f\u5fc5\u987b\u5728\u53d1\u5e03\u65f6\u7f16\u8bd1\uff0c\u5e76\u4e14\u53ef\u80fd\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u7f16\u8bd1\uff1a<\/p>\n<pre><code>namespace DotNetBenchmarkRunners\n{\n [SimpleJob(RuntimeMoniker.Net60, baseline: true)]\n [JsonExporter]\n public class Performances\n {\n       private readonly HttpClient clientMinimal =\n       new HttpClient();\n       private readonly HttpClient\n       clientControllers = new HttpClient();\n       private readonly ValidationData data = new\n       ValidationData()\n       {\n             Id = 1,\n             Description = &quot;Performance&quot;\n       };\n       [GlobalSetup]\n       public void Setup()\n       {\n             clientMinimal.BaseAddress = new\n             Uri(&quot;https:\/\/localhost:7059&quot;);\n             clientControllers.BaseAddress = new\n             Uri(&quot;https:\/\/localhost:7149&quot;);\n       }\n\n       [Benchmark]\n       public async Task Minimal_Json_Get() =&gt;\n       await clientMinimal.GetAsync(&quot;\/jsons&quot;);\n\n       [Benchmark]\n       public async Task Controller_Json_Get() =&gt;\n       await clientControllers.GetAsync(&quot;\/jsons&quot;);\n\n       [Benchmark]\n       public async Task Minimal_TextPlain_Get()\n       =&gt; await clientMinimal.\n       GetAsync(&quot;\/text-plain&quot;);\n\n       [Benchmark]\n       public async Task\n       Controller_TextPlain_Get() =&gt; await\n       clientControllers.GetAsync(&quot;\/text-plain&quot;);\n\n       [Benchmark]\n       public async Task Minimal_Validation_Post()\n       =&gt; await clientMinimal.\n       PostAsJsonAsync(&quot;\/validations&quot;, data);\n\n       [Benchmark]\n       public async Task\n       Controller_Validation_Post() =&gt; await\n       clientControllers.\n       PostAsJsonAsync(&quot;\/validations&quot;, data);\n\n }\n\n public class ValidationData\n {\n       public int Id { get; set; }\n       public string Description { get; set; }\n }\n}<\/code><\/pre>\n<\/li>\n<li>\n<p>Before launching the benchmark application, launch the web applications:<br \/>\n\u5728\u542f\u52a8\u57fa\u51c6\u6d4b\u8bd5\u5e94\u7528\u7a0b\u5e8f\u4e4b\u524d\uff0c\u8bf7\u542f\u52a8 Web \u5e94\u7528\u7a0b\u5e8f\uff1a<\/p>\n<\/li>\n<\/ol>\n<p>Minimal API application<br \/>\n\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f<\/p>\n<pre><code>dotnet .\\MinimalAPI.Sample\\bin\\Release\\net6.0\\MinimalAPI.Sample.dll --urls=&quot;https:\/\/localhost:7059\/;http:\/\/localhost:7060\/&quot;<\/code><\/pre>\n<p>Controller-based application<br \/>\n\u57fa\u4e8e\u63a7\u5236\u5668\u7684\u5e94\u7528\u7a0b\u5e8f<\/p>\n<pre><code>dotnet .\\ControllerAPI.Sample\\bin\\Release\\net6.0\\ControllerAPI.Sample.dll --urls=https:\/\/localhost:7149\/;http:\/\/localhost:7150\/<\/code><\/pre>\n<p>By launching these applications, various steps will be performed and a summary report will be extracted with the timelines that we report here:<br \/>\n\u901a\u8fc7\u542f\u52a8\u8fd9\u4e9b\u5e94\u7528\u7a0b\u5e8f\uff0c\u5c06\u6267\u884c\u5404\u79cd\u6b65\u9aa4\uff0c\u5e76\u63d0\u53d6\u4e00\u4efd\u6458\u8981\u62a5\u544a\uff0c\u5176\u4e2d\u5305\u542b\u6211\u4eec\u5728\u6b64\u5904\u62a5\u544a\u7684\u65f6\u95f4\u8868\uff1a<\/p>\n<pre><code>dotnet .\\DotNetBenchmarkRunners\\bin\\Release\\net6.0\\DotNetBenchmarkRunners.dll --filter *<\/code><\/pre>\n<p>For each method performed, the average value or the average execution time is reported.<br \/>\n\u5bf9\u4e8e\u6267\u884c\u7684\u6bcf\u79cd\u65b9\u6cd5\uff0c\u90fd\u4f1a\u62a5\u544a\u5e73\u5747\u503c\u6216\u5e73\u5747\u6267\u884c\u65f6\u95f4\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/masteringminimalapsinaspnetcore\/10T01.jpg\"><\/p>\n<p>Table 10.1 \u2013 Benchmark HTTP requests for minimal APIs and controllers<br \/>\n\u8868 10.1 \u2013 \u9488\u5bf9\u6700\u5c0f API \u548c\u63a7\u5236\u5668\u7684 HTTP \u8bf7\u6c42\u8fdb\u884c\u57fa\u51c6\u6d4b\u8bd5<\/p>\n<p>In the following table, Error denotes how much the average value may vary due to a measurement error. Finally, the standard deviation (StdDev) indicates the deviation from the mean value. The times are given in \u03bcs and are therefore very small to measure empirically if not with instruments with that just exposed.<br \/>\n\u5728\u4e0b\u8868\u4e2d\uff0cError \u8868\u793a\u5e73\u5747\u503c\u53ef\u80fd\u56e0\u6d4b\u91cf\u8bef\u5dee\u800c\u53d8\u5316\u7684\u7a0b\u5ea6\u3002\u6700\u540e\uff0c\u6807\u51c6\u5dee \uff08StdDev\uff09 \u8868\u793a\u4e0e\u5e73\u5747\u503c\u7684\u504f\u5dee\u3002\u65f6\u95f4\u4ee5 \u03bcs \u4e3a\u5355\u4f4d\uff0c\u56e0\u6b64\u5982\u679c\u4e0d\u662f\u7528\u521a\u521a\u66dd\u5149\u7684\u4eea\u5668\uff0c\u5b9e\u8bc1\u6d4b\u91cf\u7684\u65f6\u95f4\u975e\u5e38\u5c0f\u3002<\/p>\n<p>Summary<br \/>\n\u603b\u7ed3<\/p>\n<p>In the chapter, we compared the performance of minimal APIs with that of the traditional approach by using two very different methods.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u4f7f\u7528\u4e24\u79cd\u622a\u7136\u4e0d\u540c\u7684\u65b9\u6cd5\u6bd4\u8f83\u4e86\u6700\u5c0f API \u7684\u6027\u80fd\u4e0e\u4f20\u7edf\u65b9\u6cd5\u7684\u6027\u80fd\u3002<\/p>\n<p>Minimal APIs were not designed for performance alone and evaluating them solely on that basis is a poor starting point.<br \/>\n\u6700\u5c0f\u7684 API \u4e0d\u4ec5\u4ec5\u662f\u4e3a\u4e86\u6027\u80fd\u800c\u8bbe\u8ba1\u7684\uff0c\u4ec5\u6839\u636e\u8be5\u57fa\u7840\u8bc4\u4f30\u5b83\u4eec\u662f\u4e00\u4e2a\u7cdf\u7cd5\u7684\u8d77\u70b9\u3002<\/p>\n<p>Table 10.1 indicates that there are a lot of differences between the responses of minimal APIs and that of traditional ASP.NET Web API applications.<br \/>\n\u8868 10.1 \u8868\u660e\uff0c\u6700\u5c0f API \u7684\u54cd\u5e94\u4e0e\u4f20\u7edf\u7684 ASP.NET Web API \u5e94\u7528\u7a0b\u5e8f\u7684\u54cd\u5e94\u4e4b\u95f4\u5b58\u5728\u5f88\u591a\u5dee\u5f02\u3002<\/p>\n<p>The tests were conducted on the same machine with the same resources. We found that minimal APIs performed about 30% better than the traditional framework.<br \/>\n\u6d4b\u8bd5\u662f\u5728\u540c\u4e00\u53f0\u673a\u5668\u4e0a\u4ee5\u76f8\u540c\u7684\u8d44\u6e90\u8fdb\u884c\u7684\u3002\u6211\u4eec\u53d1\u73b0\uff0cminimal API \u7684\u6027\u80fd\u6bd4\u4f20\u7edf\u6846\u67b6\u9ad8\u51fa\u7ea6 30%\u3002<\/p>\n<p>We have learned about how to measure the speed of our applications \u2013 this can be useful for understanding whether the application will hold the load and what response time it can offer. We can also leverage this on small portions of critical code.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u4e86\u89e3\u4e86\u5982\u4f55\u6d4b\u91cf\u5e94\u7528\u7a0b\u5e8f\u7684\u901f\u5ea6 \u2013 \u8fd9\u5bf9\u4e8e\u4e86\u89e3\u5e94\u7528\u7a0b\u5e8f\u662f\u5426\u80fd\u591f\u627f\u53d7\u8d1f\u8f7d\u4ee5\u53ca\u5b83\u53ef\u4ee5\u63d0\u4f9b\u591a\u5c11\u54cd\u5e94\u65f6\u95f4\u975e\u5e38\u6709\u7528\u3002\u6211\u4eec\u8fd8\u53ef\u4ee5\u5c06\u5b83\u7528\u4e8e\u5173\u952e\u4ee3\u7801\u7684\u4e00\u5c0f\u90e8\u5206\u3002<\/p>\n<p>As a final note, the applications tested were practically bare bones. The validation part that should be evaluated in the ASP.NET Web API application is almost irrelevant since there are only two fields to consider. The gap between the two frameworks increases as the number of components that have been eliminated in the minimal APIs that we have already described increases.<br \/>\n\u6700\u540e\u8981\u6ce8\u610f\u7684\u662f\uff0c\u6d4b\u8bd5\u7684\u5e94\u7528\u7a0b\u5e8f\u51e0\u4e4e\u662f\u88f8\u9732\u7684\u3002\u5e94\u5728 ASP.NET Web API \u5e94\u7528\u7a0b\u5e8f\u4e2d\u8bc4\u4f30\u7684\u9a8c\u8bc1\u90e8\u5206\u51e0\u4e4e\u65e0\u5173\u7d27\u8981\uff0c\u56e0\u4e3a\u53ea\u6709\u4e24\u4e2a\u5b57\u6bb5\u9700\u8981\u8003\u8651\u3002\u968f\u7740\u6211\u4eec\u5df2\u7ecf\u63cf\u8ff0\u7684\u6700\u5c0f API \u4e2d\u5df2\u5220\u9664\u7684\u7ec4\u4ef6\u6570\u91cf\u7684\u589e\u52a0\uff0c\u8fd9\u4e24\u4e2a\u6846\u67b6\u4e4b\u95f4\u7684\u5dee\u8ddd\u4e5f\u4f1a\u589e\u52a0\u3002<\/p>\n<p>Other Books You May Enjoy<br \/>\n\u60a8\u53ef\u80fd\u559c\u6b22\u7684\u5176\u4ed6\u4e66\u7c4d<\/p>\n<p>If you enjoyed this book, you may be interested in these other books by Packt:<br \/>\n\u5982\u679c\u60a8\u559c\u6b22\u8fd9\u672c\u4e66\uff0c\u60a8\u53ef\u80fd\u4f1a\u5bf9 Packt \u7684\u8fd9\u4e9b\u5176\u4ed6\u4e66\u7c4d\u611f\u5174\u8da3\uff1a<\/p>\n<p>Customizing ASP.NET Core 6.0 - Second Edition<br \/>\n\u5b9a\u5236 ASP.NET Core 6.0 - \u7b2c\u4e8c\u7248<\/p>\n<p>J\u00fcrgen Gutsch<br \/>\nISBN: 978-1-80323-360-4<\/p>\n<p>Explore various application configurations and providers in ASP.NET Core 6<br \/>\nEnable and work with caches to improve the performance of your application<br \/>\nUnderstand dependency injection in .NET and learn how to add third-party DI containers<br \/>\nDiscover the concept of middleware and write your middleware for ASP.NET Core apps<br \/>\nCreate various API output formats in your API-driven projects<br \/>\nGet familiar with different hosting models for your ASP.NET Core app<\/p>\n<p>ASP.NET Core 6 and Angular - Fifth Edition<br \/>\nASP.NET Core 6 \u548c Angular - \u7b2c\u4e94\u7248<\/p>\n<p>Valerio De Sanctis<br \/>\nISBN: 978-1-80323-970-5<\/p>\n<p>Use the new Visual Studio Standalone TypeScript Angular template<br \/>\nImplement and consume a Web API interface with ASP.NET Core<br \/>\nSet up an SQL database server using a local instance or a cloud datastore<br \/>\nPerform C# and TypeScript debugging using Visual Studio 2022<br \/>\nCreate TDD and BDD unit tests using xUnit, Jasmine, and Karma<br \/>\nPerform DBMS structured logging using providers such as SeriLog<br \/>\nDeploy web apps to Azure App Service using IIS, Kestrel, and NGINX<br \/>\nLearn to develop fast and flexible Web APIs using GraphQL<br \/>\nAdd real-time capabilities to Angular apps with ASP.NET Core SignalR<br \/>\nPackt is searching for authors like you<br \/>\nIf you\u2019re interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mastering Minimal APIs in ASP.NET Core Copyright \u00a9 2022 Packt Publishing In memory of my mother and father, Giovanna and Francesco, for their sacrifices and for supporting me in studying and facing new challenges every day. \u4e3a\u4e86\u7eaa\u5ff5\u6211\u7684\u7236\u6bcd Giovanna \u548c Francesco\uff0c\u611f\u8c22\u4ed6\u4eec\u7684\u727a\u7272\uff0c\u4ee5\u53ca\u652f\u6301\u6211\u5b66\u4e60\u548c\u6bcf\u5929\u9762\u5bf9\u65b0\u7684\u6311\u6218\u3002 \u2013 \u5b89\u5fb7\u91cc\u4e9a\u00b7\u571f\u91cc \u2013 Andrea Tosato To my family, friends, and colleagues, who have always believed [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-1044","post","type-post","status-publish","format-standard","hentry","category-csharp"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1044","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=1044"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1044\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1044"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1044"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1044"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}