Enable CORS, Consume Web API By MVC In .NET Core

This article is the third part of the previous article 《Consume Web API By MVC In .NET Core (1), (2) andwill make a cross origin access to the Web API server.
 

Introduction

 
In the previous articles, we created a ASP.NET Core MVC app and associated a Web API service in Part I, and we made a MVC app as a client to consume Web API in the same app (origin) in Part II.
 
In this article, we will make a separated Web API server and consume it by a ASP.NET MVC Client in .NET Core in a different app (origin). We will make three parts,
  • Briefly describ the concept of CORS
  • Build the Web API server, and a separated consumer, MVC client, according to previous work;
  • Enable CORS to access Web API from a cross origin

A: Brief Discussion of CORE

 
Due to the fact that there is a lot of discussion online, we just make a very brief concept introduction here.
 
Browser security prevents a web page from making requests to a different domain than the one that served the web page. This restriction is called the same-origin policy.
 

Same origin

 
Two URLs have the same origin if they have identical schemes, hosts, and ports (RFC 6454).
 
Consume Web API By MVC In .NET Core
 
 Understanding the CORS (graph from)
 

Enable CORS

 
There are two ways to enable CORS,
  • JSONP while making the AJAX call
  • Enabling CORS in ASP.NET WEB API
The later one is safer and more flexible than earlier techniques, such as JSONP.
 

