In the fast-growing era of Web technology, everything is changing too fast. With ASP.NET, there was a time when we used to use web service (the .asmx ones), which was SOAP-based, which we can just use for consuming data from other applications, which did have that much of security in it. Most developers would take Username and Password Parameter as input and then they would allow to access web service.
As time passes, Microsoft came up with WCF which was secured but too complex to use.
Further, Microsoft came up with something new called as Web API which we can use by creating ASP.NET MVC application or directly ASP.NET Web API application which was lighter and easy to use.
But moving further, Microsoft introduces ASP.NET Core which is lighter than all its previous versions.
But when we say we are securing a webapi in ASP.NET WEB API, we use Delegate handler for validating API request.
As we jump into ASP.NET Core, there are no more handler and modules, we are introduced to something new called as Middleware, which we are going to write to validating API request.
In this article, we are going to learn that extra part, the process to create an ASP.NET Core WEB API application in which a developer can log into an application and subscribe his own services, then generate API keys, see his own documentation of API how to consume API and finally he will get his own analytics on how many requests he sends in a month. And if request sent count is greater than the user has subscribed, then he will get the response "exceeds request length".
Process
- Register Developer
- Login
- Choose Service with Max request (1000 request, 5000 requests)
- Get API key
- API Documents
- Use API key to Access service
Prerequisites
- Visual Studio 2017 with ASP.NET CORE 2.0
- SQL Server 2008 and above
Database parts
In this part, we have created the database with name "MoviesDB
" and it has seven tables which we are going to use in the application.
Tables Details
APIManagerTB
: Stores all services and API Keys MoviesTB
: Stores all movies details MusicTB
: Stores all music details RegisterUser
: Stores all Registered User details ServicesTB
: Stores all Service details LoggerTB
: Stores all request log of API HitsTB
: Stores all Request values (1000 request, 2000 request)
Creating WEB API Application
Open New Visual Studio 2017 IDE.
After opening IDE, next, we are going to create ASP.NET MVC Core project. For doing that, just click File - New - Project.
After choosing a project, a new dialog will pop up with the name "New Project". In that, we are going to choose Visual C# Project Templates - .NET Core - ASP.NET Core Web Application. Then, we are going to name the project as "MoviesAPIStore
".
After naming the project, click on OK button to create the project. A new dialog will pop up for choosing templates for creating "Web Application (Model View Controller)", click on the OK button to create the project.
After configuring the project next, we are going to see project structure.
Using Entity Framework Core Code First Approach to Connecting Application to Database
The first step we are going to add a connection string in appsettings.json file.
The second step we are going to create Models according to MoviesDB
database.
The third step we are going to add a MoviesContext folder and inside that folder, we are going to add DatabaseContext
class which will inherit Dbcontext
class.
Code Snippet of DatabaseContext Class
using Microsoft.EntityFrameworkCore;
using MoviesAPIStore.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MoviesAPIStore.MoviesContext
{
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{
}
public DbSet<RegisterUser> RegisterUser { get; set; }
public DbSet<ServicesTB> ServicesTB { get; set; }
public DbSet<HitsTB> HitsTB { get; set; }
public DbSet<APIManagerTB> APIManagerTB { get; set; }
public DbSet<MoviesTB> MoviesTB { get; set; }
public DbSet<MusicTB> MusicTB { get; set; }
public DbSet<LoggerTB> LoggerTB { get; set; }
}
}
Adding Repository Folder to Application
In this part, we are going to add Repository Folder to project. In this folder, we are going to keep all project interfaces and a concrete class.
Register User
RegisterUser
Model - Adding
IRegisterUser
Interface - Adding
RegisterUserConcrete
class - Adding Controller
- Adding View
- Setting Dependency injection in startup class for injecting dependence of
RegisterUserConcrete
class
Let’s start with RegisterUser
Model.
1. RegisterUser Model
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace MoviesAPIStore.Models
{
[Table("RegisterUser")]
public class RegisterUser
{
[Key]
public long UserID { get; set; }
[Required(ErrorMessage = "Required Username")]
[StringLength(30, MinimumLength = 2,
ErrorMessage = "Username Must be Minimum 2 Charaters")]
public string Username { get; set; }
[DataType(DataType.Password)]
[Required(ErrorMessage = "Required Password")]
[MaxLength(30, ErrorMessage = "Password cannot be Greater than 30 Charaters")]
[StringLength(31, MinimumLength = 7,
ErrorMessage = "Password Must be Minimum 7 Charaters")]
public string Password { get; set; }
public DateTime CreateDate { get; set; }
[Required(ErrorMessage = "Required EmailID")]
[RegularExpression(@"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}",
ErrorMessage = "Please enter Valid Email ID")]
public string EmailID { get; set; }
}
}
After completing with adding RegisterUser
model, next we are going to add IRegisterUser
interface and in this interface, we are going to declare all methods which we require for Registering users.
2. IRegisterUser interface
In this part, we are going to add IRegisterUser
interface to repository folder and this interface contains four methods in it:
Add
: for creating a new User which takes RegisterUser
Model as input ValidateRegisteredUser
: for validating username and password entered by the user ValidateUsername
: for validating Username
entered by user (does similar Username
already exist in database) GetLoggedUserID
: Get logged in UserID
by username and password entered by the user
Code Snippet
using MoviesAPIStore.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MoviesAPIStore.Repository
{
public interface IRegisterUser
{
void Add(RegisterUser registeruser);
bool ValidateRegisteredUser(RegisterUser registeruser);
bool ValidateUsername(RegisterUser registeruser);
long GetLoggedUserID(RegisterUser registeruser);
}
}
3. RegisterUserConcrete Class
The RegisterUserConcrete
class will inherit IRegisterUser
interface and implements all methods in it.
Code Snippet of RegisterUserConcrete Class
using MoviesAPIStore.Models;
using MoviesAPIStore.MoviesContext;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MoviesAPIStore.Repository
{
public class RegisterUserConcrete : IRegisterUser
{
private DatabaseContext _context;
public RegisterUserConcrete(DatabaseContext context)
{
_context = context;
}
public void Add(RegisterUser registeruser)
{
try
{
registeruser.UserID = 0;
_context.RegisterUser.Add(registeruser);
_context.SaveChanges();
}
catch (Exception ex)
{
throw;
}
}
public long GetLoggedUserID(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username &&
User.Password == registeruser.Password
select User.UserID).FirstOrDefault();
return usercount;
}
public bool ValidateRegisteredUser(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username &&
User.Password == registeruser.Password
select User).Count();
if (usercount > 0)
{
return true;
}
else
{
return false;
}
}
public bool ValidateUsername(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username
select User).Count();
if (usercount > 0)
{
return true;
}
else
{
return false;
}
}
}
}
After adding RegisterUserConcrete
class, next we are going to have a look at dependency injection.
Understanding Dependency Injection
In this process, we are going to use constructor injection for injecting dependency.
We are going to inject DbContext
as a dependency to RegisterUserConcrete
class to access specific entity (RegisterUser
).
Snapshot while Injecting Dbcontext
After having a look at DatabaseContext
injection, you might wonder how I am injecting dependency, right? The ASP.NET Core MVC comes with inbuilt dependency injection framework, you just need to configure it, it is located in startup.cs under ConfigureServices
method.
Configuring Dependency
In the above snapshot, you can see that we have configured DatabaseContext
dependency whenever you use DatabaseContext
class, DbContext
instance will be injected there.
And another thing you can see is that we have added services.Configure
method. This is used for getting connection string as dependency and we are getting connection string as "Configuration.GetSection("ConnectionStrings")
" - we are going to use this connection string with dapper ORM.
The last dependency which we have added is AddTransient
. In this, we need to configure an interface and concrete class.
Code Snippet
services.AddTransient<Interface, ConcreteClass>();
Whenever we are going to use interface, at that time the concrete class instance will be injected thereby dependency injection framework.
Now we have configured dependency of IRegisterUser
interface and RegisterUserConcrete
Concrete class.
As we move ahead, we are going to register more dependency in ConfigureServices
method in the same way as we need for IRegisterUser
interface and RegisterUserConcrete
Concrete class.
What is Concrete class?
The class which inherits interface is called Concrete class.
Reference: https://stackoverflow.com/questions/38138100/what-is-the-difference-between-services-addtransient-service-addscope-and-servi
In .NET's dependency injection, there are three major lifetimes:
- Singleton which creates a single instance throughout the application. It creates the instance for the first time and reuses the same object in the all calls.
- Scoped lifetime services are created once per request within the scope. It is equivalent to Singleton in the current scope. For example, in MVC, it creates one instance per each HTTP request but uses the same instance in the other calls within the same web request.
- Transient lifetime services are created each time they are requested. This lifetime works best for lightweight, stateless services.
After completing with understand dependency injection configuration, let’s add RegisterUser Controller
.
4. Adding RegisterUser Controller
For adding controller, just right click on controller folder, then choose -> Add -> inside that, choose Add New item. A new dialog will popup of Add New item. Inside that, choose "MVC Controller Class" and name your controller as "RegisterUserController
" and click on Add button to create a RegisterUser
controller.
After creating controller next, we are going to use constructor injection for inject dependency.
For that, we are going to add a constructor which will take IRegister
interface as input and at runtime, dependency injection will inject dependency of IRegister
interface which is RegisterUserConcrete
.
Code Snippet of RegisterUserController
using System;
using Microsoft.AspNetCore.Mvc;
using MoviesAPIStore.Repository;
using MoviesAPIStore.Models;
using MoviesAPIStore.AES256Encryption;
namespace MoviesAPIStore.Controllers
{
public class RegisterUserController : Controller
{
IRegisterUser _repository;
public RegisterUserController(IRegisterUser repository)
{
_repository = repository;
}
[HttpGet]
public ActionResult Create()
{
return View(new RegisterUser());
}
[HttpPost]
public ActionResult Create(RegisterUser RegisterUser)
{
try
{
if (!ModelState.IsValid)
{
return View("Create", RegisterUser);
}
if (_repository.ValidateUsername(RegisterUser))
{
ModelState.AddModelError("", "User is Already Registered");
return View("Create", RegisterUser);
}
RegisterUser.CreateDate = DateTime.Now;
RegisterUser.Password = EncryptionLibrary.EncryptText(RegisterUser.Password);
_repository.Add(RegisterUser);
TempData["UserMessage"] = "User Registered Successfully";
ModelState.Clear();
return View("Create", new RegisterUser());
}
catch
{
return View();
}
}
}
}
In RegisterUser
Controller, we have added two Action Methods of creating one for handling [HttpGet]
request and other for handling [HttpPost]
request. In [HttpPost]
request, we are going to first Validate if Username
already exists or not, if not, then we are going to Create User, next we are also taking Password as input which we cannot store in database as clear text. We need to store it in encrypted format, and for doing that, we are going to Use AES 256 algorithm.
5. RegisterUser View
Note: After Registering a User, below are the details which get stored in RegisterUser
Table.
6. RegisterUser Table
After completing the Registration part, next, we are going to create a Login page.
Login
In this part, we are going to create a login controller with two action methods. One to handle get
request and other for handling post
request and the same way we are going to add a Login view.
In controller part of LoginController
, we are going to use dependency injection to inject dependency of RegisterUserConcrete
such that we can access method inside of it.
We are going to use "ValidateRegisteredUser
" method to validate User credentials. If the user is valid, then we are going to push request to the dashboard page, else we are going to show error on the Login page.
Code Snippet of LoginController
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MoviesAPIStore.AES256Encryption;
using MoviesAPIStore.Models;
using MoviesAPIStore.Repository;
using System;
namespace MoviesAPIStore.Controllers
{
public class LoginController : Controller
{
IRegisterUser _IRegisterUser;
public LoginController(IRegisterUser IRegisterUser)
{
_IRegisterUser = IRegisterUser;
}
public ActionResult Login()
{
return View(new RegisterUser());
}
[HttpPost]
public ActionResult Login(RegisterUser RegisterUser)
{
try
{
if (string.IsNullOrEmpty(RegisterUser.Username) &&
(string.IsNullOrEmpty(RegisterUser.Password)))
{
ModelState.AddModelError("", "Enter Username and Password");
}
else if (string.IsNullOrEmpty(RegisterUser.Username))
{
ModelState.AddModelError("", "Enter Username");
}
else if (string.IsNullOrEmpty(RegisterUser.Password))
{
ModelState.AddModelError("", "Enter Password");
}
else
{
RegisterUser.Password =
EncryptionLibrary.EncryptText(RegisterUser.Password);
if (_IRegisterUser.ValidateRegisteredUser(RegisterUser))
{
var UserID = _IRegisterUser.GetLoggedUserID(RegisterUser);
HttpContext.Session.SetString("UserID", Convert.ToString(UserID));
return RedirectToAction("Dashboard", "Dashboard");
}
else
{
ModelState.AddModelError("", "User is Already Registered");
return View("Login", RegisterUser);
}
}
return View("Login", RegisterUser);
}
catch(Exception)
{
return View();
}
}
public ActionResult Logout()
{
HttpContext.Session.Clear();
return RedirectToAction("Login", "Login");
}
}
}
After login, we are going to see a simple dashboard pages with charts.
Dashboard Page
This is a simple dashboard page which I am going to explain in detail in the upcoming steps.
After having a look at the Dashboard page, you can see that we have Generate API KEY Menu. The next thing we are going to do is Create a generate API Key Page where users can generate Key for API.
Generating API Keys
Credit: Icons made by Round icons from
www.flaticon.com is licensed by CC 3.0 BY.
Adding ApplicationKeys Controller
In ApplicationKeysController
, we are going to add two action methods with name GenerateKeys
, one for handling get
and other for handling post
request.
And on GenerateKeys
page, we are going to add two dropdown lists, one for displaying service and the other for displaying Max request count.
After the user chooses these dropdowns, then the user is going to click on Create API Key button to generate API Key to access this APIs.
Let’s have a look at GenerateKeysVM
Model.
Code snippet of GenerateKeysVM Model
using MoviesAPIStore.Filters;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace MoviesAPIStore.Models
{
public class GenerateKeysVM
{
[Required(ErrorMessage ="Choose Service")]
[ServiceValidate]
public int? ServiceID { get; set; }
[HitValidate]
[Required(ErrorMessage = "Choose Max Request")]
public int? HitsID { get; set; }
public List<HitsTB> ListHits { get; set; }
public List<ServicesTB> ListServices { get; set; }
public string APIKey { get; set; }
}
}
After adding GenerateKeysVM
model, next we are going to add ServicesStore
interface and Concrete
class.
IServicesStore Interface
In IServicesStore
interface, we have added two methods GetServiceList
and GetServiceListforDashboard
.
Code Snippet of IServicesStore Interface
using MoviesAPIStore.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MoviesAPIStore.Repository
{
public interface IServicesStore
{
List<ServicesTB> GetServiceList();
List<ServicesTB> GetServiceListforDashboard();
}
}
After adding IServicesStore
interface, next, we are going to add a ServicesStore
Concrete
class.
Code Snippet of ServicesStoreConcrete Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MoviesAPIStore.Models;
using MoviesAPIStore.MoviesContext;
namespace MoviesAPIStore.Repository
{
public class ServicesStoreConcrete : IServicesStore
{
private DatabaseContext _context;
public ServicesStoreConcrete(DatabaseContext context)
{
_context = context;
}
public List<ServicesTB> GetServiceList()
{
try
{
var ServiceList = (from services in _context.ServicesTB
select services).ToList();
ServiceList.Insert(0, new ServicesTB
{ ServiceName = "---Choose Service---", ServiceID = -1 });
return ServiceList;
}
catch (Exception)
{
throw;
}
}
public List<ServicesTB> GetServiceListforDashboard()
{
try
{
var ServiceList = (from services in _context.ServicesTB
select services).ToList();
return ServiceList;
}
catch (Exception)
{
throw;
}
}
}
}
After completing with adding IServicesStore
interface and ServicesStoreConcrete
class, next we are going to add Ihits
interface.
Adding Ihits Interface
Ihits
interface contains methods used for displaying HitsList
(Max Request Dropdown) and used for displaying charts on the dashboard.
Code Snippet of Ihits Interface
using MoviesAPIStore.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MoviesAPIStore.Repository
{
public interface IHits
{
List<HitsTB> GetHitsList();
List<string> GetChartsMoviesreport();
List<string> GetChartsMusicreport();
}
}
Adding HitsConcrete Class
In this part, we have implemented all methods which are declared IHits
interface. In this concrete class for some methods, we have used Dapper ORM for getting data from the database. And you can see constructor of this class we are taking two inputs, one of DatabaseContext
and IOptions<ConnectionStrings>
, the DatabaseContext
will get an instance of DbContext
and IOptions<ConnectionStrings>
will get an instance of configuration (Connection string).
Code Snippet of HitsConcrete Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MoviesAPIStore.Models;
using MoviesAPIStore.MoviesContext;
using Dapper;
using System.Data.SqlClient;
using Microsoft.Extensions.Options;
namespace MoviesAPIStore.Repository
{
public class HitsConcrete : IHits
{
private DatabaseContext _context;
private ConnectionStrings _connectionstrings;
public HitsConcrete(DatabaseContext context,
IOptions<ConnectionStrings> connectionstrings)
{
_context = context;
_connectionstrings = connectionstrings.Value;
}
public List<HitsTB> GetHitsList()
{
try
{
var hitsList = (from services in _context.HitsTB select services).ToList();
hitsList.Insert(0, new HitsTB
{ HitsDisplay = "---Choose Max Request---", HitsID = -1 });
return hitsList;
}
catch (Exception)
{
throw;
}
}
public List<string> GetChartsMoviesreport()
{
try
{
using (SqlConnection con = new SqlConnection
(Convert.ToString(_connectionstrings.DatabaseConnection)))
{
var parameter = new DynamicParameters();
return con.Query<string>("Usp_GetChartsMoviesreport",
null, null, false,0, System.Data.CommandType.StoredProcedure).ToList();
}
}
catch (Exception)
{
throw;
}
}
public List<string> GetChartsMusicreport()
{
try
{
using (SqlConnection con = new SqlConnection
(Convert.ToString(_connectionstrings.DatabaseConnection)))
{
var parameter = new DynamicParameters();
return con.Query<string>("Usp_GetChartsMusicreport",
null, null, false, 0, System.Data.CommandType.StoredProcedure).ToList();
}
}
catch (Exception)
{
throw;
}
}
}
}
After adding IHits
interface and HitsConcrete
class, next we are going to add IAPIManager
interface.
Adding IAPIManager Interface
In this interface, we are going to add a method which is required for generating, saving and validating API Key. Along with that, we have added two methods, Deactivate
and Reactivate
Service which can be used by the user to activate and deactivate his own services.
Code Snippet of IAPIManager Interface
using MoviesAPIStore.Models;
namespace MoviesAPIStore.Repository
{
public interface IAPIManager
{
int isApikeyAlreadyGenerated(int? ServiceID, int? UserID);
int GenerateandSaveToken(APIManagerTB APIManagerTB);
APIManagerVM GetApiDetailsbyServiceIDandUserID(int? ServiceID, int? UserID);
int DeactivateService(int? ServiceID, int? UserID);
int ReactivateService(int? ServiceID, int? UserID);
}
}
After adding IAPIManager
interface next, we are going to add APIManagerConcrete
class which will implement IAPIManager
.
Code Snippet of APIManagerConcrete
using Dapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using MoviesAPIStore.Models;
using MoviesAPIStore.MoviesContext;
using System;
using System.Data.SqlClient;
using System.Linq;
namespace MoviesAPIStore.Repository
{
public class APIManagerConcrete : IAPIManager
{
private DatabaseContext _context;
private ConnectionStrings _connectionstrings;
public APIManagerConcrete(DatabaseContext context,
IOptions<ConnectionStrings> connectionstrings)
{
_context = context;
_connectionstrings = connectionstrings.Value;
}
public int isApikeyAlreadyGenerated(int? ServiceID, int? UserID)
{
try
{
var keyCount = (from apimanager in _context.APIManagerTB
where apimanager.ServiceID == ServiceID &&
apimanager.UserID == UserID
select apimanager).Count();
return keyCount;
}
catch (Exception)
{
throw;
}
}
public int GenerateandSaveToken(APIManagerTB APIManagerTB)
{
try
{
_context.APIManagerTB.Add(APIManagerTB);
return _context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
public APIManagerVM GetApiDetailsbyServiceIDandUserID(int? ServiceID, int? UserID)
{
try
{
using (SqlConnection con = new SqlConnection
(Convert.ToString(_connectionstrings.DatabaseConnection)))
{
var parameter = new DynamicParameters();
parameter.Add("@ServiceID", ServiceID);
parameter.Add("@UserID", UserID);
var apimanagervm = con.Query<APIManagerVM>
("Usp_GetApiDetailsbyServiceIDandUserID", parameter, null,
false, 0, System.Data.CommandType.StoredProcedure).SingleOrDefault();
if(apimanagervm ==null)
{
return new APIManagerVM();
}
else
{
return apimanagervm;
}
}
}
catch (Exception)
{
throw;
}
}
public int DeactivateService(int? ServiceID, int? UserID)
{
try
{
using (SqlConnection con = new SqlConnection
(Convert.ToString(_connectionstrings.DatabaseConnection)))
{
var parameter = new DynamicParameters();
parameter.Add("@ServiceID", ServiceID);
parameter.Add("@UserID", UserID);
return con.Execute("Usp_DeactivateService_update",
parameter, null, 0, System.Data.CommandType.StoredProcedure);
}
}
catch (Exception)
{
throw;
}
}
public int ReactivateService(int? ServiceID, int? UserID)
{
try
{
using (SqlConnection con = new SqlConnection
(Convert.ToString(_connectionstrings.DatabaseConnection)))
{
var parameter = new DynamicParameters();
parameter.Add("@ServiceID", ServiceID);
parameter.Add("@UserID", UserID);
return con.Execute("Usp_ReactivateService_update",
parameter, null, 0, System.Data.CommandType.StoredProcedure);
}
}
catch (Exception)
{
throw;
}
}
}
}
Snapshot of Repository
After completion of adding all interface and concrete classes which are required for generating, validating and saving API key, next we are going to add ApplicationKeys
Controller.
Adding ApplicationKeysController
In this controller, we are going to add two action methods with the name "GenerateKeys
", one for handling [HttpGet
] and other for handling [HttpPost
] request.
In [HttpGet
] GenerateKeys
method, we are going to assign values of ListServices
and ListHits
to GenerateKeysVM
View model and send that model to view the binding dropdown list.
In [HttpPost
] GenerateKeys
method, we are going to get values for Service
and Hits
which the user has chosen. Next, we are going to validate Service
and Hits
values against the database and check if this service already subscribes, if yes, then we are going to show the alert message "API Key for Chosen Service is Already Generated". If service is not already subscribed, then we are going to generate unique APIKey for that service.
Code Snippet of ApplicationKeysController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MoviesAPIStore.Repository;
using MoviesAPIStore.Models;
using MoviesAPIStore.AES256Encryption;
using Microsoft.AspNetCore.Http;
using MoviesAPIStore.Filters;
using Newtonsoft.Json;
namespace MoviesAPIStore.Controllers
{
[ValidateUserSession]
public class ApplicationKeysController : Controller
{
IServicesStore _IServicesStore;
IHits _IHits;
IAPIManager _IAPIManager;
public ApplicationKeysController
(IServicesStore IServicesStore, IHits IHits, IAPIManager IAPIManager)
{
_IServicesStore = IServicesStore;
_IHits = IHits;
_IAPIManager = IAPIManager;
}
[HttpGet]
public IActionResult GenerateKeys()
{
try
{
GenerateKeysVM generateKeysVM = new GenerateKeysVM();
generateKeysVM.ListServices = _IServicesStore.GetServiceList();
generateKeysVM.ListHits = _IHits.GetHitsList();
return View(generateKeysVM);
}
catch (Exception)
{
throw;
}
}
[HttpPost]
public IActionResult GenerateKeys(GenerateKeysVM generateKeysVM)
{
if (ModelState.IsValid)
{
var userID = Convert.ToInt32(HttpContext.Session.GetString("UserID"));
if (_IAPIManager.isApikeyAlreadyGenerated
(generateKeysVM.ServiceID, userID) > 0)
{
ModelState.AddModelError("",
"Api Key for Choosen Service is Already Generated");
generateKeysVM.ListServices = _IServicesStore.GetServiceList();
generateKeysVM.ListHits = _IHits.GetHitsList();
return View(generateKeysVM);
}
generateKeysVM.ListServices = _IServicesStore.GetServiceList();
generateKeysVM.ListHits = _IHits.GetHitsList();
if (GenerateKey(generateKeysVM) == 1)
{
TempData["APIKeyGeneratedMessage"] = "Done";
}
else
{
TempData["APIKeyGeneratedMessage"] = "Failed";
}
return View(generateKeysVM);
}
generateKeysVM.ListServices = _IServicesStore.GetServiceList();
generateKeysVM.ListHits = _IHits.GetHitsList();
return View(generateKeysVM);
}
[NonAction]
public int GenerateKey(GenerateKeysVM GenerateKeysVM)
{
try
{
APIManagerTB aPIManagerTB = new APIManagerTB()
{
APIKey = EncryptionLibrary.KeyGenerator.GetUniqueKey(),
HitsID = GenerateKeysVM.HitsID,
CreatedOn = DateTime.Now,
ServiceID = GenerateKeysVM.ServiceID,
UserID = Convert.ToInt32(HttpContext.Session.GetString("UserID")),
Status = "A"
};
return _IAPIManager.GenerateandSaveToken(aPIManagerTB);
}
catch (Exception)
{
throw;
}
}
public IActionResult DeactivateService(string ServiceID)
{
try
{
var result = _IAPIManager.DeactivateService
(Convert.ToInt32(ServiceID),
Convert.ToInt32(HttpContext.Session.GetString("UserID")));
return Json(data: result);
}
catch (Exception)
{
throw;
}
}
public IActionResult ReActivateService(string ServiceID)
{
try
{
var result = _IAPIManager.ReactivateService
(Convert.ToInt32(ServiceID),
Convert.ToInt32(HttpContext.Session.GetString("UserID")));
return Json(data: result);
}
catch (Exception)
{
throw;
}
}
}
}
After understanding work of ApplicationKeysController
, next we see how we are generating unique APIKey
and inserting that key into the database.
Code Snippet of GenerateKey
[NonAction]
public int GenerateKey(GenerateKeysVM GenerateKeysVM)
{
try
{
APIManagerTB aPIManagerTB = new APIManagerTB()
{
APIKey = EncryptionLibrary.KeyGenerator.GetUniqueKey(),
HitsID = GenerateKeysVM.HitsID,
CreatedOn = DateTime.Now,
ServiceID = GenerateKeysVM.ServiceID,
UserID = Convert.ToInt32(HttpContext.Session.GetString("UserID")),
Status = "A"
};
return _IAPIManager.GenerateandSaveToken(aPIManagerTB);
}
catch (Exception)
{
throw;
}
}
Next, we are going to add GenerateKeys
View and add two dropdown lists and a button on it.
Snapshot for GenerateKey
Next for generating a key, we are going to choose Service and Max Request and click on "Create API Key" button to generate the key.
APIManager Table View after Generating API Key
After generating Key next, we are going to Add a ServicesStore Controller which will display all services on it. After clicking on that service, you can see your API Key for that service.
Adding ServicesStore Controller
In this controller, we have two action methods. Both are [HttpGet
] methods, one for displaying service (Service
) on view and another for showing details (Details
) of that service.
Code Snippet of ServicesStore Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MoviesAPIStore.Repository;
using MoviesAPIStore.Filters;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using MoviesAPIStore.Models;
namespace MoviesAPIStore.Controllers
{
[ValidateUserSession]
public class ServicesStoreController : Controller
{
IServicesStore _IServicesStore;
IAPIManager _iAPIManager;
public ServicesStoreController
(IServicesStore IServicesStore, IAPIManager iAPIManager)
{
_IServicesStore = IServicesStore;
_iAPIManager = iAPIManager;
}
public IActionResult Service()
{
return View(_IServicesStore.GetServiceListforDashboard());
}
public IActionResult Details(string ServiceID, string ServiceName)
{
try
{
var userID = Convert.ToInt32(HttpContext.Session.GetString("UserID"));
var apiDetails = _iAPIManager.GetApiDetailsbyServiceIDandUserID
(Convert.ToInt32(ServiceID), userID);
return View(apiDetails);
}
catch (Exception)
{
throw;
}
}
}
}
View of Service.cshtml
@model List<MoviesAPIStore.Models.ServicesTB>
@{
Layout = "~/Views/Shared/_LayoutDashboard.cshtml";
}
<h2>Services</h2>
<div class="row">
@foreach (var service in Model)
{
<div class="col-sm-4">
<div class="card">
<div class="content">
<div class="row">
<div class="col-xs-5">
<div class="icon-big icon-info text-center">
<i class="ti-settings-alt"></i>
</div>
</div>
<div class="col-xs-7">
<div class="numbers">
@service.ServiceName API
</div>
</div>
</div>
<div class="footer">
<hr />
<div class="stats">
<i class="ti-reload"></i>
<a target="_blank"
href="/ServicesStore/Details/@service.ServiceID/
@service.ServiceName">Service Link</a>
</div>
</div>
</div>
</div>
</div>
}
</div>
Snapshot of Service View
Now clicking on Movies API service link, you can see details view of service with API Key for it.
Here, you have an option to deactivate service and reactivate service also by click on Deactivate button.
Snapshot of After Deactivating Service
Snapshot of After Re-activating Service
After completing adding services controller and activating and deactivating services, next we are going to create Movies
API.
Adding Movies API
In this part, before adding Web API Controller, we are going to add Interface and concrete class to get data from the database.
Adding IMovies Interface
The IMovie
’s interface contains a single method inside it which is GetMoviesStore
which will return a list of Movies
.
Code Snippet of IMovies Interface
using MoviesAPIStore.Models;
using System.Collections.Generic;
namespace MoviesAPIStore.Repository
{
public interface IMovies
{
List<MoviesTB> GetMoviesStore();
}
}
Adding MoviesConcrete Class
The Movies
concrete class implement IMovie
’s interface.
Code Snippet of MoviesConcrete Class
using MoviesAPIStore.Models;
using MoviesAPIStore.MoviesContext;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MoviesAPIStore.Repository
{
public class MoviesConcrete : IMovies
{
private DatabaseContext _context;
public MoviesConcrete(DatabaseContext context)
{
_context = context;
}
public List<MoviesTB> GetMoviesStore()
{
try
{
var listofMovies = _context.MoviesTB.ToList();
return listofMovies;
}
catch (System.Exception)
{
throw;
}
}
}
}
Adding Movies Controller
In this part, we are going to add Web API Controller with name "MoviesAPIController
" and this controller will return List
of Movies
. We have only implemented POST
method.
Code snippet
using Microsoft.AspNetCore.Mvc;
using MoviesAPIStore.Models;
using MoviesAPIStore.Repository;
using System.Collections.Generic;
namespace MoviesAPIStore.Controllers
{
[Route("api/[controller]")]
public class MoviesAPIController : Controller
{
IMovies _IMovies;
public MoviesAPIController(IMovies IMovies)
{
_IMovies = IMovies;
}
[HttpPost]
public List<MoviesTB> Post([FromQuery]string key)
{
return _IMovies.GetMoviesStore();
}
}
}
In the same way, we are going to add Music
API.
After completing adding Movies
and Music
API, next we are going to added Swagger for API documentation.
Adding Swagger to Application for API Documentation
What Is Swagger?
Swagger allows you to describe the structure of your APIs so that machines can read them.
The ability of APIs to describe their own structure is the root of all awesomeness in Swagger. Why is it so great? Well, by reading your API’s structure, we can automatically build beautiful and interactive API documentation. We can also automatically generate client libraries for your API in many languages and explore other possibilities like automated testing.
Referenced: https://swagger.io/docs/specification/2-0/what-is-swagger/
For adding Swagger to project, just right click on Movies
API store project and from the list, choose "Manage NuGet packages" a new window of NuGet will pop up. Inside that, choose to browse tab and search "Swashbuckle.AspNetCore
" and click on Install button to install Swagger.
After adding Swagger, we need to add some middleware of swagger to startup.cs class to make it work.
Below is a snapshot of startup
class in which we registered and added swagger middleware.
Now let’s save the application and run to see API documentation.
To access Swagger just after localhost port, enter "/swagger
".
Swagger UI
Wow, we have just configured it not written any code for design and documentation.
API Documentation for Movies API
API Documentation for Music API
Now we are done seeing the documentation of both APIs. Next, let’s validate API request by create Middleware.
Authenticating API Request Mechanism
In this process, we have provided API key to user (client) now while we are creating API authentication mechanism, we are going to validate every request that has come from client, and every request must contain API key which we have provided such that we can validate API key against database and check that user is authorized to access this service. If API key is valid, then the user will get a response from API. If the request is not valid, then the user will get error message about what he is missing in the request.
And one new functionally we have added in this process is we can activate and deactivate our service if user deactivate service and then he tries to access service, then he will get an error message "Service is Deactivated".
Next point is we have max request limit in this API application, if user has subscribed 1000 request and he tries to access API more than 1000 request, he will get an error message "Request Limit Exceeded"
And last scenario use might have subscribed two services and he sends Movies
API key to Music
API request and vice versa, then he will get an error message "Invalid User Key".
And if the request is valid, then we send movies collection as a response.
Till now, we have completed adding API and its documentation, now let’s have right the main process of this application is middleware to validate the request.
Let’s start with adding interface with name IValidateRequest
and declaring methods in it.
Adding IValidateRequest Interface
namespace MoviesAPIStore.Repository
{
public interface IValidateRequest
{
bool ValidateKeys(string Key);
bool IsValidServiceRequest(string Key, string ServiceName);
bool ValidateIsServiceActive(string Key);
bool CalculateCountofRequest(string Key);
}
}
We have declared four methods in the IValidateRequest
interface.
ValidateKeys
In this method, we are going to take API key as input and check if this key exists in the database.
IsValidServiceRequest
In this method, we are going to check whether the API Key is sent to valid API. For that, we are taking key and Service Name (API Name) as input.
For example, for Music
API, the developer is not send Movies
API key.
ValidateIsServiceActive
In this method, we are taking API key as input and check against a database that this service is Active or deactivate.
CalculateCountofRequest
In this method, we are taking API key as input and check request count against this API key.
For example, if the user had subscribed for 1000 request service and if he exceeds the limit.
Adding ValidateRequest Concrete Class
In this part, we are going to add Concrete
class with name ValidateRequestConcrete
which is going to inherit IValidateRequest
interface and implement all methods in it.
Code Snippet of ValidateRequest Concrete Class
using Microsoft.EntityFrameworkCore;
using MoviesAPIStore.MoviesContext;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MoviesAPIStore.Repository
{
public class ValidateRequestConcrete : IValidateRequest
{
private DatabaseContext _context;
public ValidateRequestConcrete(DatabaseContext context)
{
_context = context;
}
public bool ValidateKeys(string Key)
{
try
{
var result = (from apimanagertb in _context.APIManagerTB
where EF.Functions.Like(apimanagertb.APIKey, "%" + Key + "%")
select apimanagertb).Count();
if (result > 0)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public bool IsValidServiceRequest(string Key ,string ServiceName)
{
try
{
var serviceID = (from apimanagertb in _context.APIManagerTB
where EF.Functions.Like(apimanagertb.APIKey, "%" + Key + "%")
select apimanagertb.ServiceID).FirstOrDefault();
var serviceName = (from servicestb in _context.ServicesTB
where servicestb.ServiceID == serviceID
select servicestb.APIName).FirstOrDefault();
if (string.Equals(ServiceName, serviceName,
StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public bool ValidateIsServiceActive(string Key)
{
try
{
var result = (from apimanagertb in _context.APIManagerTB
where EF.Functions.Like(apimanagertb.APIKey, "%" + Key + "%")
&& apimanagertb.Status == "A"
select apimanagertb).Count();
if (result > 0)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public bool CalculateCountofRequest(string Key)
{
try
{
var totalRequestCount = (from apimanagertb in _context.APIManagerTB
join hittb in _context.HitsTB on
apimanagertb.HitsID equals hittb.HitsID
where apimanagertb.APIKey == Key
select hittb.Hits).FirstOrDefault();
var totalCurrentRequestCount = (from loggertb in _context.LoggerTB
where loggertb.APIKey == Key
select loggertb).Count();
if (totalCurrentRequestCount >= totalRequestCount)
{
return false;
}
else
{
return true;
}
}
catch (Exception)
{
throw;
}
}
}
}
Snapshot after Adding Interface IValidateRequest and ValidateRequestConcrete
Adding Middleware to Validate API Request
Before starting this process, let’s understand why middleware. If we create a simple application in ASP.NET MVC in that for validating request API, we use DelegatingHandler
. Now in ASP.NET Core, it has been replaced with middleware.
What is middleware?
Middleware is software that's assembled into an application pipeline to handle requests and responses.
Reference: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?tabs=aspnetcore2x
Let’s start with adding Middleware.
Note: Adding middleware, just right click on Middlewares folder, then choose -> Add ->New Item - a new dialog will pop with name Add New Item, inside that, choose "ASP.NET Core" -> "Web" then in List of templates, you will find "Middleware Class", just select it.
For adding middleware, we are going to create a Middlewares folder in the application. Inside that, we are going to add middleware with name "ApiKeyValidatorsMiddleware
".
Code Snippet of ApiKeyValidatorsMiddleware
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using MoviesAPIStore.Models;
using MoviesAPIStore.Repository;
using System;
using System.Threading.Tasks;
namespace MoviesAPIStore.Middlewares
{
public class ApiKeyValidatorsMiddleware
{
private readonly RequestDelegate _next;
IValidateRequest _IValidateRequest { get; set; }
IRequestLogger _IRequestLogger { get; set; }
public ApiKeyValidatorsMiddleware(RequestDelegate next,
IValidateRequest ivalidaterequest, IRequestLogger irequestlogger)
{
_next = next;
_IValidateRequest = ivalidaterequest;
_IRequestLogger = irequestlogger;
}
public async Task Invoke(HttpContext httpContext)
{
try
{
var remoteIpAddress = httpContext.Connection.RemoteIpAddress;
if (httpContext.Request.Path.StartsWithSegments("/api"))
{
var queryString = httpContext.Request.Query;
StringValues keyvalue;
queryString.TryGetValue("key", out keyvalue);
if (httpContext.Request.Method != "POST")
{
httpContext.Response.StatusCode = 405;
await httpContext.Response.WriteAsync("Method Not Allowed");
return;
}
if (keyvalue.Count == 0)
{
httpContext.Response.StatusCode = 400;
await httpContext.Response.WriteAsync("API Key is missing");
return;
}
else
{
string[] serviceName = httpContext.Request.Path.Value.Split('/');
if(!_IValidateRequest.IsValidServiceRequest(keyvalue, serviceName[2]))
{
httpContext.Response.StatusCode = 401;
await httpContext.Response.WriteAsync
("Invalid User Key or Request");
return;
}
else if (!_IValidateRequest.ValidateKeys(keyvalue))
{
httpContext.Response.StatusCode = 401;
await httpContext.Response.WriteAsync("Invalid User Key");
return;
}
else if (!_IValidateRequest.ValidateIsServiceActive(keyvalue))
{
httpContext.Response.StatusCode = 406;
await httpContext.Response.WriteAsync("Service is Deactived");
return;
}
else if (!_IValidateRequest.CalculateCountofRequest(keyvalue))
{
httpContext.Response.StatusCode = 406;
await httpContext.Response.WriteAsync("Request Limit Exceeded");
return;
}
else
{
string[] apiName = httpContext.Request.Path.Value.Split('/');
var loggertb = new LoggerTB()
{
LoggerID = 0,
ContentType =
Convert.ToString(httpContext.Request.ContentType),
APIKey = keyvalue,
CreatedDate = DateTime.Now,
Host = httpContext.Request.Host.Value,
IsHttps = httpContext.Request.IsHttps ? "Yes" : "No",
Path = httpContext.Request.Path,
Method = httpContext.Request.Method,
Protocol = httpContext.Request.Protocol,
QueryString = httpContext.Request.QueryString.Value,
Scheme = httpContext.Request.Scheme,
RemoteIpAddress = Convert.ToString
(httpContext.Connection.RemoteIpAddress),
LoggerAPI = apiName[2],
};
_IRequestLogger.InsertLoggingData(loggertb);
}
}
}
await _next.Invoke(httpContext);
}
catch (Exception)
{
throw;
}
}
}
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ApiKeyValidatorsMiddleware>();
}
}
}
Let’s Understand Code of Middleware
In this middleware first, we are injecting two dependencies:
IValidateRequest
IRequestLogger
IValidateRequest
interface contains all API validation methods in it.
IRequestLogger
interface contains a method for logging all API request.
Next, after adding newly middleware, you will see Invoke
method in which we are going to write the entire validation process.
- The first step we are validating is we are only going to validate API request.
- In the second step, we are going to only allow post request for all API. If it is another request, then we are going to send an error message.
- In the third step, we are going to check whether query string ("
Key
") exists in request URI.
- In the fourth step, we are going to check the key user sent in request and API he is accessing are valid. For example, For
Music
API, developer does not send Movies
API key.
- In the fifth step, we are going to check whether the key user sent is valid or not.
- In the sixth step, we are going to check API Key against a database that this service is Active or deactivate.
- In the seventh step, we are going to check API request count against this API key if it exceeds, then we are going to send an error response to the user.
Finally, if request passes all barriers, it means the request is valid.
Registering Middleware in startup.cs
Now let’s register this middleware in a startup.cs class such that every API request can be validated.
Now, we are done with adding and registering "ApiKeyValidators
" Middleware. Now let’s run the application and check how middleware works.
Accessing Latest Movies API Controller
For accessing Movies
API, we need API key which we have generated.
Snapshot of Movies API KEY
Now we are going to use POSTMAN APP
to access Movies
API.
Setting up POST Request Parameters
For downloading Postman APP, click on the below URL:
https://www.getpostman.com/postman
Setting Parameters for API.
After sending valid key and request, we got response.
Response after Sending Valid API Request
Send Invalid Request Type
We have sent Get
request to Movies
API, it shows "Method Not Allowed" because in middleware, we have only allowed POST
request for all APIs.
Send Invalid Key
We have sent an invalid API key in this request to test what response we get.
Deactivate Movies Service and Send Request
We have deactivated Movies
API Service. Let’s send a request and test what response we get.
The response we get is proper "Service is Deactivated".
Sending Request More Than We Subscribed
We have subscribed 1000 requests and we have sent complete 1000 requests. I am trying to send the request again, then it shows error message "Request Limit Exceeded".
The table where we have stored a log of all API requests.
Snapshot of LoggerTB Table
Now we have completed the security part. Let’s have a look at the Dashboard.
Final Project Structure
This is a final project structure of the application.
In this part, we can see a folder for storing AES256Encryption algorithm, filters folder contains a filter for validating user session. Middleware folder for storing all middleware of application. And repository for storing interface and concrete classes.
Note: I have keep repository folder in main application because this is a small demo application in the large application. You need to move it to a separate class library, the same way you need to do for models as well.
Dashboard
On this dashboard, you can see your Request graph for API. For Movies
API, we have sent 1000 Requests, that’s why it has to peek at the chart.
These charts are shown on data which is logged in every request.
These charts are CHARTIST.JS.
Snapshot of Dashboard
Tools Used in this Project
- cDesign Template: Paper Dashboard by Creative Tim
- Swagger
- Dapper ORM
- chartist.js Charts
- Icons from https://www.flaticon.com/
- AES256 Encryption
Conclusion
In this article, we have learned a complete cycle of API development in ASP.NET Core WEB API. We started with registering User, then we have generated API Key. Further, we have created API, along with that we have provided feature to activate and deactivate service, then we came to the main process of validating API request, and finally we have done logging of each request such that a developer or normal user knows how many times a user has requested an API.
Thank you for reading. I hope you liked my article.
History
- 6th February, 2018: Initial version