This article will give a way with one line code to consume Web API by a ASP.NET MVC Client in .NET Core.
Introduction
In the previous article (
part I of this article), we created a ASP.NET Core MVC app and associated with 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 purpose, MVC and Web API are against two different database,
MVC is against the database pubs, while Web API against database
DB_Demo_API.
In this article,
we will make the MVC app as a client to consume Web API. For
convinience of analysis and comparison, we will make another MVC module
(controller/view) that exactly the same as the previous MVC module but
with different name as
StoresMVCCallWebAPI controller (see detailes from
Part I, A, Step 1, 3, Add controller).
The Controller added is like this,
Visual Studio creates
- A StroesMVCCallWebAPI controller (Controllers/StoresMVCCallWebAPIController.cs)
- Razor view files for Create, Delete, Details, Edit, and Index pages (Views/StoresMVCCallWebAPI/*.cshtml)
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 as Red highlighted,
- <header>
- <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
- <div class="container">
- <a class="navbar-brand" asp-area="" asp-controller="StoresMVCCallWebAPI" asp-action="Index"><b>MVC Call Web API</b></a>
- <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
- aria-expanded="false" aria-label="Toggle navigation">
- <span class="navbar-toggler-icon"></span>
- </button>
- <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
- <ul class="navbar-nav flex-grow-1">
- <li class="nav-item">
- <a class="nav-link text-dark" asp-area="" asp-controller="StoresMVC" asp-action="Index">MVC app</a>
- </li>
- <li class="nav-item">
- <a class="nav-link text-dark" asp-area="" asp-controller="Swagger" asp-action="Index">Web API</a>
- </li>
- <li class="nav-item">
- <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
- </li>
- <li class="nav-item">
- <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
- </li>
- </ul>
- </div>
- </div>
- </nav>
- </header>
Then, we run the app,
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,
While the Web API (Swagger),
have different data set, that is due to it is against the different database: DB_Demo_API,
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
"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:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Http;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.EntityFrameworkCore;
- using MVCCallWebAPI.Models.DB;
-
- namespace MVCCallWebAPI.Controllers
- {
- public class StoresMVCCallWebAPIController : Controller
- {
- private readonly pubsContext _context;
-
- public StoresMVCCallWebAPIController(pubsContext context)
- {
- _context = context;
- }
-
-
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)
- {
- if (ModelState.IsValid)
- {
- _context.Add(store);
- await _context.SaveChangesAsync();
- return RedirectToAction(nameof(Index));
- }
- return View(store);
- }
- }
- }
The
Create method with a input Object Store, the following two line code
save the object into database (pubs) through entity framework.
- _context.Add(store);
- await _context.SaveChangesAsync();
Replace these two line code with class HttpClient, and the method PostAsJsonAsync to call Web API,
- HttpClient client = new HttpClient();
- string url = "https://localhost:44350/api/storesAPI/";
- await client.PostAsJsonAsync<Store>(url, store);
We got the Create method like this,
- public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)
- {
- if (ModelState.IsValid)
- {
-
-
-
-
- HttpClient client = new HttpClient();
- string url = "https://localhost:44350/api/storesWebAPI/";
-
- await client.PostAsJsonAsync<Store>(url, store);
-
- return RedirectToAction(nameof(Index));
- }
- return View(store);
- }
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 (see
highlights).
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Http;
- using System.Net.Http.Json;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.EntityFrameworkCore;
- using Newtonsoft.Json;
- using WebMVCCore5.Models.DB;
-
- namespace WebMVCCore5.Controllers
- {
- public class StoresMVCCallAPIController : Controller
- {
- private readonly pubsContext _context;
-
- HttpClient client = new HttpClient();
- string url = "https://localhost:44330/api/storesWebAPI/";
-
- public StoresMVCCallAPIController(pubsContext context)
- {
- _context = context;
- }
-
-
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)
- {
- if (ModelState.IsValid)
- {
-
-
-
-
- await client.PostAsJsonAsync<Store>(url, store);
-
- return RedirectToAction(nameof(Index));
- }
- return View(store);
- }
DALETE
We use DeleteAsync method to do the job,
-
- [HttpPost, ActionName("Delete")]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> DeleteConfirmed(string id)
- {
-
-
-
-
-
-
- await client.DeleteAsync(url + id);
-
- return RedirectToAction(nameof(Index));
- }
PUT (Edit)
We need to bring two parameters, one is id, another is the object, and we use PutAsJsonAsync method
- try
- {
-
-
-
-
-
- await client.PutAsJsonAsync<Store>(url + id, store);
- }
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.
-
- public async Task<IActionResult> Delete(string id)
- {
- if (id == null)
- {
- return NotFound();
- }
-
-
-
-
-
-
- var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));
-
- if (store == null)
- {
- return NotFound();
- }
-
- return View(store);
- }
GET
The same as Get/id, but we use List<Store>
-
- public async Task<IActionResult> Index()
- {
-
-
-
-
- return View(JsonConvert.DeserializeObject<List<Store>>(await client.GetStringAsync(url)).ToList());
- }
Finally, we got the full code (highlighted are the changed one line code for each method),
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Http;
- using System.Net.Http.Json;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.EntityFrameworkCore;
- using MVCCallWebAPI.Models.DB;
- using Newtonsoft.Json;
-
- namespace MVCCallWebAPI.Controllers
- {
- public class StoresMVCCallWebAPIController : Controller
- {
- private readonly pubsContext _context;
-
- HttpClient client = new HttpClient();
- string url = "https://localhost:44350/api/storesWebAPI/";
-
- public StoresMVCCallWebAPIController(pubsContext context)
- {
- _context = context;
- }
-
-
-
- public async Task<IActionResult> Index()
- {
-
-
-
-
- return View(JsonConvert.DeserializeObject<List<Store>>(await client.GetStringAsync(url)).ToList());
- }
-
-
- public async Task<IActionResult> Details(string id)
- {
- if (id == null)
- {
- return NotFound();
- }
-
-
-
-
-
-
- var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));
-
- if (store == null)
- {
- return NotFound();
- }
-
- return View(store);
- }
-
-
- public IActionResult Create()
- {
- return View();
- }
-
-
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)
- {
- if (ModelState.IsValid)
- {
-
-
-
-
-
- await client.PostAsJsonAsync<Store>(url, store);
-
- return RedirectToAction(nameof(Index));
- }
- return View(store);
- }
-
-
- public async Task<IActionResult> Edit(string id)
- {
- if (id == null)
- {
- return NotFound();
- }
-
-
-
-
-
- var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));
-
- if (store == null)
- {
- return NotFound();
- }
- return View(store);
- }
-
-
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> Edit(string id, [Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)
- {
- if (id != store.Stor_Id)
- {
- return NotFound();
- }
-
- if (ModelState.IsValid)
- {
- try
- {
-
-
-
-
-
- await client.PutAsJsonAsync<Store>(url + id, store);
- }
- catch (DbUpdateConcurrencyException)
- {
- if (!StoreExists(store.Stor_Id))
- {
- return NotFound();
- }
- else
- {
- throw;
- }
- }
- return RedirectToAction(nameof(Index));
- }
- return View(store);
- }
-
-
- public async Task<IActionResult> Delete(string id)
- {
- if (id == null)
- {
- return NotFound();
- }
-
-
-
-
-
-
- var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));
-
- if (store == null)
- {
- return NotFound();
- }
-
- return View(store);
- }
-
-
- [HttpPost, ActionName("Delete")]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> DeleteConfirmed(string id)
- {
-
-
-
-
-
-
- await client.DeleteAsync(url + id);
-
- return RedirectToAction(nameof(Index));
- }
-
- private bool StoreExists(string id)
- {
- return _context.Stores.Any(e => e.Stor_Id == id);
- }
- }
- }
Run and Test the app
The MVC module,
The MVC module Call Web API: the data is different from the MVC module above, but the same as the Web API module.
The Web API module,
In this article, we use MVC, and Web API templates to build out three
apps, one is MVC module, one is Web API, then we use HttpClient in MVC
module to consume Web API with only one line code manually written.