Source from :
https://www.codeproject.com/Articles/1260600/Speed-up-ASP-NET-Core-WEB-API-application-Part-1

Introduction

In this article, we review the process of creating ASP.NET WEB API application using ASP.NET Core. The main focus will be on the application productivity.

The article is divided into two parts:

Part 1. Creating a Test RESTful WEB API Service

In Part 1, we will create an asynchronous RESTful WEB API service that be able to search products in a database and get price lists of different suppliers for the particular product.

For coding, we need Microsoft Visual Studio 2017 (updated to .NET Core 2.1) and Microsoft SQL Server (any version).

We will review the following:

The Application Architecture

We will build our WEB API application using Controllers – Services – Repositories – Database architecture.

Controllers are responsible for routing – they accept http requests and invoke appropriate methods of services with parameters received from the request parameters or body. By convention, we named classes that encapsulate business logic as “Services”. After processing a request, a service returns a result of IActionResulttype to a controller. The controller does not care about the type of service result and just transmits it to the user with the http response. All methods of receiving the data from or storing the data in a database are encapsulated in Repositories. If the Service needs some data, it requests the Repository without knowing where and how the data is stored.

This pattern provides maximum decoupling of application layers and makes it easy to develop and test the application.

The Database

In our application, we use a Microsoft SQL Server. Let us create a database for our application and fill it with test data and execute the next query in the Microsoft SQL Server Management Studio:

Hide   Shrink    Copy Code
USE [master]
GO CREATE DATABASE [SpeedUpCoreAPIExampleDB]
GO USE [SpeedUpCoreAPIExampleDB]
GO CREATE TABLE [dbo].[Products] (
[ProductId] INT IDENTITY (1, 1) NOT NULL,
[SKU] NCHAR (50) NOT NULL,
[Name] NCHAR (150) NOT NULL,
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED ([ProductId] ASC)
);
GO CREATE TABLE [dbo].[Prices] (
[PriceId] INT IDENTITY (1, 1) NOT NULL,
[ProductId] INT NOT NULL,
[Value] DECIMAL (18, 2) NOT NULL,
[Supplier] NCHAR (50) NOT NULL,
CONSTRAINT [PK_Prices] PRIMARY KEY CLUSTERED ([PriceId] ASC)
);
GO ALTER TABLE [dbo].[Prices] WITH CHECK ADD CONSTRAINT [FK_Prices_Products] FOREIGN KEY([ProductId])
REFERENCES [dbo].[Products] ([ProductId])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Prices] CHECK CONSTRAINT [FK_Prices_Products]
GO INSERT INTO Products ([SKU], [Name]) VALUES ('aaa', 'Product1');
INSERT INTO Products ([SKU], [Name]) VALUES ('aab', 'Product2');
INSERT INTO Products ([SKU], [Name]) VALUES ('abc', 'Product3'); INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (1, 100, 'Bosch');
INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (1, 125, 'LG');
INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (1, 130, 'Garmin'); INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (2, 140, 'Bosch');
INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (2, 145, 'LG');
INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (2, 150, 'Garmin'); INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (3, 160, 'Bosch');
INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (3, 165, 'LG');
INSERT INTO Prices ([ProductId], [Value], [Supplier]) VALUES (3, 170, 'Garmin'); GO

Now we have a database with the name SpeedUpCoreAPIExampleDB, filled with the test data.

The Products table consists of a products list. The SKU field is for searching for products in the list.

The Prices table consists of a list of prices.

The relationship between these tables can be represented schematically:

Note! We have created a FOREIGN KEY FK_Prices_Products with the CASCADE delete rule so that the MS SQL server be able to provide data integrity between the Products and Prices tables on deleting records from the Products table.

Creating ASP.NET Core WEB API Application

In Microsoft Visual Studio, start new .NET Core project SpeedUpCoreAPIExample.

Then select Web API:

Since we are making a Web API Сore application, we should install Microsoft.AspNetCore.Mvc NuGet package. Go to menu Main menu > Tools > NuGet Package Manager > Manager NuGet Packages For Solutionand input Microsoft.AspNetCore.Mvc. Select and install the package:

