Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
Mar 28

Create a gRPC server in .NET Core

Create a gRPC server in .NET Core
Автор:
Источник:
Просмотров:
5687

Remote Procedure Calls

gRPC is a powerful framework for working with Remote Procedure Calls. RPCs allow you to write code as though it will be run on a local computer, even though it may be executed on another computer.

One of the key advantages of gRPC is its use of HTTP/2, which enables efficient and multiplexed communication between clients and servers. This allows gRPC to handle multiple requests and responses simultaneously, which can significantly improve performance and reduce latency.

In this blog post, I will add a gRPC Server to my BlogPost Project. You can follow this tutorial to setup the API Project.

• • •

Initial Setup and Configuration

Open your solution and add a new ASP.NET Core gRPC Service. I will name the project SeeSharp.gRPC

Add a new project

Let’s get to know protobuf files.

In the greet.proto file you can see a service and some messages.

Protobuf file

The file is a template file that defines a Greeter Service, and has one method “SayHello” that takes in a HelloRequest message and returns a HelloReply message. The numbers you see next to “name” and “message” are nothing but the order of the properties, so adding a new property “mobile” for example would look like this.

Message file

As you might have noticed, this proto file does not include any implementation, its just an interface or a contract.

Let’s make sure that everything is working before we start customizing.

  • Set the gRPC project as a StartUp Project and run your solution.
  • Open Postman and create a new gRPC Request.
  • Select the greet.proto file and import it (remember, the proto file is the blueprint of the service).

Postman request

Implement the Service

Now that we completed the initial setup, let’s integrate the gRPC service with our BlogPosts Api.

  • Add a project reference from gRPC to SeeSharp.Application project
  • Add a project reference from gRPC to SeeSharp.Infrastructure project

in the program.cs file register the Application and Infrastructure Services.

builder.Services.AddApplicationServices();
builder.Services.AddInfrastructureServices(builder.Configuration);

Next, add the connection string and any other configuration that was added in the REST api project. You can copy the full appsettings.json from the Api project to the gRPC project.

At this stage we have setup a gRPC service and wired up all necessary services that will be used.

Next, we will define the protobuf file. Right click on the gRPC project and add a File of type protobuf. name it “blogposts.proto”.

syntax = "proto3";
option csharp_namespace = "SeeSharp.gRPC.Protos";
package blogpost;

Add the below code to the blogposts.proto file.

message CreateBlogPostRequest{
    string title = 1;
    string category = 2;
    string author = 3;
    string content = 4;
}
message CreateBlogPostResponse{
    string id = 1;
}

In the code above, I defined two messages, a CreateBlogPostRequest message, and a CreateBlogPostResponse message.

The numbers next to each property is the order of that property. So here, I am saying that the CreateBlogPostRequest has 4 properties title, category, author, and content.

Next we will define the service contract. To do so, add the below code.

service BlogPosts{
    // Create
    rpc CreateBlogPost(CreateBlogPostRequest) returns (CreateBlogPostResponse) {}
}

The work here is repetitive, for each method we need to define a request and response message.

Here is the full protofile.

syntax = "proto3";

option csharp_namespace = "SeeSharp.gRPC.Protos";

package blogposts;

service BlogPosts{
    // Create
    rpc CreateBlogPost(CreateBlogPostRequest) returns (CreateBlogPostResponse) {}
    // Read List
    rpc GetBlogPosts(GetBlogPostsRequest) returns (GetBlogPostsResponse) {}
    // Read Single
    rpc GetBlogPostById(GetBlogPostRequest) returns (GetBlogPostReponse){}
    // Update
    rpc UpdateBlogPost(UpdateBlogPostRequest) returns(empty) {}
    // Delete
    rpc DeleteBlogPost(DeleteBlogPostRequest) returns(empty){}
}

message CreateBlogPostRequest{
    string title = 1;
    string category = 2;
    string author = 3;
    string content = 4;
}

message CreateBlogPostResponse{
    string id = 1;
}

message GetBlogPostRequest{
    string id = 1;
}

message GetBlogPostReponse{
    string id = 1;
    string title = 2;
    string category = 3;
    string author = 4;
    string content = 5;
    string dateCreated = 6;
}

message GetBlogPostsRequest{}

message GetBlogPostsResponse{
    repeated GetBlogPostReponse blogPosts = 1;
}

message UpdateBlogPostRequest{
    string id = 1;
    string title = 2;
    string category = 3;
    string author = 4;
    string content = 5;
}

message DeleteBlogPostRequest{
    string id = 1;
}

message empty{
}

Now we need to add this proto file to out project. To do so, just add the below lines in the csproj file.

