[{"data":1,"prerenderedAt":1771},["Reactive",2],{"navigation":3,"aAII9Cz3yR":204,"tags-refit":397},[4,192,200],{"title":5,"_path":6,"children":7,"icon":191},"Blog","/posts",[8,11,14,17,20,23,26,29,32,35,38,41,44,47,50,53,56,59,62,65,68,71,74,77,80,83,86,89,92,95,98,101,104,107,110,113,116,119,122,125,128,131,134,137,140,143,146,149,152,155,158,161,164,167,170,173,176,179,182,185,188],{"title":9,"_path":10},"Testing your API with REST Client","/posts/testing-your-api-with-rest-client",{"title":12,"_path":13},"HTML templating in Xamarin","/posts/html-templating-in-xamarin",{"title":15,"_path":16},"Goodbye Azure Portal, Welcome Azure CLI","/posts/welcome-azure-cli",{"title":18,"_path":19},"Coming across Gitpod","/posts/gitpod",{"title":21,"_path":22},"Handle token retrieval while querying an API","/posts/delegating-handler",{"title":24,"_path":25},"Clean up your local git branches.","/posts/cleaning-git-branches",{"title":27,"_path":28},"Automate configuration of Teams Tab SSO with PowerShell.","/posts/teams-sso-powershell",{"title":30,"_path":31},"How to do a technology watch? - Part 1","/posts/technology-watch-part1",{"title":33,"_path":34},"How to do a technology watch? - Part 2","/posts/technology-watch-part2",{"title":36,"_path":37},"You almost no longer need Key Vault references for Azure Functions.","/posts/azure-functions-custom-configuration",{"title":39,"_path":40},"How to do a technology watch? - Part 3","/posts/technology-watch-part3",{"title":42,"_path":43},"Forget DevOps, the future is already here!","/posts/devops-future",{"title":45,"_path":46},"Week 9, 2021 - Tips I learned this week","/posts/w09-2021-tips-learned-this-week",{"title":48,"_path":49},"Week 12, 2021 - Tips I learned this week","/posts/w12-2021-tips-learned-this-week",{"title":51,"_path":52},"Week 14, 2021 - Tips I learned this week","/posts/w14-2021-tips-learned-this-week",{"title":54,"_path":55},"Once upon a time in .NET","/posts/once-upon-a-time-in-dotnet",{"title":57,"_path":58},"Install your applications with winget","/posts/winget-import",{"title":60,"_path":61},"Customize your applications when installing them with winget","/posts/winget-override",{"title":63,"_path":64},"Week 22, 2021 - Tips I learned this week","/posts/w22-2021-tips-learned-this-week",{"title":66,"_path":67},"How to connect to an Azure SQL Database from C# using Azure AD","/posts/sqlclient-active-directory-authent",{"title":69,"_path":70},"Producing packages for Windows Package Manager","/posts/wingetcreate",{"title":72,"_path":73},"4 tips about GitHub Actions environment variables and contexts","/posts/github-actions-var-and-context",{"title":75,"_path":76},"AzureWebJobsStorage, the secret you don't need in your Function App.","/posts/azure-functions-without-azurewebjobsstorage",{"title":78,"_path":79},"ASP.NET Core - Lost in configuration","/posts/lost-in-configuration",{"title":81,"_path":82},"Week 39, 2021 - Tips I learned this week","/posts/w39-2021-tips-learned-this-week",{"title":84,"_path":85},"Week 41, 2021 - Tips I learned this week","/posts/w41-2021-tips-learned-this-week",{"title":87,"_path":88},"Migrating and open-sourcing my blog","/posts/migrating-blog",{"title":90,"_path":91},"Week 45, 2021 - Tips I learned this week","/posts/w45-2021-tips-learned-this-week",{"title":93,"_path":94},"Organize your GitHub stars with Astral","/posts/astral",{"title":96,"_path":97},"Pulumi with an Azure Blob Storage backend","/posts/pulumi-azure-backend",{"title":99,"_path":100},"IaC Hot Reload with Pulumi Watch","/posts/pulumi-watch",{"title":102,"_path":103},"Week 2, 2022 - Tips I learned this week","/posts/w02-2022-tips-learned-this-week",{"title":105,"_path":106},"Week 3, 2022 - Tips I learned this week","/posts/w03-2022-tips-learned-this-week",{"title":108,"_path":109},"Week 5, 2022 - Tips I learned this week","/posts/w05-2022-tips-learned-this-week",{"title":111,"_path":112},"How to provision an Azure SQL Database with Active Directory authentication","/posts/sqldatabase-active-directory-authent",{"title":114,"_path":115},"Why will I choose Pulumi over Terraform for my next project?","/posts/pulumi-vs-terraform",{"title":117,"_path":118},"Week 19, 2022 - Tips I learned this week","/posts/w19-2022-tips-learned-this-week",{"title":120,"_path":121},"Week 20, 2022 - Tips I learned this week","/posts/w20-2022-tips-learned-this-week",{"title":123,"_path":124},"Keeping secrets secure when using API Clients","/posts/http-clients-secrets",{"title":126,"_path":127},"What made me want to be a developer?","/posts/be-a-developer",{"title":129,"_path":130},"What can we do when stuck with a programming problem?","/posts/get-unstuck",{"title":132,"_path":133},"How did I automate the setup of my developer Windows laptop?","/posts/automate-developer-machine",{"title":135,"_path":136},"Discussion about API clients","/posts/http-clients",{"title":138,"_path":139},"Week 46, 2022 - Tips I learned this week","/posts/w46-2022-tips-learned-this-week",{"title":141,"_path":142},"When Pulumi met Nuke: a .NET love story","/posts/when-pulumi-met-nuke",{"title":144,"_path":145},"A year of learning and sharing - Dev Retro 2022","/posts/2022-retro",{"title":147,"_path":148},"Perform Dynamic Execution of an npm Package","/posts/pnpm-dlx",{"title":150,"_path":151},"Manage multiple Node.js versions","/posts/pnpm-env",{"title":153,"_path":154},"Introducing the Vue.js CI/CD series","/posts/vuecicd-introduction",{"title":156,"_path":157},"Execute commands using your project dependencies","/posts/pnpm-exec",{"title":159,"_path":160},"Vue.js CI/CD: Continuous Integration","/posts/vuecicd-ci",{"title":162,"_path":163},"Who is using pnpm?","/posts/pnpm-who-is-using",{"title":165,"_path":166},"Create an Azure-Ready GitHub Repository using Pulumi","/posts/azure-ready-github-repository",{"title":168,"_path":169},"Deploying to Azure from Azure DevOps without secrets","/posts/ado-workload-identity-federation",{"title":171,"_path":172},"Effortlessly Configure GitHub Repositories for Azure Deployment via OIDC","/posts/scripting-azure-ready-github-repository",{"title":174,"_path":175},"Playing with the .NET 8 Web API template","/posts/playing-with-dotnet8",{"title":177,"_path":178},"Another year of sharing and learning - Dev Retro 2023","/posts/2023-retro",{"title":180,"_path":181},"Week 4, 2024 - Tips I learned this week","/posts/w04-2024-tips-learned-this-week",{"title":183,"_path":184},"Using dependency injection with Azure .NET SDK","/posts/azure-sdk-di",{"title":186,"_path":187},"Having Fun With IT Event Calendars","/posts/it-event-calendars",{"title":189,"_path":190},"Call your Azure AD B2C protected API with authenticated HTTP requests from your JetBrains IDE","/posts/http-clients-oauth2","i-heroicons-newspaper",{"title":193,"_path":194,"children":195,"icon":199},"Goodies","/goodies",[196],{"title":197,"_path":198},"My Git Cheat Sheet","/goodies/gitcheatsheet","i-heroicons-gift-solid",{"title":201,"_path":202,"icon":203},"About","/about","i-heroicons-user-circle-solid",[205,207,209,211,214,217,220,223,226,229,231,234,237,240,242,244,247,250,253,255,258,261,264,267,270,273,276,279,282,285,287,289,292,294,297,300,303,305,308,310,313,316,319,322,325,327,329,332,335,338,341,344,347,350,353,356,359,361,363,366,369,372,375,377,380,383,385,388,391,394],[206,206],"tooling",[208,208],"vscode",[210,210],"rest",[212,213],"http","HTTP",[215,216],"razor","Razor",[218,219],"xamarin","Xamarin",[221,222],"templating","Templating",[224,225],"azure-cli","Azure CLI",[227,228],"azure","Azure",[230,230],"shell",[232,233],"github","GitHub",[235,236],"asp-net-core","ASP.NET Core",[238,239],"net",".NET",[241,241],"git",[243,243],"nushell",[245,246],"microsoft-teams","Microsoft Teams",[248,249],"powershell","PowerShell",[251,252],"azure-active-directory","Azure Active Directory",[254,254],"learning",[256,257],"azure-functions","Azure Functions",[259,260],"azure-key-vault","Azure Key Vault",[262,263],"configuration","Configuration",[265,266],"devops","DevOps",[268,269],"it","IT",[271,272],"tips-learned-this-week","tips learned this week",[274,275],"windows-terminal","Windows Terminal",[277,278],"azure-pipelines","Azure Pipelines",[280,281],"application-insights","Application Insights",[283,284],"azure-iot","Azure IoT",[286,286],"records",[288,288],"refit",[290,291],"development-box-setup","development box setup",[293,293],"winget",[295,296],"package-manager","package manager",[298,299],"azure-sql-database","Azure SQL Database",[301,302],"azure-sdk","Azure SDK",[304,304],"wingetcreate",[306,307],"github-actions","GitHub Actions",[309,309],"jq",[311,312],"pulumi","Pulumi",[314,315],"iac","IaC",[317,318],"azure-storage","Azure Storage",[320,321],"azure-signalr","Azure SignalR",[323,324],"visio","Visio",[326,326],"csharp",[328,328],"jest",[330,331],"statiq","Statiq",[333,334],"open-source","open source",[336,337],"visual-studio","Visual Studio",[339,340],"vue-js","Vue.js",[342,343],"azure-devops","Azure DevOps",[345,346],"vite","Vite",[348,349],"code-analysis","Code analysis",[351,352],"diagram","Diagram",[354,355],"terraform","Terraform",[357,358],"typescript","TypeScript",[360,360],"thoughts",[362,362],"pnpm",[364,365],"nuke","Nuke",[367,368],"pipelines","Pipelines",[370,371],"cicd","CI/CD",[373,374],"openid-connect","OpenID Connect",[376,376],"security",[378,379],"github-cli","GitHub CLI",[381,382],"microsoft-entra-id","Microsoft Entra ID",[384,384],"advent",[386,387],"finops","FinOps",[389,390],"anglesharp","AngleSharp",[392,393],"oauth2","OAuth2",[395,396],"azure-ad-b2c","Azure AD B2C",[398],{"_path":55,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":54,"description":402,"lead":403,"date":404,"image":405,"badge":407,"tags":409,"body":410,"_type":1766,"_id":1767,"_source":1768,"_file":1769,"_extension":1770},"posts",false,"","In this article, I want to talk about a few things in .NET such as HTTP requests with an Http Client, HTTP message handlers, records... For the theoretical aspect of these topics, I think the official documentation on docs.microsoft.com and many blog articles already explain them very well, better than I could ever do. But what I am interested in here is to talk about these topics through a case study.","A story about records, HTTP message handlers, HTTP client extensions...","2021-04-24T00:00:00.000Z",{"src":406},"/images/western_1.jpg",{"label":408},"Development",[236,213,286,210,288],{"type":411,"children":412,"toc":1752},"root",[413,420,427,461,560,566,579,593,688,708,1158,1184,1196,1209,1218,1267,1276,1289,1314,1323,1328,1335,1363,1372,1386,1391,1400,1434,1442,1477,1485,1497,1502,1513,1519,1548,1557,1569,1583,1588,1608,1617,1628,1642,1665,1674,1694,1703,1715,1724,1735,1741,1746],{"type":414,"tag":415,"props":416,"children":417},"element","p",{},[418],{"type":419,"value":402},"text",{"type":414,"tag":421,"props":422,"children":424},"h2",{"id":423},"introducing-the-case-study",[425],{"type":419,"value":426},"Introducing the case study",{"type":414,"tag":415,"props":428,"children":429},{},[430,432,446,448,459],{"type":419,"value":431},"I wrote a very basic ASP.NET Core API ",{"type":414,"tag":433,"props":434,"children":438},"a",{"href":435,"rel":436},"https://github.com/TechWatching/MyLotrApi/tree/a70b6f91fcbcc30a3c3a3616799e3e85817b7906",[437],"nofollow",[439],{"type":414,"tag":440,"props":441,"children":443},"code",{"className":442},[],[444],{"type":419,"value":445},"MyLotrApi",{"type":419,"value":447}," that exposes some data from the \"Lord of the Rings\" universe. This API calls another existing API ",{"type":414,"tag":433,"props":449,"children":452},{"href":450,"rel":451},"https://the-one-api.dev/",[437],[453],{"type":414,"tag":440,"props":454,"children":456},{"className":455},[],[457],{"type":419,"value":458},"The One API",{"type":419,"value":460}," to retrieve this data. The code is quite simple:",{"type":414,"tag":462,"props":463,"children":464},"ul",{},[465,504,547],{"type":414,"tag":466,"props":467,"children":468},"li",{},[469,471,477,479],{"type":419,"value":470},"a controller ",{"type":414,"tag":440,"props":472,"children":474},{"className":473},[],[475],{"type":419,"value":476},"LotrController",{"type":419,"value":478}," with 2 routes\n",{"type":414,"tag":462,"props":480,"children":481},{},[482,493],{"type":414,"tag":466,"props":483,"children":484},{},[485,491],{"type":414,"tag":440,"props":486,"children":488},{"className":487},[],[489],{"type":419,"value":490},"GET /popularmovies",{"type":419,"value":492}," that returns the movies in the \"Lord of the Rings\" universe with a rotten tomatoes score above 80",{"type":414,"tag":466,"props":494,"children":495},{},[496,502],{"type":414,"tag":440,"props":497,"children":499},{"className":498},[],[500],{"type":419,"value":501},"GET /famousorcs",{"type":419,"value":503}," that returns orc characters from the \"Lord of the Rings\" universe",{"type":414,"tag":466,"props":505,"children":506},{},[507,509,515,517],{"type":419,"value":508},"a service ",{"type":414,"tag":440,"props":510,"children":512},{"className":511},[],[513],{"type":419,"value":514},"TheOneApiService",{"type":419,"value":516}," that\n",{"type":414,"tag":462,"props":518,"children":519},{},[520,530,542],{"type":414,"tag":466,"props":521,"children":522},{},[523,525],{"type":419,"value":524},"makes the HTTP calls to the API ",{"type":414,"tag":440,"props":526,"children":528},{"className":527},[],[529],{"type":419,"value":458},{"type":414,"tag":466,"props":531,"children":532},{},[533,535,540],{"type":419,"value":534},"has one method for each endpoint of the API ",{"type":414,"tag":440,"props":536,"children":538},{"className":537},[],[539],{"type":419,"value":458},{"type":419,"value":541}," that is used",{"type":414,"tag":466,"props":543,"children":544},{},[545],{"type":419,"value":546},"uses NewtonSoft for deserializing responses",{"type":414,"tag":466,"props":548,"children":549},{},[550,552,558],{"type":419,"value":551},"a ",{"type":414,"tag":440,"props":553,"children":555},{"className":554},[],[556],{"type":419,"value":557},"Models",{"type":419,"value":559}," class that contains the different data models used by the API",{"type":414,"tag":421,"props":561,"children":563},{"id":562},"about-using-records",[564],{"type":419,"value":565},"About using records",{"type":414,"tag":415,"props":567,"children":568},{},[569,571,577],{"type":419,"value":570},"Instead of using basic C# classes for the models in this API, I used ",{"type":414,"tag":433,"props":572,"children":575},{"href":573,"rel":574},"https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#record-types",[437],[576],{"type":419,"value":286},{"type":419,"value":578},". Many people are talking about records nowadays because it is one of the latest trendy features of C# 9. Unfortunately, that makes other people think records are just another syntactic sugar added to C# that they do not need to use in their code. Yet, there are a lot of benefits in using records.",{"type":414,"tag":415,"props":580,"children":581},{},[582,584,591],{"type":419,"value":583},"In my sample, I declared my models with the ",{"type":414,"tag":433,"props":585,"children":588},{"href":586,"rel":587},"https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record#positional-syntax-for-property-definition",[437],[589],{"type":419,"value":590},"positional syntax for property definition",{"type":419,"value":592}," which is very concise. Conciseness might not be something important for you but for me, it means fewer lines of code to write and to maintain and more clearness.",{"type":414,"tag":594,"props":595,"children":598},"pre",{"className":596,"code":597,"language":326,"meta":401,"style":401},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","public record Movie(string Name, int RuntimeInMinutes, int BudgetInMillions, float RottenTomatesScore);\n",[599],{"type":414,"tag":440,"props":600,"children":601},{"__ignoreMap":401},[602],{"type":414,"tag":603,"props":604,"children":607},"span",{"class":605,"line":606},"line",1,[608,614,620,625,631,636,641,646,651,656,660,664,669,673,678,683],{"type":414,"tag":603,"props":609,"children":611},{"style":610},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[612],{"type":419,"value":613},"public",{"type":414,"tag":603,"props":615,"children":617},{"style":616},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[618],{"type":419,"value":619}," record",{"type":414,"tag":603,"props":621,"children":622},{"style":616},[623],{"type":419,"value":624}," Movie",{"type":414,"tag":603,"props":626,"children":628},{"style":627},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[629],{"type":419,"value":630},"(",{"type":414,"tag":603,"props":632,"children":633},{"style":627},[634],{"type":419,"value":635},"string",{"type":414,"tag":603,"props":637,"children":638},{"style":616},[639],{"type":419,"value":640}," Name",{"type":414,"tag":603,"props":642,"children":643},{"style":627},[644],{"type":419,"value":645},",",{"type":414,"tag":603,"props":647,"children":648},{"style":627},[649],{"type":419,"value":650}," int",{"type":414,"tag":603,"props":652,"children":653},{"style":616},[654],{"type":419,"value":655}," RuntimeInMinutes",{"type":414,"tag":603,"props":657,"children":658},{"style":627},[659],{"type":419,"value":645},{"type":414,"tag":603,"props":661,"children":662},{"style":627},[663],{"type":419,"value":650},{"type":414,"tag":603,"props":665,"children":666},{"style":616},[667],{"type":419,"value":668}," BudgetInMillions",{"type":414,"tag":603,"props":670,"children":671},{"style":627},[672],{"type":419,"value":645},{"type":414,"tag":603,"props":674,"children":675},{"style":627},[676],{"type":419,"value":677}," float",{"type":414,"tag":603,"props":679,"children":680},{"style":616},[681],{"type":419,"value":682}," RottenTomatesScore",{"type":414,"tag":603,"props":684,"children":685},{"style":627},[686],{"type":419,"value":687},");\n",{"type":414,"tag":415,"props":689,"children":690},{},[691,693,699,701,706],{"type":419,"value":692},"You can notice that I put my records in one place (the ",{"type":414,"tag":440,"props":694,"children":696},{"className":695},[],[697],{"type":419,"value":698},"Models.cs",{"type":419,"value":700}," file), partly because for this example it was faster and simpler than creating a file for each model. But when you think carefully about it, it is not such a bad thing: in one look you have a pretty good view of the different models the code is using without having to browse different files. Of course, in a real project with a lot of code, I would have grouped my models by business concern and separated these different concerns in different files with more expressive names than ",{"type":414,"tag":440,"props":702,"children":704},{"className":703},[],[705],{"type":419,"value":557},{"type":419,"value":707},". But I think the convention of using one file by class is not completely relevant in the case of records.",{"type":414,"tag":594,"props":709,"children":711},{"className":596,"code":710,"language":326,"meta":401,"style":401},"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace MyLotrApi\n{\n    public record Movie(string Name, int RuntimeInMinutes, int BudgetInMillions, float RottenTomatesScore);\n    \n    public record MovieResponse(IList\u003CMovie> Docs, int Total);\n\n    public record Character(string Name, string Realm);\n\n    public record CharacterResponse(IList\u003CCharacter> Docs, int Total);\n}\n",[712],{"type":414,"tag":440,"props":713,"children":714},{"__ignoreMap":401},[715,735,770,795,820,854,864,878,887,956,965,1028,1036,1083,1091,1149],{"type":414,"tag":603,"props":716,"children":717},{"class":605,"line":606},[718,724,730],{"type":414,"tag":603,"props":719,"children":721},{"style":720},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[722],{"type":419,"value":723},"using",{"type":414,"tag":603,"props":725,"children":727},{"style":726},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[728],{"type":419,"value":729}," System",{"type":414,"tag":603,"props":731,"children":732},{"style":627},[733],{"type":419,"value":734},";\n",{"type":414,"tag":603,"props":736,"children":738},{"class":605,"line":737},2,[739,743,747,752,757,761,766],{"type":414,"tag":603,"props":740,"children":741},{"style":720},[742],{"type":419,"value":723},{"type":414,"tag":603,"props":744,"children":745},{"style":726},[746],{"type":419,"value":729},{"type":414,"tag":603,"props":748,"children":749},{"style":627},[750],{"type":419,"value":751},".",{"type":414,"tag":603,"props":753,"children":754},{"style":726},[755],{"type":419,"value":756},"Collections",{"type":414,"tag":603,"props":758,"children":759},{"style":627},[760],{"type":419,"value":751},{"type":414,"tag":603,"props":762,"children":763},{"style":726},[764],{"type":419,"value":765},"Generic",{"type":414,"tag":603,"props":767,"children":768},{"style":627},[769],{"type":419,"value":734},{"type":414,"tag":603,"props":771,"children":773},{"class":605,"line":772},3,[774,778,782,786,791],{"type":414,"tag":603,"props":775,"children":776},{"style":720},[777],{"type":419,"value":723},{"type":414,"tag":603,"props":779,"children":780},{"style":726},[781],{"type":419,"value":729},{"type":414,"tag":603,"props":783,"children":784},{"style":627},[785],{"type":419,"value":751},{"type":414,"tag":603,"props":787,"children":788},{"style":726},[789],{"type":419,"value":790},"Linq",{"type":414,"tag":603,"props":792,"children":793},{"style":627},[794],{"type":419,"value":734},{"type":414,"tag":603,"props":796,"children":798},{"class":605,"line":797},4,[799,803,807,811,816],{"type":414,"tag":603,"props":800,"children":801},{"style":720},[802],{"type":419,"value":723},{"type":414,"tag":603,"props":804,"children":805},{"style":726},[806],{"type":419,"value":729},{"type":414,"tag":603,"props":808,"children":809},{"style":627},[810],{"type":419,"value":751},{"type":414,"tag":603,"props":812,"children":813},{"style":726},[814],{"type":419,"value":815},"Text",{"type":414,"tag":603,"props":817,"children":818},{"style":627},[819],{"type":419,"value":734},{"type":414,"tag":603,"props":821,"children":823},{"class":605,"line":822},5,[824,828,832,836,841,845,850],{"type":414,"tag":603,"props":825,"children":826},{"style":720},[827],{"type":419,"value":723},{"type":414,"tag":603,"props":829,"children":830},{"style":726},[831],{"type":419,"value":729},{"type":414,"tag":603,"props":833,"children":834},{"style":627},[835],{"type":419,"value":751},{"type":414,"tag":603,"props":837,"children":838},{"style":726},[839],{"type":419,"value":840},"Threading",{"type":414,"tag":603,"props":842,"children":843},{"style":627},[844],{"type":419,"value":751},{"type":414,"tag":603,"props":846,"children":847},{"style":726},[848],{"type":419,"value":849},"Tasks",{"type":414,"tag":603,"props":851,"children":852},{"style":627},[853],{"type":419,"value":734},{"type":414,"tag":603,"props":855,"children":857},{"class":605,"line":856},6,[858],{"type":414,"tag":603,"props":859,"children":861},{"emptyLinePlaceholder":860},true,[862],{"type":419,"value":863},"\n",{"type":414,"tag":603,"props":865,"children":867},{"class":605,"line":866},7,[868,873],{"type":414,"tag":603,"props":869,"children":870},{"style":616},[871],{"type":419,"value":872},"namespace",{"type":414,"tag":603,"props":874,"children":875},{"style":726},[876],{"type":419,"value":877}," MyLotrApi\n",{"type":414,"tag":603,"props":879,"children":881},{"class":605,"line":880},8,[882],{"type":414,"tag":603,"props":883,"children":884},{"style":627},[885],{"type":419,"value":886},"{\n",{"type":414,"tag":603,"props":888,"children":890},{"class":605,"line":889},9,[891,896,900,904,908,912,916,920,924,928,932,936,940,944,948,952],{"type":414,"tag":603,"props":892,"children":893},{"style":610},[894],{"type":419,"value":895},"    public",{"type":414,"tag":603,"props":897,"children":898},{"style":616},[899],{"type":419,"value":619},{"type":414,"tag":603,"props":901,"children":902},{"style":616},[903],{"type":419,"value":624},{"type":414,"tag":603,"props":905,"children":906},{"style":627},[907],{"type":419,"value":630},{"type":414,"tag":603,"props":909,"children":910},{"style":627},[911],{"type":419,"value":635},{"type":414,"tag":603,"props":913,"children":914},{"style":616},[915],{"type":419,"value":640},{"type":414,"tag":603,"props":917,"children":918},{"style":627},[919],{"type":419,"value":645},{"type":414,"tag":603,"props":921,"children":922},{"style":627},[923],{"type":419,"value":650},{"type":414,"tag":603,"props":925,"children":926},{"style":616},[927],{"type":419,"value":655},{"type":414,"tag":603,"props":929,"children":930},{"style":627},[931],{"type":419,"value":645},{"type":414,"tag":603,"props":933,"children":934},{"style":627},[935],{"type":419,"value":650},{"type":414,"tag":603,"props":937,"children":938},{"style":616},[939],{"type":419,"value":668},{"type":414,"tag":603,"props":941,"children":942},{"style":627},[943],{"type":419,"value":645},{"type":414,"tag":603,"props":945,"children":946},{"style":627},[947],{"type":419,"value":677},{"type":414,"tag":603,"props":949,"children":950},{"style":616},[951],{"type":419,"value":682},{"type":414,"tag":603,"props":953,"children":954},{"style":627},[955],{"type":419,"value":687},{"type":414,"tag":603,"props":957,"children":959},{"class":605,"line":958},10,[960],{"type":414,"tag":603,"props":961,"children":962},{"style":726},[963],{"type":419,"value":964},"    \n",{"type":414,"tag":603,"props":966,"children":968},{"class":605,"line":967},11,[969,973,977,982,986,991,996,1001,1006,1011,1015,1019,1024],{"type":414,"tag":603,"props":970,"children":971},{"style":610},[972],{"type":419,"value":895},{"type":414,"tag":603,"props":974,"children":975},{"style":616},[976],{"type":419,"value":619},{"type":414,"tag":603,"props":978,"children":979},{"style":616},[980],{"type":419,"value":981}," MovieResponse",{"type":414,"tag":603,"props":983,"children":984},{"style":627},[985],{"type":419,"value":630},{"type":414,"tag":603,"props":987,"children":988},{"style":616},[989],{"type":419,"value":990},"IList",{"type":414,"tag":603,"props":992,"children":993},{"style":627},[994],{"type":419,"value":995},"\u003C",{"type":414,"tag":603,"props":997,"children":998},{"style":616},[999],{"type":419,"value":1000},"Movie",{"type":414,"tag":603,"props":1002,"children":1003},{"style":627},[1004],{"type":419,"value":1005},">",{"type":414,"tag":603,"props":1007,"children":1008},{"style":616},[1009],{"type":419,"value":1010}," Docs",{"type":414,"tag":603,"props":1012,"children":1013},{"style":627},[1014],{"type":419,"value":645},{"type":414,"tag":603,"props":1016,"children":1017},{"style":627},[1018],{"type":419,"value":650},{"type":414,"tag":603,"props":1020,"children":1021},{"style":616},[1022],{"type":419,"value":1023}," Total",{"type":414,"tag":603,"props":1025,"children":1026},{"style":627},[1027],{"type":419,"value":687},{"type":414,"tag":603,"props":1029,"children":1031},{"class":605,"line":1030},12,[1032],{"type":414,"tag":603,"props":1033,"children":1034},{"emptyLinePlaceholder":860},[1035],{"type":419,"value":863},{"type":414,"tag":603,"props":1037,"children":1039},{"class":605,"line":1038},13,[1040,1044,1048,1053,1057,1061,1065,1069,1074,1079],{"type":414,"tag":603,"props":1041,"children":1042},{"style":610},[1043],{"type":419,"value":895},{"type":414,"tag":603,"props":1045,"children":1046},{"style":616},[1047],{"type":419,"value":619},{"type":414,"tag":603,"props":1049,"children":1050},{"style":616},[1051],{"type":419,"value":1052}," Character",{"type":414,"tag":603,"props":1054,"children":1055},{"style":627},[1056],{"type":419,"value":630},{"type":414,"tag":603,"props":1058,"children":1059},{"style":627},[1060],{"type":419,"value":635},{"type":414,"tag":603,"props":1062,"children":1063},{"style":616},[1064],{"type":419,"value":640},{"type":414,"tag":603,"props":1066,"children":1067},{"style":627},[1068],{"type":419,"value":645},{"type":414,"tag":603,"props":1070,"children":1071},{"style":627},[1072],{"type":419,"value":1073}," string",{"type":414,"tag":603,"props":1075,"children":1076},{"style":616},[1077],{"type":419,"value":1078}," Realm",{"type":414,"tag":603,"props":1080,"children":1081},{"style":627},[1082],{"type":419,"value":687},{"type":414,"tag":603,"props":1084,"children":1086},{"class":605,"line":1085},14,[1087],{"type":414,"tag":603,"props":1088,"children":1089},{"emptyLinePlaceholder":860},[1090],{"type":419,"value":863},{"type":414,"tag":603,"props":1092,"children":1094},{"class":605,"line":1093},15,[1095,1099,1103,1108,1112,1116,1120,1125,1129,1133,1137,1141,1145],{"type":414,"tag":603,"props":1096,"children":1097},{"style":610},[1098],{"type":419,"value":895},{"type":414,"tag":603,"props":1100,"children":1101},{"style":616},[1102],{"type":419,"value":619},{"type":414,"tag":603,"props":1104,"children":1105},{"style":616},[1106],{"type":419,"value":1107}," CharacterResponse",{"type":414,"tag":603,"props":1109,"children":1110},{"style":627},[1111],{"type":419,"value":630},{"type":414,"tag":603,"props":1113,"children":1114},{"style":616},[1115],{"type":419,"value":990},{"type":414,"tag":603,"props":1117,"children":1118},{"style":627},[1119],{"type":419,"value":995},{"type":414,"tag":603,"props":1121,"children":1122},{"style":616},[1123],{"type":419,"value":1124},"Character",{"type":414,"tag":603,"props":1126,"children":1127},{"style":627},[1128],{"type":419,"value":1005},{"type":414,"tag":603,"props":1130,"children":1131},{"style":616},[1132],{"type":419,"value":1010},{"type":414,"tag":603,"props":1134,"children":1135},{"style":627},[1136],{"type":419,"value":645},{"type":414,"tag":603,"props":1138,"children":1139},{"style":627},[1140],{"type":419,"value":650},{"type":414,"tag":603,"props":1142,"children":1143},{"style":616},[1144],{"type":419,"value":1023},{"type":414,"tag":603,"props":1146,"children":1147},{"style":627},[1148],{"type":419,"value":687},{"type":414,"tag":603,"props":1150,"children":1152},{"class":605,"line":1151},16,[1153],{"type":414,"tag":603,"props":1154,"children":1155},{"style":627},[1156],{"type":419,"value":1157},"}\n",{"type":414,"tag":415,"props":1159,"children":1160},{},[1161,1163,1168,1170,1175,1177,1182],{"type":419,"value":1162},"Another interesting feature of records is that it is easy to declare immutable data models with them. That is exactly what I did here by declaring my data models as immutable records for the objects returned by ",{"type":414,"tag":440,"props":1164,"children":1166},{"className":1165},[],[1167],{"type":419,"value":458},{"type":419,"value":1169},". Indeed it makes perfect sense in this context where my API queries ",{"type":414,"tag":440,"props":1171,"children":1173},{"className":1172},[],[1174],{"type":419,"value":458},{"type":419,"value":1176},", eventually filters the objects returned depending on what it is looking to expose and returns them without modifying their content. In no way the code should modify the data retrieved from ",{"type":414,"tag":440,"props":1178,"children":1180},{"className":1179},[],[1181],{"type":419,"value":458},{"type":419,"value":1183}," and immutable records can easily guarantee that: if the code tries to change something it won't compile.",{"type":414,"tag":415,"props":1185,"children":1186},{},[1187],{"type":414,"tag":1188,"props":1189,"children":1195},"img",{"alt":1190,"className":1191,"src":1194},"Code showing immutability of records.",[1192,1193],"rounded-lg","mx-auto","/posts/images/onceuponatime_records_1.png",[],{"type":414,"tag":415,"props":1197,"children":1198},{},[1199,1201,1207],{"type":419,"value":1200},"There is one more reason why using records in this API is a good idea: value equality. When writing unit tests I often have the case where I want to assert that an object (or a collection of objects) returned by the method under test is the same that the object(s) I was expected. However, doing an ",{"type":414,"tag":440,"props":1202,"children":1204},{"className":1203},[],[1205],{"type":419,"value":1206},"Assert.AreEqual",{"type":419,"value":1208}," on two variables of a reference type does not work because by default they are only equal if they refer to the same object.",{"type":414,"tag":415,"props":1210,"children":1211},{},[1212],{"type":414,"tag":1188,"props":1213,"children":1217},{"alt":1214,"className":1215,"src":1216},"Code showing classes not having value equality",[1192,1193],"onceuponatime_records_3.png",[],{"type":414,"tag":415,"props":1219,"children":1220},{},[1221,1223,1234,1236,1242,1244,1250,1252,1258,1260,1265],{"type":419,"value":1222},"As a workaround, you can compare the properties of your two variables (if they are value type properties) or you can use the library ",{"type":414,"tag":433,"props":1224,"children":1227},{"href":1225,"rel":1226},"https://github.com/fluentassertions/fluentassertions",[437],[1228],{"type":414,"tag":440,"props":1229,"children":1231},{"className":1230},[],[1232],{"type":419,"value":1233},"FluentAssertions",{"type":419,"value":1235}," for your asserts. But the real solution to make your asserts work is to implement ",{"type":414,"tag":440,"props":1237,"children":1239},{"className":1238},[],[1240],{"type":419,"value":1241},"IEquatable\u003CT>",{"type":419,"value":1243},", and overrides ",{"type":414,"tag":440,"props":1245,"children":1247},{"className":1246},[],[1248],{"type":419,"value":1249},"Object.Equals(Object)",{"type":419,"value":1251}," and ",{"type":414,"tag":440,"props":1253,"children":1255},{"className":1254},[],[1256],{"type":419,"value":1257},"Object.GetHashCode()",{"type":419,"value":1259}," on the classes you want to compare. It's the solution I often use, it's a bit cumbersome because it makes you write a lot of boilerplate for your data models but it helps you a lot with your unit tests assertions. The good news, if you are using records is that all this code is already done for you, you have value equality by default so an ",{"type":414,"tag":440,"props":1261,"children":1263},{"className":1262},[],[1264],{"type":419,"value":1206},{"type":419,"value":1266}," between two variables of a record type will work if all the property and field values match.",{"type":414,"tag":415,"props":1268,"children":1269},{},[1270],{"type":414,"tag":1188,"props":1271,"children":1275},{"alt":1272,"className":1273,"src":1274},"Code showing value equality of records",[1192,1193],"/posts/images/onceuponatime_records_2.png",[],{"type":414,"tag":421,"props":1277,"children":1279},{"id":1278},"what-can-be-improved-in-theoneapiservice",[1280,1282,1287],{"type":419,"value":1281},"What can be improved in ",{"type":414,"tag":440,"props":1283,"children":1285},{"className":1284},[],[1286],{"type":419,"value":514},{"type":419,"value":1288},"?",{"type":414,"tag":415,"props":1290,"children":1291},{},[1292,1294,1304,1306,1312],{"type":419,"value":1293},"Enough talking about records, let's have a closer look at ",{"type":414,"tag":433,"props":1295,"children":1298},{"href":1296,"rel":1297},"https://github.com/TechWatching/MyLotrApi/blob/a70b6f91fcbcc30a3c3a3616799e3e85817b7906/src/MyLotrApi/TheOneApiService.cs",[437],[1299],{"type":414,"tag":440,"props":1300,"children":1302},{"className":1301},[],[1303],{"type":419,"value":514},{"type":419,"value":1305}," and see what we can improve. At first sight, the code looks fine, just 2 methods that use an ",{"type":414,"tag":440,"props":1307,"children":1309},{"className":1308},[],[1310],{"type":419,"value":1311},"HttpClient",{"type":419,"value":1313}," to make a get request, ensure that the response is ok (throw an exception otherwise), retrieve the response content as a string, and deserialize it into their corresponding types with NewtonSoft. Basic code that we can often see.",{"type":414,"tag":415,"props":1315,"children":1316},{},[1317],{"type":414,"tag":1188,"props":1318,"children":1322},{"alt":1319,"className":1320,"src":1321},"The OneApiService first code version.",[1192,1193],"/posts/images/onceuponatime_theoneapiservice_1.png",[],{"type":414,"tag":415,"props":1324,"children":1325},{},[1326],{"type":419,"value":1327},"Yet, it seems that there is a bit of code duplication between the methods, not a problem as we only have two methods but it can quickly become one if we add other methods. So what can we do about that?",{"type":414,"tag":1329,"props":1330,"children":1332},"h3",{"id":1331},"adding-a-private-method-that-factorizes-the-code",[1333],{"type":419,"value":1334},"Adding a private method that factorizes the code",{"type":414,"tag":415,"props":1336,"children":1337},{},[1338,1340,1346,1348,1354,1355,1361],{"type":419,"value":1339},"This solution is something I often see: people wrap the common logic between their methods in a private method that gets called by the others. So we end up having a generic ",{"type":414,"tag":440,"props":1341,"children":1343},{"className":1342},[],[1344],{"type":419,"value":1345},"Send",{"type":419,"value":1347}," that does all the job (request, response handling, deserialization ...), and our 2 methods ",{"type":414,"tag":440,"props":1349,"children":1351},{"className":1350},[],[1352],{"type":419,"value":1353},"GetMovies",{"type":419,"value":1251},{"type":414,"tag":440,"props":1356,"children":1358},{"className":1357},[],[1359],{"type":419,"value":1360},"GetCharacters",{"type":419,"value":1362}," that have become quite trivial.",{"type":414,"tag":415,"props":1364,"children":1365},{},[1366],{"type":414,"tag":1188,"props":1367,"children":1371},{"alt":1368,"className":1369,"src":1370},"The OneApiService factorizing code in a private method.",[1192,1193],"/posts/images/onceuponatime_theoneapiservice_2.png",[],{"type":414,"tag":415,"props":1373,"children":1374},{},[1375,1377,1384],{"type":419,"value":1376},"(this code can be found ",{"type":414,"tag":433,"props":1378,"children":1381},{"href":1379,"rel":1380},"https://github.com/TechWatching/MyLotrApi/blob/3e5561cb71678e432e970edcac6509581e3aaecd/src/MyLotrApi/TheOneApiService.cs",[437],[1382],{"type":419,"value":1383},"here",{"type":419,"value":1385},")",{"type":414,"tag":415,"props":1387,"children":1388},{},[1389],{"type":419,"value":1390},"Well, the code is fine, nothing to say about that but I don't like this solution at all 😁. And here is why:",{"type":414,"tag":1392,"props":1393,"children":1394},"ol",{},[1395],{"type":414,"tag":466,"props":1396,"children":1397},{},[1398],{"type":419,"value":1399},"It adds complexity and makes the code more difficult to read",{"type":414,"tag":415,"props":1401,"children":1402},{},[1403,1405,1410,1412,1417,1419,1424,1426,1432],{"type":419,"value":1404},"When I look at the ",{"type":414,"tag":440,"props":1406,"children":1408},{"className":1407},[],[1409],{"type":419,"value":1353},{"type":419,"value":1411}," or ",{"type":414,"tag":440,"props":1413,"children":1415},{"className":1414},[],[1416],{"type":419,"value":1360},{"type":419,"value":1418}," I don't know exactly what they do, the business logic is hidden in the private ",{"type":414,"tag":440,"props":1420,"children":1422},{"className":1421},[],[1423],{"type":419,"value":1345},{"type":419,"value":1425}," method. This private method in itself is more complicated than the previous duplicated code because it has to handle different cases, like the fact that the request can be a POST or a PUT, hence the use of an ",{"type":414,"tag":440,"props":1427,"children":1429},{"className":1428},[],[1430],{"type":419,"value":1431},"HttpRequestMessage",{"type":419,"value":1433}," with the potential content to send in the request. In fact, it's quite clear that this method does too many different things.",{"type":414,"tag":1392,"props":1435,"children":1436},{"start":737},[1437],{"type":414,"tag":466,"props":1438,"children":1439},{},[1440],{"type":419,"value":1441},"It abstracts the use of the HttpClient",{"type":414,"tag":415,"props":1443,"children":1444},{},[1445,1447,1453,1455,1460,1462,1468,1470,1475],{"type":419,"value":1446},"I have nothing against abstractions, on the contrary, but I think they should add value and not completely hide what we are using under the hood. And here we are concealing the fact we are using an HttpClient, that does a ",{"type":414,"tag":440,"props":1448,"children":1450},{"className":1449},[],[1451],{"type":419,"value":1452},"GET",{"type":419,"value":1454}," request on the \"character\" route for instance. Of course, we can find part of that information in the parameters provided to the ",{"type":414,"tag":440,"props":1456,"children":1458},{"className":1457},[],[1459],{"type":419,"value":1345},{"type":419,"value":1461}," method but it is not as clear as calling the ",{"type":414,"tag":440,"props":1463,"children":1465},{"className":1464},[],[1466],{"type":419,"value":1467},"Get",{"type":419,"value":1469}," method of the ",{"type":414,"tag":440,"props":1471,"children":1473},{"className":1472},[],[1474],{"type":419,"value":1311},{"type":419,"value":1476}," as we are used to doing. Using an HttpClient already abstracts the complexity of creating an Http request and sending it, we do not need to trade this abstraction off for another less understandable abstraction.",{"type":414,"tag":1392,"props":1478,"children":1479},{"start":772},[1480],{"type":414,"tag":466,"props":1481,"children":1482},{},[1483],{"type":419,"value":1484},"It makes the code hard to maintain",{"type":414,"tag":415,"props":1486,"children":1487},{},[1488,1490,1495],{"type":419,"value":1489},"Because all the main methods of this service call this private method, it will be difficult to change something in it without breaking something else. If someday we need to do something specific linked to the HTTP call for one case it will be hard to implement it in the ",{"type":414,"tag":440,"props":1491,"children":1493},{"className":1492},[],[1494],{"type":419,"value":1345},{"type":419,"value":1496}," method, the only solution will be to add optional parameters and do a lot of conditions in the code which will quickly become dirty.",{"type":414,"tag":415,"props":1498,"children":1499},{},[1500],{"type":419,"value":1501},"If you are fond of SOLID, what I am just saying is that this solution does not satisfy several SOLID principles like the Single responsibility principle and the Open-closed principle.",{"type":414,"tag":415,"props":1503,"children":1504},{},[1505,1507,1512],{"type":419,"value":1506},"So what could be done to improve this code if it is not by factorizing it in a private method. As we said, the code in the service does too much things so maybe it's time to remove some concerns from the ",{"type":414,"tag":440,"props":1508,"children":1510},{"className":1509},[],[1511],{"type":419,"value":514},{"type":419,"value":751},{"type":414,"tag":1329,"props":1514,"children":1516},{"id":1515},"implementing-an-http-message-handler-to-simplify-the-code",[1517],{"type":419,"value":1518},"Implementing an HTTP message handler to simplify the code",{"type":414,"tag":415,"props":1520,"children":1521},{},[1522,1524,1531,1533,1538,1540,1546],{"type":419,"value":1523},"I already talked about using an HTTP message handler / delegating handler in ",{"type":414,"tag":433,"props":1525,"children":1528},{"href":1526,"rel":1527},"https://www.techwatching.dev/posts/delegating-handler",[437],[1529],{"type":419,"value":1530},"a previous article",{"type":419,"value":1532},". When registered with an HTTP client, it is a piece of code that all the HTTP requests you do on this HTTP client will go through. Hence it is a nice way of factorizing code that we want to apply to all the requests to ",{"type":414,"tag":440,"props":1534,"children":1536},{"className":1535},[],[1537],{"type":419,"value":458},{"type":419,"value":1539}," like the fact of throwing the ",{"type":414,"tag":440,"props":1541,"children":1543},{"className":1542},[],[1544],{"type":419,"value":1545},"TheOneApiException",{"type":419,"value":1547}," when the HTTP responses are not successful.",{"type":414,"tag":415,"props":1549,"children":1550},{},[1551],{"type":414,"tag":1188,"props":1552,"children":1556},{"alt":1553,"className":1554,"src":1555},"The code the OneApiErrorDelegatingHandler.",[1192,1193],"/posts/images/onceuponatime_handler_1.png",[],{"type":414,"tag":415,"props":1558,"children":1559},{},[1560,1562,1568],{"type":419,"value":1561},"(This code can be found ",{"type":414,"tag":433,"props":1563,"children":1566},{"href":1564,"rel":1565},"https://github.com/TechWatching/MyLotrApi/blob/9dfd9966044560d6e13c71d93eeba775e02bd18d/src/MyLotrApi/Services/HttpMessageHandlers/TheOneApiErrorDelegatingHandler.cs",[437],[1567],{"type":419,"value":1383},{"type":419,"value":1385},{"type":414,"tag":1329,"props":1570,"children":1572},{"id":1571},"using-httpclientjsonextensions-to-get-rid-of-the-deserialization-code",[1573,1575,1581],{"type":419,"value":1574},"Using ",{"type":414,"tag":440,"props":1576,"children":1578},{"className":1577},[],[1579],{"type":419,"value":1580},"HttpClientJsonExtensions",{"type":419,"value":1582}," to get rid of the deserialization code",{"type":414,"tag":415,"props":1584,"children":1585},{},[1586],{"type":419,"value":1587},"In the current code, there are some lines to read the response content as a string and deserialize it. The interesting code in the service is the fact of doing a GET or a POST to retrieve some data, not the boilerplate code to handle deserialization. So it would be great to be able to remove it. In .NET Framework, there used to be some HTTP client extensions that worked with NewtonSoft to do that.",{"type":414,"tag":415,"props":1589,"children":1590},{},[1591,1593,1606],{"type":419,"value":1592},"In .NET Core there is no longer that, however, there are HttpClient method extensions that use System.Text.Json to make the request and deserialize the response content in one line. For that, you need to reference the ",{"type":414,"tag":433,"props":1594,"children":1597},{"href":1595,"rel":1596},"https://www.nuget.org/packages/System.Net.Http.Json",[437],[1598,1604],{"type":414,"tag":440,"props":1599,"children":1601},{"className":1600},[],[1602],{"type":419,"value":1603},"System.Net.Http.Json",{"type":419,"value":1605}," NuGet package",{"type":419,"value":1607}," and the code becomes simpler.",{"type":414,"tag":415,"props":1609,"children":1610},{},[1611],{"type":414,"tag":1188,"props":1612,"children":1616},{"alt":1613,"className":1614,"src":1615},"The code of GetCharacters method without manual deserialization.",[1192,1193],"/posts/images/onceuponatime_theoneapiservice_3.png",[],{"type":414,"tag":415,"props":1618,"children":1619},{},[1620,1621,1627],{"type":419,"value":1561},{"type":414,"tag":433,"props":1622,"children":1625},{"href":1623,"rel":1624},"https://github.com/TechWatching/MyLotrApi/blob/4ceca01826a0de4cca593c731f812ace874924a8/src/MyLotrApi/Services/TheOneApiService.cs",[437],[1626],{"type":419,"value":1383},{"type":419,"value":1385},{"type":414,"tag":1329,"props":1629,"children":1631},{"id":1630},"exploring-an-alternative-to-implementing-the-interface-itheoneapiservice-with-refit",[1632,1634,1640],{"type":419,"value":1633},"Exploring an alternative to implementing the interface ",{"type":414,"tag":440,"props":1635,"children":1637},{"className":1636},[],[1638],{"type":419,"value":1639},"ITheOneApiService",{"type":419,"value":1641}," with Refit",{"type":414,"tag":415,"props":1643,"children":1644},{},[1645,1647,1654,1656,1663],{"type":419,"value":1646},"I am pretty happy with how we improved and simplified the code thanks to the handler and the HTTP extensions methods. Yet I think I cannot end this article without mentioning ",{"type":414,"tag":433,"props":1648,"children":1651},{"href":1649,"rel":1650},"https://github.com/reactiveui/refit",[437],[1652],{"type":419,"value":1653},"Refit",{"type":419,"value":1655},": the automatic type-safe REST library for .NET. The idea behind this ",{"type":414,"tag":433,"props":1657,"children":1660},{"href":1658,"rel":1659},"https://reactiveui.github.io/refit/",[437],[1661],{"type":419,"value":1662},"library",{"type":419,"value":1664}," is that you only have to define the interface specifying the routes you want to query and the library will generate an implementation that does the calls for you with an HttpClient. No need to implement that yourself anymore, so less code to maintain for the same result.",{"type":414,"tag":415,"props":1666,"children":1667},{},[1668],{"type":414,"tag":1188,"props":1669,"children":1673},{"alt":1670,"className":1671,"src":1672},"The code of the IOneApiService interface using Refit.",[1192,1193],"/posts/images/onceuponatime_refit_2.png",[],{"type":414,"tag":415,"props":1675,"children":1676},{},[1677,1679,1684,1686,1692],{"type":419,"value":1678},"Refit even automatically handles query parameters by mapping them to the public properties of the object in parameter of a ",{"type":414,"tag":440,"props":1680,"children":1682},{"className":1681},[],[1683],{"type":419,"value":1452},{"type":419,"value":1685}," method in the interface. To name differently your property from the query parameter you can simply add an ",{"type":414,"tag":440,"props":1687,"children":1689},{"className":1688},[],[1690],{"type":419,"value":1691},"AliasAs",{"type":419,"value":1693}," attribute as I did here.",{"type":414,"tag":415,"props":1695,"children":1696},{},[1697],{"type":414,"tag":1188,"props":1698,"children":1702},{"alt":1699,"className":1700,"src":1701},"The code of CharacterQueryParam.",[1192,1193],"/posts/images/onceuponatime_refit_3.png",[],{"type":414,"tag":415,"props":1704,"children":1705},{},[1706,1708,1713],{"type":419,"value":1707},"Using Refit might not be appropriate to your use case when you have very specific things to do but for a basic REST service like ",{"type":414,"tag":440,"props":1709,"children":1711},{"className":1710},[],[1712],{"type":419,"value":514},{"type":419,"value":1714},", it is perfect. Refit has other nice features but what I really appreciate is that it also works with delegating handlers. So I can use Refit and still use the delegating handler I previously created.",{"type":414,"tag":415,"props":1716,"children":1717},{},[1718],{"type":414,"tag":1188,"props":1719,"children":1723},{"alt":1720,"className":1721,"src":1722},"The code for registering the services using Refit.",[1192,1193],"/posts/images/onceuponatime_refit_1.png",[],{"type":414,"tag":415,"props":1725,"children":1726},{},[1727,1728,1734],{"type":419,"value":1561},{"type":414,"tag":433,"props":1729,"children":1732},{"href":1730,"rel":1731},"https://github.com/TechWatching/MyLotrApi/blob/76f85099bdb8c747717bf3e61007c276d5055e6f/src/MyLotrApi/Startup.cs",[437],[1733],{"type":419,"value":1383},{"type":419,"value":1385},{"type":414,"tag":421,"props":1736,"children":1738},{"id":1737},"to-conclude",[1739],{"type":419,"value":1740},"To conclude",{"type":414,"tag":415,"props":1742,"children":1743},{},[1744],{"type":419,"value":1745},"In this article, we have seen how we can improve some .NET code while talking about records, delegating handlers, refit...",{"type":414,"tag":1747,"props":1748,"children":1749},"style",{},[1750],{"type":419,"value":1751},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":401,"searchDepth":737,"depth":737,"links":1753},[1754,1755,1756,1765],{"id":423,"depth":737,"text":426},{"id":562,"depth":737,"text":565},{"id":1278,"depth":737,"text":1757,"children":1758},"What can be improved in TheOneApiService?",[1759,1760,1761,1763],{"id":1331,"depth":772,"text":1334},{"id":1515,"depth":772,"text":1518},{"id":1571,"depth":772,"text":1762},"Using HttpClientJsonExtensions to get rid of the deserialization code",{"id":1630,"depth":772,"text":1764},"Exploring an alternative to implementing the interface ITheOneApiService with Refit",{"id":1737,"depth":737,"text":1740},"markdown","content:1.posts:16.once-upon-a-time-in-dotnet.md","content","1.posts/16.once-upon-a-time-in-dotnet.md","md",1716749600784]