The next step is to create a data model of our application. Since we have already created the database, it seems logical to use a scaffolding mechanism to generate the data model from the database structure. But we will not use scaffolding because the database structure will not entirely reflect the application data model - in the database, we follow the convention of naming tables with plural names like “Products” and “Prices”, considering the tables to be sets of rows “Product” and “Price” respectively. In our application, we want to name entity classes “Product” and “Price”, but after scaffolding, we would have entities with the names “Products” and “Prices” created and some other objects that reflect the relationship between entities would also be created automatically.

Therefore, we would have to rewrite the code. That is why, we decided to create the data model manually.

In the Solution Explorer, right click your project and select Add > New Folder.

Name it Models. In the Models folder, let us create two entity classes, Product.cs and Price.cs.

Right click the Models folder then select Add Item > Class > Product.cs.

Enter the text of Product class:

Hide   Copy Code
namespace SpeedUpCoreAPIExample.Models
{
public class Product
{
public int ProductId { get; set; }
public string Sku { get; set; }
public string Name { get; set; }
}
}

And for the Price.cs:

Hide   Copy Code
namespace SpeedUpCoreAPIExample.Models
{
public class Price
{
public int PriceId { get; set; }
public int ProductId { get; set; }
public decimal Value { get; set; }
public string Supplier { get; set; }
}
}

In the Price class, we use the Value field to store price values - we cannot name the field “Price” as fields cannot have the same name as the name of a class (we also use “Value” field in Prices table of our database). Later in the Database context class, we will map these entities to database tables “Products” and “Prices”.

Note that in the model, we do not have any relationship between the Products and Prices entities.

Database Access with Entity Framework Core

To access the database, we will use Entity Framework Core. For this, we need to install an EntityFrameworkCore provider for our database. Go to menu Main menu > Tools > NuGet Package Manager > Manager NuGet Packages For Solution and input Microsoft.EntityFrameworkCore.SqlServer in the Browse field, as we are using a Microsoft SQL Server. Select and install the package:

To inform the Entity Framework how to work with our data model, we should create a Database context class. For this, let us create a new folder Contexts, right click on it and select Add > New Item > ASP.NET Core > Code > Class. Name the class DefaultContext.cs.

Enter the following text of the class:

Hide   Copy Code
using Microsoft.EntityFrameworkCore;
using SpeedUpCoreAPIExample.Models; namespace SpeedUpCoreAPIExample.Contexts
{
public class DefaultContext : DbContext
{
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Price> Prices { get; set; } public DefaultContext(DbContextOptions<DefaultContext> options) : base(options)
{
}
}
}

In the following lines, we mapped data model entities classes to database tables:

Hide   Copy Code
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Price> Prices { get; set; }

According to the Entity Framework Core naming convention for entity key names, model key fields should have name "Id" or EntitynameId (case insensitive) to be mapped by EFC to database keys automatically. We use the "ProductId" and "PriceId" names, which meet the convention. If we use nonstandard names for key fields, we would have to configure the keys in a DbContext explicitly.

The next step for the Database context is to declare it in the Startup class of our application. Open the Startup.cs file in the root of the application and in the ConfigureServices method, add "using" directives:

Hide   Copy Code
using Microsoft.EntityFrameworkCore;
using SpeedUpCoreAPIExample.Contexts;

and correct the ConfigureServices procedure.

Hide   Copy Code
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(); services.AddDbContext<DefaultContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultDatabase")));
}

The last step is to configure the Connection string of the database. For this, find the appsettings.json file in the root of our application and add the following ConnectionStrings session:

Hide   Copy Code
"ConnectionStrings": {
"DefaultDatabase": "Server=localhost;Database=SpeedUpCoreAPIExampleDB;Integrated Security=True;"
}

But be aware, that in the root of the application, there is also an appsettings.Development.json configuration file. By default, this file is used during the development process. So, you should duplicate the ConnectionStringssession there or Configuration.GetConnectionString may return null.

Now, our application is ready to work with the database.

Asynchronous Design Pattern

Asynchronously working is the first step of increasing productivity of our application. All the benefits of the Asynchrony will be discussed in Part 2.

We want all our repositories be working asynchronously, so they will return Task<T> and all methods will have names with the Async suffix. The suffix does not make a method asynchronous. It is used by convention to represent our intentions regarding the method. The combination async – await implements the asynchronous pattern.

Repositories

We will create two Repositories – one for the Product entity and another for the Price entity. We will declare repositories methods in the appropriate interface first, to make the repositories ready for the dependency injection.

Let us create a new folder Interfaces for the interfaces. Right click the Interfaces folder and add a new class with the name IProductsRepository.cs and change its code for:

