Featured
gRPC Service Create Using .NET Core 6.0 Entity Framework (Server & Client)
Last modified: May 12, 2022Over few years, we have been using REST(Representational State Transfer) that use HTTP1.1 method GET, POST, PUT, and DELETE to work with server-side resource to create Web API. One of the advantage of Web API is that, it serve single point of consuming server-side resource, e.g. desktop application and mobile apps.
gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It usage Http 2 and 7 times or more faster than Web API. It use Proto Buffer, As we can see below, how the server and client work bi-direction
(source : https://grpc.io/docs/what-is-grpc/introduction)
1 Server
1.1. Create a project
1.2. Install Nuget Packages
- We need going to use Entity Framework Core and InMemory Database (for demo). For you production, you need to select something like SQLServer or MySQL
1.3. Data
- Create a folder called Entities and create a file named Product and save to this folder
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Price { get; set; }
}
- Create a folder called Data and create a file named ProductContext and save to this folder.
- The DbContext use Entity Framework Core.
- By using DbSet, we mapped C# class file to Database table
public class ProductContext : DbContext
{
public ProductContext(DbContextOptions<ProductContext> options) : base(options)
{
}
public DbSet<Product> Product { get; set; }
}
- Finally for Data, we need to configure the Database Connection. It is mostly done in Program.cs (in .NET 6) however it is also possible to in DbContext
- We are use Program.cs to register it
- As mentioned before, here you might need to change to MySQL Server or MySQL server
builder.Services.AddDbContext<ProductContext>
(x => x.UseInMemoryDatabase("demo"));
1.4. Proto
- Create Proto as shown below
- Product is used to exchange between Server and Client
- ProductRowIdFilter is like get by id
- Products is collection of Product
- Empty is null/nothing pass
syntax = "proto3";
option csharp_namespace = "gRPC_Server";
package product;
service ProductSrv {
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;
int32 ProductId = 2;
string Name = 3;
int32 Price = 4;
}
message ProductRowIdFilter{
int32 ProductRowId = 1;
}
message Products{
repeated Product items = 1;
}
- Mark product proto as Server by editing Project File
<ItemGroup>
<Protobuf Include="Protos\productProtobuf.proto" GrpcServices="Server" />
</ItemGroup>
Or you can right click the productProtobuf as below
1.5. Service
- Create a file named ProductService and save it to Services folder.
public class ProductService : ProductSrv.ProductSrvBase
{
private readonly ProductContext _context;
public ProductService(ProductContext context)
{
_context = context;
}
public override Task<Products> GetAll(Empty request, ServerCallContext context)
{
var response = new Products();
var products = (from c in _context.Product
select new Product
{
ProductId = c.Id,
Name = c.Name,
Price = c.Price,
}).ToArray();
response.Items.AddRange(products);
return Task.FromResult(response);
}
public override Task<Product> GetById(ProductRowIdFilter request, ServerCallContext context)
{
var product = _context.Product.Where(w => w.Id == request.ProductId).FirstOrDefault();
var sProduct = new Product
{
Name = product.Name,
Price = product.Price,
ProductId = product.Id
};
return Task.FromResult(sProduct);
}
public override Task<Product> Post(Product request, ServerCallContext context)
{
var product = new gRPCServer.Entities.Product()
{
Name = request.Name,
Price = request.Price,
Id = request.ProductId
};
var response = _context.Product.Add(product);
_context.SaveChanges();
var resProduct = new Product()
{
Name = response.Entity.Name,
Price = response.Entity.Price,
ProductId = response.Entity.Id
};
return Task.FromResult(resProduct);
}
public override Task<Product> Put(Product request, ServerCallContext context)
{
var product = _context.Product.Where(w => w.Id == request.ProductId).FirstOrDefault();
if (product == null)
return Task.FromResult<Product>(null);
product.Id = request.ProductId;
product.Name = request.Name;
product.Price = request.Price;
_context.Update(product);
_context.SaveChanges();
var sProduct = new Product()
{
ProductId = request.ProductId,
Name = request.Name,
Price = request.Price
};
return Task.FromResult(sProduct);
}
public override Task<Empty> Delete(ProductRowIdFilter request, ServerCallContext context)
{
var product = _context.Product.Where(w => w.Id == request.ProductId).FirstOrDefault();
if (product == null)
return Task.FromResult<Empty>(null);
_context.Remove(product);
_context.SaveChanges();
return Task.FromResult<Empty>(new Empty());
}
}
1.5. Register Service in Program.cs
app.MapGrpcService<ProductService>();
Consuming gRPC Server
2 Client (ASP.NET 6 MVC)
- Create an ASP.NET 6 MVC project
- Copy the product.proto from server and paste it to client
- rename csharp_namespace to gRPC_Client
- Right click product.proto as mark it as client
2.1 Install Nuget Packages
2.2 Create a Product Controller and save it to Controllers folder
You can get Server url from launchSettings.json from Properties folder of Server App
using Grpc.Net.Client;
using Grpc.Net.Client;
using gRPC_Client;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static gRPC_Client.ProductSrv;
namespace gRPCClient.Controllers
{
public class ProductController : Controller
{
private readonly string GrpcChannelURL = "https://localhost:7018";
// GET: ProductController
public ActionResult Index()
{
using var channel = GrpcChannel.ForAddress(GrpcChannelURL);
var client = new ProductSrvClient(channel);
var products = client.GetAll(new Empty { });
return View(products);
}
// GET: ProductController/Details/5
public ActionResult Details(int id)
{
return View();
}
// GET: ProductController/Create
public ActionResult Create()
{
return View();
}
// POST: ProductController/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(Product product)
{
try
{
using var channel = GrpcChannel.ForAddress(GrpcChannelURL);
var client = new ProductSrvClient(channel);
await client.PostAsync(product);
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: ProductController/Edit/5
public async Task<ActionResult> Edit(int id)
{
var channel = GrpcChannel.ForAddress(GrpcChannelURL);
var client = new ProductSrvClient(channel);
var product = await client.GetByIdAsync(new ProductRowIdFilter { ProductId = id });
return View(product);
}
// POST: ProductController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(int id, Product product)
{
try
{
var channel = GrpcChannel.ForAddress(GrpcChannelURL);
var client = new ProductSrvClient(channel);
await client.PutAsync(product);
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: ProductController/Delete/5
public async Task<ActionResult> DeleteAsync(int id)
{
var channel = GrpcChannel.ForAddress(GrpcChannelURL);
var client = new ProductSrvClient(channel);
var product = await client.GetByIdAsync(new ProductRowIdFilter { ProductId = id });
return View(product);
}
// POST: ProductController/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id, IFormCollection collection)
{
try
{
var channel = GrpcChannel.ForAddress(GrpcChannelURL);
var client = new ProductSrvClient(channel);
client.DeleteAsync(new ProductRowIdFilter { ProductId = id });
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
}
}
2.3 - Index View
@model gRPC_Client.Products
@*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}
<a asp-action="Create" >Create</a>
<table class="table table-striped" >
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Price</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach(var product in Model.Items)
{
<tr>
<td>@product.ProductId</td>
<td>@product.Name</td>
<td>@product.Price</td>
<td>
<a asp-controller="Product"
asp-action="Edit"
class="btn btn-primary"
asp-route-id="@product.ProductId">Edit</a>
<a asp-controller="Product"
asp-action="Delete"
class="btn btn-danger"
asp-route-id="@product.ProductId">Delete</a>
</td>
</tr>
}
</tbody>
</table>
2.4 - Create View
@model gRPC_Client.Product
@*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}
@Html.AntiForgeryToken()
<form asp-action="Create" method="post">
<table class="table table-striped table-striped" >
<tr>
<td>Name</td>
<td>@Html.TextBoxFor(m => m.Name)</td>
</tr>
<tr>
<td>Price</td>
<td>@Html.TextBoxFor(m => m.Price, new { @type = "number" })</td>
</tr>
</table>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<a asp-action="Index">Back to list</a>
2.5 - Edit View
@model gRPC_Client.Product
@*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}
@Html.AntiForgeryToken()
<form asp-action="Edit" method="post">
<input type="hidden" asp-for="@Model.ProductId"/>
<table class="table table-striped" >
<tr>
<td>Name</td>
<td>Price</td>
</tr>
<tr>
<td>
<input type="text" asp-for="@Model.Name" />
</td>
<td>
<input type="text" asp-for="@Model.Price" />
</td>
</tr>
</table>
<button type="submit" class="btn btn-primary">Update</button>
</form>
<a asp-action="Index">Back to list</a>
2.6 - Delete View
@model gRPC_Client.Product
@*
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}
@Html.AntiForgeryToken()
<form asp-action="Delete" method="post">
<table class="table table-striped" >
<input type="hidden" asp-for="@Model.ProductId"/>
<tr>
<td>Id</td>
<td>Name</td>
<td>Price</td>
</tr>
<tr>
<td>@Model.ProductId</td>
<td>@Model.Name</td>
<td>@Model.Price</td>
</tr>
</table>
<button type="submit" class="btn btn-danger">Delete</button>
</form>
<a asp-action="Index">Back to list</a>
2 - Test