├── .gitattributes
├── .gitignore
├── ConsoleAppGRPC
├── ConsoleAppGRPC.csproj
├── Logging
│ └── LoggerInterceptor.cs
├── Program.cs
├── Protos
│ └── v1
│ │ └── country.proto
└── StatusManager.cs
├── Database
├── Create dbo.Country.sql
└── dbo.Country.data.sql
├── DemoGrpc.Domain
├── DemoGrpc.Domain.csproj
└── Entities
│ └── Country.cs
├── DemoGrpc.Repository
├── CountryRepository.cs
├── Database
│ └── DbContext.cs
├── DemoGrpc.Repository.csproj
└── Interfaces
│ └── ICountryRepository.cs
├── DemoGrpc.Web
├── DemoGrpc.Web.csproj
├── Logging
│ └── LoggerInterceptor.cs
├── Mapping
│ └── CountryProfiles.cs
├── Middlewares
│ └── CustomExceptionMiddleware.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Protos
│ ├── v1
│ │ ├── country.proto
│ │ └── greet.proto
│ └── v2
│ │ └── country.proto
├── Services
│ ├── CountryService.cs
│ └── ProtoService.cs
├── Startup.cs
├── Validator
│ └── CountryCreateRequestValidator.cs
├── appsettings.Development.json
└── appsettings.json
├── DemoGrpc.sln
├── DempGrpc.Services
├── CountryService.cs
├── DempGrpc.Services.csproj
└── Interfaces
│ └── ICountryService.cs
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/ConsoleAppGRPC/ConsoleAppGRPC.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | all
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/ConsoleAppGRPC/Logging/LoggerInterceptor.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 | using Grpc.Core.Interceptors;
3 | using Microsoft.Extensions.Logging;
4 | using System.Threading.Tasks;
5 |
6 | namespace ConsoleAppGRPC.Logging
7 | {
8 | public class LoggerInterceptor : Interceptor
9 | {
10 | private readonly ILogger _logger;
11 |
12 | public LoggerInterceptor(ILogger logger)
13 | {
14 | _logger = logger;
15 | }
16 |
17 | public override AsyncUnaryCall AsyncUnaryCall(
18 | TRequest request,
19 | ClientInterceptorContext context,
20 | AsyncUnaryCallContinuation continuation)
21 | {
22 | LogCall(context.Method);
23 |
24 | var call = continuation(request, context);
25 |
26 | return new AsyncUnaryCall(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose);
27 | }
28 |
29 | private async Task HandleResponse(Task t)
30 | {
31 | try
32 | {
33 | var response = await t;
34 | _logger.LogDebug($"Response received: {response}");
35 | return response;
36 | }
37 | catch (RpcException ex)
38 | {
39 | _logger.LogError($"Call error: {ex.Message}");
40 | return default;
41 | }
42 | }
43 |
44 | private void LogCall(Method method) where TRequest : class where TResponse : class
45 | {
46 | _logger.LogDebug($"Starting call. Type: {method.Type}. Request: {typeof(TRequest)}. Response: {typeof(TResponse)}");
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/ConsoleAppGRPC/Program.cs:
--------------------------------------------------------------------------------
1 | using Calzolari.Grpc.Net.Client.Validation;
2 | using DemoGrpc.Domain.Entities;
3 | using DemoGrpc.Protobufs;
4 | using Grpc.Core;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Polly;
7 | using System;
8 | using System.Linq;
9 | using System.Net.Http;
10 | using System.Threading.Tasks;
11 | using Grpc.Net.Client.Web;
12 | using Grpc.Net.Client;
13 | using System.Net;
14 | using DemoGrpc.Protobufs.V1;
15 | using Microsoft.Extensions.Logging;
16 | using static DemoGrpc.Protobufs.V1.CountryService;
17 | using System.Security.Cryptography.X509Certificates;
18 | using System.Text.Json;
19 | using System.Text;
20 |
21 | namespace ConsoleAppGRPC
22 | {
23 | class Program
24 | {
25 | static async Task Main(string[] args)
26 | {
27 | // DI
28 | var services = new ServiceCollection();
29 |
30 | var loggerFactory = LoggerFactory.Create(logging =>
31 | {
32 | logging.AddConsole();
33 | logging.SetMinimumLevel(LogLevel.Debug);
34 | });
35 |
36 | var serverErrors = new HttpStatusCode[] {
37 | HttpStatusCode.BadGateway,
38 | HttpStatusCode.GatewayTimeout,
39 | HttpStatusCode.ServiceUnavailable,
40 | HttpStatusCode.InternalServerError,
41 | HttpStatusCode.TooManyRequests,
42 | HttpStatusCode.RequestTimeout
43 | };
44 |
45 | var gRpcErrors = new StatusCode[] {
46 | StatusCode.DeadlineExceeded,
47 | StatusCode.Internal,
48 | StatusCode.NotFound,
49 | StatusCode.ResourceExhausted,
50 | StatusCode.Unavailable,
51 | StatusCode.Unknown
52 | };
53 |
54 | Func> retryFunc = (request) =>
55 | {
56 | return Policy.HandleResult(r => {
57 |
58 | var grpcStatus = StatusManager.GetStatusCode(r);
59 | var httpStatusCode = r.StatusCode;
60 |
61 | return (grpcStatus == null && serverErrors.Contains(httpStatusCode)) || // if the server send an error before gRPC pipeline
62 | (httpStatusCode == HttpStatusCode.OK && gRpcErrors.Contains(grpcStatus.Value)); // if gRPC pipeline handled the request (gRPC always answers OK)
63 | })
64 | .WaitAndRetryAsync(3, (input) => TimeSpan.FromSeconds(3 + input), (result, timeSpan, retryCount, context) =>
65 | {
66 | var grpcStatus = StatusManager.GetStatusCode(result.Result);
67 | Console.WriteLine($"Request failed with {grpcStatus}. Retry");
68 | });
69 | };
70 |
71 | /*
72 | var channel = new Channel("https://localhost:5001", ChannelCredentials.Insecure, new [] {
73 | new ChannelOption("grpc.max_receive_message_length","5242880") // 5 MB
74 | });
75 | var countryClient = new CountryServiceClient(channel);
76 |
77 |
78 | var country = await countryClient.GetByIdAsync(new CountrySearchRequest { CountryId = 1 });
79 | */
80 |
81 | // https://grpcwebdemo.azurewebsites.net
82 | // gRPC
83 | services.AddGrpcClient(o =>
84 | {
85 | o.Address = new Uri("https://localhost:5001");
86 | }).AddPolicyHandler(retryFunc);
87 | var provider = services.BuildServiceProvider();
88 | var client = provider.GetRequiredService();
89 |
90 | /*
91 | // https://grpcwebdemo.azurewebsites.net
92 | // gRPC-Web
93 | var handler = new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler());
94 | var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
95 | {
96 | HttpClient = new HttpClient(handler),
97 | LoggerFactory = loggerFactory
98 | });
99 | var clientWeb = new CountryServiceClient(channel);
100 | */
101 |
102 |
103 | try
104 | {
105 | //// Get all gRPC
106 | //var countries = (await client.GetAllAsync(new EmptyRequest())).Countries.Select(x => new Country
107 | //{
108 | // CountryId = x.Id,
109 | // Description = x.Description,
110 | // CountryName = x.Name
111 | //}).ToList();
112 |
113 | //Console.WriteLine("Found countries");
114 | //countries.ForEach(x => Console.WriteLine($"Found country {x.CountryName} ({x.CountryId}) {x.Description}"));
115 |
116 | //Console.WriteLine();
117 | //Console.WriteLine();
118 |
119 | //// Get all gRPC - web
120 | //var countriesweb = (await clientWeb.GetAllAsync(new EmptyRequest())).Countries.Select(x => new Country
121 | //{
122 | // CountryId = x.Id,
123 | // Description = x.Description,
124 | // CountryName = x.Name
125 | //}).ToList();
126 |
127 | //Console.WriteLine("Found countries with gRPC-Web");
128 | //countriesweb.ForEach(x => Console.WriteLine($"Found country with gRPC-Web: {x.CountryName} ({x.CountryId}) {x.Description}"));
129 |
130 | // Create
131 | var createdCountry = await client.CreateAsync(new CountryCreateRequest { Name = "Japan", Description = "" });
132 |
133 | //var createdCountry2 = await clientWeb.CreateAsync(new CountryCreateRequest { Name = "Japan", Description = "" });
134 | }
135 | catch (RpcException e)
136 | {
137 | var errors = e.GetValidationErrors(); // Gets validation errors list
138 | Console.WriteLine(e.Message);
139 | }
140 | }
141 |
142 | }
143 | }
--------------------------------------------------------------------------------
/ConsoleAppGRPC/Protos/v1/country.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "DemoGrpc.Protobufs.V1";
4 |
5 | package v1;
6 |
7 | service CountryService {
8 | rpc GetAll(EmptyRequest) returns (CountriesReply) {}
9 | rpc GetById (CountrySearchRequest) returns (CountryReply) {}
10 | rpc Create (CountryCreateRequest) returns (CountryReply) {}
11 | rpc Update (CountryRequest) returns (CountryReply) {}
12 | rpc Delete (CountrySearchRequest) returns (EmptyReply) {}
13 | }
14 |
15 | message EmptyRequest {
16 | }
17 |
18 | message EmptyReply {
19 | }
20 |
21 | message CountrySearchRequest {
22 | int32 CountryId = 1;
23 | }
24 |
25 | message CountryCreateRequest {
26 | string Name = 1;
27 | string Description = 2;
28 | }
29 |
30 | message CountryRequest {
31 | int32 Id = 1;
32 | string Name = 2;
33 | string Description = 3;
34 | }
35 |
36 | message CountryReply {
37 | int32 Id = 1;
38 | string Name = 2;
39 | string Description = 3;
40 | }
41 |
42 | message CountriesReply {
43 | repeated CountryReply Countries = 1;
44 | }
--------------------------------------------------------------------------------
/ConsoleAppGRPC/StatusManager.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 | using System.Linq;
3 | using System.Net;
4 | using System.Net.Http;
5 |
6 | namespace ConsoleAppGRPC
7 | {
8 | public static class StatusManager
9 | {
10 | public static StatusCode? GetStatusCode(HttpResponseMessage response)
11 | {
12 | var headers = response.Headers;
13 |
14 | if (!headers.Contains("grpc-status") && response.StatusCode == HttpStatusCode.OK)
15 | return StatusCode.OK;
16 |
17 | if (headers.Contains("grpc-status"))
18 | return (StatusCode)int.Parse(headers.GetValues("grpc-status").First());
19 |
20 | return null;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/Database/Create dbo.Country.sql:
--------------------------------------------------------------------------------
1 | USE [DemoOrmLite]
2 | GO
3 |
4 | /****** Object: Table [dbo].[Country] Script Date: 1/31/2020 11:38:02 PM ******/
5 | SET ANSI_NULLS ON
6 | GO
7 |
8 | SET QUOTED_IDENTIFIER ON
9 | GO
10 |
11 | CREATE TABLE [dbo].[Country] (
12 | [CountryId] INT IDENTITY (1, 1) NOT NULL,
13 | [CountryName] VARCHAR (50) NOT NULL,
14 | [Description] VARCHAR (50) NULL
15 | );
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Database/dbo.Country.data.sql:
--------------------------------------------------------------------------------
1 | SET IDENTITY_INSERT [dbo].[Country] ON
2 | INSERT INTO [dbo].[Country] ([CountryId], [CountryName], [Description]) VALUES (1, N'France', N'beautiful and not so cold!')
3 | INSERT INTO [dbo].[Country] ([CountryId], [CountryName], [Description]) VALUES (2, N'Canada', N'beautiful and very cold')
4 | INSERT INTO [dbo].[Country] ([CountryId], [CountryName], [Description]) VALUES (1003, N'Australia', N'incredible wildlife')
5 | INSERT INTO [dbo].[Country] ([CountryId], [CountryName], [Description]) VALUES (4003, N'Japan', N'rising sun country')
6 | SET IDENTITY_INSERT [dbo].[Country] OFF
7 |
--------------------------------------------------------------------------------
/DemoGrpc.Domain/DemoGrpc.Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/DemoGrpc.Domain/Entities/Country.cs:
--------------------------------------------------------------------------------
1 | namespace DemoGrpc.Domain.Entities
2 | {
3 | public class Country
4 | {
5 | public int CountryId { get; set; }
6 | public string CountryName { get; set; }
7 | public string Description { get; set; }
8 | }
9 | }
--------------------------------------------------------------------------------
/DemoGrpc.Repository/CountryRepository.cs:
--------------------------------------------------------------------------------
1 | using DemoGrpc.Domain.Entities;
2 | using DemoGrpc.Repository.Database;
3 | using DemoGrpc.Repository.Interfaces;
4 | using Microsoft.EntityFrameworkCore;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 |
10 | namespace DemoGrpc.Repository
11 | {
12 | public class EFCountryRepository : ICountryRepository
13 | {
14 | private readonly DemoDbContext _dbContext;
15 | public EFCountryRepository(DemoDbContext dbContext)
16 | {
17 | _dbContext = dbContext;
18 | }
19 |
20 | public async Task> GetAsync()
21 | {
22 | return await _dbContext.Country.AsNoTracking().ToListAsync();
23 | }
24 |
25 | public async Task GetByIdAsync(int countryId)
26 | {
27 | return await _dbContext.Country.AsNoTracking().FirstOrDefaultAsync(x => x.CountryId == countryId);
28 | }
29 |
30 | public async Task AddAsync(Country country)
31 | {
32 | _dbContext.Add(country);
33 | await _dbContext.SaveChangesAsync();
34 | return country;
35 | }
36 |
37 | public async Task UpdateAsync(Country country)
38 | {
39 | var countryToUpdate = await _dbContext.Country
40 | .Where(x => x.CountryId == country.CountryId)
41 | .FirstOrDefaultAsync();
42 |
43 | countryToUpdate.CountryName = country.CountryName;
44 | countryToUpdate.Description = country.Description;
45 |
46 | return await _dbContext.SaveChangesAsync();
47 | }
48 |
49 | public async Task DeleteAsync(int countryId)
50 | {
51 | var countryToDelete = await _dbContext.Country
52 | .Where(x => x.CountryId == countryId)
53 | .FirstOrDefaultAsync();
54 |
55 | _dbContext.Remove(countryToDelete);
56 | return await _dbContext.SaveChangesAsync();
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/DemoGrpc.Repository/Database/DbContext.cs:
--------------------------------------------------------------------------------
1 | using DemoGrpc.Domain.Entities;
2 | using Microsoft.EntityFrameworkCore;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace DemoGrpc.Repository.Database
8 | {
9 | public class DemoDbContext : DbContext
10 | {
11 | public DemoDbContext(DbContextOptions options)
12 | : base(options)
13 | { }
14 |
15 | public DbSet Country { get; set; }
16 | }
17 | }
--------------------------------------------------------------------------------
/DemoGrpc.Repository/DemoGrpc.Repository.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/DemoGrpc.Repository/Interfaces/ICountryRepository.cs:
--------------------------------------------------------------------------------
1 | using DemoGrpc.Domain.Entities;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace DemoGrpc.Repository.Interfaces
6 | {
7 | public interface ICountryRepository
8 | {
9 | Task> GetAsync();
10 | Task GetByIdAsync(int countryId);
11 | Task AddAsync(Country country);
12 | Task UpdateAsync(Country country);
13 | Task DeleteAsync(int countryId);
14 | }
15 | }
--------------------------------------------------------------------------------
/DemoGrpc.Web/DemoGrpc.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | 101741b2-5422-4c6d-952b-6e6cba1acc4f
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/DemoGrpc.Web/Logging/LoggerInterceptor.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 | using Grpc.Core.Interceptors;
3 | using Microsoft.Extensions.Logging;
4 | using System;
5 | using System.Threading.Tasks;
6 | using Microsoft.Data.SqlClient;
7 |
8 | namespace DemoGrpc.Web.Logging
9 | {
10 | public class LoggerInterceptor : Interceptor
11 | {
12 | private readonly ILogger _logger;
13 |
14 | public LoggerInterceptor(ILogger logger)
15 | {
16 | _logger = logger;
17 | }
18 |
19 | public override async Task UnaryServerHandler(
20 | TRequest request,
21 | ServerCallContext context,
22 | UnaryServerMethod continuation)
23 | {
24 | LogCall(context);
25 | try
26 | {
27 | return await continuation(request, context);
28 | }
29 | catch (SqlException e)
30 | {
31 | _logger.LogError(e, $"An SQL error occured when calling {context.Method}");
32 | Status status;
33 |
34 | if (e.Number == -2)
35 | {
36 | status = new Status(StatusCode.DeadlineExceeded, "SQL timeout");
37 | }
38 | else
39 | {
40 | status = new Status(StatusCode.Internal, "SQL error");
41 | }
42 | throw new RpcException(status);
43 | }
44 | catch (Exception e)
45 | {
46 | _logger.LogError(e, $"An error occured when calling {context.Method}");
47 | throw new RpcException(new Status(StatusCode.Internal, e.Message));
48 | }
49 |
50 | }
51 |
52 | public override Task ClientStreamingServerHandler(
53 | IAsyncStreamReader requestStream,
54 | ServerCallContext context,
55 | ClientStreamingServerMethod continuation)
56 | {
57 | return continuation(requestStream, context);
58 | }
59 |
60 |
61 | public override Task ServerStreamingServerHandler(
62 | TRequest request,
63 | IServerStreamWriter responseStream,
64 | ServerCallContext context,
65 | ServerStreamingServerMethod continuation)
66 | {
67 | return continuation(request, responseStream, context);
68 | }
69 |
70 | public override Task DuplexStreamingServerHandler(
71 | IAsyncStreamReader requestStream,
72 | IServerStreamWriter responseStream,
73 | ServerCallContext context,
74 | DuplexStreamingServerMethod continuation)
75 | {
76 | return continuation(requestStream, responseStream, context);
77 | }
78 |
79 | private void LogCall(ServerCallContext context)
80 | {
81 | var httpContext = context.GetHttpContext();
82 | _logger.LogDebug($"Starting call. Request: {httpContext.Request.Path}");
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/DemoGrpc.Web/Mapping/CountryProfiles.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using DemoGrpc.Domain.Entities;
3 | using DemoGrpc.Protobufs;
4 | using System.Collections.Generic;
5 | using DemoGrpc.Protobufs.V1;
6 |
7 | namespace DemoGrpc.Web.Mapping
8 | {
9 | public class CountryProfilesV1 : Profile
10 | {
11 | public CountryProfilesV1()
12 | {
13 | CreateMap()
14 | .ForMember(dest => dest.Id, source => source.MapFrom(src => src.CountryId))
15 | .ForMember(dest => dest.Name, source => source.MapFrom(src => src.CountryName))
16 | .ForMember(dest => dest.Description, source => source.MapFrom(src => src.Description));
17 |
18 | CreateMap, CountriesReply>()
19 | .ForMember(dest => dest.Countries, source => source.MapFrom(src => src));
20 |
21 | CreateMap()
22 | .ForMember(dest => dest.CountryName, source => source.MapFrom(src => src.Name))
23 | .ForMember(dest => dest.Description, source => source.MapFrom(src => src.Description));
24 |
25 | CreateMap()
26 | .ForMember(dest => dest.CountryId, source => source.MapFrom(src => src.Id))
27 | .ForMember(dest => dest.CountryName, source => source.MapFrom(src => src.Name))
28 | .ForMember(dest => dest.Description, source => source.MapFrom(src => src.Description));
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/DemoGrpc.Web/Middlewares/CustomExceptionMiddleware.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.Extensions.Logging;
4 | using System;
5 | using System.Threading.Tasks;
6 |
7 | namespace DemoAspNetCore3.Middlewares
8 | {
9 | public class CustomExceptionMiddleware
10 | {
11 | private readonly RequestDelegate _next;
12 | private readonly ILogger _logger;
13 |
14 | public CustomExceptionMiddleware(RequestDelegate next, ILogger logger)
15 | {
16 | _next = next;
17 | _logger = logger;
18 | }
19 |
20 | public async Task Invoke(HttpContext context)
21 | {
22 | try
23 | {
24 | await _next.Invoke(context);
25 | }
26 | catch (Exception ex)
27 | {
28 | HandleExceptionAsync(context, ex);
29 | }
30 | }
31 |
32 | private void HandleExceptionAsync(HttpContext context, Exception exception)
33 | {
34 | _logger.LogError(exception, "unhandled error");
35 |
36 | throw new RpcException(new Status(StatusCode.Internal, "Ooooops!"));
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/DemoGrpc.Web/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Extensions.Configuration.AzureKeyVault;
4 | using Microsoft.Extensions.Hosting;
5 | using System.IO;
6 |
7 | namespace DemoAspNetCore3
8 | {
9 | public class Program
10 | {
11 | public static void Main(string[] args)
12 | {
13 | CreateHostBuilder(args).Build().Run();
14 | }
15 |
16 | public static IHostBuilder CreateHostBuilder(string[] args) =>
17 | Host.CreateDefaultBuilder(args)
18 | .ConfigureWebHostDefaults(webBuilder =>
19 | {
20 | webBuilder.UseStartup();
21 | }).ConfigureAppConfiguration((context, config) =>
22 | {
23 | //config.AddUserSecrets();
24 |
25 | //var builtConfig = config.Build();
26 | //config.AddAzureKeyVault(
27 | // $"https://{builtConfig["KeyVault:Vault"]}.vault.azure.net/",
28 | // builtConfig["KeyVault:ClientId"],
29 | // builtConfig["KeyVault:ClientSecret"],
30 | // new DefaultKeyVaultSecretManager());
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/DemoGrpc.Web/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "DemoGrpc.Web": {
4 | "commandName": "Project",
5 | "launchBrowser": false,
6 | "applicationUrl": "https://localhost:5001",
7 | "environmentVariables": {
8 | "ASPNETCORE_ENVIRONMENT": "Development"
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DemoGrpc.Web/Protos/v1/country.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "DemoGrpc.Protobufs.V1";
4 |
5 | package v1;
6 |
7 | service CountryService {
8 | rpc GetAll(EmptyRequest) returns (CountriesReply) {}
9 | rpc GetAllStreamed(EmptyRequest) returns (stream CountryReply) {}
10 | rpc GetById (CountrySearchRequest) returns (CountryReply) {}
11 | rpc Create (CountryCreateRequest) returns (CountryReply) {}
12 | rpc Update (CountryRequest) returns (CountryReply) {}
13 | rpc Delete (CountrySearchRequest) returns (EmptyReply) {}
14 | }
15 |
16 | message EmptyRequest {
17 | }
18 |
19 | message EmptyReply {
20 | }
21 |
22 | message CountrySearchRequest {
23 | int32 CountryId = 1;
24 | }
25 |
26 | message CountryCreateRequest {
27 | string Name = 1;
28 | string Description = 2;
29 | }
30 |
31 | message CountryRequest {
32 | int32 Id = 1;
33 | string Name = 2;
34 | string Description = 3;
35 | }
36 |
37 | message CountryReply {
38 | int32 Id = 1;
39 | string Name = 2;
40 | string Description = 3;
41 | }
42 |
43 | message CountriesReply {
44 | repeated CountryReply Countries = 1;
45 | }
--------------------------------------------------------------------------------
/DemoGrpc.Web/Protos/v1/greet.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "DemoGrpc.Web.Protos.v1";
4 |
--------------------------------------------------------------------------------
/DemoGrpc.Web/Protos/v2/country.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "DemoGrpc.Web.Protos.v2";
4 |
--------------------------------------------------------------------------------
/DemoGrpc.Web/Services/CountryService.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using DemoGrpc.Domain.Entities;
3 | using DemoGrpc.Protobufs;
4 | using DempGrpc.Services.Interfaces;
5 | using Grpc.Core;
6 | using Microsoft.AspNetCore.Authorization;
7 | using System;
8 | using System.Threading.Tasks;
9 | using DemoGrpc.Protobufs.V1;
10 |
11 | namespace DemoGrpc.Web.Services.V1
12 | {
13 | public class CountryGrpcService : CountryService.CountryServiceBase
14 | {
15 | private readonly ICountryService _countryService;
16 | private readonly IMapper _mapper;
17 |
18 | public CountryGrpcService(ICountryService countryService, IMapper mapper)
19 | {
20 | _countryService = countryService;
21 | _mapper = mapper;
22 | }
23 |
24 | public override async Task GetAllStreamed(EmptyRequest request, IServerStreamWriter responseStream, ServerCallContext context)
25 | {
26 | var headers = context.GetHttpContext().Request.Headers;
27 | var lst = await _countryService.GetAsync();
28 |
29 | foreach (var country in lst)
30 | {
31 | await responseStream.WriteAsync(_mapper.Map(country));
32 | }
33 | await Task.CompletedTask;
34 | }
35 |
36 | //[Authorize]
37 | public override async Task GetAll(EmptyRequest request, ServerCallContext context)
38 | {
39 | //throw new RpcException(new Status(StatusCode.Internal, "Internal error"), "Internal error occured");
40 | var countries = await _countryService.GetAsync();
41 | return _mapper.Map(countries);
42 | }
43 |
44 | //[Authorize]
45 | public override async Task GetById(CountrySearchRequest request, ServerCallContext context)
46 | {
47 | var country = await _countryService.GetByIdAsync(request.CountryId);
48 | return _mapper.Map(country);
49 | }
50 |
51 | //[Authorize]
52 | public override async Task Create(CountryCreateRequest request, ServerCallContext context)
53 | {
54 | //var currentUser = context.GetHttpContext().User;
55 | //throw new RpcException(new Status(StatusCode.InvalidArgument,"test"), "test");
56 | var createCountry = _mapper.Map(request);
57 | var country = await _countryService.AddAsync(createCountry);
58 | return _mapper.Map(country);
59 | }
60 |
61 | //[Authorize]
62 | public override async Task Update(CountryRequest request, ServerCallContext context)
63 | {
64 | //var currentUser = context.GetHttpContext().User;
65 | var updateCountry = _mapper.Map(request);
66 | var country = await _countryService.UpdateAsync(updateCountry);
67 | return _mapper.Map(country);
68 | }
69 |
70 | //[Authorize]
71 | public override async Task Delete(CountrySearchRequest request, ServerCallContext context)
72 | {
73 | //var currentUser = context.GetHttpContext().User;
74 | await _countryService.DeleteAsync(request.CountryId);
75 | return new EmptyReply();
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/DemoGrpc.Web/Services/ProtoService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text.Json;
6 | using System.Threading.Tasks;
7 | using Microsoft.AspNetCore.Hosting;
8 |
9 | namespace DemoGrpc.Web.Services
10 | {
11 | public class ProtoService
12 | {
13 | private readonly string _baseDirectory;
14 | private readonly Dictionary> _protosByVersion;
15 |
16 | public ProtoService(IWebHostEnvironment webHost)
17 | {
18 | _baseDirectory = webHost.ContentRootPath;
19 | _protosByVersion = Get(_baseDirectory);
20 | }
21 |
22 | public async Task Get()
23 | {
24 | using (var stream = new MemoryStream())
25 | {
26 | await JsonSerializer.SerializeAsync(stream, _protosByVersion);
27 | stream.Position = 0;
28 | using var reader = new StreamReader(stream);
29 | return await reader.ReadToEndAsync();
30 | }
31 |
32 | }
33 |
34 | public string Get(int version, string protoName)
35 | {
36 | var filePath = $"{_baseDirectory}\\protos\\v{version}\\{protoName}";
37 | var exist = File.Exists(filePath);
38 |
39 | return exist ? filePath : null;
40 | }
41 |
42 | private Dictionary> Get(string baseDirectory) =>
43 |
44 | Directory.GetDirectories($"{baseDirectory}\\protos")
45 | .Select(x => new { version = x, protos = Directory.GetFiles(x).Select(Path.GetFileName)})
46 | .ToDictionary(o => Path.GetRelativePath("protos", o.version), o => o.protos);
47 |
48 | }
49 | }
--------------------------------------------------------------------------------
/DemoGrpc.Web/Startup.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Calzolari.Grpc.AspNetCore.Validation;
3 | using DemoGrpc.Repository;
4 | using DemoGrpc.Repository.Database;
5 | using DemoGrpc.Repository.Interfaces;
6 | using DemoGrpc.Web.Logging;
7 | using DemoGrpc.Web.Services;
8 | using DemoGrpc.Web.Validator;
9 | using DempGrpc.Services;
10 | using DempGrpc.Services.Interfaces;
11 | using Microsoft.AspNetCore.Builder;
12 | using Microsoft.AspNetCore.Hosting;
13 | using Microsoft.AspNetCore.Http;
14 | using Microsoft.EntityFrameworkCore;
15 | using Microsoft.Extensions.Configuration;
16 | using Microsoft.Extensions.DependencyInjection;
17 | using Microsoft.Extensions.Hosting;
18 | using System.IO;
19 | using System.Reflection;
20 | using CountryGrpcServiceV1 = DemoGrpc.Web.Services.V1.CountryGrpcService;
21 | using System.Net;
22 |
23 | namespace DemoAspNetCore3
24 | {
25 | public class Startup
26 | {
27 | public Startup(IConfiguration configuration)
28 | {
29 | Configuration = configuration;
30 | }
31 |
32 | public IConfiguration Configuration { get; }
33 |
34 | public void ConfigureServices(IServiceCollection services)
35 | {
36 | //services.AddAuthentication(options =>
37 | //{
38 | // options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
39 | // options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
40 | //}).AddJwtBearer(options =>
41 | //{
42 | // options.Authority = "https://login.microsoftonline.com/136544d9-038e-4646-afff-10accb370679";
43 | // options.Audience = "257b6c36-1168-4aac-be93-6f2cd81cec43";
44 | // options.TokenValidationParameters.ValidateLifetime = true;
45 | // options.TokenValidationParameters.ClockSkew = TimeSpan.Zero;
46 | //});
47 |
48 | //services.AddAuthorization();
49 |
50 | services.AddDbContext(options => options.UseInMemoryDatabase(databaseName: "country_db"));
51 |
52 | var serviceProvider = services.BuildServiceProvider();
53 | using (var context = new DemoDbContext(
54 | serviceProvider.GetRequiredService>()))
55 | {
56 | context.Country.AddRange(
57 | new DemoGrpc.Domain.Entities.Country
58 | {
59 | CountryId = 1,
60 | CountryName = "Canada",
61 | Description = "Maple leaf country"
62 | },
63 | new DemoGrpc.Domain.Entities.Country
64 | {
65 | CountryId = 2,
66 | CountryName = "Japon",
67 | Description = "Rising sun country"
68 | },
69 | new DemoGrpc.Domain.Entities.Country
70 | {
71 | CountryId = 3,
72 | CountryName = "Australia",
73 | Description = "Wallabies country"
74 | });
75 |
76 | context.SaveChanges();
77 | }
78 |
79 | services.AddGrpc(options =>
80 | {
81 | options.EnableMessageValidation();
82 | options.Interceptors.Add();
83 | });
84 |
85 | services.AddCors(o =>
86 | {
87 | o.AddPolicy("MyPolicy", builder =>
88 | {
89 | builder.AllowAnyOrigin();
90 | builder.AllowAnyMethod();
91 | builder.AllowAnyHeader();
92 | builder.WithExposedHeaders("Grpc-Status", "Grpc-Message");
93 | });
94 | });
95 |
96 | services.AddValidator();
97 |
98 | services.AddGrpcValidation();
99 |
100 | services.AddAutoMapper(Assembly.Load("DemoGrpc.Web"));
101 |
102 | services.AddScoped();
103 | services.AddScoped();
104 |
105 | //services.AddApplicationInsightsTelemetry();
106 |
107 | services.AddSingleton();
108 | }
109 |
110 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
111 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
112 | {
113 | if (env.IsDevelopment())
114 | {
115 | app.UseDeveloperExceptionPage();
116 | }
117 | else
118 | {
119 | app.UseHsts();
120 | }
121 |
122 | app.UseHttpsRedirection();
123 |
124 | app.UseRouting();
125 |
126 | app.UseCors("MyPolicy");
127 |
128 | app.UseGrpcWeb();
129 |
130 | //app.UseAuthentication();
131 | //app.UseAuthorization();
132 |
133 | app.UseEndpoints(endpoints =>
134 | {
135 | var protoService = endpoints.ServiceProvider.GetRequiredService();
136 |
137 | endpoints.MapGrpcService().RequireCors("MyPolicy").EnableGrpcWeb();
138 |
139 | endpoints.MapGet("/protos", async context =>
140 | {
141 | await context.Response.WriteAsync(await protoService.Get());
142 | });
143 |
144 | endpoints.MapGet("/protos/v{version:int}/{protoName}", async context =>
145 | {
146 | var version = int.Parse((string)context.Request.RouteValues["version"]);
147 | var protoName = (string)context.Request.RouteValues["protoName"];
148 |
149 | var filePath = protoService.Get(version, protoName);
150 |
151 | if (filePath != null)
152 | {
153 | await context.Response.SendFileAsync(filePath);
154 | }
155 | else
156 | {
157 | context.Response.StatusCode = (int) HttpStatusCode.NotFound;
158 | }
159 | });
160 | });
161 | }
162 | }
163 | }
--------------------------------------------------------------------------------
/DemoGrpc.Web/Validator/CountryCreateRequestValidator.cs:
--------------------------------------------------------------------------------
1 | using DemoGrpc.Protobufs;
2 | using DemoGrpc.Protobufs.V1;
3 | using FluentValidation;
4 |
5 | namespace DemoGrpc.Web.Validator
6 | {
7 | public class CountryCreateRequestValidator : AbstractValidator
8 | {
9 | public CountryCreateRequestValidator()
10 | {
11 | RuleFor(request => request.Name).NotEmpty().WithMessage("Name is mandatory.");
12 | RuleFor(request => request.Description).MinimumLength(5).WithMessage("Description is mandatory and be longer than 5 characters");
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/DemoGrpc.Web/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Grpc": "Information",
7 | "Microsoft": "Information"
8 | }
9 | },
10 | "AllowedHosts": "*"
11 | }
12 |
--------------------------------------------------------------------------------
/DemoGrpc.Web/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning",
5 | "Microsoft.Hosting.Lifetime": "Information",
6 | "Grpc": "Debug"
7 | }
8 | },
9 | "AllowedHosts": "*",
10 | "Kestrel": {
11 | "EndpointDefaults": {
12 | "Protocols": "Http2"
13 | }
14 | },
15 | "KeyVault": {
16 | "Vault": "DemoKeyVaultWebAPI",
17 | "ClientId": "257b6c36-1168-4aac-be93-6f2cd81cec43"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DemoGrpc.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29123.88
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoGrpc.Web", "DemoGrpc.Web\DemoGrpc.Web.csproj", "{02654D12-3379-42C6-B351-2A2082981CA9}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleAppGRPC", "ConsoleAppGRPC\ConsoleAppGRPC.csproj", "{C213B487-31AD-4C80-AFFD-986BEC04014E}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DempGrpc.Services", "DempGrpc.Services\DempGrpc.Services.csproj", "{CA62DD90-D735-4408-A5E6-6B230E5BD98F}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoGrpc.Repository", "DemoGrpc.Repository\DemoGrpc.Repository.csproj", "{3D61B027-2CDB-4439-8A39-2B34B3539BBB}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoGrpc.Domain", "DemoGrpc.Domain\DemoGrpc.Domain.csproj", "{C444F0F2-443D-4530-B7E5-C024C86D75F4}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Database", "Database", "{25119464-767D-459B-A3A8-A74F2CD72F81}"
17 | ProjectSection(SolutionItems) = preProject
18 | Database\Create dbo.Country.sql = Database\Create dbo.Country.sql
19 | Database\dbo.Country.data.sql = Database\dbo.Country.data.sql
20 | EndProjectSection
21 | EndProject
22 | Global
23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
24 | Debug|Any CPU = Debug|Any CPU
25 | Debug|iPhone = Debug|iPhone
26 | Debug|iPhoneSimulator = Debug|iPhoneSimulator
27 | Release|Any CPU = Release|Any CPU
28 | Release|iPhone = Release|iPhone
29 | Release|iPhoneSimulator = Release|iPhoneSimulator
30 | EndGlobalSection
31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
32 | {02654D12-3379-42C6-B351-2A2082981CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {02654D12-3379-42C6-B351-2A2082981CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {02654D12-3379-42C6-B351-2A2082981CA9}.Debug|iPhone.ActiveCfg = Debug|Any CPU
35 | {02654D12-3379-42C6-B351-2A2082981CA9}.Debug|iPhone.Build.0 = Debug|Any CPU
36 | {02654D12-3379-42C6-B351-2A2082981CA9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
37 | {02654D12-3379-42C6-B351-2A2082981CA9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
38 | {02654D12-3379-42C6-B351-2A2082981CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {02654D12-3379-42C6-B351-2A2082981CA9}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {02654D12-3379-42C6-B351-2A2082981CA9}.Release|iPhone.ActiveCfg = Release|Any CPU
41 | {02654D12-3379-42C6-B351-2A2082981CA9}.Release|iPhone.Build.0 = Release|Any CPU
42 | {02654D12-3379-42C6-B351-2A2082981CA9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
43 | {02654D12-3379-42C6-B351-2A2082981CA9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
44 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
47 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Debug|iPhone.Build.0 = Debug|Any CPU
48 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
49 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
50 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Release|iPhone.ActiveCfg = Release|Any CPU
53 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Release|iPhone.Build.0 = Release|Any CPU
54 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
55 | {C213B487-31AD-4C80-AFFD-986BEC04014E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
56 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Debug|iPhone.ActiveCfg = Debug|Any CPU
59 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Debug|iPhone.Build.0 = Debug|Any CPU
60 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
61 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
62 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Release|iPhone.ActiveCfg = Release|Any CPU
65 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Release|iPhone.Build.0 = Release|Any CPU
66 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
67 | {CA62DD90-D735-4408-A5E6-6B230E5BD98F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
68 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Debug|iPhone.ActiveCfg = Debug|Any CPU
71 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Debug|iPhone.Build.0 = Debug|Any CPU
72 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
73 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
74 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
75 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Release|Any CPU.Build.0 = Release|Any CPU
76 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Release|iPhone.ActiveCfg = Release|Any CPU
77 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Release|iPhone.Build.0 = Release|Any CPU
78 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
79 | {3D61B027-2CDB-4439-8A39-2B34B3539BBB}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
80 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
82 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Debug|iPhone.ActiveCfg = Debug|Any CPU
83 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Debug|iPhone.Build.0 = Debug|Any CPU
84 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
85 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
86 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
87 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Release|Any CPU.Build.0 = Release|Any CPU
88 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Release|iPhone.ActiveCfg = Release|Any CPU
89 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Release|iPhone.Build.0 = Release|Any CPU
90 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
91 | {C444F0F2-443D-4530-B7E5-C024C86D75F4}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
92 | EndGlobalSection
93 | GlobalSection(SolutionProperties) = preSolution
94 | HideSolutionNode = FALSE
95 | EndGlobalSection
96 | GlobalSection(ExtensibilityGlobals) = postSolution
97 | SolutionGuid = {0EAE5BA4-A4A4-4EC7-B4DA-3D4FE1F05B5D}
98 | EndGlobalSection
99 | EndGlobal
100 |
--------------------------------------------------------------------------------
/DempGrpc.Services/CountryService.cs:
--------------------------------------------------------------------------------
1 | using DemoGrpc.Domain.Entities;
2 | using DemoGrpc.Repository.Interfaces;
3 | using DempGrpc.Services.Interfaces;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Threading.Tasks;
7 |
8 | namespace DempGrpc.Services
9 | {
10 | public class CountryService : ICountryService
11 | {
12 | public ICountryRepository _countryRepository;
13 |
14 | public CountryService(ICountryRepository countryRepository)
15 | {
16 | _countryRepository = countryRepository;
17 | }
18 |
19 | public Task> GetAsync()
20 | {
21 | //throw new Exception("error in the service");
22 | return _countryRepository.GetAsync();
23 | }
24 |
25 | public Task GetByIdAsync(int countryId)
26 | {
27 | return _countryRepository.GetByIdAsync(countryId);
28 | }
29 |
30 | public async Task AddAsync(Country country)
31 | {
32 | return await _countryRepository.AddAsync(country);
33 | }
34 |
35 | public async Task UpdateAsync(Country country)
36 | {
37 | var result = await _countryRepository.UpdateAsync(country);
38 | if (result > 0)
39 | return country;
40 |
41 | throw new Exception("Update failed");
42 | }
43 |
44 | public async Task DeleteAsync(int countryId)
45 | {
46 | var result = await _countryRepository.DeleteAsync(countryId);
47 | if (result > 0)
48 | return true;
49 |
50 | throw new Exception("Delete failed");
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/DempGrpc.Services/DempGrpc.Services.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/DempGrpc.Services/Interfaces/ICountryService.cs:
--------------------------------------------------------------------------------
1 | using DemoGrpc.Domain.Entities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace DempGrpc.Services.Interfaces
8 | {
9 | public interface ICountryService
10 | {
11 | Task> GetAsync();
12 | Task GetByIdAsync(int countryId);
13 | Task AddAsync(Country country);
14 | Task UpdateAsync(Country country);
15 | Task DeleteAsync(int countryId);
16 | }
17 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Samples of gRPC services in ASP.NET Core 5.0 (work in progress)
2 |
3 | ## Prerequistes
4 |
5 | Create a database, don't forget to adjust the name of it in your connectionstring and in the sql files
6 |
7 | Create table with Sql file: https://github.com/AnthonyGiretti/aspnetcore3-grpc-samples/blob/master/Database/Create%20dbo.Country.sql
8 |
9 | Feed the table with Sql file: https://github.com/AnthonyGiretti/aspnetcore3-grpc-samples/blob/master/Database/dbo.Country.data.sql
10 |
11 | If you don't setup a database, the sample runs by default by In Memory database
12 |
13 | ## Samples
14 | Sample of Layered architecture (Ntier)
15 |
16 | Sample of Repository pattern with EF Core 3
17 |
18 | Sample of gRPC CRUD operation
19 |
20 | Sample of gRPC request Validation with [https://github.com/AnthonyGiretti/grpc-aspnetcore-validator]
21 |
22 | Sample of mapping with AutoMapper
23 |
24 | Sample of gRPC request interceptions
25 |
26 | Sample of token validation and get authenticated user from ServerCallContext
27 |
28 | Sample of KeyVault configuration and usage
29 |
30 | Sample of .NET Core 5.0 client CRUD consumption
31 |
32 | Sample of .NET Core 5.0 client using Polly resiliency
33 |
34 | Sample of integration tests (work in progess)
35 |
36 | Sample of healthcheck (work in progess)
37 |
--------------------------------------------------------------------------------