Hide   Copy Code
using SpeedUpCoreAPIExample.Models;
using System.Collections.Generic;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Repositories
{
public interface IProductsRepository
{
Task<IEnumerable<Product>> GetAllProductsAsync();
Task<Product> GetProductAsync(int productId);
Task<IEnumerable<Product>> FindProductsAsync(string sku);
Task<Product> DeleteProductAsync(int productId);
}
}

Then for the IPricesRepository.cs:

Hide   Copy Code
using SpeedUpCoreAPIExample.Models;
using System.Collections.Generic;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Interfaces
{
public interface IPricesRepository
{
Task<IEnumerable<Price>> GetPricesAsync(int productId);
}
}

Repositories Implementation

Create a new folder Repositories and add the ProductsRepository class with the code:

Hide   Shrink    Copy Code
using Microsoft.EntityFrameworkCore;
using SpeedUpCoreAPIExample.Contexts;
using SpeedUpCoreAPIExample.Interfaces;
using SpeedUpCoreAPIExample.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Repositories
{
public class ProductsRepository : IProductsRepository
{
private readonly DefaultContext _context; public ProductsRepository(DefaultContext context)
{
_context = context;
} public async Task<IEnumerable<Product>> GetAllProductsAsync()
{
return await _context.Products.ToListAsync();
} public async Task<Product> GetProductAsync(int productId)
{
return await _context.Products.Where(p => p.ProductId == productId).FirstOrDefaultAsync();
} public async Task<IEnumerable<Product>> FindProductsAsync(string sku)
{
return await _context.Products.Where(p => p.Sku.Contains(sku)).ToListAsync();
} public async Task<Product> DeleteProductAsync(int productId)
{
Product product = await GetProductAsync(productId); if (product != null)
{
_context.Products.Remove(product); await _context.SaveChangesAsync();
} return product;
}
}
}

In the ProductsRepository class constructor, we injected DefaultContext using dependency injection.

Then create PricesRepository.cs with the code:

Hide   Copy Code
using Microsoft.EntityFrameworkCore;
using SpeedUpCoreAPIExample.Contexts;
using SpeedUpCoreAPIExample.Interfaces;
using SpeedUpCoreAPIExample.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Repositories
{
public class PricesRepository : IPricesRepository
{
private readonly DefaultContext _context; public PricesRepository(DefaultContext context)
{
_context = context;
} public async Task<IEnumerable<Price>> GetPricesAsync(int productId)
{
return await _context.Prices.Where(p => p.ProductId == productId).ToListAsync();
}
}
}

The last step is to declare our repositories in the Startup class. Add "using" directives in the ConfigureServices method of the Startup.cs:

Hide   Copy Code
using SpeedUpCoreAPIExample.Interfaces;
using SpeedUpCoreAPIExample.Repositories;

and after DefaultContext declaration:

Hide   Copy Code
services.AddScoped<IProductsRepository, ProductsRepository>();
services.AddScoped<IPricesRepository, PricesRepository>();

Note! The sequence of declarations matters for the dependency injection – if you want to inject DefaultContext into repositories, the DefaultContext should be declared before repositories. For repositories, we use Scoped lifetime model because the system registers Database context with Scoped model automatically. And a repository that uses the context should have the same lifetime model.

Services

Our “Services” classes encapsulate all the business logic except accessing the database – for this, they have repositories injected. All services methods run asynchronously and return IActionResult depending on the result of data processing. Services handle errors as well and perform output result formatting accordingly.

Before we start Implementation of the services, let us think of the data we are going to send back to a user. For example, our model class “Price” has two fields “PriceId” and “ProductId” that we use for obtaining data from the database, but they mean nothing for users. More than that, we can occasionally uncover some sensitive data if our APIs respond with the entire entity. Besides that, we will use “Price” field to return a price value that is more common when working with pricelists. And we will use the “Id” field to return ProductId. “Id” name will correspond to the name of the API’s parameters for product identification (which will be observed in “Controllers” section).

So, it is a good practice to create a Data Model for output with a limited set of fields.

Let us create a new folder, ViewModels and add two classes there:

Hide   Copy Code
namespace SpeedUpCoreAPIExample.ViewModels
{
public class ProductViewModel
{
public int Id { get; set; }
public string Sku { get; set; }
public string Name { get; set; }
}
}

and:

Hide   Copy Code
namespace SpeedUpCoreAPIExample.ViewModels
{
public class PriceViewModel
{
public decimal Price { get; set; }
public string Supplier { get; set; }
}
}

These classes are shortened and safe versions of entity classes, Product and Price without extra fields and with adjusted fields names.

Services Interfaces

One service can do all the job, but we will create as many services as repositories. As usual, we start from declaring services method in interfaces.

Right click on the Interfaces folder and create a new class, IProductsService, with the following code:

Hide   Copy Code
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Interfaces
{
public interface IProductsService
{
Task<IActionResult> GetAllProductsAsync();
Task<IActionResult> GetProductAsync(int productId);
Task<IActionResult> FindProductsAsync(string sku);
Task<IActionResult> DeleteProductAsync(int productId);
}
}

And then, IPricesService with the code:

Hide   Copy Code
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Interfaces
{
public interface IPricesService
{
Task<IActionResult> GetPricesAsync(int productId);
}
}

Implementation of the Services

Create a new folder, Services, and add a new class, ProductsService:

Hide   Shrink    Copy Code
using Microsoft.AspNetCore.Mvc;
using SpeedUpCoreAPIExample.Interfaces;
using SpeedUpCoreAPIExample.Models;
using SpeedUpCoreAPIExample.ViewModels;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Services
{
public class ProductsService : IProductsService
{
private readonly IProductsRepository _productsRepository; public ProductsService(IProductsRepository productsRepository)
{
_productsRepository = productsRepository;
} public async Task<IActionResult> FindProductsAsync(string sku)
{
try
{
IEnumerable<Product> products = await _productsRepository.FindProductsAsync(sku); if (products != null)
{
return new OkObjectResult(products.Select(p => new ProductViewModel()
{
Id = p.ProductId,
Sku = p.Sku.Trim(),
Name = p.Name.Trim()
}
));
}
else
{
return new NotFoundResult();
}
}
catch
{
return new ConflictResult();
}
} public async Task<IActionResult> GetAllProductsAsync()
{
try
{
IEnumerable<Product> products = await _productsRepository.GetAllProductsAsync(); if (products != null)
{
return new OkObjectResult(products.Select(p => new ProductViewModel()
{
Id = p.ProductId,
Sku = p.Sku.Trim(),
Name = p.Name.Trim()
}
));
}
else
{
return new NotFoundResult();
}
}
catch
{
return new ConflictResult();
}
} public async Task<IActionResult> GetProductAsync(int productId)
{
try
{
Product product = await _productsRepository.GetProductAsync(productId); if (product != null)
{
return new OkObjectResult(new ProductViewModel()
{
Id = product.ProductId,
Sku = product.Sku.Trim(),
Name = product.Name.Trim()
});
}
else
{
return new NotFoundResult();
}
}
catch
{
return new ConflictResult();
}
} public async Task<IActionResult> DeleteProductAsync(int productId)
{
try
{
Product product = await _productsRepository.DeleteProductAsync(productId); if (product != null)
{
return new OkObjectResult(new ProductViewModel()
{
Id = product.ProductId,
Sku = product.Sku.Trim(),
Name = product.Name.Trim()
});
}
else
{
return new NotFoundResult();
}
}
catch
{
return new ConflictResult();
}
}
}
}

And the PricesService:

Hide   Shrink    Copy Code
using Microsoft.AspNetCore.Mvc;
using SpeedUpCoreAPIExample.Interfaces;
using SpeedUpCoreAPIExample.Models;
using SpeedUpCoreAPIExample.ViewModels;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Services
{
public class PricesService : IPricesService
{
private readonly IPricesRepository _pricesRepository; public PricesService(IPricesRepository pricesRepository)
{
_pricesRepository = pricesRepository;
} public async Task<IActionResult> GetPricesAsync(int productId)
{
try
{
IEnumerable<Price> pricess = await _pricesRepository.GetPricesAsync(productId); if (pricess != null)
{
return new OkObjectResult(pricess.Select(p => new PriceViewModel()
{
Price = p.Value,
Supplier = p.Supplier.Trim()
}
)
.OrderBy(p => p.Price)
.ThenBy(p => p.Supplier)
);
}
else
{
return new NotFoundResult();
}
}
catch
{
return new ConflictResult();
}
}
}
}

Data Integrity between the Products and Prices Tables