<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  <Protobuf Include="Protos\blogposts.proto" GrpcServices="Server" />
</ItemGroup>

Remember, any change to the protofile requires a rebuild of the project. Once the project is rebuilt, the classes (messages) will be constructed and will be available for use.

Next, lets create a BlogPostService.cs

In the services folder, add a new file and call it BlogPostsService.cs

using SeeSharp.gRPC.Protos;

namespace SeeSharp.gRPC.Services;

public class BlogPostsService : BlogPosts.BlogPostsBase
{

}

Notice how we inherited from BlogPosts.BlogPostsBase which was constructed when we built our project based on the blogposts.proto file.

In the service class, we need to inject IMediatR and override the methods implementations.

Add the below code.

public override async Task<CreateBlogPostResponse> CreateBlogPost(CreateBlogPostRequest request, ServerCallContext context)
{
    var command = new CreateBlogPostCommand
    {
        Title = request.Title,
        Author = request.Author,
        Category = request.Category,
        Content = request.Content
    };
    var result = await _mediator.Send(command);

    return await Task.FromResult(new CreateBlogPostResponse { Id = result.ToString() });
}

Let’s test. open postman, and create a new gRPC request. Alternatively, you can change the protobuf file from greet.proto to blogposts.proto and reload the methods.

Postman gRPC request

SQL table records

Success! We were able to create a blogpost using gRPC. Let’s implement the other endpoints.

GetAllBlogPosts

public override async Task<GetBlogPostsResponse> GetBlogPosts(GetBlogPostsRequest request, ServerCallContext context)
{
    var result = await _mediator.Send(new GetBlogPostsQuery());

    var response = new GetBlogPostsResponse();

    response.BlogPosts.AddRange(result.Select(blogpost => new GetBlogPostReponse
    {
        Id = blogpost.Id.ToString(),
        Title = blogpost.Title,
        Author = blogpost.Author,
        Category = blogpost.Category,
        Content = blogpost.Content,
        DateCreated = blogpost.DateCreated
    }));

    return await Task.FromResult(response);
}

and the result?

Blog posts response

The same goes for GetBlogPost, UpdateBlogPost, and DeleteBlogPost. Below is the full BlogPostService.cs file.

using Grpc.Core;
using MediatR;
using SeeSharp.Application.Features.BlogPosts.Commands.CreateBlogPost;
using SeeSharp.Application.Features.BlogPosts.Commands.DeleteBlogPost;
using SeeSharp.Application.Features.BlogPosts.Commands.UpdateBlogPost;
using SeeSharp.Application.Features.BlogPosts.Queries;
using SeeSharp.gRPC.Protos;

namespace SeeSharp.gRPC.Services;

public class BlogPostsService : BlogPosts.BlogPostsBase
{
    private readonly IMediator _mediator;

    public BlogPostsService(IMediator mediator)
    {
        _mediator = mediator;
    }

    public override async Task<CreateBlogPostResponse> CreateBlogPost(CreateBlogPostRequest request, ServerCallContext context)
    {
        var command = new CreateBlogPostCommand
        {
            Title = request.Title,
            Author = request.Author,
            Category = request.Category,
            Content = request.Content
        };
        var result = await _mediator.Send(command);
        return await Task.FromResult(new CreateBlogPostResponse { Id = result.ToString() });
    }

    public override async Task<GetBlogPostReponse> GetBlogPostById(GetBlogPostRequest request, ServerCallContext context)
    {
        var query = new GetBlogPostByIdQuery(Guid.Parse(request.Id));
        var result = await _mediator.Send(query);
        var response = new GetBlogPostReponse
        {
            Id = result.Id.ToString(),
            Title = result.Title,
            Category = result.Category,
            Author = result.Author,
            Content = result.Content,
            DateCreated = result.DateCreated
        };
        return await Task.FromResult(response);
    }

    public override async Task<GetBlogPostsResponse> GetBlogPosts(GetBlogPostsRequest request, ServerCallContext context)
    {
        var result = await _mediator.Send(new GetBlogPostsQuery());
        var response = new GetBlogPostsResponse();
        response.BlogPosts.AddRange(result.Select(blogpost => new GetBlogPostReponse
        {
            Id = blogpost.Id.ToString(),
            Title = blogpost.Title,
            Author = blogpost.Author,
            Category = blogpost.Category,
            Content = blogpost.Content,
            DateCreated = blogpost.DateCreated
        }));
        return await Task.FromResult(response);
    }

