JSON API Implementation In .NET Core Without Using DBContext

Here is my new blog about JSON API with support in .NET Core with DDD model or any other design pattern.
 
Introduction

JSON API is a specification for how a client should request that resources be fetched or modified, and how a server should respond to those requests.

JSON API is designed to minimize both the number of requests and the amount of data transmitted between clients and servers. This efficiency is achieved without compromising readability, flexibility, or discoverability.

The current version of JSON API is 1.0. and will always be backward compatible. And JSON API requires the use of the JSON API media type application/vnd.api+jsonfor exchanging data.

  1. services.AddJsonApi<AppDbContext>(); 
But in DDD model we will not expose our entities in the controller, like below.
  1. public class ArticlesController : JsonApiController<Article>  
  2. {  
  3.     public ArticlesController(  
  4.         IJsonApiContext jsonApiContext,  
  5.         IResourceService<Article> resourceService)   
  6.     : base(jsonApiContext, resourceService) { }  

Also in this implementation we have overriden all our GET, PUT, Update and Delete commands. Because JSON API has their own implementions.
 
Also, I have seen the main drawback with filtering is that it will work in case-sensitive mode. In case you send your attribute and value in a different model, it will throw you an error. If you have seen JSON API code in GitHub you will find these codes.
  1. Class Customer{    
  2.   public string FirstName {getset;}    
  3.   public string LastName {getset;}    
  4.   }   
  5.   
  6. // Also There were doing un-necessary implemenation to make first letter in the attribute make Uppercase.  
  7. // eg:- URL:- /api/articles?filter[firstname]=sam  
  8.   
  9. private List<FilterQuery> ParseFilterQuery(string key, string value)  
  10. {  
  11. var queries = new List<FilterQuery>();  
  12.   
  13. var propertyName = key.Split('['']')[1].ToProperCase();  
  14. }  
  15.   
  16.  // Atribute name look like FirstName, once they apply Propercase method  
  17. var concreteType = typeof(TSource);    
  18. var property = concreteType.GetProperty(filterQuery.FilteredAttribute.InternalAttributeName);    
  19. if (property == null)    
  20. throw new ArgumentException($"'{filterQuery.FilteredAttribute.InternalAttributeName}' is not a valid property of '{concreteType}'");    
  21.     
  22. // here it will throw you the exception because it is not able to find the Attribute name, hence your sending filter with lower case  
  23.   
Also, JSON API will first fetch data and then apply filtering, sorting, and paging. 
 
So, I have created a custom JSON API with the support of a large-scale application and it will fetch the data from the database rather than using an in-memory query.  In regards to filter, I will explain to you how our library will support us to fetch data using filters.
 
First, we have to install my custom library, here is the code:
  1. Install-Package VisionPlanner.JsonAPI -Version 1.1.1 
Once we have installed this package, we can now register in startup class as shown below,
  1. services.AddJsonApi(opt =>  
  2.             {  
  3.                 opt.Namespace = "api/v1";  
  4.                 opt.DefaultPageSize = 5;  
  5.                 opt.IncludeTotalRecordCount = true;  
  6.             }); 
Then we have to inject services which are provided by the library in controller constructor.
  1. public class JsonAPISuccessController : Controller  
  2.    {  
  3.        private readonly IJsonoutputService _jsonoutputService;  
  4.        
  5.        public JsonAPISuccessController(IJsonoutputService jsonoutputService)  
  6.        {  
  7.            _jsonoutputService = jsonoutputService;  
  8.        } 
  9. }
And we have to inject the same service in your repository class because here we are implementing the actual logic to fetch the data from the database using filter expressions.
  1. public class ModelService  
  2. {  
  3.     private readonly IJsonoutputService _jsonoutputService;  
  4.   
  5.     public ModelService(IJsonoutputService jsonoutputService)  
  6.     {  
  7.         _jsonoutputService = jsonoutputService;  
  8.     }  

In this service we have all the data access methods like select, insert, update and delete etc. Here, we are going to write one private method to get the total count of the entity or a table.
  1. private int getTotalItems<TSource>(Expression expressions)  
  2. {  
  3.    var usersCount = expressions != null ? MockingUserDetails().AsQueryable().Where(expressions.ToString()).ToList().Count() : MockingUserDetails().AsQueryable().ToList().Count();
  4.    return usersCount;

If you look closely, you'll see that we are sending the expression to this method, becuase this expression will help to get the accurate data from the database.
  1. public JsonParseObject getParseObject()  
  2. {  
  3.    var oblDetails = _jsonoutputService.GetFilterExpression<UserDetails>();  
  4.    oblDetails.TotalCount = getTotalItems<UserDetails>(oblDetails.Expression);  
  5.    return oblDetails;  

This method is public and we will call this from controller, but totalcount is private and we will call from this method.
 
Then we have to call our actual method to GetAllAsyn(). This method needs expression, sorting parameters, and paging parameter, but the sort and filter are optional parameters.
  1. public List<UserDetails> getUserDetails<TSource>(JsonParseObject parseObject)  
  2. {  
  3.     var query = parseObject.Expression != null ? MockingUserDetails().AsQueryable().Where(parseObject.Expression.ToString()).ToList() :  
  4.                                   MockingUserDetails().AsQueryable().ToList();  
  5.   
  6.     if (parseObject.SortQueries != null)  
  7.     {  
  8.         var orderedEntities = query.AsQueryable().Sort(parseObject.SortQueries[0]);  
  9.   
  10.         if (parseObject.SortQueries.Count() > 1)  
  11.         {  
  12.             for (var i = 1; i < parseObject.SortQueries.Count(); i++)  
  13.                 orderedEntities = orderedEntities.Sort(parseObject.SortQueries[i]);  
  14.         }  
  15.   
  16.         if (orderedEntities != null)  
  17.             query = orderedEntities.ToList();  
  18.     }  
  19.   
  20.     var results = query.Skip(parseObject.pageSize * (parseObject.pageNumber - 1))  
  21.                    .Take(parseObject.pageSize);  
  22.   
  23.     return results.ToList();  
  24. }  
This method needs references from the library to utlize extensions and properties,
 
"using Visionplanner.JsonAPI.Extensions;"
"using Visionplanner.JsonAPI.Models;"
"using Visionplanner.JsonAPI.Repository;"
 
Service layer implementation is completed, so we have to call these services in the controller correctly to fetch the data.
  1. [HttpGet, Route("userdetails")]  
  2.         [ProducesResponseType((int)HttpStatusCode.OK)]  
  3.         [ProducesResponseType((int)HttpStatusCode.NotFound)]  
  4.         public IActionResult GetUserDetails()  
  5.         {  
  6.             var details = _modelService.getParseObject(); // it will return JsonParseobject with Filter/Sort/Paging 

  7.             var userDetails = _modelService.getUserDetails<UserDetails>(details); // it will retuern processed data 

  8.             var results = _jsonoutputService.GetParsingData<UserDetails>(userDetails.AsQueryable(), details.pageSize, details.pageNumber, details.TotalCount); // it will return seralize data.  
  9.   
  10.             if (results.Data.Count() > 0)  
  11.                 return new ObjectResult("SuccessCode") { StatusCode = 200 };  
  12.             else  
  13.                 return new ObjectResult("NotApplicable") { StatusCode = 406 };  
  14.         } 
We can see that we are calling three methods,
  1. The method will prepare jsonParseobject (inside we have Expression built based on the url and sorting parameters and paging parameters)
  2. This is the actual method that will return data from the service layer.
  3. This method is the JSON library method to transform data and prepare the JSON object.
  1. {  
  2.     "links": [  
  3.         {  
  4.             "href""http://localhost:59312/api/v1.0/Contact/tenantid/76E23186-D637-46E3-9D07-845DD928D3B6?page[size]=5&page[number]=1",  
  5.             "rel""first",  
  6.             "method""GET"  
  7.         }  
  8.     ],  
  9.     "meta": {  
  10.         "total-records": 1  
  11.     },  
  12.     "data": {  
  13.         "data": [  
  14.             {  
  15.                 "type""userviewmodel",  
  16.                 "id""2e2d946b-9874-4e97-b732-a97000b74513",  
  17.                 "attributes": {  
  18.                     "firstName""krishna",  
  19.                     "lastName""jaya",  
  20.                     "initial""k",  
  21.                     "gender""Male",  
  22.                     "email""[email protected]",  
  23.                     "optedOut""Yes",  
  24.                     "linekedHomePage""www.google.com",  
  25.                     "phone""1234567890",  
  26.                     "attachment""Nothing",  
  27.                     "roles": [  
  28.                         {  
  29.                             "roleName""DecisionMaker"  
  30.                         }  
  31.                     ],  
  32.                     "notes""Testing User",  
  33.                     "contactType""UserContact",  
  34.                     "status""Active",  
  35.                     "salutation""MR",  
  36.                     "tenantId""76e23186-d637-46e3-9d07-845dd928d3b6"  
  37.                 }  
  38.             }  
  39.         ]  
  40.     }  

So, our library will help us to make our lives easier, we don't need to worry about the search, sort and paging. 
 
Here is a sample JSON standard URL to fetch the data from the library.
 
sort:- /jsonapi-success/userdetails?sort=firstName,lastname
Search:- /jsonapi-success/userdetails?filter[firstname]=kri&filter[lastname]=jaya
Paging:- /jsonapi-success/userdetails?page[size]=1&page[number]=1 
 
In JSON API the filter will work one by one using for loop, but in our library, it will prepare the complete expression like below and send it to the actual method to fetch the data from the database.
 
(c=>c.FirstName.Tolower().Contains("kri") && c.LastName.ToLower().Contains("jaya"))
 
Also, you can prepare a url with a combination. And all are an option in the url if you provide it will apply it; otherwise it will return a complete set of data.
 
plain url:- /jsonapi-success/userdetails "it will return complete data like above example out" .
 
By default, paging will also apply based on the page size we have to register in startup class.
 
I hope this document will help you understand how to use the library and this library will help to solve the problem I have mentioned above.
 
Please let me know in case you have any other concerns so that I can try to embed those changes and release a new package.