<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FaithInErrorsZORO</title>
    <description>The latest articles on DEV Community by FaithInErrorsZORO (@faith_in_errors_).</description>
    <link>https://dev.to/faith_in_errors_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3026937%2F68b81842-6f0b-4f35-8108-f6cedd686f25.jpg</url>
      <title>DEV Community: FaithInErrorsZORO</title>
      <link>https://dev.to/faith_in_errors_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/faith_in_errors_"/>
    <language>en</language>
    <item>
      <title>ASP.net Resting!</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 04 Nov 2026 16:52:59 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/aspnet-resting-46lh</link>
      <guid>https://dev.to/faith_in_errors_/aspnet-resting-46lh</guid>
      <description>&lt;p&gt;In this post we're cooking up two apps that work together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;FoodMenuApi&lt;/strong&gt;, a REST API over a small restaurant database of &lt;strong&gt;dishes&lt;/strong&gt;, their &lt;strong&gt;cuisines&lt;/strong&gt; and &lt;strong&gt;categories&lt;/strong&gt;, and which &lt;strong&gt;restaurants&lt;/strong&gt; have them on the menu.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FoodMenuClient&lt;/strong&gt;, an &lt;a href="https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793897479914000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793897479914000&amp;amp;sa=E&lt;/a&gt; Core MVC website that calls the API with &lt;code&gt;HttpClient&lt;/code&gt;, so users can browse, search, add, edit and remove dishes through normal web forms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Along the way we'll cover EF Core scaffolding, keeping controllers skinny with a repository, DTOs, CORS, proper HTTP status codes (including a &lt;strong&gt;409 Conflict&lt;/strong&gt; when you try to delete a dish that's still on a restaurant's menu), and a tiny JavaScript page that calls the API directly.&lt;/p&gt;

&lt;p&gt;Here's the API we're building:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/dishes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All dishes, sorted by name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/dishes/5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One dish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/dishes?name=pad&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dishes whose name &lt;strong&gt;starts with&lt;/strong&gt; "pad", any case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/dishes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a dish (data in the body)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/dishes/5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update a dish (data in the body)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/dishes/5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete a dish, or 409 if it's on a menu&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The API runs on &lt;strong&gt;&lt;code&gt;http://localhost:5050&lt;/code&gt;&lt;/strong&gt; and the MVC site on &lt;strong&gt;&lt;code&gt;http://localhost:5060&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The finished project
&lt;/h2&gt;

&lt;p&gt;Files marked ★ are ones we write or edit. Files marked ⚙ are generated by EF Core scaffolding.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FoodMenuSolution/
├── FoodMenu.sql ★ (database script)
│
├── FoodMenuApi/ ← Part 1: the REST API
│ ├── Controllers/
│ │ └── DishesController.cs ★
│ ├── DTOs/
│ │ ├── DishDTO.cs ★
│ │ └── DishInputDTO.cs ★
│ ├── Models/
│ │ ├── Category.cs ⚙
│ │ ├── Cuisine.cs ⚙
│ │ ├── Dish.cs ⚙
│ │ ├── FoodMenuContext.cs ⚙
│ │ ├── MenuItem.cs ⚙
│ │ └── Restaurant.cs ⚙
│ ├── Repositories/
│ │ ├── DishRepository.cs ★
│ │ └── IDishRepository.cs ★
│ ├── Properties/
│ │ └── launchSettings.json ★ (fixed port 5050)
│ ├── wwwroot/
│ │ └── index.html ★ (JavaScript page)
│ ├── appsettings.json ★
│ └── Program.cs ★
│
└── FoodMenuClient/ ← Part 2: the MVC website
├── Controllers/
│ ├── DishesController.cs ★
│ └── HomeController.cs (template)
├── Models/
│ ├── ApiResult.cs ★
│ ├── Dish.cs ★ ([DataContract] class)
│ └── ErrorViewModel.cs (template)
├── Services/
│ └── DishApiClient.cs ★ (all the HttpClient code)
├── Views/
│ ├── Dishes/
│ │ ├── Create.cshtml ★
│ │ ├── Delete.cshtml ★
│ │ ├── Details.cshtml ★
│ │ ├── Edit.cshtml ★
│ │ └── Index.cshtml ★
│ └── Shared/
│ └── _Layout.cshtml ★ (one nav link)
├── Properties/
│ └── launchSettings.json ★ (fixed port 5060)
├── appsettings.json ★
└── Program.cs ★
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Part 1: The REST API
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Step 1: Create the database
&lt;/h2&gt;

&lt;p&gt;Our database has five tables. &lt;code&gt;menu_items&lt;/code&gt; is the link table that says which restaurant serves which dish, and it's the reason some deletes will be refused later.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cuisines (cuisine_id PK, cuisine_name)
categories (category_id PK, category_name)
dishes (dish_id PK, dish_name, cuisine_id FK, category_id FK, calories, price)
restaurants (restaurant_id PK, restaurant_name, phone, city)
menu_items (restaurant_id PK/FK, dish_id PK/FK, daily_portions) ← links dishes to restaurants
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run this script in SQL Server (SSMS, or &lt;strong&gt;View → SQL Server Object Explorer&lt;/strong&gt; in Visual Studio):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;FoodMenu&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;FoodMenu&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;cuisines&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;cuisine_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;cuisine_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;category_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;category_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dishes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;dish_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;dish_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;cuisine_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;cuisines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuisine_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;category_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;calories&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;restaurant_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;restaurant_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;menu_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;restaurant_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restaurant_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;dish_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;dishes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;daily_portions&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restaurant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dish_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cuisines&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuisine_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Italian'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Japanese'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Mexican'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Thai'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Canadian'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Indian'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Appetizer'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Main'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Dessert'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Soup'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;dishes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cuisine_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Margherita Pizza'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;850&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Tiramisu'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Salmon Sushi Roll'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Miso Soup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Tacos al Pastor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;650&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Churros'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Pad Thai'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Tom Yum Soup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Poutine'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;950&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Butter Tarts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;420&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Butter Chicken'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;780&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Samosas'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Pakoras'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;restaurants&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restaurant_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Chez Gatineau'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'819-555-0101'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Gatineau'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Ottawa Eats'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'613-555-0202'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ottawa'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Dishes 1, 3, 7 and 9 are on a menu, so deleting them gives 409 Conflict&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;menu_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restaurant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dish_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;daily_portions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create the Web API project
&lt;/h2&gt;

&lt;p&gt;In Visual Studio: &lt;strong&gt;Create a new project&lt;/strong&gt; → &lt;strong&gt;&lt;a href="https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793897479915000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793897479915000&amp;amp;sa=E&lt;/a&gt; Core Web API&lt;/strong&gt;. Name the project &lt;code&gt;FoodMenuApi&lt;/code&gt; and the solution &lt;code&gt;FoodMenuSolution&lt;/code&gt;, and keep &lt;strong&gt;Use controllers&lt;/strong&gt; checked.&lt;/p&gt;

&lt;p&gt;Then install these four NuGet packages (right-click the project → &lt;strong&gt;Manage NuGet Packages&lt;/strong&gt;), all published by Microsoft:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Microsoft.EntityFrameworkCore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Microsoft.EntityFrameworkCore.Design&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Microsoft.EntityFrameworkCore.SqlServer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Microsoft.EntityFrameworkCore.Tools&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Delete the template's &lt;code&gt;WeatherForecast.cs&lt;/code&gt; and &lt;code&gt;Controllers/WeatherForecastController.cs&lt;/code&gt;, since we won't need them.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 3: Add the connection string
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;appsettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"ConnectionStrings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"FoodMenu"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Server=(localdb)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;MSSQLLocalDB;Database=FoodMenu;Trusted_Connection=True;TrustServerCertificate=True"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"Logging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"LogLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Information"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"Microsoft.AspNetCore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warning"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"AllowedHosts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Using a different SQL Server? Swap in its connection string, for example &lt;code&gt;Server=myserver, 1433;Database=FoodMenu;Authentication=Active Directory Default;TrustServerCertificate=True&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 4: Let EF Core write the models for us
&lt;/h2&gt;

&lt;p&gt;Instead of typing out entity classes by hand, EF Core can &lt;strong&gt;scaffold&lt;/strong&gt; them from the database. Open &lt;strong&gt;Tools → NuGet Package Manager → Package Manager Console&lt;/strong&gt;, make sure the default project is &lt;code&gt;FoodMenuApi&lt;/code&gt;, and run:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Scaffold-DbContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Name=ConnectionStrings:FoodMenu"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.EntityFrameworkCore.SqlServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutputDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FoodMenuContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Name=ConnectionStrings:FoodMenu&lt;/code&gt; reads the connection string from appsettings.json, so it doesn't end up in your code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-OutputDir Models&lt;/code&gt; puts the classes in a Models folder.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-Context FoodMenuContext&lt;/code&gt; names the DbContext.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-Force&lt;/code&gt; lets you re-run it later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll get &lt;code&gt;Dish&lt;/code&gt;, &lt;code&gt;Cuisine&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;, &lt;code&gt;Restaurant&lt;/code&gt;, &lt;code&gt;MenuItem&lt;/code&gt; and &lt;code&gt;FoodMenuContext&lt;/code&gt;. Snake_case columns turn into PascalCase properties. Here's the generated &lt;code&gt;Models/Dish.cs&lt;/code&gt; (you don't type this):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dish&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;DishId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DishName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;CuisineId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Calories&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;Cuisine&lt;/span&gt; &lt;span class="n"&gt;Cuisine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;ICollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MenuItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MenuItems&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MenuItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The context exposes &lt;code&gt;Dishes&lt;/code&gt;, &lt;code&gt;Cuisines&lt;/code&gt;, &lt;code&gt;Categories&lt;/code&gt;, &lt;code&gt;Restaurants&lt;/code&gt; and &lt;code&gt;MenuItems&lt;/code&gt; as &lt;code&gt;DbSet&lt;/code&gt;s.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 5: Pin the port
&lt;/h2&gt;

&lt;p&gt;By default the port is random, which is annoying when another app needs to find your API. Fix it in &lt;code&gt;Properties/launchSettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.google.com/url?q=https://json.schemastore.org/launchsettings.json&amp;amp;source=gmail&amp;amp;ust=1793897479915000&amp;amp;sa=E"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"profiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"commandName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"dotnetRunMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"launchBrowser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"launchUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api/dishes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"applicationUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.google.com/url?q=http://localhost:5050&amp;amp;source=gmail&amp;amp;ust=1793897479915000&amp;amp;sa=E"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"environmentVariables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"ASPNETCORE_ENVIRONMENT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Development"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Pick the &lt;strong&gt;http&lt;/strong&gt; profile from the dropdown next to the green Run button.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 6: DTOs, the public face of a dish
&lt;/h2&gt;

&lt;p&gt;It's tempting to return the &lt;code&gt;Dish&lt;/code&gt; entity straight from the API, but that causes problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It exposes database details like navigation properties and foreign keys the client doesn't care about.&lt;/li&gt;
&lt;li&gt;Navigation properties can create loops when serialized.&lt;/li&gt;
&lt;li&gt;If you accept &lt;code&gt;Dish&lt;/code&gt; in a POST, &lt;code&gt;[ApiController]&lt;/code&gt; validation complains that &lt;strong&gt;"The Cuisine field is required"&lt;/strong&gt;, because the navigation properties are non-nullable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is &lt;strong&gt;DTOs&lt;/strong&gt; (Data Transfer Objects): small classes shaped for the outside world. Create a &lt;code&gt;DTOs&lt;/code&gt; folder with two of them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DTOs/DishDTO.cs&lt;/code&gt; is what the API &lt;strong&gt;sends out&lt;/strong&gt;. It swaps unhelpful ids for readable names:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.DTOs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DishDTO&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;DishId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DishName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;CuisineId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;CuisineName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;CategoryName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Calories&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;DTOs/DishInputDTO.cs&lt;/code&gt; is what the API &lt;strong&gt;accepts&lt;/strong&gt; for POST and PUT. There's no id because the database generates it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.DTOs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DishInputDTO&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;StringLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DishName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;CuisineId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Calories&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7: A repository to keep the controller skinny
&lt;/h2&gt;

&lt;p&gt;If you scaffold an API controller, Visual Studio stuffs all the EF Core code right inside it, which makes a "fat" controller. A cleaner approach is a &lt;strong&gt;repository&lt;/strong&gt;: one class that owns all the data access, so the controller only deals with HTTP.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;Repositories&lt;/code&gt; folder. First the interface, &lt;code&gt;Repositories/IDishRepository.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.DTOs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.Repositories&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;DeleteResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Deleted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Conflict&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IDishRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DishInputDTO&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;UpdateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DishInputDTO&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false = not found&lt;/span&gt;
&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DeleteResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DeleteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then the implementation, &lt;code&gt;Repositories/DishRepository.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.DTOs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.Repositories&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DishRepository&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDishRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;FoodMenuContext&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;DishRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FoodMenuContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;_context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Entity -&amp;gt; DTO (manual mapping)&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;DishDTO&lt;/span&gt; &lt;span class="nf"&gt;ToDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Dish&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DishDTO&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;DishId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;DishName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;CuisineId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CuisineId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;CuisineName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cuisine&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;CuisineName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;CategoryName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;CategoryName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;Calories&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Calories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// All dishes, or only those whose name starts with `name` (any case), sorted by name&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;IQueryable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Dish&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dishes&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cuisine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;lower&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Dish&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dishes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dishes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ToDto&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;Dish&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dishes&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cuisine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ToDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DishInputDTO&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dish&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;DishName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;CuisineId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CuisineId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;Calories&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Calories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dishes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// the database generates DishId here&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishId&lt;/span&gt;&lt;span class="p"&gt;))!;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;UpdateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DishInputDTO&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;Dish&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dishes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// find it first&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CuisineId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CuisineId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoryId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Calories&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Calories&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DeleteResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DeleteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;Dish&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dishes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DeleteResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Referential integrity: a dish on a restaurant's menu can't be deleted&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;onAMenu&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MenuItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AnyAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onAMenu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DeleteResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conflict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dishes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbUpdateException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DeleteResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conflict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// any other foreign key problem&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DeleteResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deleted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The search uses &lt;code&gt;ToLower()&lt;/code&gt; on &lt;strong&gt;both&lt;/strong&gt; sides plus &lt;code&gt;StartsWith&lt;/code&gt;, so &lt;code&gt;pad&lt;/code&gt;, &lt;code&gt;PAD&lt;/code&gt; and &lt;code&gt;pAd&lt;/code&gt; all find "Pad Thai".&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 8: Wire it all up in Program.cs
&lt;/h2&gt;

&lt;p&gt;Now we register the DbContext and the repository, and turn on &lt;strong&gt;CORS&lt;/strong&gt;. Browsers block JavaScript from calling an API on a different origin (a different port counts) unless the API explicitly allows it. CORS is that permission.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.Repositories&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Database&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FoodMenuContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FoodMenu"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="c1"&gt;// Repository&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IDishRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DishRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// CORS: allow JavaScript from other origins to call this API&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AllowAll"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyOrigin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyHeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyMethod&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// .NET 9+ template default; older templates use AddSwaggerGen()&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapOpenApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// app.UseHttpsRedirection(); // off: we're using plain https://www.google.com/url?q=http://localhost:5050&amp;amp;source=gmail&amp;amp;ust=1793897479915000&amp;amp;sa=E&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDefaultFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// "/" serves wwwroot/index.html&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// serves files from wwwroot&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AllowAll"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// must come BEFORE MapControllers&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 9: The (skinny) API controller
&lt;/h2&gt;

