.NTE Core Web API Example
2021-06-05 11:04
标签:sqlserver receiving extent lines dep menu cci try dir Source from : 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: 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: 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 This pattern provides maximum decoupling of application layers and makes it easy to develop and test the application. 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: Now we have a database with the name The The The relationship between these tables can be represented schematically: Note! We have created a FOREIGN KEY In Microsoft Visual Studio, start new .NET Core project Then select Web API: Since we are making a Web API Сore application, we should install 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 “ 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 And for the Price.cs: In the Note that in the model, we do not have any relationship between the Products and Prices entities. To access the database, we will use Entity Framework Core. For this, we need to install an To inform the Entity Framework how to work with our data model, we should create a Enter the following text of the class: In the following lines, we mapped data model entities classes to database tables: According to the Entity Framework Core naming convention for entity key names, model key fields should have name " The next step for the Database context is to declare it in the and correct the 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 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 Now, our application is ready to work with the database. 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 We will create two Repositories – one for the 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: Then for the IPricesRepository.cs: Create a new folder Repositories and add the In the Then create PricesRepository.cs with the code: The last step is to declare our repositories in the and after Note! The sequence of declarations matters for the dependency injection – if you want to inject Our “ 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 “ 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: and: These classes are shortened and safe versions of entity classes, 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, And then, Create a new folder, Services, and add a new class, And the In the 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 In our example, we use the Foreign Keys with cascade delete to enforce data integrity. Note! Obviously, in a real application, the In the constructor of a service, we inject appropriate repository via dependency injection. Each method gets data from the repository inside Note, that responses The last step for the services is to declare them in the and declare As our services are lightweight and stateless, we can use the Transient Services scope model. The final 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 [HttpDelete] routing PricesController routes: [HttpGet] routing Right click the Controllers folder, then select Add Item > Class > ProductsController.cs and change the text for: The same for the 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, “ 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. 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 http://localhost:49858/api/products Click Send button and you will receive a json formatted list of all products: Check the particular http://localhost:49858/api/products/1 Find products by part of SKU: http://localhost:49858/api/products/find/aa Then check http://localhost:49858/api/prices/1 Check Change a method in Swagger for http://localhost:49858/api/products/3 To check the deletion result, we can call API http://localhost:49858/api/products/3 with Calling http://localhost:49858/api/prices/3 will return an empty set of prices. 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. In Part 2 of this article, we will use various approaches to increase the application‘s productivity. 感觉service层直接返回API Result 不太合适。 另外sqlserver的全文搜索必要性不高,这块可以考虑直接用缓存解决问题。 改进架构后的源代码地址: .NTE Core Web API Example 标签:sqlserver receiving extent lines dep menu cci try dir 原文地址:https://www.cnblogs.com/tylertang/p/10802772.html
https://www.codeproject.com/Articles/1260600/Speed-up-ASP-NET-Core-WEB-API-application-Part-1
Introduction
Part 1. Creating a Test RESTful WEB API Service
The Application Architecture
IActionResult
type 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.The Database
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
SpeedUpCoreAPIExampleDB
, filled with the test data.Products
table consists of a products
list. The SKU field is for searching for products
in the list.Prices
table consists of a list of prices.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
SpeedUpCoreAPIExample
.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: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.Product
class:namespace SpeedUpCoreAPIExample.Models
{
public class Product
{
public int ProductId { get; set; }
public string Sku { get; set; }
public string Name { get; set; }
}
}
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; }
}
}
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
”.Database Access with Entity Framework Core
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: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.using Microsoft.EntityFrameworkCore;
using SpeedUpCoreAPIExample.Models;
namespace SpeedUpCoreAPIExample.Contexts
{
public class DefaultContext : DbContext
{
public virtual DbSet
public virtual DbSet
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.Startup
class of our application. Open the Startup.cs file in the root of the application and in the ConfigureServices
method, add "using
" directives:using Microsoft.EntityFrameworkCore;
using SpeedUpCoreAPIExample.Contexts;
ConfigureServices
procedure.public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext
ConnectionStrings
session:"ConnectionStrings": {
"DefaultDatabase": "Server=localhost;Database=SpeedUpCoreAPIExampleDB;Integrated Security=True;"
}
ConnectionStrings
session there or Configuration.GetConnectionString
may return null
.Asynchronous Design Pattern
Task
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
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.using SpeedUpCoreAPIExample.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SpeedUpCoreAPIExample.Repositories
{
public interface IProductsRepository
{
Task
using SpeedUpCoreAPIExample.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SpeedUpCoreAPIExample.Interfaces
{
public interface IPricesRepository
{
Task
Repositories Implementation
ProductsRepository
class with the 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
ProductsRepository
class constructor, we injected DefaultContext
using dependency injection.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
Startup
class. Add "using
" directives in the ConfigureServices
method of the Startup.cs:using SpeedUpCoreAPIExample.Interfaces;
using SpeedUpCoreAPIExample.Repositories;
DefaultContext
declaration:services.AddScoped
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
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.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).namespace SpeedUpCoreAPIExample.ViewModels
{
public class ProductViewModel
{
public int Id { get; set; }
public string Sku { get; set; }
public string Name { get; set; }
}
}
namespace SpeedUpCoreAPIExample.ViewModels
{
public class PriceViewModel
{
public decimal Price { get; set; }
public string Supplier { get; set; }
}
}
Product
and Price
without extra fields and with adjusted fields names.Services Interfaces
IProductsService
, with the following code:using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace SpeedUpCoreAPIExample.Interfaces
{
public interface IProductsService
{
Task
IPricesService
with the code:using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace SpeedUpCoreAPIExample.Interfaces
{
public interface IPricesService
{
Task
Implementation of the Services
ProductsService
: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
PricesService
: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
Data Integrity between the Products and Prices Tables
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.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 DeleteProductAsync
method. But we must think of doing this in a single transaction, because our application can fail when a Product
has already been deleted but not the prices. So, we can lose data integrity.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
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.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.Startup
class. Add using
directive:using SpeedUpCoreAPIExample.Services;
Services
in ConfigureServices
method after repositories declaration:services.AddTransient
ConfigureServices
method at this stage is:public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext
The Controllers
Creating Controllers
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
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.PricesController
controller: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
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
.Examine the Application
GET
method and input URL of the API:Product
:PricesController
API:Delete
API.DELETE
and call API:GET
method. The result will be 404 Not Found
.Summary
Points of Interest
https://github.com/GreensBird/SpeedUpCoreAPIExample.git