In the ProductsService, we have the DeleteProductAsync method, which invokes an appropriate method for the PricesRepository to delete a product data row from the Products table. We have also established a relationship between the Products and Prices tables by means of the FK_Prices_Products foreign key. Since the FK_Prices_Products foreign key has a CASCADE delete rule, when deleting records from the Products table the related records from the Prices table will also be deleted automatically.

There are some other possible approaches to enforce data integrity without Foreign Keys with cascade delete in a database. For instance, we can configure Entity Framework to perform the cascade deleting with the WillCascadeOnDelete() method. But this also demands to reengineer our data model. Another approach is to realize a method DeletePricessAsync in the PricesService and call it with the DeleteProductAsyncmethod. But we must think of doing this in a single transaction, because our application can fail when a Producthas already been deleted but not the prices. So, we can lose data integrity.

In our example, we use the Foreign Keys with cascade delete to enforce data integrity.

Note! Obviously, in a real application, the DeleteProductAsync method should not be invoked so easily because the important data can be lost by accident or intentionally. In our example, we use it just to expose the data integrity idea.

Services Pattern

In the constructor of a service, we inject appropriate repository via dependency injection. Each method gets data from the repository inside try – catch construction and returns the IActionResult accordingly to the data processing result. When returning a dataset, the data translates to a class from the ViewModel folder.

Note, that responses OkObjectResult()NotFoundResult()ConflictResult(), etc. correspond to Controller’s ControllerBase Ok()NotFound()Conflict() methods respectively. A Service sends its response to a Controller with the same IActionResult type, as a Controller sends to a user. This means that a Controller can pass the response directly to a user without the necessity to adjust it.

The last step for the services is to declare them in the Startup class. Add using directive:

Hide   Copy Code
using SpeedUpCoreAPIExample.Services;

and declare Services in ConfigureServices method after repositories declaration:

Hide   Copy Code
services.AddTransient<IProductsService, ProductsService>();
services.AddTransient<IPricesService, PricesService>();

As our services are lightweight and stateless, we can use the Transient Services scope model.

The final ConfigureServices method at this stage is:

Hide   Copy Code
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(); services.AddDbContext<DefaultContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultDatabase"))); services.AddScoped<IProductsRepository, ProductsRepository>();
services.AddScoped<IPricesRepository, PricesRepository>(); services.AddTransient<IProductsService, ProductsService>();
services.AddTransient<IPricesService, PricesService>();
}

The Controllers

In our design pattern, we have not left much for the controllers but just to be gateways for incoming requests - no business logic, data access, error handling and so on. A controller will receive incoming requests according to its routes, call an appropriate method of a service, that we injected via dependency injection, and returns the results of these methods.

As usual, we will create two small controllers instead of a big one, because they work with logically different data and have different routes to their APIs:

ProductsController routes:

[HttpGet] routing

  • /api/products – returns the whole list of products
  • /api/products/1 – returns one product with Id = 1
  • /api/products/find/aaa – returns list of products which Sku field consists of parameter “sku” value “aaa”

[HttpDelete] routing

  • /api/product/1 – removes product with Id = 1 and its prices (cascading)

PricesController routes:

[HttpGet] routing

  • /api/prices/1 – returns list of prices of product with Id = 1

Creating Controllers

Right click the Controllers folder, then select Add Item > Class > ProductsController.cs and change the text for:

Hide   Shrink    Copy Code
using Microsoft.AspNetCore.Mvc;
using SpeedUpCoreAPIExample.Interfaces;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductsController : Controller
{
private readonly IProductsService _productsService; public ProductsController(IProductsService productsService)
{
_productsService = productsService;
} // GET /api/products
[HttpGet]
public async Task<IActionResult> GetAllProductsAsync()
{
return await _productsService.GetAllProductsAsync();
} // GET /api/products/5
[HttpGet("{id}")]
public async Task<IActionResult> GetProductAsync(int id)
{
return await _productsService.GetProductAsync(id);
} // GET /api/products/find
[HttpGet("find/{sku}")]
public async Task<IActionResult> FindProductsAsync(string sku)
{
return await _productsService.FindProductsAsync(sku);
} // DELETE /api/products/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProductAsync(int id)
{
return await _productsService.DeleteProductAsync(id);
}
}
}

Controller’s name should have “Controller” suffix. Using the directive [Route("api/[controller]")]means that the basic route of all ProductsController, the controller’s API will be /api/products.