&lt;p&gt;Right-click &lt;strong&gt;Controllers&lt;/strong&gt; → &lt;strong&gt;Add&lt;/strong&gt; → &lt;strong&gt;Controller&lt;/strong&gt; → &lt;strong&gt;API&lt;/strong&gt; → &lt;strong&gt;API Controller with actions, using Entity Framework&lt;/strong&gt;. Pick model &lt;code&gt;Dish&lt;/code&gt;, context &lt;code&gt;FoodMenuContext&lt;/code&gt;, and name it &lt;strong&gt;&lt;code&gt;DishesController&lt;/code&gt;&lt;/strong&gt; (plural, which makes the route &lt;code&gt;api/dishes&lt;/code&gt;). Then replace the generated code with this version, which only handles HTTP and leaves the data work to the repository:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.DTOs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.Repositories&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FoodMenuApi.Controllers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="c1"&gt;// → api/dishes&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DishesController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IDishRepository&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;DishesController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDishRepository&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;_repo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// GET api/dishes → all dishes, sorted&lt;/span&gt;
&lt;span class="c1"&gt;// GET api/dishes?name=pad → dishes starting with "pad"&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetDishes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dishes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;dishes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"No dishes start with '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dishes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// GET api/dishes/5&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetDish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Dish &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dish&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// POST api/dishes (dish in the body)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DishDTO&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;PostDish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DishInputDTO&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;DishDTO&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 201 Created, with a Location header pointing at GetDish&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;CreatedAtAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetDish&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DishId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbUpdateException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not add the dish. Check that CuisineId and CategoryId exist."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// PUT api/dishes/5 (dish in the body)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;PutDish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DishInputDTO&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Dish &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;NoContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 204&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbUpdateException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not update the dish. Check that CuisineId and CategoryId exist."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// DELETE api/dishes/5&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DeleteDish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;DeleteResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;DeleteResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Dish &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found."&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;DeleteResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conflict&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Dish &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is on a restaurant's menu and can't be deleted."&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// 409&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;NoContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 204&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A quick cheat sheet of the status codes we're returning:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Helper&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ok(x)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;GET worked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CreatedAtAction(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;201&lt;/td&gt;
&lt;td&gt;POST worked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NoContent()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;204&lt;/td&gt;
&lt;td&gt;PUT or DELETE worked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BadRequest(msg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;Bad data or a missing foreign key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NotFound(msg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;No such dish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Conflict(msg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;409&lt;/td&gt;
&lt;td&gt;Dish is still on a menu&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Step 10: A tiny JavaScript front end
&lt;/h2&gt;

&lt;p&gt;To show CORS and &lt;code&gt;fetch&lt;/code&gt; in action, add &lt;code&gt;wwwroot/index.html&lt;/code&gt; (create the &lt;code&gt;wwwroot&lt;/code&gt; folder if it isn't there). It opens at `&lt;a href="https://www.google.com/url?q=http://localhost:5050/%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;
Dish Finder


body { font-family: sans-serif; margin: 20px; }
table { border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ccc; padding: 4px 8px; }
#msg { color: darkred; }




&lt;h1&gt;🍜 Dish Finder&lt;/h1&gt;

&lt;p&gt;&lt;br&gt;
Search&lt;br&gt;
Show all&lt;/p&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Id&lt;/th&gt;
&lt;th&gt;Dish&lt;/th&gt;
&lt;th&gt;Cuisine&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Calories&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody id="rows"&gt;&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;const apiUrl = "&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes&amp;amp;amp;source=gmail&amp;amp;amp;ust=1793897479916000&amp;amp;amp;sa=E&lt;/a&gt;";&lt;/p&gt;

&lt;p&gt;async function load(name) {&lt;br&gt;
const url = name ? &lt;code&gt;${apiUrl}?name=${encodeURIComponent(name)}&lt;/code&gt; : apiUrl;&lt;br&gt;
document.getElementById("msg").textContent = "";&lt;br&gt;
document.getElementById("rows").innerHTML = "";&lt;/p&gt;

&lt;p&gt;try {&lt;br&gt;
const response = await fetch(url);&lt;/p&gt;

&lt;p&gt;if (!response.ok) {&lt;br&gt;
document.getElementById("msg").textContent =&lt;br&gt;
&lt;code&gt;Error ${response.status}: ${await response.text()}&lt;/code&gt;;&lt;br&gt;
return;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const dishes = await response.json(); // JSON keys are camelCase&lt;/p&gt;

&lt;p&gt;document.getElementById("rows").innerHTML = dishes.map(d =&amp;gt; &lt;code&gt;&lt;br&gt;
&amp;amp;lt;tr&amp;amp;gt;&lt;br&gt;
&amp;amp;lt;td&amp;amp;gt;${d.dishId}&amp;amp;lt;/td&amp;amp;gt;&lt;br&gt;
&amp;amp;lt;td&amp;amp;gt;${d.dishName}&amp;amp;lt;/td&amp;amp;gt;&lt;br&gt;
&amp;amp;lt;td&amp;amp;gt;${d.cuisineName ?? ""}&amp;amp;lt;/td&amp;amp;gt;&lt;br&gt;
&amp;amp;lt;td&amp;amp;gt;${d.categoryName ?? ""}&amp;amp;lt;/td&amp;amp;gt;&lt;br&gt;
&amp;amp;lt;td&amp;amp;gt;${d.calories}&amp;amp;lt;/td&amp;amp;gt;&lt;br&gt;
&amp;amp;lt;td&amp;amp;gt;$${d.price.toFixed(2)}&amp;amp;lt;/td&amp;amp;gt;&lt;br&gt;
&amp;amp;lt;/tr&amp;amp;gt;&lt;/code&gt;).join("");&lt;br&gt;
} catch (err) {&lt;br&gt;
document.getElementById("msg").textContent = "Could not reach the API: " + err;&lt;br&gt;
}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;document.getElementById("btnAll").addEventListener("click", () =&amp;gt; load(""));&lt;br&gt;
document.getElementById("btnSearch").addEventListener("click",&lt;br&gt;
() =&amp;gt; load(document.getElementById("name").value));&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notice the JSON keys are &lt;strong&gt;camelCase&lt;/strong&gt; (&lt;code&gt;dishName&lt;/code&gt;, not &lt;code&gt;DishName&lt;/code&gt;). &lt;a href="https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt; Core does that by default.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 11: Test the API with Postman
&lt;/h2&gt;

&lt;p&gt;Before writing a client, make sure the API behaves. In Postman:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;Expected&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All dishes&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;`&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;200, sorted A to Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One dish&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;`&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes/7%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes/7%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;200, Pad Thai&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;`&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes/999%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes/999%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;`&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes?name%3DPAk%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes?name%3DPAk%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;200, Pakoras&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;`&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;201 with the new id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;`&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes/%7BnewId%7D%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes/%7BnewId%7D%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;204&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete (free)&lt;/td&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;`&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes/%7BnewId%7D%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes/%7BnewId%7D%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;204&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete (on a menu)&lt;/td&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;`&lt;a href="https://www.google.com/url?q=http://localhost:5050/api/dishes/1%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/api/dishes/1%60&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;409 Conflict&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For POST and PUT, choose &lt;strong&gt;Body → raw → JSON&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
"dishName": "Maple Pancakes",&lt;br&gt;
"cuisineId": 5,&lt;br&gt;
"categoryId": 3,&lt;br&gt;
"calories": 600,&lt;br&gt;
"price": 9.50&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Part 2: Consuming the API from MVC
&lt;/h1&gt;

&lt;p&gt;Our API works. Now let's give it a proper website.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 12: Add an MVC project to the same solution
&lt;/h2&gt;

&lt;p&gt;Right-click the &lt;strong&gt;solution&lt;/strong&gt; → &lt;strong&gt;Add&lt;/strong&gt; → &lt;strong&gt;New Project&lt;/strong&gt; → &lt;strong&gt;&lt;a href="https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E&lt;/a&gt; Core Web App (Model-View-Controller)&lt;/strong&gt; → name it &lt;code&gt;FoodMenuClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To run both at once, right-click the &lt;strong&gt;solution&lt;/strong&gt; → &lt;strong&gt;Configure Startup Projects&lt;/strong&gt; → &lt;strong&gt;Multiple startup projects&lt;/strong&gt;, and set both to &lt;strong&gt;Start&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The 12-step recipe for calling a REST API
&lt;/h2&gt;

&lt;p&gt;Every call in the client follows the same recipe. Here's where each step shows up in the code below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;A controller action, with an optional id&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DishesController&lt;/code&gt; (client)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Create an &lt;code&gt;HttpClient&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DishApiClient.CreateClient()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Clear the default headers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Accept.Clear()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Accept &lt;code&gt;application/json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Accept.Add(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Build the URI string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_baseUrl&lt;/code&gt; + id or &lt;code&gt;?name=&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Read the response as a stream&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ReadAsStreamAsync()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;A class whose properties match the JSON&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Models/Dish.cs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Mark it with &lt;code&gt;[DataContract]&lt;/code&gt; / &lt;code&gt;[DataMember]&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Models/Dish.cs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Give it constructors&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Models/Dish.cs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Deserialize with &lt;code&gt;DataContractJsonSerializer&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DishApiClient.Read&amp;lt;T&amp;gt;()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Wait for &lt;code&gt;ReadObject&lt;/code&gt; to finish&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DishApiClient.Read&amp;lt;T&amp;gt;()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Pass the result to the view&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DishesController&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Step 13: Pin the client's port and store the API URL
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Properties/launchSettings.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
"$schema": "https://www.google.com/url?q=https://json.schemastore.org/launchsettings.json&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E",&lt;br&gt;
"profiles": {&lt;br&gt;
"http": {&lt;br&gt;
"commandName": "Project",&lt;br&gt;
"dotnetRunMessages": true,&lt;br&gt;
"launchBrowser": true,&lt;br&gt;
"applicationUrl": "https://www.google.com/url?q=http://localhost:5060&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E",&lt;br&gt;
"environmentVariables": {&lt;br&gt;
"ASPNETCORE_ENVIRONMENT": "Development"&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;appsettings.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
"Logging": {&lt;br&gt;
"LogLevel": {&lt;br&gt;
"Default": "Information",&lt;br&gt;
"Microsoft.AspNetCore": "Warning"&lt;br&gt;
}&lt;br&gt;
},&lt;br&gt;
"AllowedHosts": "*",&lt;br&gt;
"DishesApi": {&lt;br&gt;
"BaseUrl": "https://www.google.com/url?q=http://localhost:5050/api/dishes&amp;amp;source=gmail&amp;amp;ust=1793897479916000&amp;amp;sa=E"&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 14: A model that matches the JSON
&lt;/h2&gt;

&lt;p&gt;The client needs its own &lt;code&gt;Dish&lt;/code&gt; class shaped like the API's JSON. Each &lt;code&gt;[DataMember(Name = "...")]&lt;/code&gt; must match a JSON key &lt;strong&gt;exactly&lt;/strong&gt;, camelCase included.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Models/Dish.cs&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
using System.ComponentModel.DataAnnotations;&lt;br&gt;
using System.Runtime.Serialization;&lt;/p&gt;

&lt;p&gt;namespace FoodMenuClient.Models&lt;br&gt;
{&lt;br&gt;
[DataContract]&lt;br&gt;
public class Dish&lt;br&gt;
{&lt;br&gt;
[DataMember(Name = "dishId")]&lt;br&gt;
[Display(Name = "Id")]&lt;br&gt;
public int DishId { get; set; }&lt;/p&gt;

&lt;p&gt;[DataMember(Name = "dishName")]&lt;br&gt;
[Display(Name = "Dish")]&lt;br&gt;
[Required]&lt;br&gt;
public string DishName { get; set; } = string.Empty;&lt;/p&gt;

&lt;p&gt;[DataMember(Name = "cuisineId")]&lt;br&gt;
[Display(Name = "Cuisine Id")]&lt;br&gt;
public int CuisineId { get; set; }&lt;/p&gt;

&lt;p&gt;[DataMember(Name = "cuisineName")]&lt;br&gt;
[Display(Name = "Cuisine")]&lt;br&gt;
public string? CuisineName { get; set; }&lt;/p&gt;

&lt;p&gt;[DataMember(Name = "categoryId")]&lt;br&gt;
[Display(Name = "Category Id")]&lt;br&gt;
public int CategoryId { get; set; }&lt;/p&gt;

&lt;p&gt;[DataMember(Name = "categoryName")]&lt;br&gt;
[Display(Name = "Category")]&lt;br&gt;
public string? CategoryName { get; set; }&lt;/p&gt;

&lt;p&gt;[DataMember(Name = "calories")]&lt;br&gt;
[Display(Name = "Calories")]&lt;br&gt;
public int Calories { get; set; }&lt;/p&gt;

&lt;p&gt;[DataMember(Name = "price")]&lt;br&gt;
[Display(Name = "Price")]&lt;br&gt;
public decimal Price { get; set; }&lt;/p&gt;

&lt;p&gt;public Dish() { }&lt;/p&gt;

&lt;p&gt;public Dish(int dishId, string dishName, int cuisineId, int categoryId, int calories, decimal price)&lt;br&gt;
{&lt;br&gt;
DishId = dishId;&lt;br&gt;
DishName = dishName;&lt;br&gt;
CuisineId = cuisineId;&lt;br&gt;
CategoryId = categoryId;&lt;br&gt;
Calories = calories;&lt;br&gt;
Price = price;&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We'll also want the &lt;strong&gt;status code&lt;/strong&gt; along with the data, so the UI can say things like "404, not found". &lt;code&gt;Models/ApiResult.cs&lt;/code&gt; is a small wrapper for that:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;csharp&lt;br&gt;
namespace FoodMenuClient.Models&lt;br&gt;
{&lt;br&gt;
public class ApiResult&amp;lt;T&amp;gt;&lt;br&gt;
{&lt;br&gt;
public T? Data { get; set; }&lt;br&gt;
public int StatusCode { get; set; }&lt;br&gt;
public bool Success { get; set; }&lt;br&gt;
public string? Message { get; set; }&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 15: One class for all the HTTP calls
&lt;/h2&gt;

&lt;p&gt;Just like the repository on the API side, we'll keep &lt;code&gt;HttpClient&lt;/code&gt; code out of the controller. Create a &lt;code&gt;Services&lt;/code&gt; folder and add &lt;code&gt;DishApiClient.cs&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
using System.Net.Http.Headers;&lt;br&gt;
using System.Runtime.Serialization.Json;&lt;br&gt;
using System.Text;&lt;br&gt;
using FoodMenuClient.Models;&lt;/p&gt;

&lt;p&gt;namespace FoodMenuClient.Services&lt;br&gt;
{&lt;br&gt;
public class DishApiClient&lt;br&gt;
{&lt;br&gt;
private readonly string _baseUrl;&lt;/p&gt;

&lt;p&gt;public DishApiClient(IConfiguration config)&lt;br&gt;
{&lt;br&gt;
_baseUrl = config["DishesApi:BaseUrl"]&lt;br&gt;
?? throw new InvalidOperationException("DishesApi:BaseUrl is missing from appsettings.json");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Steps 2-4: create the client, clear headers, accept JSON&lt;br&gt;
private static HttpClient CreateClient()&lt;br&gt;
{&lt;br&gt;
var client = new HttpClient();&lt;br&gt;
client.DefaultRequestHeaders.Accept.Clear();&lt;br&gt;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));&lt;br&gt;
return client;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Steps 6, 10 and 11: stream → DataContractJsonSerializer → object&lt;br&gt;
private static async Task Read(HttpResponseMessage response)&lt;br&gt;
{&lt;br&gt;
using Stream stream = await response.Content.ReadAsStreamAsync();&lt;br&gt;
var serializer = new DataContractJsonSerializer(typeof(T));&lt;br&gt;
return (T)serializer.ReadObject(stream)!;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Object → JSON request body (for POST and PUT)&lt;br&gt;
private static StringContent ToJson(Dish dish)&lt;br&gt;
{&lt;br&gt;
var serializer = new DataContractJsonSerializer(typeof(Dish));&lt;br&gt;
using var ms = new MemoryStream();&lt;br&gt;
serializer.WriteObject(ms, dish);&lt;br&gt;
string json = Encoding.UTF8.GetString(ms.ToArray());&lt;br&gt;
return new StringContent(json, Encoding.UTF8, "application/json");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;private static ApiResult Fail(HttpResponseMessage response, string body) =&amp;gt; new ApiResult&lt;br&gt;
{&lt;br&gt;
StatusCode = (int)response.StatusCode,&lt;br&gt;
Success = false,&lt;br&gt;
Message = string.IsNullOrWhiteSpace(body) ? response.ReasonPhrase : body&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;private static ApiResult Unreachable(Exception ex) =&amp;gt; new ApiResult&lt;br&gt;
{&lt;br&gt;
StatusCode = 0,&lt;br&gt;
Success = false,&lt;br&gt;
Message = "Could not reach the API. Is FoodMenuApi running? " + ex.Message&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;// GET api/dishes or api/dishes?name=xxx&lt;br&gt;
public async Task&amp;gt;&amp;gt; GetAllAsync(string? name)&lt;br&gt;
{&lt;br&gt;
using HttpClient client = CreateClient();&lt;br&gt;
string uri = string.IsNullOrWhiteSpace(name)&lt;br&gt;
? _baseUrl&lt;br&gt;
: $"{_baseUrl}?name={Uri.EscapeDataString(name)}"; // step 5&lt;/p&gt;

&lt;p&gt;try&lt;br&gt;
{&lt;br&gt;
HttpResponseMessage response = await client.GetAsync(uri);&lt;br&gt;
if (!response.IsSuccessStatusCode)&lt;br&gt;
return Fail&amp;gt;(response, await response.Content.ReadAsStringAsync());&lt;/p&gt;

&lt;p&gt;return new ApiResult&amp;gt;&lt;br&gt;
{&lt;br&gt;
Data = await Read&amp;gt;(response),&lt;br&gt;
StatusCode = (int)response.StatusCode,&lt;br&gt;
Success = true&lt;br&gt;
};&lt;br&gt;
}&lt;br&gt;
catch (HttpRequestException ex) { return Unreachable&amp;gt;(ex); }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// GET api/dishes/5&lt;br&gt;
public async Task&amp;gt; GetByIdAsync(int id)&lt;br&gt;
{&lt;br&gt;
using HttpClient client = CreateClient();&lt;br&gt;
try&lt;br&gt;
{&lt;br&gt;
HttpResponseMessage response = await client.GetAsync($"{_baseUrl}/{id}");&lt;br&gt;
if (!response.IsSuccessStatusCode)&lt;br&gt;
return Fail(response, await response.Content.ReadAsStringAsync());&lt;/p&gt;

&lt;p&gt;return new ApiResult&lt;br&gt;
{&lt;br&gt;
Data = await Read(response),&lt;br&gt;
StatusCode = (int)response.StatusCode,&lt;br&gt;
Success = true&lt;br&gt;
};&lt;br&gt;
}&lt;br&gt;
catch (HttpRequestException ex) { return Unreachable(ex); }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// POST api/dishes&lt;br&gt;
public async Task&amp;gt; CreateAsync(Dish dish)&lt;br&gt;
{&lt;br&gt;
using HttpClient client = CreateClient();&lt;br&gt;
try&lt;br&gt;
{&lt;br&gt;
HttpResponseMessage response = await client.PostAsync(_baseUrl, ToJson(dish));&lt;br&gt;
if (!response.IsSuccessStatusCode)&lt;br&gt;
return Fail(response, await response.Content.ReadAsStringAsync());&lt;/p&gt;

&lt;p&gt;return new ApiResult&lt;br&gt;
{&lt;br&gt;
Data = await Read(response), // the API sends back the new dish&lt;br&gt;
StatusCode = (int)response.StatusCode,&lt;br&gt;
Success = true,&lt;br&gt;
Message = "Dish added."&lt;br&gt;
};&lt;br&gt;
}&lt;br&gt;
catch (HttpRequestException ex) { return Unreachable(ex); }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// PUT api/dishes/5&lt;br&gt;
public async Task&amp;gt; UpdateAsync(int id, Dish dish)&lt;br&gt;
{&lt;br&gt;
using HttpClient client = CreateClient();&lt;br&gt;
try&lt;br&gt;
{&lt;br&gt;
HttpResponseMessage response = await client.PutAsync($"{_baseUrl}/{id}", ToJson(dish));&lt;br&gt;
if (!response.IsSuccessStatusCode)&lt;br&gt;
return Fail(response, await response.Content.ReadAsStringAsync());&lt;/p&gt;

&lt;p&gt;return new ApiResult&lt;br&gt;
{&lt;br&gt;
Data = true,&lt;br&gt;
StatusCode = (int)response.StatusCode,&lt;br&gt;
Success = true,&lt;br&gt;
Message = $"Dish {id} updated."&lt;br&gt;
};&lt;br&gt;
}&lt;br&gt;
catch (HttpRequestException ex) { return Unreachable(ex); }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// DELETE api/dishes/5&lt;br&gt;
public async Task&amp;gt; DeleteAsync(int id)&lt;br&gt;
{&lt;br&gt;
using HttpClient client = CreateClient();&lt;br&gt;
try&lt;br&gt;
{&lt;br&gt;
HttpResponseMessage response = await client.DeleteAsync($"{_baseUrl}/{id}");&lt;br&gt;
if (!response.IsSuccessStatusCode)&lt;br&gt;
return Fail(response, await response.Content.ReadAsStringAsync());&lt;/p&gt;

&lt;p&gt;return new ApiResult&lt;br&gt;
{&lt;br&gt;
Data = true,&lt;br&gt;
StatusCode = (int)response.StatusCode,&lt;br&gt;
Success = true,&lt;br&gt;
Message = $"Dish {id} deleted."&lt;br&gt;
};&lt;br&gt;
}&lt;br&gt;
catch (HttpRequestException ex) { return Unreachable(ex); }&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Two things worth knowing about &lt;code&gt;DataContractJsonSerializer&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single object deserializes as &lt;code&gt;Read&amp;lt;Dish&amp;gt;&lt;/code&gt;, and an array as &lt;code&gt;Read&amp;lt;List&amp;lt;Dish&amp;gt;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It can't parse ISO date strings like &lt;code&gt;"2026-11-04T12:00:00"&lt;/code&gt; into &lt;code&gt;DateTime&lt;/code&gt;. If an API you're calling returns dates, make those properties &lt;code&gt;string&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Step 16: Register the API client
&lt;/h2&gt;

&lt;p&gt;The client's &lt;code&gt;Program.cs&lt;/code&gt; is the MVC template with &lt;strong&gt;one line added&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
using FoodMenuClient.Services;&lt;/p&gt;

&lt;p&gt;var builder = WebApplication.CreateBuilder(args);&lt;/p&gt;

&lt;p&gt;// Add services to the container.&lt;br&gt;
builder.Services.AddControllersWithViews();&lt;br&gt;
builder.Services.AddScoped(); // ★ added&lt;/p&gt;

&lt;p&gt;var app = builder.Build();&lt;/p&gt;

&lt;p&gt;// Configure the HTTP request pipeline.&lt;br&gt;
if (!app.Environment.IsDevelopment())&lt;br&gt;
{&lt;br&gt;
app.UseExceptionHandler("/Home/Error");&lt;br&gt;
app.UseHsts();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;app.UseHttpsRedirection();&lt;br&gt;
app.UseStaticFiles();&lt;/p&gt;

&lt;p&gt;app.UseRouting();&lt;/p&gt;

&lt;p&gt;app.UseAuthorization();&lt;/p&gt;

&lt;p&gt;app.MapControllerRoute(&lt;br&gt;
name: "default",&lt;br&gt;
pattern: "{controller=Home}/{action=Index}/{id?}");&lt;/p&gt;

&lt;p&gt;app.Run();&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 17: The MVC controller
&lt;/h2&gt;

&lt;p&gt;Right-click &lt;strong&gt;Controllers&lt;/strong&gt; → &lt;strong&gt;Add&lt;/strong&gt; → &lt;strong&gt;Controller&lt;/strong&gt; → &lt;strong&gt;MVC Controller - Empty&lt;/strong&gt; → &lt;code&gt;DishesController&lt;/code&gt;. Each action calls the API client and decides which view or message to show:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
using FoodMenuClient.Models;&lt;br&gt;
using FoodMenuClient.Services;&lt;br&gt;
using Microsoft.AspNetCore.Mvc;&lt;/p&gt;

&lt;p&gt;namespace FoodMenuClient.Controllers&lt;br&gt;
{&lt;br&gt;
public class DishesController : Controller&lt;br&gt;
{&lt;br&gt;
private readonly DishApiClient _api;&lt;/p&gt;

&lt;p&gt;public DishesController(DishApiClient api)&lt;br&gt;
{&lt;br&gt;
_api = api;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// GET /Dishes or /Dishes?name=pad&lt;br&gt;
public async Task Index(string? name)&lt;br&gt;
{&lt;br&gt;
ApiResult&amp;gt; result = await _api.GetAllAsync(name);&lt;/p&gt;

&lt;p&gt;ViewBag.Search = name;&lt;br&gt;
ViewBag.Status = result.StatusCode;&lt;br&gt;
ViewBag.Error = result.Success ? null : result.Message;&lt;/p&gt;

&lt;p&gt;return View(result.Data ?? new List()); // step 12&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// GET /Dishes/Details/5&lt;br&gt;
public async Task Details(int id)&lt;br&gt;
{&lt;br&gt;
ApiResult result = await _api.GetByIdAsync(id);&lt;br&gt;
if (!result.Success)&lt;br&gt;
{&lt;br&gt;
TempData["Error"] = $"({result.StatusCode}) {result.Message}";&lt;br&gt;
return RedirectToAction(nameof(Index));&lt;br&gt;
}&lt;br&gt;
ViewBag.Status = result.StatusCode;&lt;br&gt;
return View(result.Data);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// GET /Dishes/Create&lt;br&gt;
[HttpGet]&lt;br&gt;
public IActionResult Create()&lt;br&gt;
{&lt;br&gt;
return View(new Dish());&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// POST /Dishes/Create&lt;br&gt;
[HttpPost]&lt;br&gt;
[ValidateAntiForgeryToken]&lt;br&gt;
public async Task Create(Dish dish)&lt;br&gt;
{&lt;br&gt;
if (!ModelState.IsValid) return View(dish);&lt;/p&gt;

&lt;p&gt;ApiResult result = await _api.CreateAsync(dish);&lt;br&gt;
if (!result.Success)&lt;br&gt;
{&lt;br&gt;
ViewBag.Error = $"({result.StatusCode}) {result.Message}";&lt;br&gt;
return View(dish);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;TempData["Message"] = $"({result.StatusCode}) Dish added with id {result.Data!.DishId}.";&lt;br&gt;
return RedirectToAction(nameof(Details), new { id = result.Data.DishId });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// GET /Dishes/Edit/5 → form pre-filled with the current values&lt;br&gt;
[HttpGet]&lt;br&gt;
public async Task Edit(int id)&lt;br&gt;
{&lt;br&gt;
ApiResult result = await _api.GetByIdAsync(id);&lt;br&gt;
if (!result.Success)&lt;br&gt;
{&lt;br&gt;
TempData["Error"] = $"({result.StatusCode}) {result.Message}";&lt;br&gt;
return RedirectToAction(nameof(Index));&lt;br&gt;
}&lt;br&gt;
return View(result.Data);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// POST /Dishes/Edit/5&lt;br&gt;
[HttpPost]&lt;br&gt;
[ValidateAntiForgeryToken]&lt;br&gt;
public async Task Edit(int id, Dish dish)&lt;br&gt;
{&lt;br&gt;
if (!ModelState.IsValid) return View(dish);&lt;/p&gt;

&lt;p&gt;ApiResult result = await _api.UpdateAsync(id, dish);&lt;br&gt;
if (!result.Success)&lt;br&gt;
{&lt;br&gt;
ViewBag.Error = $"({result.StatusCode}) {result.Message}";&lt;br&gt;
return View(dish);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;TempData["Message"] = $"({result.StatusCode}) {result.Message}";&lt;br&gt;
return RedirectToAction(nameof(Details), new { id });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// GET /Dishes/Delete/5 → "are you sure?" page&lt;br&gt;
[HttpGet]&lt;br&gt;
public async Task Delete(int id)&lt;br&gt;
{&lt;br&gt;
ApiResult result = await _api.GetByIdAsync(id);&lt;br&gt;
if (!result.Success)&lt;br&gt;
{&lt;br&gt;
TempData["Error"] = $"({result.StatusCode}) {result.Message}";&lt;br&gt;
return RedirectToAction(nameof(Index));&lt;br&gt;
}&lt;br&gt;
return View(result.Data);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// POST /Dishes/Delete/5&lt;br&gt;
[HttpPost, ActionName("Delete")]&lt;br&gt;
[ValidateAntiForgeryToken]&lt;br&gt;
public async Task DeleteConfirmed(int id)&lt;br&gt;
{&lt;br&gt;
ApiResult result = await _api.DeleteAsync(id);&lt;/p&gt;

&lt;p&gt;if (result.Success)&lt;br&gt;
TempData["Message"] = $"({result.StatusCode}) {result.Message}";&lt;br&gt;
else if (result.StatusCode == 409)&lt;br&gt;
TempData["Error"] = $"(409) Dish {id} is on a restaurant's menu, so it can't be deleted.";&lt;br&gt;
else&lt;br&gt;
TempData["Error"] = $"({result.StatusCode}) {result.Message}";&lt;/p&gt;

&lt;p&gt;return RedirectToAction(nameof(Index));&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 18: The views
&lt;/h2&gt;

&lt;p&gt;Create the folder &lt;code&gt;Views/Dishes&lt;/code&gt; and add five views.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on the Razor style:&lt;/strong&gt; these views use explicit expressions like &lt;code&gt;@(Model.DishName)&lt;/code&gt; and wrap &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;foreach&lt;/code&gt; inside &lt;code&gt;@{ ... }&lt;/code&gt; blocks. That's standard Razor and behaves exactly like the shorter form without parentheses that you may be used to. Either style works in Visual Studio.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Index.cshtml: the menu, with search
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
@model List&lt;br&gt;
@{&lt;br&gt;
ViewData["Title"] = "Dishes";&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;🍽️ Dishes&lt;/h2&gt;

&lt;p&gt;@{&lt;br&gt;
if (TempData["Message"] != null)&lt;br&gt;
{&lt;/p&gt;

@(TempData["Message"])

&lt;p&gt;}&lt;br&gt;
if (TempData["Error"] != null)&lt;br&gt;
{&lt;/p&gt;

@(TempData["Error"])

&lt;p&gt;}&lt;br&gt;
if (ViewBag.Error != null)&lt;br&gt;
{&lt;/p&gt;

(@(ViewBag.Status)) @(ViewBag.Error)

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;






Search
&lt;a&gt;Show all&lt;/a&gt;
&lt;a&gt;Add a dish&lt;/a&gt;








Find by id



&lt;p&gt;Status returned: @(ViewBag.Status) | @(Model.Count) dish(es)&lt;/p&gt;


@{
foreach (var d in Model)
{

}
}
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Id&lt;/th&gt;
&lt;th&gt;Dish&lt;/th&gt;
&lt;th&gt;Cuisine&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Calories&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;@(d.DishId)&lt;/td&gt;
&lt;td&gt;@(d.DishName)&lt;/td&gt;
&lt;td&gt;@(d.CuisineName)&lt;/td&gt;
&lt;td&gt;@(d.CategoryName)&lt;/td&gt;
&lt;td&gt;@(d.Calories)&lt;/td&gt;
&lt;td&gt;@(d.Price.ToString("C"))&lt;/td&gt;
&lt;td&gt;
&lt;a&gt;Details&lt;/a&gt; |
&lt;a&gt;Edit&lt;/a&gt; |
&lt;a&gt;Delete&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Details.cshtml
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
@model FoodMenuClient.Models.Dish&lt;br&gt;
@{&lt;br&gt;
ViewData["Title"] = "Dish Details";&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;@(Model.DishName)&lt;/h2&gt;

&lt;p&gt;@{&lt;br&gt;
if (TempData["Message"] != null)&lt;br&gt;
{&lt;/p&gt;

&lt;p&gt;@(TempData["Message"])&lt;/p&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Status returned: @(ViewBag.Status)&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;Id&lt;/dt&gt;
&lt;dd&gt;@(Model.DishId)&lt;/dd&gt;

&lt;dt&gt;Cuisine&lt;/dt&gt;
&lt;dd&gt;@(Model.CuisineName) (id @(Model.CuisineId))&lt;/dd&gt;

&lt;dt&gt;Category&lt;/dt&gt;
&lt;dd&gt;@(Model.CategoryName) (id @(Model.CategoryId))&lt;/dd&gt;

&lt;dt&gt;Calories&lt;/dt&gt;
&lt;dd&gt;@(Model.Calories) kcal&lt;/dd&gt;

&lt;dt&gt;Price&lt;/dt&gt;
&lt;dd&gt;@(Model.Price.ToString("C"))&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;&lt;a&gt;Edit&lt;/a&gt;&lt;br&gt;
&lt;a&gt;Delete&lt;/a&gt;&lt;br&gt;
&lt;a&gt;Back to dishes&lt;/a&gt;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create.cshtml
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;asp-for&lt;/code&gt; tag helpers build each label and input from the model, and &lt;code&gt;asp-validation-for&lt;/code&gt; shows any validation error for that field.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
@model FoodMenuClient.Models.Dish&lt;br&gt;
@{&lt;br&gt;
ViewData["Title"] = "Add a Dish";&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;Add a Dish&lt;/h2&gt;

&lt;p&gt;@{&lt;br&gt;
if (ViewBag.Error != null)&lt;br&gt;
{&lt;/p&gt;

&lt;p&gt;@(ViewBag.Error)&lt;/p&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Add&lt;br&gt;
&lt;a&gt;Cancel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit.cshtml
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
@model FoodMenuClient.Models.Dish&lt;br&gt;
@{&lt;br&gt;
ViewData["Title"] = "Edit Dish";&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;Edit: @(Model.DishName)&lt;/h2&gt;

&lt;p&gt;@{&lt;br&gt;
if (ViewBag.Error != null)&lt;br&gt;
{&lt;/p&gt;

&lt;p&gt;@(ViewBag.Error)&lt;/p&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Save&lt;br&gt;
&lt;a&gt;Cancel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete.cshtml
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
@model FoodMenuClient.Models.Dish&lt;br&gt;
@{&lt;br&gt;
ViewData["Title"] = "Delete Dish";&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;Delete this dish?&lt;/h2&gt;

&lt;dl&gt;
&lt;dt&gt;Id&lt;/dt&gt;
&lt;dd&gt;@(Model.DishId)&lt;/dd&gt;
&lt;dt&gt;Dish&lt;/dt&gt;
&lt;dd&gt;@(Model.DishName)&lt;/dd&gt;
&lt;dt&gt;Cuisine&lt;/dt&gt;
&lt;dd&gt;@(Model.CuisineName)&lt;/dd&gt;
&lt;dt&gt;Price&lt;/dt&gt;
&lt;dd&gt;@(Model.Price.ToString("C"))&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Delete&lt;br&gt;
&lt;a&gt;Cancel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a nav link in Views/Shared/_Layout.cshtml
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;html&lt;br&gt;
&amp;lt;li class="nav-item"&amp;gt;&lt;br&gt;
&amp;lt;a class="nav-link text-dark" asp-area="" asp-controller="Dishes" asp-action="Index"&amp;gt;Dishes&amp;lt;/a&amp;gt;&lt;br&gt;
&amp;lt;/li&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 19: Run the whole thing
&lt;/h2&gt;

&lt;p&gt;Hit &lt;strong&gt;Start&lt;/strong&gt; and both apps launch: the API on 5050 and the website on 5060. Head to `&lt;a href="https://www.google.com/url?q=http://localhost:5060/Dishes%60&amp;amp;source=gmail&amp;amp;ust=1793897479917000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5060/Dishes%60&amp;amp;source=gmail&amp;amp;ust=1793897479917000&amp;amp;sa=E&lt;/a&gt; and try it out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The dish list comes back sorted alphabetically.&lt;/li&gt;
&lt;li&gt;Searching &lt;code&gt;pAD&lt;/code&gt; finds Pad Thai, and searching &lt;code&gt;zzz&lt;/code&gt; shows a friendly 404 message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find by id&lt;/strong&gt; with &lt;code&gt;999&lt;/code&gt; explains that the dish doesn't exist instead of crashing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a dish&lt;/strong&gt; creates "Maple Pancakes" and takes you to its details page with its new id.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit&lt;/strong&gt; updates it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete&lt;/strong&gt; works on your new dish (204), but trying to delete &lt;strong&gt;Margherita Pizza&lt;/strong&gt; (id 1) is refused with a 409, because it's on Chez Gatineau's menu.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And `&lt;a href="https://www.google.com/url?q=http://localhost:5050/%60&amp;amp;source=gmail&amp;amp;ust=1793897479917000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://localhost:5050/%60&amp;amp;source=gmail&amp;amp;ust=1793897479917000&amp;amp;sa=E&lt;/a&gt; still serves the little JavaScript Dish Finder.&lt;/p&gt;

&lt;p&gt;Here's what happens on a single page load:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Browser ──► FoodMenuClient (MVC, :5060)&lt;br&gt;
DishesController.Index()&lt;br&gt;
└─► DishApiClient.GetAllAsync()&lt;br&gt;
HttpClient GET https://www.google.com/url?q=http://localhost:5050/api/dishes&amp;amp;source=gmail&amp;amp;ust=1793897479917000&amp;amp;sa=E&lt;br&gt;
│&lt;br&gt;
▼&lt;br&gt;
FoodMenuApi (Web API, :5050)&lt;br&gt;
DishesController.GetDishes() (skinny)&lt;br&gt;
└─► DishRepository.GetAllAsync() (LINQ + EF Core)&lt;br&gt;
└─► SQL Server (FoodMenu database)&lt;br&gt;
│&lt;br&gt;
JSON ◄──────────────┘ [ { "dishId": 7, "dishName": "Pad Thai", ... } ]&lt;br&gt;
│&lt;br&gt;
DataContractJsonSerializer → List&amp;lt;Dish&amp;gt;&lt;br&gt;
│&lt;br&gt;
View(List&amp;lt;Dish&amp;gt;) → HTML table&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"The Cuisine field is required" on POST.&lt;/strong&gt; The API is accepting the &lt;code&gt;Dish&lt;/code&gt; entity instead of &lt;code&gt;DishInputDTO&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"blocked by CORS policy" in the browser console.&lt;/strong&gt; &lt;code&gt;AddCors&lt;/code&gt;/&lt;code&gt;UseCors&lt;/code&gt; is missing, or &lt;code&gt;UseCors&lt;/code&gt; comes after &lt;code&gt;MapControllers&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Could not reach the API" in the website.&lt;/strong&gt; The API isn't running, or the port in the client's &lt;code&gt;appsettings.json&lt;/code&gt; doesn't match the API's &lt;code&gt;launchSettings.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blank values in the website.&lt;/strong&gt; A &lt;code&gt;[DataMember(Name = "...")]&lt;/code&gt; doesn't match the JSON key exactly. Compare against the JSON in Postman.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search is case sensitive.&lt;/strong&gt; You forgot &lt;code&gt;ToLower()&lt;/code&gt; on one side of the comparison.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Reusing this for other APIs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Calling someone else's API:&lt;/strong&gt; open it in Postman, build a &lt;code&gt;[DataContract]&lt;/code&gt; class that matches its JSON, change &lt;code&gt;BaseUrl&lt;/code&gt;, and copy &lt;code&gt;DishApiClient&lt;/code&gt; with your class swapped in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building an API over a different database:&lt;/strong&gt; scaffold it, write DTOs, add a repository and its interface, register them in &lt;code&gt;Program.cs&lt;/code&gt;, and keep the controller focused on HTTP.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;We built a REST API with EF Core, kept the controller lean with a repository, shaped responses with DTOs, returned meaningful status codes, opened it to JavaScript with CORS, and then built an MVC site that calls it through &lt;code&gt;HttpClient&lt;/code&gt;. The recipe is the same whatever's on the menu. 🍕&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>dotnet</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ASP.net Consuming soap!</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 04 Nov 2026 16:44:34 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/aspnet-consuming-soap-2nf1</link>
      <guid>https://dev.to/faith_in_errors_/aspnet-consuming-soap-2nf1</guid>
      <description>&lt;p&gt;SOAP feels old next to REST and JSON, but plenty of companies still run SOAP services, and .NET makes calling one surprisingly painless. In this post we'll build a small &lt;strong&gt;Food Trip Planner&lt;/strong&gt;: you type in a country you want to eat your way through, and the app tells you what you need to know before booking the flight.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the country's &lt;strong&gt;code&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;capital city&lt;/strong&gt; (where you'll probably land and eat first)&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;currency&lt;/strong&gt; you'll be paying for meals in&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;phone code&lt;/strong&gt;, for calling ahead to book restaurants&lt;/li&gt;
&lt;li&gt;the country's &lt;strong&gt;flag&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;signature dish&lt;/strong&gt; to try first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the country data comes from the free &lt;strong&gt;CountryInfo SOAP service&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.google.com/url?q=http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso&amp;amp;source=gmail&amp;amp;ust=1793896833444000&amp;amp;sa=E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick is that we can't get everything in one call. We first ask the service for the country's ISO code, then &lt;strong&gt;use that code&lt;/strong&gt; for every other call. (The service has a &lt;code&gt;FullCountryInfo&lt;/code&gt; method that returns everything at once, but we'll skip it on purpose, since chaining calls is the skill worth practicing.)&lt;/p&gt;

&lt;p&gt;Let's build it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the finished project looks like
&lt;/h2&gt;

&lt;p&gt;Here's every file we'll touch. Files marked ★ are ones we write or edit. Everything else comes from the template or is generated by Visual Studio.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FoodTripPlanner/ (solution)
└── FoodTripPlanner/ (project)
├── Connected Services/
│ └── ServiceReferenceCountryInfo/
│ ├── ConnectedService.json (generated)
│ └── Reference.cs (generated: the SOAP client)
├── Controllers/
│ ├── HomeController.cs (template)
│ └── TripController.cs ★
├── Models/
│ ├── ErrorViewModel.cs (template)
│ └── TripInfo.cs ★
├── Services/
│ └── TripPlannerService.cs ★ (all the SOAP calls live here)
├── Views/
│ ├── Home/ ... (template)
│ ├── Shared/
│ │ └── _Layout.cshtml ★ (one nav link)
│ └── Trip/
│ ├── Index.cshtml ★ (search form)
│ └── Result.cshtml ★ (trip card)
├── wwwroot/ ... (template)
├── appsettings.json ★ (service URL)
└── Program.cs ★ (register our service)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1: Create the MVC project
&lt;/h2&gt;

&lt;p&gt;Open Visual Studio and choose &lt;strong&gt;Create a new project&lt;/strong&gt; → &lt;strong&gt;&lt;a href="https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793896833444000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793896833444000&amp;amp;sa=E&lt;/a&gt; Core Web App (Model-View-Controller)&lt;/strong&gt;. Name it &lt;code&gt;FoodTripPlanner&lt;/code&gt;, pick the latest .NET version, and leave Authentication on &lt;strong&gt;None&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Point Visual Studio at the WSDL
&lt;/h2&gt;

&lt;p&gt;A SOAP service describes itself in a &lt;strong&gt;WSDL&lt;/strong&gt; file. Visual Studio reads it and generates a ready-to-use C# client for us. The WSDL lives at the service URL plus &lt;code&gt;?WSDL&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Solution Explorer, right-click the &lt;strong&gt;project&lt;/strong&gt; → &lt;strong&gt;Add&lt;/strong&gt; → &lt;strong&gt;Service Reference...&lt;/strong&gt; (or right-click &lt;strong&gt;Connected Services&lt;/strong&gt; → &lt;strong&gt;Add&lt;/strong&gt; → &lt;strong&gt;WCF Web Service&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;WCF Web Service&lt;/strong&gt; and click Next.&lt;/li&gt;
&lt;li&gt;Paste in `&lt;a href="https://www.google.com/url?q=http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL%60&amp;amp;source=gmail&amp;amp;ust=1793896833445000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL%60&amp;amp;source=gmail&amp;amp;ust=1793896833445000&amp;amp;sa=E&lt;/a&gt; and click &lt;strong&gt;Go&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set the namespace to &lt;strong&gt;&lt;code&gt;ServiceReferenceCountryInfo&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Finish&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Visual Studio installs the &lt;code&gt;System.ServiceModel.*&lt;/code&gt; NuGet packages and generates &lt;code&gt;Reference.cs&lt;/code&gt;. These are the parts of it we'll use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client class&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CountryInfoServiceSoapTypeClient&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Endpoint enum&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CountryInfoServiceSoapTypeClient.EndpointConfiguration.CountryInfoServiceSoap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Currency type&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tCurrency&lt;/code&gt;, with &lt;code&gt;sISOCode&lt;/code&gt; and &lt;code&gt;sName&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Methods&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CountryISOCodeAsync&lt;/code&gt;, &lt;code&gt;CapitalCityAsync&lt;/code&gt;, &lt;code&gt;CountryCurrencyAsync&lt;/code&gt;, &lt;code&gt;CountryIntPhoneCodeAsync&lt;/code&gt;, &lt;code&gt;CountryFlagAsync&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on &lt;code&gt;.Body&lt;/code&gt;:&lt;/strong&gt; depending on how the reference was generated, each call returns a response object with the value inside &lt;code&gt;.Body&lt;/code&gt;, like &lt;code&gt;(await client.CapitalCityAsync(code)).Body.CapitalCityResult&lt;/code&gt;. That's the version used in this post. If IntelliSense doesn't show &lt;code&gt;.Body&lt;/code&gt; for you, the method returns the value directly, so just use &lt;code&gt;await client.CapitalCityAsync(code)&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3: Keep the service URL in appsettings.json
&lt;/h2&gt;

&lt;p&gt;Hard-coding URLs is a habit worth avoiding. Put it in config so it can change without recompiling:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
"Logging": {&lt;br&gt;
"LogLevel": {&lt;br&gt;
"Default": "Information",&lt;br&gt;
"Microsoft.AspNetCore": "Warning"&lt;br&gt;
}&lt;br&gt;
},&lt;br&gt;
"AllowedHosts": "*",&lt;br&gt;
"CountryInfoService": {&lt;br&gt;
"Url": "https://www.google.com/url?q=http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso&amp;amp;source=gmail&amp;amp;ust=1793896833445000&amp;amp;sa=E"&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notice it's plain &lt;strong&gt;http&lt;/strong&gt;. This free service doesn't do https.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: A model to collect the results
&lt;/h2&gt;

&lt;p&gt;Since the answers arrive one call at a time, we need somewhere to keep them. &lt;code&gt;Models/TripInfo.cs&lt;/code&gt; gets one property per piece of information. We fill it in as each call comes back, then hand the finished object to the view.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
namespace FoodTripPlanner.Models&lt;br&gt;
{&lt;br&gt;
public class TripInfo&lt;br&gt;
{&lt;br&gt;
public string CountryName { get; set; } = string.Empty;&lt;br&gt;
public string? IsoCode { get; set; }&lt;br&gt;
public string? Capital { get; set; }&lt;br&gt;
public string? Currency { get; set; }&lt;br&gt;
public string? CurrencyCode { get; set; }&lt;br&gt;
public string? PhoneCode { get; set; }&lt;br&gt;
public string? FlagUrl { get; set; }&lt;br&gt;
public string? SignatureDish { get; set; }&lt;br&gt;
public string? ErrorMessage { get; set; }&lt;/p&gt;

&lt;p&gt;// True when nothing went wrong&lt;br&gt;
public bool Found =&amp;gt; string.IsNullOrEmpty(ErrorMessage);&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: The service class that talks SOAP
&lt;/h2&gt;

&lt;p&gt;This is the heart of the app. Rather than cramming SOAP calls into a controller, we'll put them in their own class. Create a &lt;code&gt;Services&lt;/code&gt; folder and add &lt;code&gt;TripPlannerService.cs&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
using System.Globalization;&lt;br&gt;
using System.ServiceModel;&lt;br&gt;
using FoodTripPlanner.Models;&lt;br&gt;
using ServiceReferenceCountryInfo;&lt;/p&gt;

&lt;p&gt;namespace FoodTripPlanner.Services&lt;br&gt;
{&lt;br&gt;
public class TripPlannerService&lt;br&gt;
{&lt;br&gt;
private readonly string _serviceUrl;&lt;/p&gt;

&lt;p&gt;// Our own little bit of data: what to eat first, keyed by ISO code&lt;br&gt;
private static readonly Dictionary SignatureDishes = new()&lt;br&gt;
{&lt;br&gt;
["CA"] = "Poutine",&lt;br&gt;
["IT"] = "Neapolitan pizza",&lt;br&gt;
["ZA"] = "Bobotie",&lt;br&gt;
["JP"] = "Sushi",&lt;br&gt;
["MX"] = "Tacos al pastor",&lt;br&gt;
["FR"] = "Coq au vin",&lt;br&gt;
["IN"] = "Butter chicken",&lt;br&gt;
["TH"] = "Pad thai",&lt;br&gt;
["GR"] = "Moussaka",&lt;br&gt;
["KR"] = "Bibimbap"&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;public TripPlannerService(IConfiguration config)&lt;br&gt;
{&lt;br&gt;
_serviceUrl = config["CountryInfoService:Url"]&lt;br&gt;
?? throw new InvalidOperationException("CountryInfoService:Url is missing from appsettings.json");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;public async Task PlanAsync(string countryName)&lt;br&gt;
{&lt;br&gt;
var trip = new TripInfo { CountryName = countryName };&lt;/p&gt;

&lt;p&gt;if (string.IsNullOrWhiteSpace(countryName))&lt;br&gt;
{&lt;br&gt;
trip.ErrorMessage = "Please enter a country.";&lt;br&gt;
return trip;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// The service is case sensitive, so "south africa" has to become "South Africa"&lt;br&gt;
string name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(countryName.Trim().ToLower());&lt;br&gt;
trip.CountryName = name;&lt;/p&gt;

&lt;p&gt;// Build the client using the URL from appsettings.json&lt;br&gt;
var client = new CountryInfoServiceSoapTypeClient(&lt;br&gt;
CountryInfoServiceSoapTypeClient.EndpointConfiguration.CountryInfoServiceSoap,&lt;br&gt;
_serviceUrl);&lt;/p&gt;

&lt;p&gt;try&lt;br&gt;
{&lt;br&gt;
// Call 1: country name -&amp;gt; ISO code&lt;br&gt;
var isoResponse = await client.CountryISOCodeAsync(name);&lt;br&gt;
string code = isoResponse.Body.CountryISOCodeResult;&lt;/p&gt;

&lt;p&gt;// An unknown country is NOT an exception, the service just returns this text&lt;br&gt;
if (string.IsNullOrWhiteSpace(code) || code.StartsWith("No country found"))&lt;br&gt;
{&lt;br&gt;
trip.ErrorMessage = "No country found by that name";&lt;br&gt;
return trip;&lt;br&gt;
}&lt;br&gt;
trip.IsoCode = code;&lt;/p&gt;

&lt;p&gt;// Calls 2-5 all use the code from call 1&lt;br&gt;
var capitalResponse = await client.CapitalCityAsync(code);&lt;br&gt;
trip.Capital = capitalResponse.Body.CapitalCityResult;&lt;/p&gt;

&lt;p&gt;// Currency comes back as an OBJECT (tCurrency), not a string&lt;br&gt;
var currencyResponse = await client.CountryCurrencyAsync(code);&lt;br&gt;
tCurrency currency = currencyResponse.Body.CountryCurrencyResult;&lt;br&gt;
trip.Currency = string.IsNullOrWhiteSpace(currency?.sName) ? "None" : currency.sName;&lt;br&gt;
trip.CurrencyCode = currency?.sISOCode;&lt;/p&gt;

&lt;p&gt;var phoneResponse = await client.CountryIntPhoneCodeAsync(code);&lt;br&gt;
trip.PhoneCode = phoneResponse.Body.CountryIntPhoneCodeResult;&lt;/p&gt;

&lt;p&gt;var flagResponse = await client.CountryFlagAsync(code);&lt;br&gt;
trip.FlagUrl = flagResponse.Body.CountryFlagResult;&lt;/p&gt;

&lt;p&gt;// Our own data, also keyed by the ISO code&lt;br&gt;
trip.SignatureDish = SignatureDishes.TryGetValue(code, out string? dish)&lt;br&gt;
? dish&lt;br&gt;
: "Ask a local!";&lt;br&gt;
}&lt;br&gt;
catch (Exception ex)&lt;br&gt;
{&lt;br&gt;
trip.ErrorMessage = "The country service could not be reached: " + ex.Message;&lt;br&gt;
}&lt;br&gt;
finally&lt;br&gt;
{&lt;br&gt;
// Always close the connection (Abort if it broke)&lt;br&gt;
if (client.State == CommunicationState.Faulted)&lt;br&gt;
client.Abort();&lt;br&gt;
else&lt;br&gt;
await client.CloseAsync();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;return trip;&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A few details worth pointing out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Case sensitivity.&lt;/strong&gt; &lt;code&gt;canada&lt;/code&gt; returns nothing and &lt;code&gt;Canada&lt;/code&gt; works, so &lt;code&gt;ToTitleCase&lt;/code&gt; normalizes whatever the user types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Not found" isn't an error.&lt;/strong&gt; The service happily returns the string &lt;code&gt;"No country found by that name"&lt;/code&gt;, so we check for it ourselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Currency is a record.&lt;/strong&gt; Every other call returns a string, but &lt;code&gt;CountryCurrency&lt;/code&gt; returns a &lt;code&gt;tCurrency&lt;/code&gt; object. Grab &lt;code&gt;.sName&lt;/code&gt; for the display name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closing the client.&lt;/strong&gt; WCF clients hold a connection open, so close them when you're done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your generated client has no &lt;code&gt;.Body&lt;/code&gt;, the calls simply become:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;csharp&lt;br&gt;
string code = await client.CountryISOCodeAsync(name);&lt;br&gt;
trip.Capital = await client.CapitalCityAsync(code);&lt;br&gt;
tCurrency currency = await client.CountryCurrencyAsync(code);&lt;br&gt;
trip.PhoneCode = await client.CountryIntPhoneCodeAsync(code);&lt;br&gt;
trip.FlagUrl = await client.CountryFlagAsync(code);&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Register the service in Program.cs
&lt;/h2&gt;

&lt;p&gt;To get the service injected into our controller, register it. This is the template's &lt;code&gt;Program.cs&lt;/code&gt; with &lt;strong&gt;one line added&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
using FoodTripPlanner.Services;&lt;/p&gt;

&lt;p&gt;var builder = WebApplication.CreateBuilder(args);&lt;/p&gt;

&lt;p&gt;// Add services to the container.&lt;br&gt;
builder.Services.AddControllersWithViews();&lt;br&gt;
builder.Services.AddScoped(); // ★ added&lt;/p&gt;

&lt;p&gt;var app = builder.Build();&lt;/p&gt;

&lt;p&gt;// Configure the HTTP request pipeline.&lt;br&gt;
if (!app.Environment.IsDevelopment())&lt;br&gt;
{&lt;br&gt;
app.UseExceptionHandler("/Home/Error");&lt;br&gt;
app.UseHsts();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;app.UseHttpsRedirection();&lt;br&gt;
app.UseStaticFiles();&lt;/p&gt;

&lt;p&gt;app.UseRouting();&lt;/p&gt;

&lt;p&gt;app.UseAuthorization();&lt;/p&gt;

&lt;p&gt;app.MapControllerRoute(&lt;br&gt;
name: "default",&lt;br&gt;
pattern: "{controller=Home}/{action=Index}/{id?}");&lt;/p&gt;

&lt;p&gt;app.Run();&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If your template uses &lt;code&gt;app.MapStaticAssets()&lt;/code&gt; and &lt;code&gt;.WithStaticAssets()&lt;/code&gt;, keep those and just add the &lt;code&gt;AddScoped&lt;/code&gt; line.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: A small controller
&lt;/h2&gt;

&lt;p&gt;Because the service does the heavy lifting, the controller stays tiny. Right-click &lt;strong&gt;Controllers&lt;/strong&gt; → &lt;strong&gt;Add&lt;/strong&gt; → &lt;strong&gt;Controller&lt;/strong&gt; → &lt;strong&gt;MVC Controller - Empty&lt;/strong&gt; → &lt;code&gt;TripController&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`csharp&lt;br&gt;
using FoodTripPlanner.Models;&lt;br&gt;
using FoodTripPlanner.Services;&lt;br&gt;
using Microsoft.AspNetCore.Mvc;&lt;/p&gt;

&lt;p&gt;namespace FoodTripPlanner.Controllers&lt;br&gt;
{&lt;br&gt;
public class TripController : Controller&lt;br&gt;
{&lt;br&gt;
private readonly TripPlannerService _planner;&lt;/p&gt;

&lt;p&gt;public TripController(TripPlannerService planner)&lt;br&gt;
{&lt;br&gt;
_planner = planner;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// GET /Trip -&amp;gt; the search form&lt;br&gt;
[HttpGet]&lt;br&gt;
public IActionResult Index()&lt;br&gt;
{&lt;br&gt;
return View();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// POST /Trip -&amp;gt; call the SOAP service and show the result&lt;br&gt;
[HttpPost]&lt;br&gt;
[ValidateAntiForgeryToken]&lt;br&gt;
public async Task Index(string countryName)&lt;br&gt;
{&lt;br&gt;
TripInfo trip = await _planner.PlanAsync(countryName);&lt;br&gt;
return View("Result", trip);&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: The search form
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;Trip&lt;/code&gt; folder under &lt;code&gt;Views&lt;/code&gt;, then add &lt;code&gt;Index.cshtml&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
@{&lt;br&gt;
ViewData["Title"] = "Plan a Food Trip";&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;🍽️ Plan a Food Trip&lt;/h2&gt;

&lt;p&gt;Where do you want to eat next? Enter a country and we'll get you ready.&lt;/p&gt;

&lt;p&gt;Country&lt;/p&gt;

&lt;p&gt;Plan my trip&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9: The trip card
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Views/Trip/Result.cshtml&lt;/code&gt; shows either the error or the full trip card. Note the flag is shown as an &lt;strong&gt;actual image&lt;/strong&gt;, not a link:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
@model FoodTripPlanner.Models.TripInfo&lt;br&gt;
@{&lt;br&gt;
ViewData["Title"] = "Your Food Trip";&lt;br&gt;
}&lt;/p&gt;

&lt;h2&gt;Food trip to @Model.CountryName&lt;/h2&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/if"&gt;@if&lt;/a&gt; (!Model.Found)&lt;br&gt;
{&lt;/p&gt;

&lt;p&gt;@Model.ErrorMessage&lt;/p&gt;

&lt;p&gt;}&lt;br&gt;
else&lt;br&gt;
{&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/%40Model.FlagUrl" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/%40Model.FlagUrl" alt="Flag of @Model.CountryName" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Country code&lt;/th&gt;
&lt;td&gt;@Model.IsoCode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Land in (capital)&lt;/th&gt;
&lt;td&gt;@Model.Capital&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Pay for meals in&lt;/th&gt;
&lt;td&gt;@Model.Currency (@Model.CurrencyCode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Phone code for reservations&lt;/th&gt;
&lt;td&gt;+@Model.PhoneCode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Eat this first&lt;/th&gt;
&lt;td&gt;&lt;strong&gt;@Model.SignatureDish&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;br&gt;
}

&lt;p&gt;&lt;a&gt;Plan another trip&lt;/a&gt;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 10: Add it to the navigation
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;Views/Shared/_Layout.cshtml&lt;/code&gt;, find the &lt;code&gt;&amp;lt;ul class="navbar-nav ..."&amp;gt;&lt;/code&gt; list and add one item:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;Home&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;Privacy&lt;/a&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;a&gt;Plan a Trip&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 11: Take it for a spin
&lt;/h2&gt;

&lt;p&gt;Run the app, click &lt;strong&gt;Plan a Trip&lt;/strong&gt;, and try a few countries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You type&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Capital&lt;/th&gt;
&lt;th&gt;Currency&lt;/th&gt;
&lt;th&gt;Signature dish&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Canada&lt;/td&gt;
&lt;td&gt;CA&lt;/td&gt;
&lt;td&gt;Ottawa&lt;/td&gt;
&lt;td&gt;Canadian Dollars&lt;/td&gt;
&lt;td&gt;Poutine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;italy&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;td&gt;Rome&lt;/td&gt;
&lt;td&gt;Euro&lt;/td&gt;
&lt;td&gt;Neapolitan pizza&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;South Africa&lt;/td&gt;
&lt;td&gt;ZA&lt;/td&gt;
&lt;td&gt;Cape Town&lt;/td&gt;
&lt;td&gt;South Africa Rands&lt;/td&gt;
&lt;td&gt;Bobotie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bouvet Island&lt;/td&gt;
&lt;td&gt;BV&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Krone&lt;/td&gt;
&lt;td&gt;Ask a local!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wakanda&lt;/td&gt;
&lt;td&gt;No country found by that name&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lowercase &lt;code&gt;italy&lt;/code&gt; works thanks to &lt;code&gt;ToTitleCase&lt;/code&gt;, and the made-up country gets a friendly message instead of a crash.&lt;/p&gt;

&lt;p&gt;Here's how the calls chain together behind the scenes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
User types "italy"&lt;br&gt;
│&lt;br&gt;
▼ ToTitleCase → "Italy"&lt;br&gt;
CountryISOCode("Italy") ──► "IT" (or "No country found by that name" → stop)&lt;br&gt;
│&lt;br&gt;
├─► CapitalCity("IT") ──► "Rome"&lt;br&gt;
├─► CountryCurrency("IT") ──► tCurrency { sISOCode = "EUR", sName = "Euro" }&lt;br&gt;
├─► CountryIntPhoneCode("IT") ──► "39"&lt;br&gt;
├─► CountryFlag("IT") ──► "https://www.google.com/url?q=http://www.oorsprong.org/.../Italy.jpg&amp;amp;source=gmail&amp;amp;ust=1793896833445000&amp;amp;sa=E"&lt;br&gt;
└─► SignatureDishes["IT"] ──► "Neapolitan pizza" (our own data)&lt;br&gt;
│&lt;br&gt;
▼&lt;br&gt;
TripInfo object → Result.cshtml&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Using a different SOAP service
&lt;/h2&gt;

&lt;p&gt;The same recipe works for any SOAP service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add → Service Reference&lt;/strong&gt; with the new service's &lt;code&gt;?WSDL&lt;/code&gt; URL.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;Connected Services/&amp;lt;YourNamespace&amp;gt;/Reference.cs&lt;/code&gt; and search for &lt;code&gt;class&lt;/code&gt; and &lt;code&gt;Async(&lt;/code&gt; to find the client class, the &lt;code&gt;EndpointConfiguration&lt;/code&gt; enum, and each method's parameters and return types.&lt;/li&gt;
&lt;li&gt;Put the URL in &lt;code&gt;appsettings.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Call it with the same pattern:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;csharp&lt;br&gt;
var client = new XxxSoapClient(XxxSoapClient.EndpointConfiguration.XxxSoap, url);&lt;br&gt;
var response = await client.SomeMethodAsync(arg);&lt;br&gt;
var value = response.Body.SomeMethodResult; // or: var value = await client.SomeMethodAsync(arg);&lt;br&gt;
await client.CloseAsync();&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If a method returns an object (like &lt;code&gt;tCurrency&lt;/code&gt;), check its properties in &lt;code&gt;Reference.cs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Watch for "not found" values coming back as normal text, not just exceptions.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;With a generated client, calling SOAP from &lt;a href="https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793896833445000&amp;amp;sa=E" rel="noopener noreferrer"&gt;https://www.google.com/url?q=http://ASP.NET&amp;amp;source=gmail&amp;amp;ust=1793896833445000&amp;amp;sa=E&lt;/a&gt; Core comes down to: add the reference, create the client with a URL from config, &lt;code&gt;await&lt;/code&gt; each method, and read the result (sometimes out of &lt;code&gt;.Body&lt;/code&gt;). The pattern worth remembering is &lt;strong&gt;chaining&lt;/strong&gt;: one call gives you a key, and that key unlocks the rest. Bon appétit! 🍝&lt;/p&gt;

</description>
      <category>api</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Writing tests in python?</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 14 Oct 2026 02:28:10 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/writing-tests-in-python-k3l</link>
      <guid>https://dev.to/faith_in_errors_/writing-tests-in-python-k3l</guid>
      <description>&lt;p&gt;from colorama import init, Fore&lt;br&gt;
from duration import *&lt;/p&gt;

&lt;p&gt;def print_pass(the_data, i):&lt;br&gt;
    print(f'{Fore.GREEN}Test {i} with {the_data} passed')&lt;/p&gt;

&lt;p&gt;def print_fail(the_data, i):&lt;br&gt;
    print(f'''{Fore.RED}*** Test {i} with {the_data} failed.''')&lt;/p&gt;

&lt;p&gt;def print_tests():&lt;br&gt;
    _test_default = Duration(60)&lt;br&gt;
    _test_minutes = Duration(10, "m")&lt;br&gt;
    _test_hours = Duration(2, "h")&lt;br&gt;
    _test_sec = Duration(120, "s")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print('''
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Test #1 - simple declarations&lt;br&gt;
-----------------------------''')&lt;br&gt;
    test_objects = (&lt;br&gt;
        _test_default,&lt;br&gt;
        _test_minutes,&lt;br&gt;
        _test_hours,&lt;br&gt;
        _test_sec&lt;br&gt;
    )&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_print_out = (
    "60.00 s",
    "600.00 s",
    "7200.00 s",
    "120.00 s"
)

test_repr_out = (
    "Duration(60.00, 's')",
    "Duration(10.00, 'm')",
    "Duration(2.00, 'h')",
    "Duration(120.00, 's')"
)

for i, test_obj in enumerate(test_objects):
    try:
        assert str(test_obj) == test_print_out[i], f'Expected {test_print_out[i]} received {str(test_obj)}'
        print_pass(f'print of {test_obj}', i+1)
        assert repr(test_obj) == test_repr_out[i], f'Expected {test_repr_out[i]} received {repr(test_obj)}'
        print_pass(f'repr of {repr(test_obj)}', i+1)

    except AssertionError as test_data:
        print_fail(test_data, i+1)
        continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;def add_tests():&lt;br&gt;
    print('''&lt;/p&gt;

&lt;p&gt;Test #2 - Addition&lt;br&gt;
------------------''')&lt;br&gt;
    _test_default = Duration(60)&lt;br&gt;
    _test_minutes = Duration(5, "m")&lt;br&gt;
    _test_hours = Duration(1, "h")&lt;br&gt;
    _test_list = [Duration(10), Duration(2, 'm'), Duration(1, 'h')]&lt;br&gt;
    _test_inc = Duration(60)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_objects = (
    _test_default + _test_minutes,
    _test_default + 30,
    sum(_test_list),
    15 + _test_minutes
)

test_add_out = (
    "Duration(360.00, 's')",
    "Duration(90.00, 's')",
    "Duration(3730.00, 's')",
    "Duration(5.25, 'm')"
)

for i, test_obj in enumerate(test_objects):
    try:
        assert repr(test_obj) == test_add_out[i], f'Expected {test_add_out[i]} received {repr(test_obj)}'
        print_pass(f'print of {repr(test_obj)}', i+1)

    except AssertionError as test_data:
        print_fail(test_data, i+1)
        continue

_test_inc += 30
try:
    assert repr(_test_inc) == "Duration(90.00, 's')", f'Expected Duration(90.00, s) received {repr(_test_inc)}'
    print_pass(f'Incremental test of {repr(_test_inc)}', 1)
except AssertionError as test_data:
    print_fail(test_data, 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;def sub_tests():&lt;br&gt;
    print('''&lt;/p&gt;

&lt;p&gt;Test #3 - Test all subtraction cases&lt;br&gt;
------------------------------------''')&lt;br&gt;
    _test_default = Duration(120)&lt;br&gt;
    _test_minutes = Duration(1, "m")&lt;br&gt;
    _test_dec = Duration(100)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_objects = (
    _test_default - _test_minutes,
    _test_default - 30
)

test_sub_out = (
    "Duration(60.00, 's')",
    "Duration(90.00, 's')"
)

try:
    reverse_sub = 200 - Duration(60, "s")
    assert repr(reverse_sub) == "Duration(140.00, 's')", \
        f"Expected Duration(140.00, 's') received {repr(reverse_sub)}"
    print_pass(f'reverse subtraction: {repr(reverse_sub)}', 6)
except AssertionError as test_data:
    print_fail(test_data, 6)

for i, test_obj in enumerate(test_objects):
    try:
        assert repr(test_obj) == test_sub_out[i], f'Expected {test_sub_out[i]} received {repr(test_obj)}'
        print_pass(f'print of {repr(test_obj)}', i+1)

    except AssertionError as test_data:
        print_fail(test_data, i+1)
        continue

_test_dec -= 40
try:
    assert repr(_test_dec) == "Duration(60.00, 's')", f'Expected Duration(60.00, s) received {repr(_test_dec)}'
    print_pass(f'Decremental test of {repr(_test_dec)}', 1)
except AssertionError as test_data:
    print_fail(test_data, 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;def comp_tests():&lt;br&gt;
    print('''&lt;/p&gt;

&lt;p&gt;Test #4 - Test comparisons between Duration objects&lt;br&gt;
--------------------------''')&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_test_comp1 = Duration(60)
_test_comp2 = Duration(1, "m")
_test_comp7 = Duration(100)
_test_comp8 = Duration(101)

count = 1
try:
    assert _test_comp1 == _test_comp2, f'Equality tested failed for {repr(_test_comp1)} and {repr(_test_comp2)}'
    print_pass(f'comparison: {repr(_test_comp1)} = {repr(_test_comp2)} ', count)
except AssertionError as test_data:
    print_fail(test_data, count)

count += 1
try:
    assert not (_test_comp7 &amp;gt; _test_comp8), f'Greater than test failed for {repr(_test_comp7)} and {repr(_test_comp8)}'
    print_pass(f'comparison: {repr(_test_comp7)} not &amp;gt; {repr(_test_comp8)} ', count)
    assert _test_comp7 &amp;lt; _test_comp8, f'Less than test failed for {repr(_test_comp7)} and {repr(_test_comp8)}'
    print_pass(f'comparison: {repr(_test_comp7)} &amp;lt; {repr(_test_comp8)} ', count)
except AssertionError as test_data:
    print_fail(test_data, count)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;def exception_tests():&lt;br&gt;
    print('''&lt;/p&gt;

&lt;p&gt;Test #5 - Test Exceptions&lt;br&gt;
-------------------------''')&lt;br&gt;
    count = 1&lt;br&gt;
    try:&lt;br&gt;
        Duration("abc")&lt;br&gt;
        print_fail(f'Value error not thrown when value is string', count)&lt;br&gt;
    except ValueError as msg:&lt;br&gt;
        try:&lt;br&gt;
            assert str(msg) == "Value must be a number", f'Value error does not have correct message. Received {msg}'&lt;br&gt;
            print_pass(f'Value error thrown with message: {msg} as expected', count)&lt;br&gt;
        except AssertionError as test_data:&lt;br&gt;
            print_fail(test_data, count)&lt;br&gt;
    count += 1&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
    Duration(-10)
    print_fail(f'Less than zero exception not thrown when value is negative', count)
except DurationLessThanZeroException as msg:
    try:
        assert str(msg) == "Duration cannot be negative", f'Exception does not have correct message. Received {msg}'
        print_pass(f'Negative-value error thrown with message: {msg} as expected', count)
    except AssertionError as test_data:
        print_fail(test_data, count)

count += 1
try:
    Duration(10, "abc")
    print_fail(f'Incorrect unit exception not thrown', count)
except TimeUnitNotDefinedException as msg:
    try:
        assert str(msg) == "Unit cannot be used", f'Exception does not have correct message. Received {msg}'
        print_pass(f'Unit error thrown with message: {msg} as expected', count)
    except AssertionError as test_data:
        print_fail(test_data, count)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    init(autoreset=True)&lt;br&gt;
    print_tests()&lt;br&gt;
    add_tests()&lt;br&gt;
    sub_tests()&lt;br&gt;
    comp_tests()&lt;br&gt;
    exception_tests()&lt;/p&gt;

</description>
      <category>coding</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>Dice comprehension list in python</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 14 Oct 2026 02:26:49 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/dice-comprehension-list-in-python-4mhi</link>
      <guid>https://dev.to/faith_in_errors_/dice-comprehension-list-in-python-4mhi</guid>
      <description>&lt;p&gt;import random&lt;/p&gt;

&lt;p&gt;def main():&lt;br&gt;
    # Two Dice&lt;br&gt;
    dice_combos = [(d1, d2) for d1 in range(1, 7) for d2 in range(1, 7)]&lt;br&gt;
    dice_rolled = [random.choice(dice_combos) for _ in range(100)]&lt;br&gt;
    sum_of_rolls = {i: 0 for i in range(2, 13)}&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for roll in dice_rolled:
    sum_of_rolls[sum(roll)] += 1
print("2 Dice Rolled:", dice_rolled[:5], "...") # Preview
print("2 Dice Sums:", sum_of_rolls)

# Three Dice
dice_combos_3 = [(d1, d2, d3) for d1 in range(1, 7) for d2 in range(1, 7) for d3 in range(1, 7)]
dice_rolled_3 = [random.choice(dice_combos_3) for _ in range(100)]
sum_of_rolls_3 = {i: 0 for i in range(3, 19)}

for roll in dice_rolled_3:
    sum_of_rolls_3[sum(roll)] += 1
print("3 Dice Sums:", sum_of_rolls_3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

</description>
      <category>code</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Hello world app with dictionaries and comprehension lists in python.</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 14 Oct 2026 02:26:16 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/hello-world-app-with-dictionaries-and-comprehension-lists-in-python-1m0k</link>
      <guid>https://dev.to/faith_in_errors_/hello-world-app-with-dictionaries-and-comprehension-lists-in-python-1m0k</guid>
      <description>&lt;p&gt;def add_entry(phone_book):&lt;br&gt;
    name = input("Enter name: ").title()&lt;br&gt;
    if name in phone_book:&lt;br&gt;
        print("There is already an entry for a student by that name, try another name")&lt;br&gt;
    else:&lt;br&gt;
        phone = input("Enter phone number: ")&lt;br&gt;
        phone_book[name] = phone&lt;/p&gt;

&lt;p&gt;def search_entry(phone_book):&lt;br&gt;
    name = input("Enter name: ").title()&lt;br&gt;
    if name in phone_book:&lt;br&gt;
        print(f"{name}: {phone_book[name]}")&lt;br&gt;
    else:&lt;br&gt;
        print("There is no entry for a student by that name.")&lt;/p&gt;

&lt;p&gt;def search_prefix(phone_book):&lt;br&gt;
    prefix = input("Enter starting letters: ").title()&lt;br&gt;
    matches = {k: v for k, v in phone_book.items() if k.startswith(prefix)}&lt;br&gt;
    if not matches:&lt;br&gt;
        print("No matches found.")&lt;br&gt;
    else:&lt;br&gt;
        for k in sorted(matches.keys()):&lt;br&gt;
            print(f"{k}: {matches[k]}")&lt;/p&gt;

&lt;p&gt;def print_book(phone_book):&lt;br&gt;
    for key in sorted(phone_book.keys()):&lt;br&gt;
        print(f"{key}: {phone_book[key]}")&lt;/p&gt;

&lt;p&gt;def main():&lt;br&gt;
    phone_book = {}&lt;br&gt;
    while True:&lt;br&gt;
        choice = input("\n1-Add, 2-Search, 3-Search Prefix, 4-Print All, 5-Exit: ")&lt;br&gt;
        if choice == '1': add_entry(phone_book)&lt;br&gt;
        elif choice == '2': search_entry(phone_book)&lt;br&gt;
        elif choice == '3': search_prefix(phone_book)&lt;br&gt;
        elif choice == '4': print_book(phone_book)&lt;br&gt;
        elif choice == '5': break&lt;br&gt;
        else: print("Error: Invalid choice.")&lt;/p&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>coding</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Reading and writing with files in python?</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 14 Oct 2026 02:25:09 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/reading-and-writing-with-files-in-python-a9g</link>
      <guid>https://dev.to/faith_in_errors_/reading-and-writing-with-files-in-python-a9g</guid>
      <description>&lt;p&gt;def sum_column(filename):&lt;br&gt;
    total, error_count = 0, 0&lt;br&gt;
    with open(filename, 'r') as file:&lt;br&gt;
        for line in file:&lt;br&gt;
            try:&lt;br&gt;
                total += int(line.strip())&lt;br&gt;
            except ValueError:&lt;br&gt;
                error_count += 1&lt;br&gt;
    return total, error_count&lt;/p&gt;

&lt;p&gt;def sum_all(filename):&lt;br&gt;
    total, error_count = 0, 0&lt;br&gt;
    with open(filename, 'r') as file:&lt;br&gt;
        for line in file:&lt;br&gt;
            for num in line.split():&lt;br&gt;
                try:&lt;br&gt;
                    total += int(num)&lt;br&gt;
                except ValueError:&lt;br&gt;
                    error_count += 1&lt;br&gt;
    return total, error_count&lt;/p&gt;

&lt;p&gt;def main():&lt;br&gt;
    filename = input("Enter a file name: ")&lt;br&gt;
    operation = input("Enter operation (sum_column or sum_all): ")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
    if operation == "sum_column":
        result, errors = sum_column(filename)
        print(f"Sum: {result}, Errors: {errors}")
    elif operation == "sum_all":
        result, errors = sum_all(filename)
        print(f"Sum: {result}, Errors: {errors}")
    else:
        print("Invalid operation entered.")
except FileNotFoundError:
    print(f"User-friendly message: The file '{filename}' does not exist.")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>code</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Python classes ? being pythonic</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 14 Oct 2026 02:24:37 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/python-classes-being-pythonic-epe</link>
      <guid>https://dev.to/faith_in_errors_/python-classes-being-pythonic-epe</guid>
      <description>&lt;p&gt;class MassLessThanZeroException(Exception):&lt;br&gt;
    pass&lt;/p&gt;

&lt;p&gt;class UnitNotDefinedException(Exception):&lt;br&gt;
    pass&lt;/p&gt;

&lt;p&gt;class Mass:&lt;br&gt;
    _conversion = {&lt;br&gt;
        'mg': 0.000001,&lt;br&gt;
        'g': 0.001,&lt;br&gt;
        'kg': 1,&lt;br&gt;
        'lb': 0.453592,&lt;br&gt;
        'oz': 0.02835,&lt;br&gt;
        'stone': 6.35029&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def __init__(self, value, unit='kg'):
    if not isinstance(value, (int, float)) or isinstance(value, bool):
        raise ValueError("Value must be a number")
    if value &amp;lt; 0:
        raise MassLessThanZeroException("Value cannot be negative")
    if unit not in self._conversion:
        raise UnitNotDefinedException("Unit cannot be used")

    self.value = value
    self.unit = unit

def convert_to_kg(self):
    return self.value * Mass._conversion[self.unit]

def __str__(self):
    return f"{self.convert_to_kg():.2f} kg"

def __repr__(self):
    return f"Mass({self.value:.2f}, '{self.unit}')"

def __add__(self, other):
    if isinstance(other, (int, float)):
        total = self.convert_to_kg() + other
    else:
        total = self.convert_to_kg() + other.convert_to_kg()
    return Mass(total / Mass._conversion[self.unit], self.unit)

def __iadd__(self, other):
    return self + other

def __radd__(self, other):
    return self + other

def __sub__(self, other):
    if isinstance(other, (int, float)):
        total = self.convert_to_kg() - other
    else:
        total = self.convert_to_kg() - other.convert_to_kg()
    return Mass(max(0, total) / Mass._conversion[self.unit], self.unit)

def __isub__(self, other):
    return self - other

def __rsub__(self, other):
    if isinstance(other, (int, float)):
        total = other - self.convert_to_kg()
    else:
        total = other.convert_to_kg() - self.convert_to_kg()
    return Mass(max(0, total) / Mass._conversion[self.unit], self.unit)

def __lt__(self, other):
    return self.convert_to_kg() &amp;lt; other.convert_to_kg()

def __gt__(self, other):
    return self.convert_to_kg() &amp;gt; other.convert_to_kg()

def __eq__(self, other):
    return abs(self.convert_to_kg() - other.convert_to_kg()) &amp;lt; 0.01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>code</category>
      <category>coding</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>getting into python functional programming any thoughts ?</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 14 Oct 2026 02:19:00 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/getting-into-python-functional-programming-any-thoughts--3el0</link>
      <guid>https://dev.to/faith_in_errors_/getting-into-python-functional-programming-any-thoughts--3el0</guid>
      <description>&lt;p&gt;from functools import reduce&lt;/p&gt;

&lt;h1&gt;
  
  
  1. CUSTOM DISCOUNT CALCULATOR (Scenario: Retail Discounts)
&lt;/h1&gt;

&lt;p&gt;def make_discount_calculator(discount_rate):&lt;br&gt;
    def calculate_discount(price):&lt;br&gt;
        return price * discount_rate&lt;br&gt;
    return calculate_discount&lt;/p&gt;

&lt;h1&gt;
  
  
  2. CONVERT TO A PURE FUNCTION (Scenario: Applying Wholesale Markup)
&lt;/h1&gt;

&lt;p&gt;def apply_markup(costs):&lt;br&gt;
    if not costs:&lt;br&gt;
        return []&lt;br&gt;
    return [costs[0] * 1.25] + apply_markup(costs[1:])&lt;/p&gt;

&lt;h1&gt;
  
  
  3. ID NUMBER GENERATOR (Scenario: Database ID Assignment)
&lt;/h1&gt;

&lt;p&gt;def make_id_tracker(current_id):&lt;br&gt;
    def get_next_id():&lt;br&gt;
        return current_id, make_id_tracker(current_id + 1)&lt;br&gt;
    return get_next_id&lt;/p&gt;

&lt;h1&gt;
  
  
  4. REWRITE WITH YIELD (Scenario: Database ID Assignment Generator)
&lt;/h1&gt;

&lt;p&gt;def make_id_tracker_2(current_id):&lt;br&gt;
    while True:&lt;br&gt;
        yield current_id&lt;br&gt;
        current_id += 1&lt;/p&gt;

&lt;h1&gt;
  
  
  5. GAME SCORES REPORT (Scenario: Multiplayer Match Results)
&lt;/h1&gt;

&lt;p&gt;def process_scores(scores):&lt;br&gt;
    qualifying_scores = filter(lambda x: x &amp;gt;= 50, scores)&lt;br&gt;
    buffed_scores = map(lambda x: x + 10, qualifying_scores)&lt;br&gt;
    total_team_score = reduce(lambda x, y: x + y, buffed_scores, 0)&lt;br&gt;
    return total_team_score&lt;/p&gt;

&lt;h1&gt;
  
  
  6. INVENTORY TRACKER SIMULATION (Scenario: Warehouse Stock)
&lt;/h1&gt;

&lt;p&gt;def make_inventory(stock_level):&lt;br&gt;
    def inventory(action, quantity):&lt;br&gt;
        if action == "restock":&lt;br&gt;
            return make_inventory(stock_level + quantity)&lt;br&gt;
        elif action == "sell":&lt;br&gt;
            return make_inventory(stock_level - quantity)&lt;br&gt;
        elif action == "audit":&lt;br&gt;
            return stock_level, make_inventory(stock_level)&lt;br&gt;
        else:&lt;br&gt;
            raise ValueError("Unknown action")&lt;br&gt;
    return inventory&lt;/p&gt;

&lt;h1&gt;
  
  
  7. EVENT PROCESSING WITHOUT LOOPS (Scenario: Applying Buffs to Player Stats)
&lt;/h1&gt;

&lt;p&gt;def map_recursively(operation, elements):&lt;br&gt;
    if not elements:&lt;br&gt;
        return []&lt;br&gt;
    return [operation(elements[0])] + map_recursively(operation, elements[1:])&lt;/p&gt;

&lt;h1&gt;
  
  
  8. IMAGE PROCESSING PIPELINE (Scenario: Batch Image Resizing)
&lt;/h1&gt;

&lt;p&gt;def compose_funcs(f, g):&lt;br&gt;
    return lambda x: f(g(x))&lt;/p&gt;

&lt;p&gt;def make_image_processor():&lt;br&gt;
    downscale = lambda pixels: pixels * 0.5&lt;br&gt;
    apply_brightness = lambda pixels: pixels + 15&lt;br&gt;
    compress = lambda pixels: round(pixels, 0)&lt;br&gt;
    return compose_funcs(compress, compose_funcs(apply_brightness, downscale))&lt;/p&gt;

&lt;h1&gt;
  
  
  9. TEXT FORMATTING PIPELINE (Scenario: Chat Filter)
&lt;/h1&gt;

&lt;p&gt;def strip_vowels(word):&lt;br&gt;
    if not word:&lt;br&gt;
        return ""&lt;br&gt;
    char = word[0]&lt;br&gt;
    rest = strip_vowels(word[1:])&lt;br&gt;
    if char.lower() in "aeiou":&lt;br&gt;
        return rest&lt;br&gt;
    return char + rest&lt;/p&gt;

&lt;p&gt;def chain_processors(routines):&lt;br&gt;
    return lambda data: reduce(lambda current_val, routine: routine(current_val), routines, data)&lt;/p&gt;

&lt;h1&gt;
  
  
  10. FLATTENING DEPARTMENT DATA (Scenario: Corporate Org Chart)
&lt;/h1&gt;

&lt;p&gt;def unnest_structure(hierarchy):&lt;br&gt;
    if not hierarchy:&lt;br&gt;
        return []&lt;br&gt;
    if isinstance(hierarchy[0], list):&lt;br&gt;
        return unnest_structure(hierarchy[0]) + unnest_structure(hierarchy[1:])&lt;br&gt;
    return [hierarchy[0]] + unnest_structure(hierarchy[1:])&lt;/p&gt;

&lt;p&gt;def main():&lt;br&gt;
    print("--- 1. CUSTOM DISCOUNT CALCULATOR ---")&lt;br&gt;
    student_discount = make_discount_calculator(0.20)&lt;br&gt;
    print(f"20% discount on $200: ${student_discount(200)}")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print("\n--- 2. CONVERT TO A PURE FUNCTION ---")
costs = [100, 200, 50]
print(f"Original costs: {costs}")
print(f"With markup: {apply_markup(costs)}")

print("\n--- 3. ID NUMBER GENERATOR ---")
tracker = make_id_tracker(1000)
id1, tracker = tracker()
id2, tracker = tracker()
print(f"Generated IDs: {id1}, {id2}")

print("\n--- 4. REWRITE WITH YIELD ---")
tracker_gen = make_id_tracker_2(1000)
print(f"Generated IDs (yield): {next(tracker_gen)}, {next(tracker_gen)}")

print("\n--- 5. GAME SCORES REPORT ---")
scores = [40, 80, 55, 30, 90]
print(f"Processed Total Score: {process_scores(scores)}")

print("\n--- 6. INVENTORY TRACKER SIMULATION ---")
inv = make_inventory(500)
inv = inv("restock", 200)
inv = inv("sell", 50)
current_stock, inv = inv("audit", 0)
print(f"Current Inventory Stock: {current_stock}")

print("\n--- 7. EVENT PROCESSING WITHOUT LOOPS ---")
double_buff = lambda x: x * 2
stats = [10, 20, 30]
print(f"Buffed Stats: {map_recursively(double_buff, stats)}")

print("\n--- 8. IMAGE PROCESSING PIPELINE ---")
pipeline = make_image_processor()
print(f"Processed Pixel Value (from 100): {pipeline(100)}")

print("\n--- 9. TEXT FORMATTING PIPELINE ---")
strip_whitespace = lambda s: s.strip()
lowercase = lambda s: s.lower()
text_pipeline = chain_processors([strip_whitespace, lowercase, strip_vowels])
print(f"Processed Text: {text_pipeline('  HELLO World  ')}")

print("\n--- 10. FLATTENING DEPARTMENT DATA ---")
org_chart = ["Executive", ["Engineering", ["Frontend", "Backend"]], "HR"]
print(f"Flattened Org Chart: {unnest_structure(org_chart)}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    main()&lt;/p&gt;

</description>
      <category>functional</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Some asp.net fizz buzz testing</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 30 Sep 2026 02:28:17 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/some-aspnet-fizz-buzz-testing-1h1c</link>
      <guid>https://dev.to/faith_in_errors_/some-aspnet-fizz-buzz-testing-1h1c</guid>
      <description>&lt;p&gt;using System;&lt;br&gt;
using System.Collections.Generic;&lt;br&gt;
using PartA.Models; &lt;br&gt;
using Xunit;&lt;/p&gt;

&lt;p&gt;namespace PartA.Tests&lt;br&gt;
{&lt;br&gt;
    public class FizzBuzzTests&lt;br&gt;
    {&lt;br&gt;
        private readonly FizzBuzz _fizzBuzz;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public FizzBuzzTests()
    {
        _fizzBuzz = new FizzBuzz();
    }

    // 1st test - Returns list 1 to 100[cite: 3]
    [Fact]
    public void GetList_ReturnsList1To100_WhenNoParametersPassed()
    {
        // ACT
        List&amp;lt;string&amp;gt; result = _fizzBuzz.getList();

        // ASSERT
        Assert.Equal(100, result.Count);
        Assert.Equal("1", result[0]);
        Assert.Equal("Buzz", result[99]); // Number 100
    }

    // 2nd test - Fizz for 3 multiples[cite: 3]
    [Fact]
    public void GetList_ReturnsFizz_ForMultiplesOfThree()
    {
        // ACT
        List&amp;lt;string&amp;gt; result = _fizzBuzz.getList();

        // ASSERT
        Assert.Equal("Fizz", result[2]); // Index 2 represents number 3
        Assert.Equal("Fizz", result[8]); // Index 8 represents number 9
    }

    // 3rd test - Buzz for 5 multiples[cite: 3]
    [Fact]
    public void GetList_ReturnsBuzz_ForMultiplesOfFive()
    {
        // ACT
        List&amp;lt;string&amp;gt; result = _fizzBuzz.getList();

        // ASSERT
        Assert.Equal("Buzz", result[4]); // Index 4 represents number 5
        Assert.Equal("Buzz", result[9]); // Index 9 represents number 10
    }

    // 4th test - FizzBuzz for 3&amp;amp;5[cite: 3]
    [Fact]
    public void GetList_ReturnsFizzBuzz_ForMultiplesOfThreeAndFive()
    {
        // ACT
        List&amp;lt;string&amp;gt; result = _fizzBuzz.getList();

        // ASSERT
        Assert.Equal("FizzBuzz", result[14]); // Index 14 represents number 15
        Assert.Equal("FizzBuzz", result[29]); // Index 29 represents number 30
    }

    // 5th test - Upper limit with default[cite: 3]
    [Fact]
    public void GetList_RespectsUpperLimit_WhenPassed()
    {
        // ARRANGE
        int bramUpperLimit = 10;

        // ACT
        List&amp;lt;string&amp;gt; result = _fizzBuzz.getList(bramUpperLimit);

        // ASSERT
        Assert.Equal(10, result.Count);
        Assert.Equal("Buzz", result[9]); // Number 10
    }

    // 6th test - Lower limit with default[cite: 3]
    [Fact]
    public void GetList_RespectsLowerLimit_WhenPassed()
    {
        // ARRANGE
        int bramUpperLimit = 20;
        int bramLowerLimit = 15;

        // ACT
        List&amp;lt;string&amp;gt; result = _fizzBuzz.getList(bramUpperLimit, bramLowerLimit);

        // ASSERT
        Assert.Equal(6, result.Count); // 15, 16, 17, 18, 19, 20
        Assert.Equal("FizzBuzz", result[0]); // Number 15
    }

    // 7th test - Argument out of Range exception[cite: 3]
    [Fact]
    public void GetList_ThrowsArgumentOutOfRangeException_WhenLowerGreaterThanUpper()
    {
        // ARRANGE
        int bramUpperLimit = 10;
        int bramLowerLimit = 20;

        // ACT &amp;amp; ASSERT
        Assert.Throws&amp;lt;ArgumentOutOfRangeException&amp;gt;(() =&amp;gt; _fizzBuzz.getList(bramUpperLimit, bramLowerLimit));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>softwaredevelopment</category>
      <category>testing</category>
    </item>
    <item>
      <title>Getting into C# testing</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 30 Sep 2026 02:18:23 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/getting-into-c-testing-3kho</link>
      <guid>https://dev.to/faith_in_errors_/getting-into-c-testing-3kho</guid>
      <description>&lt;p&gt;Here are the tests I am running for a school registration system : using System.Collections.Generic;&lt;br&gt;
using System.ComponentModel.DataAnnotations;&lt;br&gt;
using SportsWeb.Models;&lt;br&gt;
using Xunit;&lt;/p&gt;

&lt;p&gt;namespace SportsWeb.Tests.Models&lt;br&gt;
{&lt;br&gt;
    public class RegistrationViewModelValidationTests&lt;br&gt;
    {&lt;br&gt;
        private static IList Validate(object model)&lt;br&gt;
        {&lt;br&gt;
            var ctx = new ValidationContext(model, serviceProvider: null, items: null);&lt;br&gt;
            var results = new List();&lt;br&gt;
            Validator.TryValidateObject(model, ctx, results, validateAllProperties: true);&lt;br&gt;
            return results;&lt;br&gt;
        }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [Fact]
    public void Missing_Season_IsInvalid()
    {
        // ARRANGE: Simulating a missing drop-down selection
        var vm = new RegistrationViewModel
        {
            AthleteName = "Bram",
            Season = null, 
            Sport = "Basketball"
        };

        // ACT
        var results = Validate(vm);

        // ASSERT
        Assert.Contains(results, r =&amp;gt; r.MemberNames.Contains(nameof(RegistrationViewModel.Season)));
    }

    [Fact]
    public void Missing_Sport_IsInvalid()
    {
        // ARRANGE: Simulating no radio button being selected
        var vm = new RegistrationViewModel
        {
            AthleteName = "Bram",
            Season = "Fall",
            Sport = null 
        };

        var results = Validate(vm);

        Assert.Contains(results, r =&amp;gt; r.MemberNames.Contains(nameof(RegistrationViewModel.Sport)));
    }

    [Fact]
    public void Valid_RegistrationModel_Passes()
    {
        // ARRANGE
        var vm = new RegistrationViewModel
        {
            AthleteName = "Bram",
            Season = "Winter",
            Sport = "Basketball"
        };

        // ACT
        var results = Validate(vm);

        // ASSERT
        Assert.Empty(results);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;using System;&lt;br&gt;
using SportsWeb.Models;&lt;br&gt;
using SportsWeb.Services;&lt;br&gt;
using Xunit;&lt;/p&gt;

&lt;p&gt;namespace SportsWeb.Tests.Services&lt;br&gt;
{&lt;br&gt;
    public class RegistrationServiceTests&lt;br&gt;
    {&lt;br&gt;
        private readonly IRegistrationService _service;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public RegistrationServiceTests()
    {
        // Testing the concrete service implementation
        _service = new RegistrationService(); 
    }

    [Fact]
    public void CalculateFee_ReturnsStandardRate_ForWinterBasketball()
    {
        // Arrange
        var vm = new RegistrationViewModel { Season = "Winter", Sport = "Basketball" };

        // Act
        decimal fee = _service.CalculateFee(vm);

        // Assert
        Assert.Equal(150m, fee);
    }

    [Theory]
    [InlineData("Summer", "Soccer", 100)]
    [InlineData("Spring", "Tennis", 120)]
    public void CalculateFee_ReturnsCorrectRates_ForAllSeasons(string season, string sport, decimal expectedFee)
    {
        // Arrange
        var vm = new RegistrationViewModel { Season = season, Sport = sport };

        // Act
        decimal fee = _service.CalculateFee(vm);

        // Assert
        Assert.Equal(expectedFee, fee);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;here are the controller tests : using Microsoft.AspNetCore.Mvc;&lt;br&gt;
using Moq;&lt;br&gt;
using SportsWeb.Controllers;&lt;br&gt;
using SportsWeb.Models;&lt;br&gt;
using SportsWeb.Services;&lt;br&gt;
using Xunit;&lt;/p&gt;

&lt;p&gt;namespace SportsWeb.Tests.Controllers&lt;br&gt;
{&lt;br&gt;
    public class RegistrationControllerTests&lt;br&gt;
    {&lt;br&gt;
        private readonly Mock _mockService;&lt;br&gt;
        private readonly RegistrationController _controller;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public RegistrationControllerTests()
    {
        _mockService = new Mock&amp;lt;IRegistrationService&amp;gt;();
        _controller = new RegistrationController(_mockService.Object);
    }

    [Fact]
    public void CreatePost_InvalidModel_ReturnsSameView()
    {
        // ARRANGE
        // Simulate ASP.NET Core catching a missing radio button selection
        _controller.ModelState.AddModelError("Sport", "Please select a sport.");

        var vm = new RegistrationViewModel { AthleteName = "Bram", Season = "Fall", Sport = null };

        // ACT
        var result = _controller.Create(vm) as ViewResult;

        // ASSERT
        Assert.NotNull(result);
        Assert.False(_controller.ModelState.IsValid);
        Assert.Same(vm, result.Model); // Ensure the form is sent back to the user with their inputs intact

        // Verify the database/service was NOT called since the model was invalid
        _mockService.VerifyNoOtherCalls(); 
    }

    [Fact]
    public void CreatePost_ValidModel_CallsServiceAndRedirects()
    {
        // ARRANGE
        var vm = new RegistrationViewModel { AthleteName = "Bram", Season = "Winter", Sport = "Basketball" };

        // Set up the mock to return true when saving
        _mockService.Setup(s =&amp;gt; s.ProcessRegistration(vm)).Returns(true);

        // ACT
        var result = _controller.Create(vm) as RedirectToActionResult;

        // ASSERT
        Assert.NotNull(result);
        Assert.Equal("Success", result.ActionName); // Ensure it redirects to the Success page

        // Verify the service was called exactly once with the provided model
        _mockService.Verify(s =&amp;gt; s.ProcessRegistration(vm), Times.Once);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>Getting started with C# SOLID Principles</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Wed, 30 Sep 2026 02:00:55 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/getting-started-with-c-solid-principles-2h44</link>
      <guid>https://dev.to/faith_in_errors_/getting-started-with-c-solid-principles-2h44</guid>
      <description>&lt;p&gt;In my repo I've got this `using MusicLab.Projects.Models;&lt;br&gt;
using System.Collections.Generic;&lt;br&gt;
using System.Linq;&lt;/p&gt;

&lt;p&gt;namespace MusicLab.Projects.Data&lt;br&gt;
{&lt;br&gt;
public class InstrumentRepository : IInstrumentRepository&lt;br&gt;
{&lt;br&gt;
private readonly MusicDbContext _context;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public InstrumentRepository(MusicDbContext context)
{
    _context = context;
}

public List&amp;lt;Instrument&amp;gt; GetInstruments()
{
    return _context.GetInstruments();
}

public List&amp;lt;Instrument&amp;gt; GetInstrumentsByCategory(string category)
{
    return _context.GetInstruments().Where(i =&amp;gt; i.Category == category).ToList();
}

public Instrument FindById(int id)
{
    return _context.FindById(id);
}

public void Add(Instrument i)
{
    _context.Add(i);
}

public void Update(Instrument i)
{
    _context.Update(i);
}

public void Remove(int id)
{
    _context.Remove(id);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}`&lt;/p&gt;

&lt;p&gt;and then in my interface :&lt;/p&gt;

&lt;p&gt;C#&lt;br&gt;
using MusicLab.Projects.Models;&lt;/p&gt;

&lt;p&gt;namespace MusicLab.Projects.Utils&lt;br&gt;
{&lt;br&gt;
    public interface ITaxCalculator&lt;br&gt;
    {&lt;br&gt;
        decimal CalculateTax(Instrument instrument);&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class DefaultTaxCalculator : ITaxCalculator
{
    public decimal CalculateTax(Instrument instrument)
    {
        // Standard tax calculation replacing the static method
        return instrument.Price * 0.15m; 
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
and then my csv exporter :&lt;/p&gt;

&lt;p&gt;C#&lt;br&gt;
using MusicLab.Projects.Models;&lt;br&gt;
using System.Collections.Generic;&lt;br&gt;
using System.Text;&lt;/p&gt;

&lt;p&gt;namespace MusicLab.Projects.Utils&lt;br&gt;
{&lt;br&gt;
    public interface ICsvExporter&lt;br&gt;
    {&lt;br&gt;
        string ExportInstrumentsCsv(List instruments);&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class CsvExporter : ICsvExporter
{
    public string ExportInstrumentsCsv(List&amp;lt;Instrument&amp;gt; instruments)
    {
        var sb = new StringBuilder();
        sb.AppendLine("Id,Name,Price,Category");
        foreach (var i in instruments)
        {
            sb.AppendLine($"{i.Id},{i.Name},{i.Price},{i.Category}");
        }
        return sb.ToString();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
and then the controller :&lt;/p&gt;

&lt;p&gt;C#&lt;br&gt;
using Microsoft.AspNetCore.Mvc;&lt;br&gt;
using MusicLab.Projects.Data;&lt;br&gt;
using MusicLab.Projects.Models;&lt;br&gt;
using MusicLab.Projects.Utils;&lt;br&gt;
using System.Collections.Generic;&lt;br&gt;
using System.Text;&lt;/p&gt;

&lt;p&gt;namespace MusicLab.Projects.Controllers&lt;br&gt;
{&lt;br&gt;
    public class InstrumentsController : Controller&lt;br&gt;
    {&lt;br&gt;
        private readonly IInstrumentRepository _repository;&lt;br&gt;
        private readonly ICsvExporter _exporter;&lt;br&gt;
        private readonly ITaxCalculator _taxCalculator;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public InstrumentsController(IInstrumentRepository repository, ICsvExporter exporter, ITaxCalculator taxCalculator)
    {
        _repository = repository;
        _exporter = exporter;
        _taxCalculator = taxCalculator;
    }

    public IActionResult Index(string category)
    {
        List&amp;lt;Instrument&amp;gt; instruments;

        if (!string.IsNullOrWhiteSpace(category))
        {
            instruments = _repository.GetInstrumentsByCategory(category);
        }
        else
        {
            instruments = _repository.GetInstruments();
        }

        return View(instruments);
    }

    public IActionResult Details(int id)
    {
        Instrument instrument = _repository.FindById(id);
        if (instrument == null) return NotFound();

        ViewData["Tax"] = _taxCalculator.CalculateTax(instrument);
        return View(instrument);
    }

    [HttpPost]
    public IActionResult Create(Instrument model)
    {
        if (model == null) return BadRequest("No data");

        // Replaced manual model.IsValid() with built-in ModelState validation
        if (!ModelState.IsValid) return BadRequest("Invalid instrument"); 

        _repository.Add(model);
        return Redirect("/Instruments/Index");
    }

    public IActionResult Export()
    {
        List&amp;lt;Instrument&amp;gt; instruments = _repository.GetInstruments();
        string csv = _exporter.ExportInstrumentsCsv(instruments);
        byte[] bytes = Encoding.UTF8.GetBytes(csv);
        return File(bytes, "text/csv", "instruments.csv");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
and then program.cs :&lt;/p&gt;

&lt;p&gt;C#&lt;br&gt;
using Microsoft.AspNetCore.Builder;&lt;br&gt;
using Microsoft.Extensions.DependencyInjection;&lt;br&gt;
using MusicLab.Projects.Data;&lt;br&gt;
using MusicLab.Projects.Utils;&lt;/p&gt;

&lt;p&gt;var builder = WebApplication.CreateBuilder(args);&lt;/p&gt;

&lt;p&gt;builder.Services.AddControllersWithViews();&lt;/p&gt;

&lt;p&gt;// Register the mock DbContext as a Singleton so the list persists in memory&lt;br&gt;
builder.Services.AddSingleton(); &lt;/p&gt;

&lt;p&gt;// Register dependencies for Dependency Injection&lt;br&gt;
builder.Services.AddScoped();&lt;br&gt;
builder.Services.AddScoped();&lt;br&gt;
builder.Services.AddScoped();&lt;/p&gt;

&lt;p&gt;var app = builder.Build();&lt;/p&gt;

&lt;p&gt;app.UseRouting();&lt;br&gt;
app.MapControllerRoute(&lt;br&gt;
    name: "default",&lt;br&gt;
    pattern: "{controller=Instruments}/{action=Index}/{id?}");&lt;/p&gt;

&lt;p&gt;app.Run();&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Using sessions in asp.net anyone have any insight?</title>
      <dc:creator>FaithInErrorsZORO</dc:creator>
      <pubDate>Fri, 17 Apr 2026 03:39:10 +0000</pubDate>
      <link>https://dev.to/faith_in_errors_/using-sessions-in-aspnet-anyone-have-any-insight-3pnn</link>
      <guid>https://dev.to/faith_in_errors_/using-sessions-in-aspnet-anyone-have-any-insight-3pnn</guid>
      <description>&lt;p&gt;here is how i set it up in program.cs : &lt;/p&gt;

&lt;p&gt;builder.Services.AddDistributedMemoryCache();&lt;br&gt;
builder.Services.AddSession(options =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    options.IdleTimeout = TimeSpan.FromMinutes(10); // [cite: 16]&lt;br&gt;
    options.Cookie.HttpOnly = true;&lt;br&gt;
    options.Cookie.IsEssential = true;&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;var app = builder.Build();&lt;/p&gt;

&lt;p&gt;[cite_start]// 2. Add Session Middleware (between Routing and Endpoints) [cite: 17]&lt;br&gt;
app.UseRouting();&lt;br&gt;
app.UseSession(); &lt;br&gt;
app.UseAuthorization();&lt;/p&gt;

&lt;p&gt;here is how i reference the session&lt;/p&gt;

&lt;p&gt;[[HttpPost]&lt;br&gt;
public IActionResult Index(string lastName)&lt;br&gt;
{&lt;br&gt;
    var player = _context.Players.FirstOrDefault(p =&amp;gt; p.LastName == lastName);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (player == null)
{
    ViewBag.Message = "No player found with that last name.";
    return View();
}

// Store IDs in Session for later use
HttpContext.Session.SetString("playerId", player.PlayerId.ToString());
HttpContext.Session.SetInt32("coachId", player.CoachId ?? 0);
HttpContext.Session.SetInt32("teamId", player.TeamId ?? 0);

return View("Display", player);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;action to show the stats:&lt;/p&gt;

&lt;p&gt;public IActionResult ShowStats()&lt;br&gt;
{&lt;br&gt;
    var pId = HttpContext.Session.GetString("playerId");&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var playerWithStats = _context.Players
    .Include(p =&amp;gt; p.GameRecords)
        .ThenInclude(gr =&amp;gt; gr.StatCategory)
    .FirstOrDefault(p =&amp;gt; p.PlayerId.ToString() == pId);

return View(playerWithStats);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;dif types : &lt;/p&gt;

&lt;p&gt;public IActionResult ShowCoach()&lt;br&gt;
{&lt;br&gt;
    var pId = HttpContext.Session.GetString("playerId");&lt;br&gt;
    var cId = HttpContext.Session.GetInt32("coachId");&lt;br&gt;
    var tId = HttpContext.Session.GetInt32("teamId");&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var player = _context.Players.Find(int.Parse(pId));
var coach = _context.Coaches.Find(cId);
var team = _context.Teams.Find(tId);

// Join class/ViewModel to pass all three objects to the view
var viewModel = new PlayerCoachTeamViewModel { 
    Player = player, 
    Coach = coach, 
    Team = team 
};

return View(viewModel);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

</description>
      <category>backend</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
