Consume Web API By MVC In .NET Core (2), Client

This article will give you a way  to consume Web API by a ASP.NET MVC Client in .NET Core with one line of code.
 

Introduction

 
In the previous article (Part I of this article),  we created a ASP.NET Core MVC app and associate a Web API service in it:
  •  MVC is a client/server app, with a web page as a client and SQL server as server, linked by Entity Framework;
  •  Web API is a Server side service, with a RESTful output for consumer, that is linked to database by entity framework.
For our test purposes, MVC and Web API are against two different database, MVC is against the database pubs, while Web API is against database DB_Demo_API.
 
In this article, we will make the MVC app as a client to consume Web API. For the purposes of convenient analysis and comparison, we will make another MVC module (controller/view) that's exactly the same as the previous MVC module but with a different name, StoresMVCCallWebAPI controller (see details from Part I, linked above, Part A, Step 1, 3, Add controller).
 
The added Controller is like this:
 
Consume Web API By MVC Client In .NET Core Client
 
Visual Studio Create
  • A StroesMVCCallWebAPI controller (Controllers/StoresMVCCallWebAPIController.cs)
  • Razor view files for Create, Delete, Details, Edit, and Index pages (Views/StoresMVCCallWebAPI/*.cshtml)
Consume Web API By MVC Client In .NET Core Client
 
The behavior of the new controller,  StroesMVCCallWebAPI, is exactly the same as the old MVC controller, StroesMVC.
 

Run and Test the app

 
Modify the header of the file: Views/Shared/_layout.cshtml Views, shown below:
  • At the app level, we modify as StoresMVCCallWebAPI controller, named as MVC Call Web API
  • Move StoresMVC controller to the  second level, with name as MVC app
  1. <header>    
  2.     <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">    
  3.         <div class="container">    
  4.             <a class="navbar-brand" asp-area="" asp-controller="StoresMVCCallWebAPI" asp-action="Index"><b>MVC Call Web API</b></a>    
  5.             <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"    
  6.                     aria-expanded="false" aria-label="Toggle navigation">    
  7.                 <span class="navbar-toggler-icon"></span>    
  8.             </button>    
  9.             <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">    
  10.                 <ul class="navbar-nav flex-grow-1">    
  11.                     <li class="nav-item">    
  12.                         <a class="nav-link text-dark" asp-area="" asp-controller="StoresMVC" asp-action="Index">MVC app</a>    
  13.                     </li>    
  14.                     <li class="nav-item">    
  15.                         <a class="nav-link text-dark" asp-area="" asp-controller="Swagger" asp-action="Index">Web API</a>    
  16.                     </li>    
  17.                     <li class="nav-item">    
  18.                         <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>    
  19.                     </li>    
  20.                     <li class="nav-item">    
  21.                         <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>    
  22.                     </li>    
  23.                 </ul>    
  24.             </div>    
  25.         </div>    
  26.     </nav>    
  27. </header>      
Then, we run the app,
 
Consume Web API By MVC Client In .NET Core Client
 
We can see the two controller endpoints: https://localhost:44350/StoresMVCCallWebAPI (above) and https://localhost:44350/StoresMVC (below) are exactly the same, because the are against the same database --- pubs,
 
Consume Web API By MVC Client In .NET Core Client
 
While the Web API (Swagger),
 
Consume Web API By MVC Client In .NET Core Client
 
 has a different data set, that is due the fact that it is against the different database: DB_Demo_API,
 
Consume Web API By MVC Client In .NET Core Client
 
At the end of the article, the controller StoresMVCCallWebAPI will consume the Web API, then these two will share the same database, and get the same result.
 

How to Consume RESTful APIs

 
We can see the most comprehensive list of ways to consume RESTful APIs in your C# projects from this article 《A Few Great Ways to Consume RESTful API in C#,we borrowed here,
 
"There are several ways to consume a RESTful API in C#,
  •     HttpWebRequest/Response Class
  •     WebClient Class
  •     HttpClient Class
  •     RestSharp NuGet Package
  •     ServiceStack Http Utils
  •     Flurl
  •     DalSoft.RestClient
    Every one of these has pros and cons."
     
    In this article, we will choose to use HttpClient from Microsoft for our project. In practice or production, you may choose different ones.
     

    One Line Code Implementation

     
    The database context is used in each of the CRUD methods in the both MVC Controller and Web API ApiController. They have the same methods, same signatures, and implementations.  For each action, we will use one line code to redirect the direct database pubs, to the Web API that is against database DB_Demo_API. 
     
    POST
     
    We start from Create,  because this is simplest. Get rid of all other actions, we have the controller class with Create method:
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Linq;  
    4. using System.Net.Http;  
    5. using System.Threading.Tasks;  
    6. using Microsoft.AspNetCore.Mvc;  
    7. using Microsoft.EntityFrameworkCore;  
    8. using MVCCallWebAPI.Models.DB;  
    9.   
    10. namespace MVCCallWebAPI.Controllers  
    11. {  
    12.     public class StoresMVCCallWebAPIController : Controller  
    13.     {  
    14.         private readonly pubsContext _context;  
    15.   
    16.         public StoresMVCCallWebAPIController(pubsContext context)  
    17.         {  
    18.             _context = context;  
    19.         }  
    20.    
    21.         // POST: StoresMVCCallWebAPI/Create  
    22.         // To protect from overposting attacks, enable the specific properties you want to bind to.  
    23.         // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.  
    24.         [HttpPost]  
    25.         [ValidateAntiForgeryToken]  
    26.         public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)  
    27.         {  
    28.             if (ModelState.IsValid)  
    29.             {  
    30.                 _context.Add(store);  
    31.                 await _context.SaveChangesAsync();  
    32.                 return RedirectToAction(nameof(Index));  
    33.             }  
    34.             return View(store);  
    35.         }  
    36.     }  
    37. }  
    The Create method with an input Object Store, the following two-line code saves the object into the database (pubs) through entity framework.
    1. _context.Add(store);      
    2. await _context.SaveChangesAsync();      
    Replace this two line code with class HttpClient, and the method PostAsJsonAsync to call Web API,
    1. HttpClient client = new HttpClient();    
    2. string url = "https://localhost:44350/api/storesAPI/";    
    3. await client.PostAsJsonAsync<Store>(url, store);   
    We got the Create method like this,
    1. public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)  
    2. {  
    3.     if (ModelState.IsValid)  
    4.     {  
    5.         //_context.Add(store);  
    6.         //await _context.SaveChangesAsync();  
    7.   
    8.         // Consume API  
    9.         HttpClient client = new HttpClient();  
    10.         string url = "https://localhost:44350/api/storesWebAPI/";  
    11.   
    12.         await client.PostAsJsonAsync<Store>(url, store);  
    13.   
    14.         return RedirectToAction(nameof(Index));  
    15.     }  
    16.     return View(store);  
    17. }  
    We can move the two line shared code (Create HttpClient class and define url address ) into class level, then we only use one line code to complete the job to consume Web API for the Create method.
    1. using System;    
    2. using System.Collections.Generic;    
    3. using System.Linq;    
    4. using System.Net.Http;    
    5. using System.Net.Http.Json;    
    6. using System.Threading.Tasks;    
    7. using Microsoft.AspNetCore.Mvc;    
    8. using Microsoft.EntityFrameworkCore;    
    9. using Newtonsoft.Json;    
    10. using WebMVCCore5.Models.DB;    
    11.     
    12. namespace WebMVCCore5.Controllers    
    13. {    
    14.     public class StoresMVCCallAPIController : Controller    
    15.     {    
    16.         private readonly pubsContext _context;    
    17.     
    18.        HttpClient client = new HttpClient();    
    19.        string url = "https://localhost:44330/api/storesWebAPI/";    
    20.     
    21.         public StoresMVCCallAPIController(pubsContext context)    
    22.         {    
    23.             _context = context;    
    24.         }    
    25.     
    26.         // POST: StoresMVCCallAPI/Create    
    27.         // To protect from overposting attacks, enable the specific properties you want to bind to.    
    28.         // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.    
    29.         [HttpPost]    
    30.         [ValidateAntiForgeryToken]    
    31.         public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)    
    32.         {    
    33.             if (ModelState.IsValid)    
    34.             {    
    35.                 //_context.Add(store);    
    36.                 //await _context.SaveChangesAsync();    
    37.     
    38.                 // Consume API      
    39.                 await client.PostAsJsonAsync<Store>(url, store);    
    40.     
    41.                 return RedirectToAction(nameof(Index));    
    42.             }    
    43.             return View(store);    
    44.         }     
    DELETE
     
    We use DeleteAsync method to do the job,
    1. // POST: StoresMVCCallAPI/Delete/5    
    2. [HttpPost, ActionName("Delete")]    
    3. [ValidateAntiForgeryToken]    
    4. public async Task<IActionResult> DeleteConfirmed(string id)    
    5. {    
    6.     // Original Code:    
    7.     //var store = await _context.Stores.FindAsync(id);    
    8.     //_context.Stores.Remove(store);    
    9.     //await _context.SaveChangesAsync();    
    10.     
    11.     // Consume API    
    12.     await client.DeleteAsync(url + id);    
    13.     
    14.     return RedirectToAction(nameof(Index));    
    15. }     
     
    PUT (Edit)
     
    We need to bring two parameters, one is id, another is the object, and we use PutAsJsonAsync method
    1. try    
    2. {    
    3.     // Original code:    
    4.     //_context.Update(store);    
    5.     //await _context.SaveChangesAsync();    
    6.     
    7.     // Consume API    
    8.     await client.PutAsJsonAsync<Store>(url + id, store);    
    9. }      
    GET/id
     
    There are three places to use the GET method, but actually, they are the same. We use GetStringAsync. Here, due to getting data, we need to Deserialize the JSON into class, we use JsonConvert.DeserializeObject in Newtonsoft.Json namespace.
    1. // GET: StoresMVCCallAPI/Delete/5    
    2. public async Task<IActionResult> Delete(string id)    
    3. {    
    4.     if (id == null)    
    5.     {    
    6.         return NotFound();    
    7.     }    
    8.     
    9.     // Original code:    
    10.     //var store = await _context.Stores    
    11.     //    .FirstOrDefaultAsync(m => m.Stor_Id == id);    
    12.     
    13.     // Consume API    
    14.     var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));    
    15.     
    16.     if (store == null)    
    17.     {    
    18.         return NotFound();    
    19.     }    
    20.     
    21.     return View(store);    
    22. }    
    GET
     
    The same as Get/ID, but we use List<Store>
    1. // GET: StoresMVCCallAPI    
    2. public async Task<IActionResult> Index()    
    3. {    
    4.     // Original code:    
    5.     //return View(await _context.Stores.ToListAsync());    
    6.     
    7.     // Consume API    
    8.     return View(JsonConvert.DeserializeObject<List<Store>>(await client.GetStringAsync(url)).ToList());    
    9. }     
    Finally, we got the full code where the changed one line of code for each method noted as // Consume API, and the // Original code is comment out:
    1. using System.Collections.Generic;    
    2. using System.Linq;    
    3. using System.Net.Http;    
    4. using System.Net.Http.Json;    
    5. using System.Threading.Tasks;    
    6. using Microsoft.AspNetCore.Mvc;    
    7. using Microsoft.EntityFrameworkCore;    
    8. using MVCCallWebAPI.Models.DB;    
    9. using Newtonsoft.Json;    
    10.     
    11. namespace MVCCallWebAPI.Controllers    
    12. {    
    13.     public class StoresMVCCallWebAPIController : Controller    
    14.     {    
    15.         private readonly pubsContext _context;    
    16.     
    17.        HttpClient client = new HttpClient();    
    18.        string url = "https://localhost:44350/api/storesWebAPI/";    
    19.     
    20.         public StoresMVCCallWebAPIController(pubsContext context)    
    21.         {    
    22.             _context = context;    
    23.         }    
    24.     
    25.     
    26.         // GET: StoresMVCCallAPI    
    27.         public async Task<IActionResult> Index()    
    28.         {    
    29.             // Original code:    
    30.             //return View(await _context.Stores.ToListAsync());    
    31.     
    32.             // Consume API    
    33.             return View(JsonConvert.DeserializeObject<List<Store>>(await client.GetStringAsync(url)).ToList());    
    34.         }    
    35.     
    36.         // GET: StoresMVCCallWebAPI/Details/5    
    37.         public async Task<IActionResult> Details(string id)    
    38.         {    
    39.             if (id == null)    
    40.             {    
    41.                 return NotFound();    
    42.             }    
    43.     
    44.             // Original code:    
    45.             //var store = await _context.Stores    
    46.             //    .FirstOrDefaultAsync(m => m.Stor_Id == id);    
    47.     
    48.             // Consume API    
    49.             var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));    
    50.     
    51.             if (store == null)    
    52.             {    
    53.                 return NotFound();    
    54.             }    
    55.     
    56.             return View(store);    
    57.         }    
    58.     
    59.         // GET: StoresMVCCallWebAPI/Create    
    60.         public IActionResult Create()    
    61.         {    
    62.             return View();    
    63.         }    
    64.     
    65.         // POST: StoresMVCCallWebAPI/Create    
    66.         // To protect from overposting attacks, enable the specific properties you want to bind to.    
    67.         // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.    
    68.         [HttpPost]    
    69.         [ValidateAntiForgeryToken]    
    70.         public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)    
    71.         {    
    72.             if (ModelState.IsValid)    
    73.             {    
    74.                 // Original code:    
    75.                 //_context.Add(store);    
    76.                 //await _context.SaveChangesAsync();    
    77.     
    78.                 // Consume API    
    79.                 await client.PostAsJsonAsync<Store>(url, store);    
    80.     
    81.                 return RedirectToAction(nameof(Index));    
    82.             }    
    83.             return View(store);    
    84.         }    
    85.     
    86.         // GET: StoresMVCCallWebAPI/Edit/5    
    87.         public async Task<IActionResult> Edit(string id)    
    88.         {    
    89.             if (id == null)    
    90.             {    
    91.                 return NotFound();    
    92.             }    
    93.     
    94.             // Original code:    
    95.             //var store = await _context.Stores.FindAsync(id);    
    96.     
    97.             // Consume API    
    98.             var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));    
    99.     
    100.             if (store == null)    
    101.             {    
    102.                 return NotFound();    
    103.             }    
    104.             return View(store);    
    105.         }    
    106.     
    107.         // POST: StoresMVCCallWebAPI/Edit/5    
    108.         // To protect from overposting attacks, enable the specific properties you want to bind to.    
    109.         // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.    
    110.         [HttpPost]    
    111.         [ValidateAntiForgeryToken]    
    112.         public async Task<IActionResult> Edit(string id, [Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)    
    113.         {    
    114.             if (id != store.Stor_Id)    
    115.             {    
    116.                 return NotFound();    
    117.             }    
    118.     
    119.             if (ModelState.IsValid)    
    120.             {    
    121.                 try    
    122.                 {    
    123.                     // Original code:    
    124.                     //_context.Update(store);    
    125.                     //await _context.SaveChangesAsync();    
    126.     
    127.                     // Consume API    
    128.                     await client.PutAsJsonAsync<Store>(url + id, store);    
    129.                 }    
    130.                 catch (DbUpdateConcurrencyException)    
    131.                 {    
    132.                     if (!StoreExists(store.Stor_Id))    
    133.                     {    
    134.                         return NotFound();    
    135.                     }    
    136.                     else    
    137.                     {    
    138.                         throw;    
    139.                     }    
    140.                 }    
    141.                 return RedirectToAction(nameof(Index));    
    142.             }    
    143.             return View(store);    
    144.         }    
    145.     
    146.         // GET: StoresMVCCallWebAPI/Delete/5    
    147.         public async Task<IActionResult> Delete(string id)    
    148.         {    
    149.             if (id == null)    
    150.             {    
    151.                 return NotFound();    
    152.             }    
    153.     
    154.             // Original code:    
    155.             //var store = await _context.Stores    
    156.             //    .FirstOrDefaultAsync(m => m.Stor_Id == id);    
    157.     
    158.             // Consume API    
    159.             var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));    
    160.     
    161.             if (store == null)    
    162.             {    
    163.                 return NotFound();    
    164.             }    
    165.     
    166.             return View(store);    
    167.         }    
    168.     
    169.         // POST: StoresMVCCallWebAPI/Delete/5    
    170.         [HttpPost, ActionName("Delete")]    
    171.         [ValidateAntiForgeryToken]    
    172.         public async Task<IActionResult> DeleteConfirmed(string id)    
    173.         {    
    174.             // Original Code:    
    175.             //var store = await _context.Stores.FindAsync(id);    
    176.             //_context.Stores.Remove(store);    
    177.             //await _context.SaveChangesAsync();    
    178.     
    179.             // Consume API    
    180.             await client.DeleteAsync(url + id);    
    181.     
    182.             return RedirectToAction(nameof(Index));    
    183.         }    
    184.     
    185.         private bool StoreExists(string id)    
    186.         {    
    187.             return _context.Stores.Any(e => e.Stor_Id == id);    
    188.         }    
    189.     }    
    190. }      

    Run and Test the app

     
    The MVC module,
     
    Consume Web API By MVC Client In .NET Core Client
     
    The MVC module calls Web API: the data is different from the MVC module above, but the same as the Web API module.
     
    Consume Web API By MVC Client In .NET Core Client
      
    The Web API module,
     
    Consume Web API By MVC Client In .NET Core Client
     

    Conclusion

     
    In this article, we used MVC, and Web API templates to build out three apps, one is MVC module, one is Web API, then we used HttpClient in MVC module to consume Web API with only one line of code written manually.