B: Set up Separated Server and Client

 
From previous articles, we already have had an app with three modules (controllers), one Web API server, one MVC module, and another MVC as client to consume a Web API server. Now, we can use the previous app as either the server or client, and then create another counter part. For the simplest method, we use the MVC client from previous articles, and recreate the Web API server below (for the reader's convinience, I repeat the whole procedure without explanations),
  • Step 1: Create an ASP.NET Core Web API application;
  • Step 2: Set up Database;
  • Step 3,:Set up Entity Model in Visual Studio Web API Project;
  • Step 4: Set up a Database Context;
  • Step 5: Scaffold API Controller with Action using Entity Framework;
  • Step 6: Run and Test app
Step 1 - Create an ASP.NET Core Web API application
 
We use the current version of Visual Studio 2019 16.8 and .NET 5.0 SDK to build the app.
  1. Start Visual Studio and select Create a new project.
  2. In the Create a new project dialog, select ASP.NET Core Web Application > Next.
  3. In the Configure your new project dialog, enter WebAPIStores for Project name.
  4. Select Create.
  5. In the Create a new ASP.NET Core web application dialog, select,

    1. .NET Core and ASP.NET Core 5.0 in the dropdowns.
    2. ASP.NET Core Web Api.
    3. Create
 
 
Step 2 - Set up Database
 
We use the database table Stores created in database DB_Demo_API,
 
Consume Web API By MVC In .NET Core
 
The code for the table,
  1. USE [DB_Demo_API]  
  2. GO  
  3.   
  4. /****** Object:  Table [dbo].[Stores]    Script Date: 12/26/2020 1:47:06 PM ******/  
  5. SET ANSI_NULLS ON  
  6. GO  
  7.   
  8. SET QUOTED_IDENTIFIER ON  
  9. GO  
  10.   
  11. CREATE TABLE [dbo].[Stores](  
  12.     [Stor_Id] [nvarchar](450) NOT NULL,  
  13.     [Stor_Name] [nvarchar](maxNULL,  
  14.     [Stor_Address] [nvarchar](maxNULL,  
  15.     [City] [nvarchar](maxNULL,  
  16.     [State] [nvarchar](maxNULL,  
  17.     [Zip] [nvarchar](maxNULL,  
  18.  CONSTRAINT [PK_Stores] PRIMARY KEY CLUSTERED   
  19. (  
  20.     [Stor_Id] ASC  
  21. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFFON [PRIMARY]  
  22. ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]  
  23. GO  
Step 3 - Set up Entity Model in Visual Studio Web API Project
 
Right click project => add => New Folder, named as Model, then add a class Store,
  1. using System.ComponentModel.DataAnnotations;  
  2.   
  3. namespace WebAPIStores.Models  
  4. {  
  5.     public partial class Store  
  6.     {  
  7.         [Key]  
  8.         public string Stor_Id { getset; }  
  9.         public string Stor_Name { getset; }  
  10.         public string Stor_Address { getset; }  
  11.         public string City { getset; }  
  12.         public string State { getset; }  
  13.         public string Zip { getset; }  
  14.     }  
  15. }  
Step 4 - Set up a Database Context
 
1. Create a new Database Context class, named DB_Demo_APIContext.cs,
  1. using Microsoft.EntityFrameworkCore;  
  2. #nullable disable  
  3. namespace MVCCallWebAPI.Models.DB {  
  4.     public partial class DB_Demo_APIContext: DbContext {  
  5.         public DB_Demo_APIContext() {}  
  6.         public DB_Demo_APIContext(DbContextOptions < DB_Demo_APIContext > options): base(options) {}  
  7.         public virtual DbSet < Store > Stores {  
  8.             get;  
  9.             set;  
  10.         }  
  11.     }  
  12. }   
2. Add the new Connection in the appsettings.json file,
  1. {  
  2.   "Logging": {  
  3.     "LogLevel": {  
  4.       "Default""Information",  
  5.       "Microsoft""Warning",  
  6.       "Microsoft.Hosting.Lifetime""Information"  
  7.     }  
  8.   },  
  9.   
  10.   "ConnectionStrings": {  
  11.     "DB_Demo_APIConnection""Data Source=localhost;Initial Catalog=DB_Demo_API;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"  
  12.   },  
  13.   
  14.   "AllowedHosts""*"  
  15. }  
3. Register the database connection context into Class starup.cs inside ConfigureServices,
  1. // This method gets called by the runtime. Use this method to add services to the container.  
  2. public void ConfigureServices(IServiceCollection services)  
  3. {   
  4.     // Register SQL database configuration context as services.   
  5.     services.AddDbContext<DB_Demo_APIContext>(options =>  
  6.     {  
  7.         options.UseSqlServer(Configuration.GetConnectionString("DB_Demo_APIConnection"));  
  8.     });  
  9.   
  10.     services.AddControllers();  
  11.     services.AddSwaggerGen(c =>  
  12.     {  
  13.         c.SwaggerDoc("v1"new OpenApiInfo { Title = "WebAPIStores", Version = "v1" });  
  14.     });  
  15. }  
Step 5 - Scaffold API Controller with Action using Entity Framework
 
Note, you might need to add 'Microsoft.EntityFrameworkCore.SqlServer', and 'Microsoft.EntityFrameworkCore.Design' here if you did not.
  • Select API Controller with actions, using Entity Framework, and then select Add.

    Consume Web API By MVC In .NET Core
  • In the Add API Controller with actions, using Entity Framework dialog,
    • Model class -Store(MVCCallWebAPI.Models)
    • Data context class - DB_Demo_APIContext (MVCCallWebAPI.Models)
    • Controller name - Change the default StoresController to StoresWebAPIController
    • Select Add
Consume Web API By MVC In .NET Core
 
Here, we make the controller the same name as previous parts, so you do not need to change code in the client MVC model.
 
Step 6 - Run and Test app
 
You will see the Swagger interface directly by using .NET Core 5.0,
 
Consume Web API By MVC In .NET Core 
 

C: Anable CORS to access Web API from a cross origin Client

 
We use the app from the previous article (Part II) as Client to consume the server above by modifying one line of code to change the server address below, and now we are ready for the CORS test.
  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.        string url = "https://localhost:44381/api/storesWebAPI/";      
  20.       
  21.         public StoresMVCCallWebAPIController(pubsContext context)      
  22.         {      
  23.             _context = context;      
  24.         }      
  25.       
  26.       
  27.         // GET: StoresMVCCallAPI      
  28.         public async Task<IActionResult> Index()      
  29.         {      
  30.             // Original code:      
  31.             //return View(await _context.Stores.ToListAsync());      
  32.       
  33.             // Consume API      
  34.             return View(JsonConvert.DeserializeObject<List<Store>>(await client.GetStringAsync(url)).ToList());      
  35.         }      
  36.       
  37.         // GET: StoresMVCCallWebAPI/Details/5      
  38.         public async Task<IActionResult> Details(string id)      
  39.         {      
  40.             if (id == null)      
  41.             {      
  42.                 return NotFound();      
  43.             }      
  44.       
  45.             // Original code:      
  46.             //var store = await _context.Stores      
  47.             //    .FirstOrDefaultAsync(m => m.Stor_Id == id);      
  48.       
  49.             // Consume API      
  50.             var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));      
  51.       
  52.             if (store == null)      
  53.             {      
  54.                 return NotFound();      
  55.             }      
  56.       
  57.             return View(store);      
  58.         }      
  59.       
  60.         // GET: StoresMVCCallWebAPI/Create      
  61.         public IActionResult Create()      
  62.         {      
  63.             return View();      
  64.         }      
  65.       
  66.         // POST: StoresMVCCallWebAPI/Create      
  67.         // To protect from overposting attacks, enable the specific properties you want to bind to.      
  68.         // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.      
  69.         [HttpPost]      
  70.         [ValidateAntiForgeryToken]      
  71.         public async Task<IActionResult> Create([Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)      
  72.         {      
  73.             if (ModelState.IsValid)      
  74.             {      
  75.                 // Original code:      
  76.                 //_context.Add(store);      
  77.                 //await _context.SaveChangesAsync();      
  78.       
  79.                 // Consume API      
  80.                 await client.PostAsJsonAsync<Store>(url, store);      
  81.       
  82.                 return RedirectToAction(nameof(Index));      
  83.             }      
  84.             return View(store);      
  85.         }      
  86.       
  87.         // GET: StoresMVCCallWebAPI/Edit/5      
  88.         public async Task<IActionResult> Edit(string id)      
  89.         {      
  90.             if (id == null)      
  91.             {      
  92.                 return NotFound();      
  93.             }      
  94.       
  95.             // Original code:      
  96.             //var store = await _context.Stores.FindAsync(id);      
  97.       
  98.             // Consume API      
  99.             var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));      
  100.       
  101.             if (store == null)      
  102.             {      
  103.                 return NotFound();      
  104.             }      
  105.             return View(store);      
  106.         }      
  107.       
  108.         // POST: StoresMVCCallWebAPI/Edit/5      
  109.         // To protect from overposting attacks, enable the specific properties you want to bind to.      
  110.         // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.      
  111.         [HttpPost]      
  112.         [ValidateAntiForgeryToken]      
  113.         public async Task<IActionResult> Edit(string id, [Bind("Stor_Id,Stor_Name,Stor_Address,City,State,Zip")] Store store)      
  114.         {      
  115.             if (id != store.Stor_Id)      
  116.             {      
  117.                 return NotFound();      
  118.             }      
  119.       
  120.             if (ModelState.IsValid)      
  121.             {      
  122.                 try      
  123.                 {      
  124.                     // Original code:      
  125.                     //_context.Update(store);      
  126.                     //await _context.SaveChangesAsync();      
  127.       
  128.                     // Consume API      
  129.                     await client.PutAsJsonAsync<Store>(url + id, store);      
  130.                 }      
  131.                 catch (DbUpdateConcurrencyException)      
  132.                 {      
  133.                     if (!StoreExists(store.Stor_Id))      
  134.                     {      
  135.                         return NotFound();      
  136.                     }      
  137.                     else      
  138.                     {      
  139.                         throw;      
  140.                     }      
  141.                 }      
  142.                 return RedirectToAction(nameof(Index));      
  143.             }      
  144.             return View(store);      
  145.         }      
  146.       
  147.         // GET: StoresMVCCallWebAPI/Delete/5      
  148.         public async Task<IActionResult> Delete(string id)      
  149.         {      
  150.             if (id == null)      
  151.             {      
  152.                 return NotFound();      
  153.             }      
  154.       
  155.             // Original code:      
  156.             //var store = await _context.Stores      
  157.             //    .FirstOrDefaultAsync(m => m.Stor_Id == id);      
  158.       
  159.             // Consume API      
  160.             var store = JsonConvert.DeserializeObject<Store>(await client.GetStringAsync(url + id));      
  161.       
  162.             if (store == null)      
  163.             {      
  164.                 return NotFound();      
  165.             }      
  166.       
  167.             return View(store);      
  168.         }      
  169.       
  170.         // POST: StoresMVCCallWebAPI/Delete/5      
  171.         [HttpPost, ActionName("Delete")]      
  172.         [ValidateAntiForgeryToken]      
  173.         public async Task<IActionResult> DeleteConfirmed(string id)      
  174.         {      
  175.             // Original Code:      
  176.             //var store = await _context.Stores.FindAsync(id);      
  177.             //_context.Stores.Remove(store);      
  178.             //await _context.SaveChangesAsync();      
  179.       
  180.             // Consume API      
  181.             await client.DeleteAsync(url + id);      
  182.       
  183.             return RedirectToAction(nameof(Index));      
  184.         }      
  185.       
  186.         private bool StoreExists(string id)      
  187.         {      
  188.             return _context.Stores.Any(e => e.Stor_Id == id);      
  189.         }      
  190.     }      
  191. }       

Summary

 
In this article, we have made a separated Web API server, modified the existing ASP.NET MVC Client to consume the Web API Server. In the next article, part IV, we will demostrate and analyze CORS for different situations.