{"id":4921,"date":"2022-04-20T14:34:00","date_gmt":"2022-04-20T21:34:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=4921"},"modified":"2022-04-21T16:04:43","modified_gmt":"2022-04-21T23:04:43","slug":"tutorial-build-grpc-odata-in-asp-net-core","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/tutorial-build-grpc-odata-in-asp-net-core\/","title":{"rendered":"Tutorial: Build gRPC &#038; OData in ASP.NET Core"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>gRPC (<strong>g<\/strong>oogle <strong>R<\/strong>emote <strong>P<\/strong>rocedure <strong>C<\/strong>all) is a high-performance remote procedure call framework that helps developers to build and consume remote services using the same way as calling local APIs. Different from gRPC, OData (<strong>O<\/strong>pen <strong>Data<\/strong> Protocol) is an\u00a0<a href=\"https:\/\/www.oasis-open.org\/committees\/tc_home.php?wg_abbrev=odata\">OASIS standard<\/a>\u00a0that defines a set of best practices for developers to build and consume RESTful APIs using HTTP request\/response. Both approaches have their own strong points, for example, gRPC has less network usage with protobuf binary serialization, on the contrary, OData uses JSON as data format for human readability. In addition, OData has powerful query options functionality that can be used to <em>sharpen<\/em> the data from service.<\/p>\n<p>Given these, it could be difficult to choose which one to use. But, why? Why we must choose one. In this post, I will create an ASP.NET Core Web Application containing the gRPC service and OData service together. I\u2019d like to use this post to share with you the idea of how to build a gRPC service from scratch, and how to build the OData service using gRPC auto-generated classes. Most importantly, how to consume gRPC and OData service from the client. Below is my simple design structure, an ASP.NET Core server that provides the OData endpoint and the gRPC service. Both depend on a data repository interface to provide data access.<\/p>\n<p><img decoding=\"async\" width=\"1429\" height=\"534\" class=\"wp-image-4922\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/shape-polygon-description-automatically-generate.png\" alt=\"Shape, polygon Description automatically generated\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/shape-polygon-description-automatically-generate.png 1429w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/shape-polygon-description-automatically-generate-300x112.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/shape-polygon-description-automatically-generate-1024x383.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/shape-polygon-description-automatically-generate-768x287.png 768w\" sizes=\"(max-width: 1429px) 100vw, 1429px\" \/><\/p>\n<p>Let\u2019s get started!<\/p>\n<h2>Prerequisites<\/h2>\n<p>Our first step is to create a blank solution called <strong>gRPC.OData<\/strong> using Visual Studio 2022. In the solution, create an ASP.NET Core web application called <strong>gRPC.OData.Server<\/strong> targeting .NET 6.0 platform, uncheck OpenApi support for simplicity.<\/p>\n<p>Once we have our project generated, let\u2019s install the following Nuget packages that help generate gRPC proxy classes and build the application.<\/p>\n<ul>\n<li>Install-Package Microsoft.AspNetCore.OData -version 8.0.8<\/li>\n<li>Install-Package Grpc.AspNetCore -version 2.44.0<\/li>\n<li>Install-Package Grpc.Tools -version 2.45.0<\/li>\n<\/ul>\n<h2>Protocol Buffer<\/h2>\n<p>gRPC is contract-based design workflow which means we first need a .proto file to define the structure of the data (or the messages) and the corresponding service.<\/p>\n<p>So, let\u2019s create a\u00a0\u201c<strong>protos<\/strong>\u201d\u00a0folder in the server project. In the folder, create a file named <strong>bookstore.proto<\/strong> with the following content. (Note: I only list and implement parts of methods in the post for simplicity. You can find the whole CRUD scenario <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/gRPC.OData\/gRPC.OData.Server\/Protos\/Bookstore.proto\">here<\/a>.)<\/p>\n<pre class=\"lang:proto decode:true\">syntax = \"proto3\";\r\n\r\npackage bookstores;\r\n\r\nimport \"google\/protobuf\/empty.proto\";\r\n\r\n\/\/ The API manages shelves and books resources. Shelves contain books.\r\nservice Bookstore {\r\n\r\n    \/\/ Returns a list of all shelves in the bookstore.\r\n    rpc ListShelves(google.protobuf.Empty) returns (ListShelvesResponse) {}\r\n\r\n    \/\/ Returns a list of books on a shelf.\r\n    rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {}\r\n}\r\n\r\n\/\/ A shelf resource.\r\nmessage Shelf {\r\n    int64 id = 1;\r\n    string theme = 2;\r\n}\r\n\r\n\/\/ A book resource.\r\nmessage Book {\r\n    int64 id = 1;\r\n    string author = 2;\r\n    string title = 3;\r\n}\r\n\r\n\/\/ Response to ListShelves call.\r\nmessage ListShelvesResponse {\r\n    repeated Shelf shelves = 1;\r\n}\r\n\r\n\/\/ Request message for ListBooks method.\r\nmessage ListBooksRequest {\r\n    int64 shelf = 1;\r\n}\r\n\r\n\/\/ Response message to ListBooks method.\r\nmessage ListBooksResponse {\r\n    repeated Book books = 1;\r\n}\r\n<\/pre>\n<p>Where:<\/p>\n<ul>\n<li><strong>Bookstore<\/strong> is the gRPC service that contains two RPC definitions, <strong>ListShelves<\/strong> &amp; <strong>ListBooks<\/strong>.<\/li>\n<li><strong>Shelf &amp; Book<\/strong> are two resource\/entity types.<\/li>\n<li><strong>ListShelvesResponse<\/strong>, <strong>ListBooksRequest<\/strong>, <strong>ListBooksResponse<\/strong> are types for proto request and response.<\/li>\n<\/ul>\n<p>Now, right click the <strong>bookstore.proto<\/strong> from the solution explorer in Visual Studio, select properties menu, do the following configuration in the Property pages:<\/p>\n<p><img decoding=\"async\" width=\"882\" height=\"271\" class=\"wp-image-4923\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image.png 882w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-300x92.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-768x236.png 768w\" sizes=\"(max-width: 882px) 100vw, 882px\" \/><\/p>\n<p>This configuration inserts the following lines into <strong>gRPC.OData.Server.csproj<\/strong>.<\/p>\n<pre class=\"lang:c# decode:true\">\r\n&lt;ItemGroup&gt;\r\n  &lt;Protobuf Include=\"proto\\BookStore.proto\" GrpcServices=\"Server\" \/&gt;\r\n&lt;\/ItemGroup&gt;\r\n<\/pre>\n<p>We finished the contract, let\u2019s build the solution, and let the protobuf compiler generate the proxy classes based on the <strong>bookstore.proto<\/strong>.<\/p>\n<p>After building, you can find two C# files created under the \u201cobj\\debug\\net6.0\\protos\u201d folder, where:<\/p>\n<ol>\n<li><strong>BookStore.cs<\/strong>: contains the proxy classes for the messages defined in the <strong>.proto<\/strong> file.<\/li>\n<li><strong>BookStoreGrpc.cs<\/strong>: contains the proxy classes for the service and RPC methods defined under service section in the <strong>.proto<\/strong> file.<\/li>\n<\/ol>\n<p>Here\u2019s the part-codes of auto-generated class <strong>Shelf<\/strong>. Anytime when <strong>bookstore.proto<\/strong> gets changed, the proxy classes will be re-generated automatically.<\/p>\n<p><img decoding=\"async\" width=\"1050\" height=\"227\" class=\"wp-image-4924\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-1.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-1.png 1050w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-1-300x65.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-1-1024x221.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-1-768x166.png 768w\" sizes=\"(max-width: 1050px) 100vw, 1050px\" \/><\/p>\n<h2>Data Repository<\/h2>\n<p>As mentioned, we need a data repository to provide data access both for gRPC and OData. Since we have the proxy classes, such as C# class <strong>Shelf<\/strong> and <strong>Book, <\/strong>we can define the repository interface. Let\u2019s create a new folder named <strong>Models<\/strong>, create an interface as follows in this folder:<\/p>\n<pre class=\"lang:c# decode:true\">\r\nusing Bookstores;\r\n\r\nnamespace gRPC.OData.Server.Models\r\n{\r\n    public interface IShelfBookRepository\r\n    {\r\n        IEnumerable&lt;Shelf&gt; GetShelves();\r\n\r\n        IEnumerable&lt;Book&gt; GetBooks(long shelfId);\r\n    }\r\n}\r\n<\/pre>\n<p><strong>Shelf<\/strong> and <strong>Book<\/strong> have an one-to-many relationship. However, the proto and the corresponding proxy classes don\u2019t include such. We need to add such relationship ourselves by creating the partial class for <strong>Shelf<\/strong> as:<\/p>\n<pre class=\"lang:c# decode:true\">\r\nnamespace Bookstores \/\/ keep the same namespace\r\n{\r\n    public sealed partial class Shelf\r\n    {\r\n        public IList&lt;Book&gt; Books { get; set; }\r\n    }\r\n}\r\n<\/pre>\n<p>For simplicity and tutorial only, we can build an in-memory data repository to play the scenarios as:<\/p>\n<pre class=\"lang:c# decode:true\">\r\npublic class ShelfBookInMemoryRepository : IShelfBookRepository\r\n{\r\n    private static IList&lt;Shelf&gt; _sheves;\r\n\r\n    static ShelfBookInMemoryRepository()\r\n    {\r\n        \u2026\u2026 \/\/ omits the shelves codes, find in the sample repository\r\n    }\r\n\r\n    public IEnumerable&lt;Shelf&gt; GetShelves() =&gt; _sheves;\r\n\r\n    public IEnumerable&lt;Book&gt; GetBooks(long shelfId)\r\n    {\r\n        Shelf shelf = _sheves.FirstOrDefault(s =&gt; s.Id == shelfId);\r\n\r\n        if (shelf is null)\r\n        {\r\n            return Enumerable.Empty&lt;Book&gt;();\r\n        }\r\n\r\n        return shelf.Books;\r\n    }\r\n}\r\n<\/pre>\n<p>Be noted, that I only list some parts of the implementation, you can find the whole implementation of the in-memory repository <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/gRPC.OData\/gRPC.OData.Server\/Models\/ShelfBookInMemoryRepository.cs\">here<\/a>.<\/p>\n<h2>Build OData Model<\/h2>\n<p>We can use the auto-generated <strong>Shelf<\/strong> and <strong>Book<\/strong> types as our Data model to generate the OData Edm model. Let\u2019s create a new class named <strong>EdmModelBuilder<\/strong> in <strong>Models<\/strong> folder as:<\/p>\n<pre class=\"lang:c# decode:true\">\r\npublic static IEdmModel GetEdmModel()\r\n{\r\n    var builder = new ODataModelBuilder();\r\n\r\n    var shelf = builder.EntityType&lt;Shelf&gt;();\r\n    shelf.HasKey(b =&gt; b.Id);\r\n    shelf.Property(b =&gt; b.Theme);\r\n\r\n    var bookMessage = builder.EntityType&lt;Book&gt;();\r\n    bookMessage.HasKey(b =&gt; b.Id);\r\n    bookMessage.Property(b =&gt; b.Title);\r\n    bookMessage.Property(b =&gt; b.Author);\r\n\r\n    builder.EntitySet&lt;Book&gt;(\"Books\");\r\n    builder.EntitySet&lt;Shelf&gt;(\"Shelves\").HasManyBinding(s =&gt; s.Books, \"Books\");\r\n\r\n    return builder.GetEdmModel();\r\n}\r\n<\/pre>\n<p>Since we create the extra property named \u201c<strong>Books<\/strong>\u201d for type <strong>Shelf<\/strong>, we can easily build the navigation link between <strong>Shelf<\/strong> and <strong>Book <\/strong>by calling<strong> HasManyBinding <\/strong>fluent API.<\/p>\n<h2>Build gRPC service<\/h2>\n<p>Protobuf compiler auto-generates the gRPC methods for us in the <strong>BookstoreBase<\/strong> proxy class. However, as shown below, the method is unimplemented and useless.<\/p>\n<p><img decoding=\"async\" width=\"1058\" height=\"198\" class=\"wp-image-4925\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-2.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-2.png 1058w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-2-300x56.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-2-1024x192.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-2-768x144.png 768w\" sizes=\"(max-width: 1058px) 100vw, 1058px\" \/><\/p>\n<p>To make the gRPC work, we must override the methods auto generated from compiler. Let\u2019s create a new folder named <strong>services<\/strong>, create a new file named <strong>BookstoreService.cs<\/strong> in the folder with the following contents:<\/p>\n<pre class=\"lang:c# decode:true\">\r\nusing Bookstores;\r\n\r\nnamespace gRPC.OData.Services\r\n{\r\n    public class BookstoreService : Bookstore.BookstoreBase\r\n    {\r\n        private readonly ILogger _logger;\r\n\r\n        private readonly IShelfBookRepository _shelfBookRepository;\r\n\r\n        public BookstoreService(ILoggerFactory loggerFactory, IShelfBookRepository shelfBookRepository)\r\n        {\r\n            _logger = loggerFactory.CreateLogger&lt;BookstoreService&gt;();\r\n\r\n            _shelfBookRepository = shelfBookRepository;\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Be noted, that we injected the data repository through the constructor injection.<\/p>\n<h2>Web Application configuration<\/h2>\n<p>Since we have the <strong>BookstoreService<\/strong> and the OData Edm model ready, we can config the ASP.NET core web application to enable gRPC and OData endpoints. First, we register gRPC (by calling <strong>AddGrpc()<\/strong>) and OData (by calling <strong>AddOData(\u2026)<\/strong>) services into the service collections in the Program.cs as follows (Be noted, we also register the repository as transient service.)<\/p>\n<pre class=\"lang:c# decode:true\">\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\n\/\/ Add services to the container.\r\n\r\nbuilder.Services.AddTransient&lt;IShelfBookRepository, ShelfBookInMemoryRepository&gt;();\r\n\r\nbuilder.Services.AddControllers()\r\n\r\n.AddOData(opt =&gt; opt.EnableQueryFeatures().AddRouteComponents(\"odata\", EdmModelBuilder.GetEdmModel()));\r\n\r\nbuilder.Services.AddGrpc();\r\n<\/pre>\n<p>Second, we must register the gRPC service class (by calling <strong>MapGrpcService&lt;T&gt;()<\/strong>) into the request pipeline to build the gRPC endpoints. Since OData is built upon the ASP.NET Core endpoint routing, there\u2019s no extra configuration needed. Below is the finished configuration:<\/p>\n<pre class=\"lang:c# decode:true\">\r\nvar app = builder.Build();\r\n\r\n\/\/ Configure the HTTP request pipeline.\r\n\r\napp.UseEndpointDebug(); \/\/ send \"\/$endpoint\" for route debug\r\n\r\napp.UseHttpsRedirection();\r\n\r\napp.UseAuthorization();\r\n\r\napp.MapGrpcService&lt;BookstoreService&gt;();\r\n\r\napp.MapControllers();\r\n\r\napp.Run();\r\n<\/pre>\n<p>Be noted, \u2018<strong>app.UseEndpointDebug();<\/strong>\u2019 is a middleware added in the sample project (find <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/gRPC.OData\/gRPC.OData.Server\/Extensions\/EndpointDebugMiddleware.cs\">here<\/a>) to help debug the endpoints. Since we enabled this middleware, you can run the project and <strong>view<\/strong> <a href=\"https:\/\/localhost:7260\/$endpoint\">https:\/\/localhost:7260\/$endpoint<\/a> in your browser (remember change the port number <strong>to your own<\/strong>), to receive an endpoint mappings page as follows. (You can get a full endpoint mappings page using the final project <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/gRPC.OData\/gRPC.OData.Server\">here<\/a>):<\/p>\n<p><img decoding=\"async\" width=\"1193\" height=\"268\" class=\"wp-image-4926\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-3.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-3.png 1193w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-3-300x67.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-3-1024x230.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-3-768x173.png 768w\" sizes=\"(max-width: 1193px) 100vw, 1193px\" \/><\/p>\n<p>From the endpoint mapping page, we can find:<\/p>\n<ol>\n<li>All gRPC methods defined in the <strong>bookstore.proto<\/strong> are built as an endpoint with <strong>POST<\/strong> HTTP Method.<\/li>\n<li>The route pattern of gRPC endpoint is <strong>\u201c\/{servicefullname}\/{methodname}\u201d, <\/strong>for example: <strong>\u201c\/bookstores.Bookstore\/ListShelves<\/strong>\u201d<\/li>\n<li>gRPC also generates two endpoints to handle unimplemented gRPC requests.<\/li>\n<li>Two OData endpoints are built by default for accessing the metadata.<\/li>\n<\/ol>\n<p>You can send a HTTP request at \u201cGET <a href=\"https:\/\/localhost:7260\/odata\/$metadata\">https:\/\/localhost:7260\/odata\/$metadata<\/a>\u201d to view the OData metadata.<\/p>\n<pre class=\"lang:xml decode:true\">\r\n&lt;?xml\u00a0version=\"1.0\"\u00a0encoding=\"utf-8\"?&gt;\r\n&lt;edmx:Edmx\u00a0Version=\"4.0\"\u00a0xmlns:edmx=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edmx\"&gt;\r\n  &lt;edmx:DataServices&gt;\r\n    &lt;Schema\u00a0Namespace=\"Bookstores\"\u00a0xmlns=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edm\"&gt;\r\n      &lt;EntityType\u00a0Name=\"Shelf\"&gt;\r\n        &lt;Key&gt;\r\n          &lt;PropertyRef\u00a0Name=\"Id\"\u00a0\/&gt;\r\n        &lt;\/Key&gt;\r\n        &lt;Property\u00a0Name=\"Id\"\u00a0Type=\"Edm.Int64\"\u00a0Nullable=\"false\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Theme\"\u00a0Type=\"Edm.String\"\u00a0\/&gt;\r\n        &lt;NavigationProperty\u00a0Name=\"Books\"\u00a0Type=\"Collection(Bookstores.Book)\"\u00a0\/&gt;\r\n      &lt;\/EntityType&gt;\r\n      &lt;EntityType\u00a0Name=\"Book\"&gt;\r\n        &lt;Key&gt;\r\n          &lt;PropertyRef\u00a0Name=\"Id\"\u00a0\/&gt;\r\n        &lt;\/Key&gt;\r\n        &lt;Property\u00a0Name=\"Id\"\u00a0Type=\"Edm.Int64\"\u00a0Nullable=\"false\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Title\"\u00a0Type=\"Edm.String\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Author\"\u00a0Type=\"Edm.String\"\u00a0\/&gt;\r\n      &lt;\/EntityType&gt;\r\n    &lt;\/Schema&gt;\r\n    &lt;Schema\u00a0Namespace=\"Default\"\u00a0xmlns=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edm\"&gt;\r\n      &lt;EntityContainer\u00a0Name=\"Container\"&gt;\r\n        &lt;EntitySet\u00a0Name=\"Books\"\u00a0EntityType=\"Bookstores.Book\"\u00a0\/&gt;\r\n        &lt;EntitySet\u00a0Name=\"Shelves\"\u00a0EntityType=\"Bookstores.Shelf\"&gt;\r\n          &lt;NavigationPropertyBinding\u00a0Path=\"Books\"\u00a0Target=\"Books\"\u00a0\/&gt;\r\n        &lt;\/EntitySet&gt;\r\n      &lt;\/EntityContainer&gt;\r\n    &lt;\/Schema&gt;\r\n  &lt;\/edmx:DataServices&gt;\r\n&lt;\/edmx:Edmx&gt;\r\n<\/pre>\n<h2>Implement gRPC Endpoint<\/h2>\n<p>As mentioned, we must override the methods auto generated by gRPC compiler to make gRPC endpoint work. Let\u2019s open <strong>BookstoreService<\/strong> class we created above and add the following codes:<\/p>\n<pre class=\"lang:c# decode:true\">\r\npublic class BookstoreService : Bookstore.BookstoreBase\r\n{\r\n    \u2026\u2026 \/\/ omit the constructor\r\n\r\n    \/\/ list shelves\r\n    public override Task&lt;ListShelvesResponse&gt; ListShelves(Empty request, ServerCallContext context)\r\n    {\r\n        IEnumerable&lt;Shelf&gt; shelves = _shelfBookRepository.GetShelves();\r\n\r\n        ListShelvesResponse response = new ListShelvesResponse();\r\n        foreach (var shelf in shelves)\r\n        {\r\n            response.Shelves.Add(shelf);\r\n        }\r\n\r\n        return Task.FromResult(response);\r\n    }\r\n\r\n    \/\/ list the books\r\n    public override Task&lt;ListBooksResponse&gt; ListBooks(ListBooksRequest request, ServerCallContext context)\r\n    {\r\n        IEnumerable&lt;Book&gt; books = _shelfBookRepository.GetBooks(request.Shelf);\r\n\r\n        ListBooksResponse response = new ListBooksResponse();\r\n        foreach (Book book in books)\r\n        {\r\n            response.Books.Add(book);\r\n        };\r\n\r\n        return Task.FromResult(response);\r\n    }\r\n}\r\n<\/pre>\n<p>Where:<\/p>\n<ol>\n<li>We override the <strong>ListShelves<\/strong> and <strong>ListBooks<\/strong> methods from \u2018<strong>Bookstore.BookstoreBase<\/strong>\u2019 base class.<\/li>\n<li>We use repository service injected from the constructor to do the real data access.<\/li>\n<\/ol>\n<p>You can find the whole implementation of <strong>BookstoreService<\/strong> <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/gRPC.OData\/gRPC.OData.Server\/Services\/BookstoreService.cs\">here<\/a>.<\/p>\n<h2>Implement OData Endpoint<\/h2>\n<p>Let\u2019s create a controller named <strong>ShelfBooksController<\/strong> under <strong>Controllers<\/strong> folder to handle OData HTTP requests:<\/p>\n<pre class=\"lang:c# decode:true\">\r\n[Route(\"odata\")]\r\npublic class ShelfBooksController : ODataController\r\n{\r\n    private readonly IShelfBookRepository _shelfBookRepository;\r\n\r\n    public ShelfBooksController(IShelfBookRepository shelfBookRepository)\r\n    {\r\n        _shelfBookRepository = shelfBookRepository;\r\n    }\r\n\r\n    [HttpGet(\"Shelves\")]\r\n    [EnableQuery]\r\n    public IActionResult ListShelves()\r\n    {\r\n        return Ok(_shelfBookRepository.GetShelves());\r\n    }\r\n\r\n    [HttpGet(\"Shelves\/{shelf}\/Books\")]\r\n    [EnableQuery]\r\n    public IActionResult ListBooks(long shelf)\r\n    {\r\n        return Ok(_shelfBookRepository.GetBooks(shelf));\r\n    }\r\n}\r\n<\/pre>\n<p>Where:<\/p>\n<ol>\n<li>We use the OData attribute routing by decorating <strong>[Route(\u201codata\u201d)]<\/strong> on controller and <strong>[HttGet(\u201c\u2026\u2026\u201d)]<\/strong> on action.<\/li>\n<li>We use the constructor dependency injection to inject the repository service to do the data access also.<\/li>\n<li>We enable the OData query by decorating <strong>[EnableQuery] <\/strong>on action.<\/li>\n<\/ol>\n<p>You can find the whole implementation of <strong>ShelfBooksController<\/strong> <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/gRPC.OData\/gRPC.OData.Server\/Controllers\/ShelfBooksController.cs\">here<\/a>.<\/p>\n<h2>Build gRPC and OData client<\/h2>\n<p>Ok, we finished our service for gRPC and OData, it\u2019s time to build a client to consume them. Basically, you can use any HTTP client as OData client. In this post, I\u2019d like to build a console application both for the gRPC and OData.<\/p>\n<p>Let&#8217;s create a new console application named <strong>gRPC.OData.client<\/strong> in the <strong>gRPC.OData<\/strong> solution.<\/p>\n<p>Create a\u00a0<strong>Protos<\/strong>\u00a0folder in the client project, and copy the \u2018<strong>Protos\\bookstore.proto<\/strong>\u2019\u00a0file from the <strong>gRPC.OData.Server<\/strong> project to the\u00a0<strong>Protos<\/strong>\u00a0folder in the client project.<\/p>\n<p>Double click the <strong>gRPC.OData.client<\/strong> project in the solution explorer, add the following contents into the <strong>gRPC.OData.Client.csproj<\/strong> file as:<\/p>\n<pre class=\"lang:c# decode:true\">\r\n&lt;ItemGroup&gt;\r\n  &lt;PackageReference Include=\"Google.Protobuf\" Version=\"3.20.0\" \/&gt;\r\n  &lt;PackageReference Include=\"Grpc.Net.Client\" Version=\"2.44.0\" \/&gt;\r\n  &lt;PackageReference Include=\"Grpc.Tools\" Version=\"2.45.0\"&gt;\r\n    &lt;PrivateAssets&gt;all&lt;\/PrivateAssets&gt;\r\n    &lt;IncludeAssets&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;\/IncludeAssets&gt;\r\n  &lt;\/PackageReference&gt;\r\n&lt;\/ItemGroup&gt;\r\n\r\n&lt;ItemGroup&gt;\r\n  &lt;Protobuf Include=\"proto\\BookStore.proto\" GrpcServices=\"Client\" \/&gt;\r\n&lt;\/ItemGroup&gt;\r\n<\/pre>\n<p>Be noted, the value of <strong>GrpcServices <\/strong>attribute in the above &lt;Protobuf \u2026\/&gt; XML tag is \u201c<strong>client<\/strong>\u201d for the client project, meanwhile, it\u2019s \u201c<strong>server<\/strong>\u201d value at the server project.<\/p>\n<p>Build the client project and let the protobuf compiler generate the client proxy classes. You can find the auto-generated client C# files under \u201cobj\u201d subfolder.<\/p>\n<p>Let\u2019s create a class to handle the gRPC client:<\/p>\n<pre class=\"lang:c# decode:true\">\r\ninternal class GrpcBookstoreClient\r\n{\r\n    private readonly string _baseUri;\r\n\r\n    public GrpcBookstoreClient(string baseUri)\r\n    {\r\n        _baseUri = baseUri;\r\n    }\r\n\r\n    public async Task ListShelves()\r\n    {\r\n        Console.WriteLine(\"\\ngRPC: List shelves:\");\r\n\r\n        using var channel = GrpcChannel.ForAddress(_baseUri);\r\n        var client = new Bookstore.BookstoreClient(channel);\r\n\r\n        var listShelvesResponse = await client.ListShelvesAsync(new Empty());\r\n        foreach (var shelf in listShelvesResponse.Shelves)\r\n        {\r\n            Console.WriteLine($\"\\t-{shelf.Id}): {shelf.Theme}\");\r\n        }\r\n\r\n        Console.WriteLine();\r\n    }\r\n\r\n    public async Task ListBooks(long shelfId)\r\n    {\r\n        Console.WriteLine($\"\\ngRPC: List books at shelf '{shelfId}':\");\r\n\r\n        using var channel = GrpcChannel.ForAddress(_baseUri);\r\n        var client = new Bookstore.BookstoreClient(channel);\r\n\r\n        var listBooksResponse = await client.ListBooksAsync(new ListBooksRequest { Shelf = shelfId });\r\n        foreach (var book in listBooksResponse.Books)\r\n        {\r\n            Console.WriteLine($\"\\t-{book.Id}): &lt;&lt;{book.Title}&gt;&gt; by {book.Author}\");\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Create another class to handle the OData client:<\/p>\n<pre class=\"lang:c# decode:true\">\r\ninternal class ODataBookstoreClient\r\n{\r\n    private readonly string _baseUri;\r\n\r\n    public ODataBookstoreClient(string baseUri)\r\n    {\r\n        _baseUri = baseUri;\r\n    }\r\n\r\n    public async Task ListShelves()\r\n    {\r\n        Console.WriteLine($\"\\nOData: List Shelves:\");\r\n\r\n        string requestUri = $\"{_baseUri}\/odata\/shelves\";\r\n        using var client = new HttpClient();\r\n        var response = await client.GetAsync(requestUri);\r\n\r\n        Console.WriteLine(\"--Status code: \" + response.StatusCode.ToString());\r\n        string body = await response.Content.ReadAsStringAsync();\r\n        Console.WriteLine(\"--Response body:\");\r\n        Console.WriteLine(BeautifyJson(body)); \/\/ find BeautifyJson from sample repository\r\n        Console.WriteLine();\r\n    }\r\n\r\n    public async Task ListBooks(long shelfId)\r\n    {\r\n        Console.WriteLine($\"\\nOData: List books at shelf '{shelfId}':\");\r\n\r\n        string requestUri = $\"{_baseUri}\/odata\/shelves\/{shelfId}\/books\";\r\n        using var client = new HttpClient();\r\n        var response = await client.GetAsync(requestUri);\r\n\r\n        Console.WriteLine(\"--Status code: \" + response.StatusCode.ToString());\r\n        string body = await response.Content.ReadAsStringAsync();\r\n        Console.WriteLine(\"--Response body:\");\r\n        Console.WriteLine(BeautifyJson(body));\r\n        Console.WriteLine();\r\n    }\r\n}\r\n<\/pre>\n<p>Now, update <strong>Program.cs<\/strong> as:<\/p>\n<pre class=\"lang:c# decode:true\">\r\nusing gRPC.OData.Client;\r\n\r\nstring baseUri = \"https:\/\/localhost:7260\";\r\n\r\nGrpcBookstoreClient gRPCbookStore = new GrpcBookstoreClient(baseUri);\r\n\r\nawait gRPCbookStore.ListShelves();\r\n\r\nawait gRPCbookStore.ListBooks(2);\r\n\r\nODataBookstoreClient oDataBookStore = new ODataBookstoreClient(baseUri);\r\n\r\nawait oDataBookStore.ListShelves();\r\n\r\nawait oDataBookStore.ListBooks(2);\r\n<\/pre>\n<p>Ok, we\u2019re ready to run the whole solution. Right-click the properties menu from the solution, select the multiple startup projects option, and move the server project to the top as shown below.<\/p>\n<p><img decoding=\"async\" width=\"776\" height=\"280\" class=\"wp-image-4927\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-4.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-4.png 776w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-4-300x108.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-4-768x277.png 768w\" sizes=\"(max-width: 776px) 100vw, 776px\" \/><\/p>\n<p>Now, Ctrl+F5 to run the projects. The service runs first as:<\/p>\n<p><img decoding=\"async\" width=\"608\" height=\"203\" class=\"wp-image-4928\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-5.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-5.png 608w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-5-300x100.png 300w\" sizes=\"(max-width: 608px) 100vw, 608px\" \/><\/p>\n<p>Then the client project runs and outputs the following result:<\/p>\n<p><img decoding=\"async\" width=\"780\" height=\"1132\" class=\"wp-image-4929\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-6.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-6.png 780w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-6-207x300.png 207w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-6-706x1024.png 706w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/04\/word-image-6-768x1115.png 768w\" sizes=\"(max-width: 780px) 100vw, 780px\" \/><\/p>\n<p>The final sample project has the whole CRUD implementations for both gRPC and OData. You can find it <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/gRPC.OData\/gRPC.OData.Client\">here<\/a>.<\/p>\n<h2>Apply OData query<\/h2>\n<p>gRPC response body, which is protobuf binary format, is smaller than OData response body, which is JSON human-readable format. However, one of the advantages of OData is its powerful query option mechanism. For example, we can send the following OData request with a query in any HTTP client:<\/p>\n<p><strong>Get https:\/\/localhost:7260\/odata\/Shelves?$expand=Books($filter=Id lt 12)<\/strong><\/p>\n<p>We can get the following result:<\/p>\n<pre class=\"lang:json decode:true\">\r\n{\r\n  \"@odata.context\":\u00a0\"https:\/\/localhost:7260\/odata\/$metadata#Shelves(Books())\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Id\":\u00a01,\r\n      \"Theme\":\u00a0\"Fiction\",\r\n      \"Books\":\u00a0[\r\n        {\r\n          \"Id\":\u00a011,\r\n          \"Title\":\u00a0\"\u897f\u6e38\u8bb0\",\r\n          \"Author\":\u00a0\"\u5433\u627f\u6069\"\r\n        }\r\n      ]\r\n    },\r\n    {\r\n      \"Id\":\u00a02,\r\n      \"Theme\":\u00a0\"Classics\",\r\n      \"Books\":\u00a0[]\r\n    },\r\n    {\r\n      \"Id\":\u00a03,\r\n      \"Theme\":\u00a0\"Computer\",\r\n      \"Books\":\u00a0[]\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>That is OData built-in functionality, no extra coding is required.<\/p>\n<h2>Summary<\/h2>\n<p>This post went through a process to build and consume gRPC and OData service together in one ASP.NET Core Web Application. gRPC and OData have their own usage scenarios, advantages, and disadvantages. Hope the contents and implementations in this post can help you to build your own service easier in the future. Again, please do not hesitate to leave your comments below or let me know your thoughts through\u00a0<a href=\"mailto:saxu@microsoft.com\">saxu@microsoft.com<\/a>. Thanks.<\/p>\n<p>I uploaded the whole project to\u00a0<a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/gRPC.OData\" target=\"_blank\" rel=\"noopener\">this<\/a>\u00a0repository.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction gRPC (google Remote Procedure Call) is a high-performance remote procedure call framework that helps developers to build and consume remote services using the same way as calling local APIs. Different from gRPC, OData (Open Data Protocol) is an\u00a0OASIS standard\u00a0that defines a set of best practices for developers to build and consume RESTful APIs using [&hellip;]<\/p>\n","protected":false},"author":514,"featured_media":4958,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1472,1],"tags":[502,1478,48],"class_list":["post-4921","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-asp-net-core","category-odata","tag-asp-net-core","tag-grpc","tag-odata"],"acf":[],"blog_post_summary":"<p>Introduction gRPC (google Remote Procedure Call) is a high-performance remote procedure call framework that helps developers to build and consume remote services using the same way as calling local APIs. Different from gRPC, OData (Open Data Protocol) is an\u00a0OASIS standard\u00a0that defines a set of best practices for developers to build and consume RESTful APIs using [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4921","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/users\/514"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=4921"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4921\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/4958"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=4921"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=4921"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=4921"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}