    public override async Task<empty> UpdateBlogPost(UpdateBlogPostRequest request, ServerCallContext context)
    {
        var command = new UpdateBlogPostCommand
        {
            Id = Guid.Parse(request.Id),
            Title = request.Title,
            Category = request.Category,
            Author = request.Author,
            Content = request.Content,
        };
        await _mediator.Send(command);
        return new empty();
    }

    public override async Task<empty> DeleteBlogPost(DeleteBlogPostRequest request, ServerCallContext context)
    {
        var command = new DeleteBlogPostCommand(Guid.Parse(request.Id));
        await _mediator.Send(command);
        return new empty();
    }
}

Enable JsonTranscoding

The gRPC Service is ready, but how do we enable communication over http? We need to enable JsonTranscoding in our project.

To do so, install the below package:

dotnet add package Microsoft.AspNetCore.Grpc.JsonTranscoding

Once installed, it needs to be registered in the services pool. Open your program.cs file and add the below code.

// Add services to the container.
builder.Services.AddGrpc().AddJsonTranscoding();

In the project, create 2 files in the below directory (those are settings files)

API protobuf files

Now we need to add Service Annotations for each rpc service in our protofile.

rpc CreateBlogPost(CreateBlogPostRequest) returns (CreateBlogPostResponse) {
  option (google.api.http) = {
    post: "/v1/blogposts",
    body: "*"
  };
}

The blogposts.proto file will look like this.

syntax = "proto3";

option csharp_namespace = "SeeSharp.gRPC.Protos";

import "google/api/annotations.proto";

package blogposts;

service BlogPosts{
    // Create
    rpc CreateBlogPost(CreateBlogPostRequest) returns (CreateBlogPostResponse) {
        option (google.api.http) = {
            post: "/v1/blogposts",
            body: "*"
        };
    }

    // Read List
    rpc GetBlogPosts(GetBlogPostsRequest) returns (GetBlogPostsResponse) {
        option (google.api.http) = {
            get: "/v1/blogposts"
        };
    }

    // Read Single
    rpc GetBlogPostById(GetBlogPostRequest) returns (GetBlogPostReponse){
        option (google.api.http) = {
            get: "/v1/blogposts/{id}"
        };
    }

    // Update
    rpc UpdateBlogPost(UpdateBlogPostRequest) returns(empty) {
        option (google.api.http) = {
            put : "/v1/blogposts",
            body: "*"
        };
    }

    // Delete
    rpc DeleteBlogPost(DeleteBlogPostRequest) returns(empty){
        option (google.api.http) = {
            delete: "/v1/blogposts/{id}"
        };
    }
}

message CreateBlogPostRequest{
    string title = 1;
    string category = 2;
    string author = 3;
    string content = 4;
}

message CreateBlogPostResponse{
    string id = 1;
}

message GetBlogPostRequest{
    string id = 1;
}

message GetBlogPostReponse{
    string id = 1;
    string title = 2;
    string category = 3;
    string author = 4;
    string content = 5;
    string dateCreated = 6;
}

message GetBlogPostsRequest{}

message GetBlogPostsResponse{
    repeated GetBlogPostReponse blogPosts = 1;
}

message UpdateBlogPostRequest{
    string id = 1;
    string title = 2;
    string category = 3;
    string author = 4;
    string content = 5;
}

message DeleteBlogPostRequest{
    string id = 1;
}

message empty{
}

As a result, we are able to call gRPC service over http/s.

1.5 - Testing

Open the browser, or Postman, and browse https://localhost:[port]/v1/blogposts

Testing

Testing

Source Code

You can find the source code under this Github Repo. Use the ‘Feature-gRPC’ Branch.

• • •

Stay tuned! :)

Похожее
Jul 9, 2023
Author: Kenji Elzerman
When you have multiple applications and they need to communicate with each other to exchange data you might want to use a protocol that makes something like that happen. In C#, the HTTPClient class provides a powerful and flexible way...
Jan 15
Author: MESCIUS inc.
HyperText markup language, commonly referred to as HTML, has been the foundation of creating and navigating web pages from the very beginning. Its significance further increases provided how the world is moving towards digitization. Hence, working with this format just...
Mar 19, 2021
Author: Mukesh Murugan
In this article, We will talk about Onion Architecture In ASP.NET Core and it’s advantages. We will also together build a WebApi that follows a variant of Onion Architecture so that we get to see why it is important to...
Apr 1
Author: Ravindra Devrani
Fluent validation is a third party library for validating your models in .NET. It is totally free. Why fluent validation? If you already have used data annotation for validation, then you must be aware of validation in .NET. So you...
Написать сообщение
Тип
Почта
Имя
*Сообщение