The same for the PricesController controller:

Hide   Copy Code
using Microsoft.AspNetCore.Mvc;
using SpeedUpCoreAPIExample.Interfaces;
using System.Threading.Tasks; namespace SpeedUpCoreAPIExample.Contexts
{
[Route("api/[controller]")]
[ApiController]
public class PricesController : ControllerBase
{
private readonly IPricesService _pricesService; public PricesController(IPricesService pricesService)
{
_pricesService = pricesService;
} // GET /api/prices/1
[HttpGet("{Id}")]
public async Task<IActionResult> GetPricesAsync(int id)
{
return await _pricesService.GetPricesAsync(id);
}
}
}

Now everything is almost ready to start our application for the first time. Before we launch the application, let us look inside the launchSettings.json file in folder /Properties of the application. We can see, “launchUrl": "api/values". Let us remove that “/values”. In this file, we can also change a port number in the applicationUrl parameter: "applicationUrl": "http://localhost:49858/", in our case, the port is 49858.

And we can remove the ValuesController.cs controller from the Controllers folder. The controller was created automatically by the Visual Studio and we do not use it in our application.

Start the application by clicking Main Menu > Debug > Start Without Debugging (or press Ctrl+F5). The application will be opened in the Internet Explorer browser (by default) and the URL will be http://localhost:49858/api.

Examine the Application

We will use the Swagger tool to examine our application. It is better to use Google Chrome or Firefox browsers for this. So, open Firefox and enter https://inspector.swagger.io/builder in the URL field. You will be asked to install the Swagger Inspector Extension.

Add the Extension.

Now we have a button in the browser to start the extension. Open it, select GET method and input URL of the API:

http://localhost:49858/api/products

Click Send button and you will receive a json formatted list of all products:

Check the particular Product:

http://localhost:49858/api/products/1

Find products by part of SKU:

http://localhost:49858/api/products/find/aa

Then check PricesController API:

http://localhost:49858/api/prices/1

Check Delete API.

Change a method in Swagger for DELETE and call API:

http://localhost:49858/api/products/3

To check the deletion result, we can call API http://localhost:49858/api/products/3 with GET method. The result will be 404 Not Found.

Calling http://localhost:49858/api/prices/3 will return an empty set of prices.

Summary

At last, we have the working ASP.NET Core RESTful WEB API service.

So far, everything has been quite trivial and just a preparation for exploring problems with the Application productivity.

But something important has already been done at this stage for increasing application performance - implementing asynchronous design pattern.

Points of Interest

In Part 2 of this article, we will use various approaches to increase the application's productivity.

感觉service层直接返回API Result 不太合适。

另外本例使用全文搜索必要性不高,这块可以考虑直接用缓存或普通索引解决问题。

全文索引和普通索引

两种索引的功能和结构都是不同的
普通索引的结构主要以B+树和哈希索引为主,用于实现对字段中数据的精确查找,比如查找某个字段值等于给定值的记录,A=10这种查询,因此适合数值型字段和短文本字段
全文索引是用于检索字段中是否包含或不包含指定的关键字,有点像搜索引擎的功能,其内部的索引结构采用的是与搜索引擎相同的倒排索引结构,其原理是对字段中的文本进行分词,然后为每一个出现的单词记录一个索引项,这个索引项中保存了所有出现过该单词的记录的信息,也就是说在索引中找到这个单词后,就知道哪些记录的字段中包含这个单词了。因此适合用大文本字段的查找。

大字段之所以不适合做普通索引,最主要的原因是普通索引对检索条件只能进行精确匹配,而大字段中的文本内容很多,通常也不会在这种字段上执行精确的文本匹配查询,而更多的是基于关键字的全文检索查询,例如你查一篇文章信息,你会只输入一些关键字,而不是把整篇文章输入查询(如果有整篇文章也就不用查询了)。而全文索引正是适合这种查询需求。

改进架构后的源代码地址:
https://github.com/GreensBird/SpeedUpCoreAPIExample.git

