ایجاد سرویس GRPC با استفاده از NET Core 6.0 EF برای عملیات CRUD
در ایم مقاله من سعی میکنم توضیحات مختصری در سطح مبتدی برای درک بهتر ارائه کنم.
REST چیست؟
همه ما می دانیم REST (Representational State Transfer) یک سبک معماری است که دستورالعمل هایی را برای طراحی API های وب ارائه می دهد.
از روشهای استاندارد موجود HTTP 1.1 مانند GET، POST، PUT و DELETE برای کار با منابع server-side استفاده میکند. REST API ها از URL های پیش تعریف شده ای را ارائه می دهند که مشتری باید برای اتصال به سرور از آنها استفاده کند.
GRPC به معنای Google Remote Procedure Call است
بنابراین برای ساخت یک برنامه GRPC باید سرویس GRPC ایجاد کنیم و بعداً برای مصرف باید مشتری ایجاد کنیم
در پروژه باید بسته nuget زیر را نصب کنیم.
Grpc.AspNetCore قبلاً به طور پیش فرض هنگام ایجاد پروژه نصب شده است.
Create Database yourDb
USE [yourDb]
CREATE TABLE [dbo].[products]
(
[productrowid] [INT] IDENTITY(1, 1) NOT NULL,
[productid] [VARCHAR](20) NOT NULL,
[productname] [VARCHAR](200) NOT NULL,
[categoryname] [VARCHAR](200) NOT NULL,
[manufacturer] [VARCHAR](200) NOT NULL,
[price] [INT] NOT NULL,
PRIMARY KEY CLUSTERED ( [productrowid] ASC )WITH (pad_index = OFF,
statistics_norecompute = OFF, ignore_dup_key = OFF, allow_row_locks = on,
allow_page_locks = on, optimize_for_sequential_key = OFF) ON [PRIMARY],
UNIQUE NONCLUSTERED ( [productid] ASC )WITH (pad_index = OFF,
statistics_norecompute = OFF, ignore_dup_key = OFF, allow_row_locks = on,
allow_page_locks = on, optimize_for_sequential_key = OFF) ON [PRIMARY]
)
ON [PRIMARY]
go
یک کلاس جدید "products.proto" ایجاد کنید
برای دسترسی به سرویس gRPC در برنامه مشتری مانند ASP.NET Core، Blazor و غیره، باید بسته Grpc.AspNetCore.Web را در پروژه gRPC نصب کنیم. این بسته پشتیبانی gRPC-Web را برای ASP.NET Core در کنار پشتیبانی gRPC HTTP/2 فراهم می کند.
package products;
service ProductsService {
rpc GetAll(Empty) returns(Products);
rpc GetById(ProductRowIdFilter) returns(Product);
rpc Post(Product) returns(Product);
rpc Put(Product) returns(Product);
rpc Delete(ProductRowIdFilter) returns(Empty);
}
message Empty {}
message Product {
int32 ProductRowId = 1;
string ProductId = 2;
string ProductName = 3;
string CategoryName = 4;
string Manufacturer = 5;
int32 Price = 6;
}
message ProductRowIdFilter {
int32 productRowId = 1;
}
message Products {
repeated Product items = 1;
}
ساختار محصولات
فایل products.proto حاوی فرمت های پیام زیر است
1) محصول، این یک ساختار داده پیام برای تبادل داده های محصول است
2) ProductRowIdFilter، این یک ساختار پیام برای خواندن رکورد محصول بر اساس ProductRowId است ( واکشی توسط ID)
3) محصولات، این یک ساختار پیام است که برای دریافت مجموعه محصولات استفاده می شود
4) خالی، یک ساختار پیام است که نشان دهنده پاسخ خالی از سرور و داده های خالی برای ارسال از مشتری به سرور است.
برای مشاهده کد سی شارپ از فایل پروتو، فایل پروژه را تغییر دهید
روی فایل products.proto کلیک راست کرده و ویژگی های زیر را بررسی کنید
اکنون پروژه را بسازید، فایل های C# مانند زیر در پوشه obj تولید می شوند
اکنون در پوشه Services یک فایل کلاس جدید اضافه کنید و نام آن را ProductsAppService.cs بگذارید.
در اینجا کد کامل کلاس "ProductsAppService.cs" آمده است
using Grpc.Core;
using GrpcService;
using GrpcService.Models;
using System.Linq;
using System.Threading.Tasks;
using static GrpcService.ProductsService;
namespace GrpcService.Services {
public class ProductsAppService: ProductsServiceBase {
public BlazorContext dbContext;
public ProductsAppService(BlazorContext DBContext) {
dbContext = DBContext;
}
#region GetAll
public override Task < Products > GetAll(Empty request, ServerCallContext context) {
Products response = new Products();
var products = from prd in dbContext.Products
select new Product() {
ProductRowId = prd.ProductRowId,
ProductId = prd.ProductId,
ProductName = prd.ProductName,
CategoryName = prd.CategoryName,
Manufacturer = prd.Manufacturer,
Price = prd.Price
};
response.Items.AddRange(products.ToArray());
return Task.FromResult(response);
}
#endregion
#region GetById
public override Task < Product > GetById(ProductRowIdFilter request, ServerCallContext context) {
var product = dbContext.Products.Find(request.ProductRowId);
var searchedProduct = new Product() {
ProductRowId = product.ProductRowId,
ProductId = product.ProductId,
ProductName = product.ProductName,
CategoryName = product.CategoryName,
Manufacturer = product.Manufacturer,
Price = product.Price
};
return Task.FromResult(searchedProduct);
}
#endregion
#region PostInsert
public override Task < Product > Post(Product request, ServerCallContext context) {
var prdAdded = new Models.Product() {
ProductId = request.ProductId,
ProductName = request.ProductName,
CategoryName = request.CategoryName,
Manufacturer = request.Manufacturer,
Price = request.Price
};
var res = dbContext.Products.Add(prdAdded);
dbContext.SaveChanges();
var response = new Product() {
ProductRowId = res.Entity.ProductRowId,
ProductId = res.Entity.ProductId,
ProductName = res.Entity.ProductName,
CategoryName = res.Entity.CategoryName,
Manufacturer = res.Entity.Manufacturer,
Price = res.Entity.Price
};
return Task.FromResult < Product > (response);
}
#endregion
#region PUTUPDATE
public override Task < Product > Put(Product request, ServerCallContext context) {
Models.Product prd = dbContext.Products.Find(request.ProductRowId);
if (prd == null) {
return Task.FromResult < Product > (null);
}
prd.ProductRowId = request.ProductRowId;
prd.ProductId = request.ProductId;
prd.ProductName = request.ProductName;
prd.CategoryName = request.CategoryName;
prd.Manufacturer = request.Manufacturer;
prd.Price = request.Price;
dbContext.Products.Update(prd);
dbContext.SaveChanges();
return Task.FromResult < Product > (new Product() {
ProductRowId = prd.ProductRowId,
ProductId = prd.ProductId,
ProductName = prd.ProductName,
CategoryName = prd.CategoryName,
Manufacturer = prd.Manufacturer,
Price = prd.Price
});
}
#endregion
#region DELETE
public override Task < Empty > Delete(ProductRowIdFilter request, ServerCallContext context) {
Models.Product prd = dbContext.Products.Find(request.ProductRowId);
if (prd == null) {
return Task.FromResult < Empty > (null);
}
dbContext.Products.Remove(prd);
dbContext.SaveChanges();
return Task.FromResult < Empty > (new Empty());
}
#endregion
}
}
اکنون مرحله نهایی است. کد زیر را در فایل Program.cs جایگذاری کنید تا خطمشیهای AppDbContext و CORS در سرویسها را مطابق شکل زیر ثبت کنید.
using GrpcService.Models;
using GrpcService.Services;
using Microsoft.EntityFrameworkCore;
using System;
var builder = WebApplication.CreateBuilder(args);
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
// Add services to the container.
builder.Services.AddGrpc();
builder.Services.AddDbContext < BlazorContext > (item => item.UseSqlServer(builder.Configuration.GetConnectionString("AppDbContext")));
builder.Services.AddCors(options => {
options.AddPolicy("cors", policy => {
policy.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService < GreeterService > ();
app.MapGrpcService < ProductsAppService > ();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();
اکنون پروژه را بسازید و مطمئن شوید که هیچ خطایی وجود ندارد.
اگر خطایی وجود نداشته باشد، سرویسهای gRPC با موفقیت میزبانی میشوند، همانطور که در شکل زیر نشان داده شده است
خوب , ما با موفقیت سرویس GRPC را ایجاد کردیم.
در مقاله بعدی، نحوه استفاده از سرویس gRPC را در برنامه کلاینت .NET Core Console نشان خواهیم داد.
توجه داشته باشید که این بسته ها برای هر نوع gRPC Client.Install-Package Grpc.Tools قابل اجرا هستند.
Install-Package Grpc.Net.Client
Install-Package Google.Protobuf