.NTE Core Web API Example的更多相关文章

  1. 使用 Swagger 自动生成 ASP.NET Core Web API 的文档、在线帮助测试文档(ASP.NET Core Web API 自动生成文档)

    对于开发人员来说,构建一个消费应用程序时去了解各种各样的 API 是一个巨大的挑战.在你的 Web API 项目中使用 Swagger 的 .NET Core 封装 Swashbuckle 可以帮助你 ...

  2. 在ASP.NET Core Web API上使用Swagger提供API文档

    我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的API文档功能.当设置IISExpress的默认启动路由到Swagger的API文档页 ...

  3. Docker容器环境下ASP.NET Core Web API应用程序的调试

    本文主要介绍通过Visual Studio 2015 Tools for Docker – Preview插件,在Docker容器环境下,对ASP.NET Core Web API应用程序进行调试.在 ...

  4. 在docker中运行ASP.NET Core Web API应用程序

    本文是一篇指导快速演练的文章,将介绍在docker中运行一个ASP.NET Core Web API应用程序的基本步骤,在介绍的过程中,也会对docker的使用进行一些简单的描述.对于.NET Cor ...

  5. ASP.NET Core Web API Cassandra CRUD 操作

    在本文中,我们将创建一个简单的 Web API 来实现对一个 “todo” 列表的 CRUD 操作,使用 Apache Cassandra 来存储数据,在这里不会创建 UI ,Web API 的测试将 ...

  6. 在Mac下创建ASP.NET Core Web API

    在Mac下创建ASP.NET Core Web API 这系列文章是参考了.NET Core文档和源码,可能有人要问,直接看官方的英文文档不就可以了吗,为什么还要写这些文章呢? 原因如下: 官方文档涉 ...

  7. ASP.NET Core Web API 开发-RESTful API实现

    ASP.NET Core Web API 开发-RESTful API实现 REST 介绍: 符合REST设计风格的Web API称为RESTful API. 具象状态传输(英文:Representa ...

  8. Core Web API上使用Swagger提供API文档

    在ASP.NET Core Web API上使用Swagger提供API文档   我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的AP ...

  9. Docker容器环境下ASP.NET Core Web API

    Docker容器环境下ASP.NET Core Web API应用程序的调试 本文主要介绍通过Visual Studio 2015 Tools for Docker – Preview插件,在Dock ...

随机推荐

  1. 另外一种获取redis cluster主从关系和slot分布的方法

    条条大路通罗马,通过最近学习redis cluster 观察其输出,发现了另外一种获取master-slave关系的方法. [redis@lxd-vm1 ~]$ cat get_master_slav ...

  2. 洛谷 pP2708 硬币翻转

    题目描述 从前有很多个硬币摆在一行,有正面朝上的,也有背面朝上的.正面朝上的用1表示,背面朝上的用0表示.现在要求从这行的第一个硬币开始,将前若干个硬币一起翻面,问如果要将所有硬币翻到正面朝上,最少要 ...

  3. LaTeX技巧009:中国象棋的LaTeX排版

    Latex可以排版容易排版中国象棋, 围棋, 国际象棋棋谱和乐谱, 详情请见. http://bbs.chinatex.org/forum.php?mod=viewthread&tid=498 ...

  4. Hadoop 集群ssh免密登录设置

    0.安装命令: yum list installed | grep openssh-server 命令检查ssh安装有没有安装,如果查询出来有就表示安装了,否则反之 通过 yum install op ...

  5. PP: Think globally, act locally: A deep neural network approach to high-dimensional time series forecasting

    Problem: high-dimensional time series forecasting ?? what is "high-dimensional" time serie ...

  6. Eclipse设置代码模板

    个人博客 地址:http://www.wenhaofan.com/article/20180904173808 根据下列路径打开配置窗口 Window->Preferences->Java ...

  7. 利用redis,为Django项目储存session

    1.准备工作 pip install redis pip install django-redis-sessions==0.5.6 (推荐使用此版本) 创建一个django项目,新建一个booktes ...

  8. python3练习100题——029

    原题链接:http://www.runoob.com/python/python-exercise-example29.html 题目:给一个不多于5位的正整数,要求:一.求它是几位数,二.逆序打印出 ...

  9. VSCode配置Go插件和第三方拓展包

    前言 VSCode现在已经发展的相当完善,很多语言都比较推荐使用其来编写,Go语言也一样,前提你电脑已经有了Go环境和最新版本的VSCode 插件安装 直接在拓展插件中搜索Go,就可以安装Go插件 安 ...

  10. 简单的Spring1.0小配置

    开始Spring AOP的小理解 拿一个小例子来说吧!    老师上课   这样的例子!    老师上课--就是一个核心的业务!     那么上课之前需要点名,天气太热,需要开空调! 这个时候,一个老 ...