diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/BSN.Commons.sln b/BSN.Commons.sln index 85418b3..dba6192 100644 --- a/BSN.Commons.sln +++ b/BSN.Commons.sln @@ -43,6 +43,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BSN.Commons.Orm.EntityFrame EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.Users", "Source\BSN.Commons.Users\BSN.Commons.Users.csproj", "{213ABCEF-7E9A-4CE5-A3EF-289C9781344D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GrpcIntegrationTest", "GrpcIntegrationTest", "{54C74546-38B4-4618-AA40-334463F49C54}" + ProjectSection(SolutionItems) = preProject + Test\GrpcIntegrationTest\README.md = Test\GrpcIntegrationTest\README.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.GrpcIntegrationTest.Sample.Service", "Test\GrpcIntegrationTest\BSN.Commons.GrpcIntegrationTest.Sample.Service\BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj", "{08C7C14B-D2E9-4FA0-BD0C-7DDA9582DC6D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract", "Test\GrpcIntegrationTest\BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract\BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract.csproj", "{283F2163-BB83-4770-AA1A-F52FEFE8E1CF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.GrpcIntegrationTest.Sample.Console", "Test\GrpcIntegrationTest\BSN.Commons.GrpcIntegrationTest.Sample.Console\BSN.Commons.GrpcIntegrationTest.Sample.Console.csproj", "{7799FBC7-13EB-4382-83D7-FE9396039C77}" + ProjectSection(ProjectDependencies) = postProject + {08C7C14B-D2E9-4FA0-BD0C-7DDA9582DC6D} = {08C7C14B-D2E9-4FA0-BD0C-7DDA9582DC6D} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.TestHelpers", "Source\BSN.Commons.TestHelpers\BSN.Commons.TestHelpers.csproj", "{1ACB3F86-D8AA-4CA4-B0BD-B1833E235F14}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSN.Commons.Grpc.IntegratonTest", "Test\GrpcIntegrationTest\BSN.Commons.Grpc.IntegratonTest\BSN.Commons.Grpc.IntegratonTest.csproj", "{C5213190-E6BD-4B45-9C09-0951D3387CAD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -81,6 +99,26 @@ Global {213ABCEF-7E9A-4CE5-A3EF-289C9781344D}.Debug|Any CPU.Build.0 = Debug|Any CPU {213ABCEF-7E9A-4CE5-A3EF-289C9781344D}.Release|Any CPU.ActiveCfg = Release|Any CPU {213ABCEF-7E9A-4CE5-A3EF-289C9781344D}.Release|Any CPU.Build.0 = Release|Any CPU + {08C7C14B-D2E9-4FA0-BD0C-7DDA9582DC6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08C7C14B-D2E9-4FA0-BD0C-7DDA9582DC6D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08C7C14B-D2E9-4FA0-BD0C-7DDA9582DC6D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08C7C14B-D2E9-4FA0-BD0C-7DDA9582DC6D}.Release|Any CPU.Build.0 = Release|Any CPU + {283F2163-BB83-4770-AA1A-F52FEFE8E1CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {283F2163-BB83-4770-AA1A-F52FEFE8E1CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {283F2163-BB83-4770-AA1A-F52FEFE8E1CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {283F2163-BB83-4770-AA1A-F52FEFE8E1CF}.Release|Any CPU.Build.0 = Release|Any CPU + {7799FBC7-13EB-4382-83D7-FE9396039C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7799FBC7-13EB-4382-83D7-FE9396039C77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7799FBC7-13EB-4382-83D7-FE9396039C77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7799FBC7-13EB-4382-83D7-FE9396039C77}.Release|Any CPU.Build.0 = Release|Any CPU + {1ACB3F86-D8AA-4CA4-B0BD-B1833E235F14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ACB3F86-D8AA-4CA4-B0BD-B1833E235F14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ACB3F86-D8AA-4CA4-B0BD-B1833E235F14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ACB3F86-D8AA-4CA4-B0BD-B1833E235F14}.Release|Any CPU.Build.0 = Release|Any CPU + {C5213190-E6BD-4B45-9C09-0951D3387CAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5213190-E6BD-4B45-9C09-0951D3387CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5213190-E6BD-4B45-9C09-0951D3387CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5213190-E6BD-4B45-9C09-0951D3387CAD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -94,6 +132,12 @@ Global {906FEED8-23E0-4EEF-B902-C39325C50480} = {5C6BA7B5-832A-495A-AF5E-C2A74F6A1EF9} {335F645B-C85F-42C2-9185-A216101F60C7} = {DC377ADC-CC9D-4785-81BE-726DBF5F3096} {213ABCEF-7E9A-4CE5-A3EF-289C9781344D} = {DC377ADC-CC9D-4785-81BE-726DBF5F3096} + {54C74546-38B4-4618-AA40-334463F49C54} = {5C6BA7B5-832A-495A-AF5E-C2A74F6A1EF9} + {08C7C14B-D2E9-4FA0-BD0C-7DDA9582DC6D} = {54C74546-38B4-4618-AA40-334463F49C54} + {283F2163-BB83-4770-AA1A-F52FEFE8E1CF} = {54C74546-38B4-4618-AA40-334463F49C54} + {7799FBC7-13EB-4382-83D7-FE9396039C77} = {54C74546-38B4-4618-AA40-334463F49C54} + {1ACB3F86-D8AA-4CA4-B0BD-B1833E235F14} = {DC377ADC-CC9D-4785-81BE-726DBF5F3096} + {C5213190-E6BD-4B45-9C09-0951D3387CAD} = {54C74546-38B4-4618-AA40-334463F49C54} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BCAF76D3-AA3C-4D0F-8D10-34065F8FED09} diff --git a/Source/BSN.Commons.PresentationInfrastructure/BSN.Commons.PresentationInfrastructure.csproj b/Source/BSN.Commons.PresentationInfrastructure/BSN.Commons.PresentationInfrastructure.csproj index dcbf75e..02cd215 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/BSN.Commons.PresentationInfrastructure.csproj +++ b/Source/BSN.Commons.PresentationInfrastructure/BSN.Commons.PresentationInfrastructure.csproj @@ -42,6 +42,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -57,6 +58,10 @@ + + + + True diff --git a/Source/BSN.Commons.PresentationInfrastructure/IResponse.cs b/Source/BSN.Commons.PresentationInfrastructure/IResponse.cs index 4707af3..b1fce44 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/IResponse.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/IResponse.cs @@ -4,11 +4,36 @@ namespace BSN.Commons.PresentationInfrastructure { - public interface IResponse + /// + /// Represents a single response of a command/query service. + /// + public interface IResponse { - IList InvalidItems { get; set; } + /// + /// Distinction between successful and unsuccessful result. + /// bool IsSuccess { get; } + + /// + /// Human-readable message for the End-User. + /// string Message { get; set; } + + /// + /// Corresponding HttpStatusCode. + /// ResponseStatusCode StatusCode { get; set; } } + + /// + /// Represents a single response of a command/query service with additional informations about invalid items. + /// + /// + public interface IResponse : IResponse + { + /// + /// Invalid items of the request object. + /// + IList InvalidItems { get; set; } + } } diff --git a/Source/BSN.Commons.PresentationInfrastructure/ResponseBase.cs b/Source/BSN.Commons.PresentationInfrastructure/ResponseBase.cs index 83f0d4a..6a72dbe 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/ResponseBase.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/ResponseBase.cs @@ -2,13 +2,25 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; using System.Text.Json.Serialization; namespace BSN.Commons.PresentationInfrastructure { [Obsolete("Due to incompatability with Grpc this response type is only used for backward compatibility.")] + [DataContract] public class ResponseBase : IResponse { + [DataMember(Order = 1)] + [JsonConverter(typeof(JsonForceDefaultConverter))] + public ResponseStatusCode StatusCode { get; set; } + + [DataMember(Order = 2)] + public string Message { get; set; } + + [DataMember(Order = 3)] + public IList InvalidItems { get; set; } + /// /// Gets a value that indicates whether the HTTP response was successful. /// @@ -17,12 +29,11 @@ public class ResponseBase : IResponse /// /// More info: https://docs.microsoft.com/en-us/uwp/api/windows.web.http.httpresponsemessage.issuccessstatuscode?view=winrt-19041 public bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; + } - public string Message { get; set; } - - [JsonConverter(typeof(JsonForceDefaultConverter))] - public ResponseStatusCode StatusCode { get; set; } - - public IList InvalidItems { get; set; } + [Obsolete("Due to incompatability with Grpc this response type is only used for backward compatibility.")] + [DataContract] + public class ErrorResponseBase : ResponseBase + { } } diff --git a/Source/BSN.Commons.PresentationInfrastructure/ResponseStatusCode.cs b/Source/BSN.Commons.PresentationInfrastructure/ResponseStatusCode.cs index c72cda4..5f4396a 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/ResponseStatusCode.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/ResponseStatusCode.cs @@ -1,12 +1,15 @@ namespace BSN.Commons.PresentationInfrastructure { + /// + /// Response status codes based on the HTTP status codes. + /// public enum ResponseStatusCode { - // - // Summary: - // Equivalent to HTTP status 200. System.Net.HttpStatusCode.OK indicates that the - // request succeeded and that the requested information is in the response. This - // is the most common status code to receive. + /// + /// Equivalent to HTTP status 200. System.Net.HttpStatusCode.OK indicates that the + /// request succeeded and that the requested information is in the response. This + /// is the most common status code to receive. + /// OK = 200, // // Summary: diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/CollectionViewModel.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/CollectionViewModel.cs index 2bd9b09..eb11115 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/CollectionViewModel.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/CollectionViewModel.cs @@ -1,15 +1,18 @@ using System.Collections.Generic; using System.Runtime.Serialization; -namespace BSN.Commons.Responses +namespace BSN.Commons.Responses { /// /// Collection schema for generating responses.. /// /// Elements type. - [DataContract] + [DataContract] public class CollectionViewModel { + /// + /// Default constructor. + /// public CollectionViewModel() { } /// @@ -17,5 +20,5 @@ public class CollectionViewModel /// [DataMember(Order = 1)] public IEnumerable Items { get; set; } - } -} + } +} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/InvalidItem.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/InvalidItem.cs index 82ee944..1a73dfb 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/InvalidItem.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/InvalidItem.cs @@ -1,10 +1,10 @@ using System.Runtime.Serialization; - -namespace BSN.Commons.Responses -{ + +namespace BSN.Commons.Responses +{ /// /// Represents a request validation issue for the API consumers. - /// + /// [DataContract] public class InvalidItem { @@ -22,4 +22,4 @@ public class InvalidItem [DataMember(Order = 2)] public string Reason { get; set; } } -} +} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginatedResponse.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginatedResponse.cs index 5ffc6f9..bc419eb 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginatedResponse.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginatedResponse.cs @@ -4,54 +4,57 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; -namespace BSN.Commons.Responses +namespace BSN.Commons.Responses { - /// - /// Generic response base for paginated data. - /// - /// - /// Paginated response provides metadata for navigation purposes. - /// - /// Data type. - [DataContract] - public class PaginatedResponse: IResponse where T : class + /// + /// Generic response base for paginated data. + /// + /// + /// Paginated response provides metadata for navigation purposes. + /// + /// Data type. + [DataContract] + public class PaginatedResponse : IResponse where T : class { + /// + /// Default constructor. + /// public PaginatedResponse() { } /// /// Corresponding HttpStatusCode. /// - [DataMember(Order = 1)] - [JsonConverter(typeof(JsonForceDefaultConverter))] - public ResponseStatusCode StatusCode { get; set; } - + [DataMember(Order = 1)] + [JsonConverter(typeof(JsonForceDefaultConverter))] + public ResponseStatusCode StatusCode { get; set; } + /// /// Data payload (Collection). /// - [DataMember(Order = 2)] - public CollectionViewModel Data { get; set; } - + [DataMember(Order = 2)] + public CollectionViewModel Data { get; set; } + /// /// Human-readable message for the End-User. /// - [DataMember(Order = 3)] + [DataMember(Order = 3)] public string Message { get; set; } /// /// Pagination metadata used by the client for data navigation purposes. /// - [DataMember(Order = 4)] - public PaginationMetadata Meta { get; set; } - + [DataMember(Order = 4)] + public PaginationMetadata Meta { get; set; } + /// /// Invalid items of the request object. /// - [DataMember(Order = 5)] - public IList InvalidItems { get; set; } - + [DataMember(Order = 5)] + public IList InvalidItems { get; set; } + /// /// Distinction between successful and unsuccessful result. /// - public bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; - } -} + public bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; + } +} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginationMetadata.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginationMetadata.cs index 836e9a8..984f46b 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginationMetadata.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginationMetadata.cs @@ -1,37 +1,40 @@ using System.Runtime.Serialization; -namespace BSN.Commons.Responses -{ - /// - /// Pagination meta data. - /// - [DataContract] - public class PaginationMetadata - { - public PaginationMetadata() { } - - /// - /// Current page number - /// - [DataMember(Order = 1)] - public uint Page { get; set; } - - /// - /// Total number of pages - /// - [DataMember(Order = 2)] - public uint PageCount { get; set; } - - /// - /// Number of records per page - /// - [DataMember(Order = 3)] - public uint PageSize { get; set; } - - /// - /// Total number of records - /// - [DataMember(Order = 4)] - public uint RecordCount { get; set; } - } -} +namespace BSN.Commons.Responses +{ + /// + /// Pagination meta data. + /// + [DataContract] + public class PaginationMetadata + { + /// + /// Default constructor. + /// + public PaginationMetadata() { } + + /// + /// Current page number + /// + [DataMember(Order = 1)] + public uint Page { get; set; } + + /// + /// Total number of pages + /// + [DataMember(Order = 2)] + public uint PageCount { get; set; } + + /// + /// Number of records per page + /// + [DataMember(Order = 3)] + public uint PageSize { get; set; } + + /// + /// Total number of records + /// + [DataMember(Order = 4)] + public uint RecordCount { get; set; } + } +} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/Response.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/Response.cs index 12364fb..e829216 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/Response.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/Response.cs @@ -1,10 +1,12 @@ -using BSN.Commons.Converters; -using BSN.Commons.PresentationInfrastructure; +using BSN.Commons.Converters; +using BSN.Commons.PresentationInfrastructure; +using BSN.Commons.Utilities; +using ProtoBuf; using System.Collections.Generic; using System.Runtime.Serialization; -using System.Text.Json.Serialization; - -namespace BSN.Commons.Responses +using System.Text.Json.Serialization; + +namespace BSN.Commons.Responses { /// /// Basic response for command services to return the result of an operation. @@ -15,32 +17,29 @@ namespace BSN.Commons.Responses /// namely the 'StatusCode' property of the 'ResponseBase' class which should keep it's default numeral value when being converted. /// [DataContract] + // TODO: [ProtoInclude(100, typeof(ErrorResponse))] + // TODO: [ProtoInclude(101, typeof(Response<>))] public class Response: IResponse { - public Response() { } - /// - /// Corresponding HttpStatusCode. + /// Default constructor. /// + public Response() { } + + /// [DataMember(Order = 1)] [JsonConverter(typeof(JsonForceDefaultConverter))] public ResponseStatusCode StatusCode { get; set; } - /// - /// Human-readable message for the End-User. - /// + /// [DataMember(Order = 2)] public string Message { get; set; } - /// - /// Invalid items of the request object. - /// + /// [DataMember(Order = 3)] public IList InvalidItems { get; set; } - /// - /// Distinction between successful and unsuccessful result. - /// + /// public bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; } @@ -53,36 +52,21 @@ public class Response: IResponse /// namely the 'StatusCode' property of the 'ResponseBase' class which should keep it's default numeral value when being converted. /// [DataContract] - public class Response where T : class + // TODO: [ProtoImplement(typeof(Response))] + public class Response : Response where T : class { - /// - /// Corresponding HttpStatusCode. - /// - [DataMember(Order = 1)] - [JsonConverter(typeof(JsonForceDefaultConverter))] - public ResponseStatusCode StatusCode { get; set; } - /// /// Data payload. /// [DataMember(Order = 2)] public T Data { get; set; } + } - /// - /// Human-readable message for the End-User. - /// - [DataMember(Order = 3)] - public string Message { get; set; } - - /// - /// Invalid items of the request object. - /// - [DataMember(Order = 4)] - public IList InvalidItems { get; set; } - - /// - /// Distinction between successful and unsuccessful result. - /// - public bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; + /// + /// Generic error response type for command/query services to return the error results. + /// + [ProtoImplement(typeof(Response))] + public class ErrorResponse : Response + { } -} +} diff --git a/Source/BSN.Commons.TestHelpers/BSN.Commons.TestHelpers.csproj b/Source/BSN.Commons.TestHelpers/BSN.Commons.TestHelpers.csproj new file mode 100644 index 0000000..ba7efaa --- /dev/null +++ b/Source/BSN.Commons.TestHelpers/BSN.Commons.TestHelpers.csproj @@ -0,0 +1,54 @@ + + + net6.0;net8.0 + enable + disable + 1.13.0 + 1.13.0 + 1.13.0 + BSN Developers + BSN Company + Test Helpers for .NET projects + BSN Co 2023-2023 + MIT + https://github.com/BSVN/Commons + https://github.com/BSVN/Commons.git + git + Please see CHANGELOG.md + True + True + BSN.Commons.TestHelpers + README.md + True + snupkg + BSN.jpg + BSN;Commons;Onion;Enterprise;Infrastructure;DDD;TDD + + true + + true + + + + true + + + + true + + + + + + + + + + + + + + + + + diff --git a/Source/BSN.Commons.TestHelpers/ForwardingLoggerProvider.cs b/Source/BSN.Commons.TestHelpers/ForwardingLoggerProvider.cs new file mode 100644 index 0000000..ee1042d --- /dev/null +++ b/Source/BSN.Commons.TestHelpers/ForwardingLoggerProvider.cs @@ -0,0 +1,70 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using Microsoft.Extensions.Logging; +using System; + +namespace BSN.Commons.TestHelpers +{ + internal class ForwardingLoggerProvider : ILoggerProvider + { + private readonly LogMessage _logAction; + + public ForwardingLoggerProvider(LogMessage logAction) + { + _logAction = logAction; + } + + public ILogger CreateLogger(string categoryName) + { + return new ForwardingLogger(categoryName, _logAction); + } + + public void Dispose() + { + } + + internal class ForwardingLogger : ILogger + { + private readonly string _categoryName; + private readonly LogMessage _logAction; + + public ForwardingLogger(string categoryName, LogMessage logAction) + { + _categoryName = categoryName; + _logAction = logAction; + } + + public IDisposable BeginScope(TState state) + { + return null!; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + _logAction(logLevel, _categoryName, eventId, formatter(state, exception), exception); + } + } + } +} + diff --git a/Source/BSN.Commons.TestHelpers/GrpcTestContext.cs b/Source/BSN.Commons.TestHelpers/GrpcTestContext.cs new file mode 100644 index 0000000..d2e2907 --- /dev/null +++ b/Source/BSN.Commons.TestHelpers/GrpcTestContext.cs @@ -0,0 +1,57 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Logging; + +namespace BSN.Commons.TestHelpers +{ + internal class GrpcTestContext : IDisposable where TStartup : class + { + private readonly Stopwatch _stopwatch; + private readonly GrpcTestFixture _fixture; + private readonly TextWriter _outputHelper; + + public GrpcTestContext(GrpcTestFixture fixture, TextWriter outputHelper) + { + _stopwatch = Stopwatch.StartNew(); + _fixture = fixture; + _outputHelper = outputHelper; + _fixture.LoggedMessage += WriteMessage; + } + + private void WriteMessage(LogLevel logLevel, string category, EventId eventId, string message, Exception? exception) + { + var log = $"{_stopwatch.Elapsed.TotalSeconds:N3}s {category} - {logLevel}: {message}"; + if (exception != null) + { + log += Environment.NewLine + exception.ToString(); + } + _outputHelper.WriteLine(log); + } + + public void Dispose() + { + _fixture.LoggedMessage -= WriteMessage; + } + } +} \ No newline at end of file diff --git a/Source/BSN.Commons.TestHelpers/GrpcTestFixture.cs b/Source/BSN.Commons.TestHelpers/GrpcTestFixture.cs new file mode 100644 index 0000000..958a646 --- /dev/null +++ b/Source/BSN.Commons.TestHelpers/GrpcTestFixture.cs @@ -0,0 +1,80 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text; + +namespace BSN.Commons.TestHelpers +{ + public delegate void LogMessage(LogLevel logLevel, string categoryName, EventId eventId, string message, Exception? exception); + + public class GrpcTestFixture : IDisposable where TStartup : class + { + private TestServer? _server; + private HttpMessageHandler? _handler; + private Action? _configureWebHost; + private readonly WebApplicationFactory _factory; + + public event LogMessage? LoggedMessage; + + public GrpcTestFixture() + { + LoggerFactory = new LoggerFactory(); + LoggerFactory.AddProvider(new ForwardingLoggerProvider((logLevel, category, eventId, message, exception) => + { + LoggedMessage?.Invoke(logLevel, category, eventId, message, exception); + })); + + _factory = new WebApplicationFactory() + .WithWebHostBuilder(builder => + { + builder.ConfigureServices(services => + { + services.AddSingleton(LoggerFactory); + }); + }); + } + + public void ConfigureWebHost(Action configure) + { + _configureWebHost = configure; + } + + private void EnsureServer() + { + if (_server == null) + { + _server = _factory.Server; + _handler = _server.CreateHandler(); + } + } + + public LoggerFactory LoggerFactory { get; } + + public HttpMessageHandler Handler + { + get + { + EnsureServer(); + return _handler!; + } + } + + public void Dispose() + { + _handler?.Dispose(); + _server?.Dispose(); + } + + public IDisposable GetTestContext(TextWriter outputHelper) + { + return new GrpcTestContext(this, outputHelper); + } + } +} diff --git a/Source/BSN.Commons.TestHelpers/IntegrationTestBase.cs b/Source/BSN.Commons.TestHelpers/IntegrationTestBase.cs new file mode 100644 index 0000000..fefed57 --- /dev/null +++ b/Source/BSN.Commons.TestHelpers/IntegrationTestBase.cs @@ -0,0 +1,58 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using Grpc.Net.Client; +using Microsoft.Extensions.Logging; +using System; +using System.IO; + +namespace BSN.Commons.TestHelpers +{ + public class IntegrationTestBase : IDisposable where TStartup : class + { + private GrpcChannel? _channel; + private IDisposable? _testContext; + + protected GrpcTestFixture Fixture { get; set; } + + protected ILoggerFactory LoggerFactory => Fixture.LoggerFactory; + + protected GrpcChannel Channel => _channel ??= CreateChannel(); + + protected GrpcChannel CreateChannel() + { + return GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions + { + LoggerFactory = LoggerFactory, + HttpHandler = Fixture.Handler + }); + } + + public IntegrationTestBase(GrpcTestFixture fixture, TextWriter outputHelper) + { + Fixture = fixture; + _testContext = Fixture.GetTestContext(outputHelper); + } + + public void Dispose() + { + _testContext?.Dispose(); + _channel = null; + } + } +} diff --git a/Source/BSN.Commons/BSN.Commons.csproj b/Source/BSN.Commons/BSN.Commons.csproj index 5bebe0a..9bf3ff2 100644 --- a/Source/BSN.Commons/BSN.Commons.csproj +++ b/Source/BSN.Commons/BSN.Commons.csproj @@ -63,6 +63,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/BSN.Commons/Utilities/ProtoImplementAttribute.cs b/Source/BSN.Commons/Utilities/ProtoImplementAttribute.cs new file mode 100644 index 0000000..9421aff --- /dev/null +++ b/Source/BSN.Commons/Utilities/ProtoImplementAttribute.cs @@ -0,0 +1,99 @@ +using ProtoBuf.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; + +namespace BSN.Commons.Utilities +{ + /// + /// TODO + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class ProtoImplementAttribute : Attribute + { + /// + /// This constructor defines one required parameter + /// + /// Type of base class or interface that you want to implement it + public ProtoImplementAttribute(Type BaseType) + { + this.BaseType = BaseType; + } + + internal Type BaseType { get; } + } + + /// + /// Active polymorphism for protobuf-net code first approach. + /// This class is used to enable polymorphism for protobuf-net code first approach on a specific assembly based on ProtoImplementAttribute. + /// + /// + /// You must call this class in the startup of your application. + /// + public static class GrpcPolymorphismActivator + { + /// + /// Enable polymorphism for protobuf-net code first approach on a specific assembly based on ProtoImplementAttribute. + /// + /// + /// + /// + public static void Enable(Assembly assembly, (Type, Type)[] extraListOfDeriveds) + { + // TODO: Use C# source generator https://stackoverflow.com/q/64926889/1539100 + + // ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(Response), false) + // .Add(1, nameof(Response.StatusCode)) + // .Add(2, nameof(Response.Message)) + // .Add(3, nameof(Response.InvalidItems)) + // .AddSubType(100, typeof(ErrorResponse)) + // .AddSubType(101, typeof(Response)); + + Dictionary> listOfDerivedTypes = new Dictionary>(); + foreach (var type in from type in assembly.GetTypes() + where type.GetCustomAttribute() != null + select type) + { + ProtoImplementAttribute protoImplementAttribute = type.GetCustomAttribute(); + + if (listOfDerivedTypes.TryGetValue(protoImplementAttribute.BaseType, out List derivedTypes)) + derivedTypes.Add(type); + else + listOfDerivedTypes.Add(protoImplementAttribute.BaseType, new List() { type }); + } + + foreach ((Type @base, Type derived) in extraListOfDeriveds) + { + if (listOfDerivedTypes.TryGetValue(@base, out List derivedTypes)) + derivedTypes.Add(derived); + else + listOfDerivedTypes.Add(@base, new List() { derived }); + } + + foreach (var derivedType in listOfDerivedTypes) + { + MetaType @base = ProtoBuf.Meta.RuntimeTypeModel.Default.Add(derivedType.Key, false); + int maxOrder = 0; + foreach (var property in derivedType.Key.GetProperties()) + { + DataMemberAttribute dataMemberAttribute = property.GetCustomAttribute(); + if (property.GetCustomAttribute() == null) + continue; + @base = @base.Add(dataMemberAttribute.Order, dataMemberAttribute.Name ?? property.Name); + maxOrder = Math.Max(maxOrder, dataMemberAttribute.Order); + } + + // Reserve some order for well-known derived types + maxOrder += 100; + + foreach (var type in derivedType.Value) + { + @base.AddSubType(++maxOrder, type); + } + } + } + } +} diff --git a/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/BSN.Commons.Grpc.IntegratonTest.csproj b/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/BSN.Commons.Grpc.IntegratonTest.csproj new file mode 100644 index 0000000..cce3cda --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/BSN.Commons.Grpc.IntegratonTest.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/GlobalUsings.cs b/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/GreeterServiceTests.cs b/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/GreeterServiceTests.cs new file mode 100644 index 0000000..d443fc3 --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.Grpc.IntegratonTest/GreeterServiceTests.cs @@ -0,0 +1,35 @@ +using BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract; +using BSN.Commons.GrpcIntegrationTest.Sample.Service; +using BSN.Commons.GrpcIntegrationTest.Sample.Service.Services; +using BSN.Commons.TestHelpers; +using ProtoBuf.Grpc.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BSN.Commons.Grpc.IntegratonTest +{ + [TestFixture] + internal class GreeterServiceTests : IntegrationTestBase + { + public GreeterServiceTests() : base(new GrpcTestFixture(), TestContext.Out) + { + } + + [Test] + public async Task SayHelloTest() + { + // Arrenge + var client = Channel.CreateGrpcService(); + + // Act + var reply = client.SayHello(new HelloRequest { Name = "GreeterClient" }); + + // Assert + Assert.That(reply, Is.Not.Null); + Assert.That(reply.Message, Is.EqualTo("Hello GreeterClient")); + } + } +} diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract.csproj b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract.csproj new file mode 100644 index 0000000..a860960 --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.1 + enable + + + + + + + + + + + diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract/IGreeterService.cs b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract/IGreeterService.cs new file mode 100644 index 0000000..5b7f3fc --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract/IGreeterService.cs @@ -0,0 +1,32 @@ +using BSN.Commons.PresentationInfrastructure; +using BSN.Commons.Responses; +using ProtoBuf.Grpc; +using System; +using System.Runtime.Serialization; +using System.ServiceModel; +using System.Threading.Tasks; + +namespace BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract +{ + [DataContract] + public class SayHelloViewModel + { + [DataMember(Order = 1)] + public string Message { get; set; } + } + + [DataContract] + public class HelloRequest + { + [DataMember(Order = 1)] + public string Name { get; set; } + } + + [ServiceContract] + public interface IGreeterService + { + [OperationContract] + Response SayHello(HelloRequest request, + CallContext context = default); + } +} diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Console/BSN.Commons.GrpcIntegrationTest.Sample.Console.csproj b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Console/BSN.Commons.GrpcIntegrationTest.Sample.Console.csproj new file mode 100644 index 0000000..5d0f2b4 --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Console/BSN.Commons.GrpcIntegrationTest.Sample.Console.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Console/Program.cs b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Console/Program.cs new file mode 100644 index 0000000..fc625be --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Console/Program.cs @@ -0,0 +1,39 @@ +using BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract; +using Grpc.Net.Client; +using ProtoBuf.Grpc.Client; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace BSN.Commons.GrpcIntegrationTest.Sample.Console +{ + internal class Program + { + internal static async Task Main(string[] args) + { + + ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate; + + using var channel = GrpcChannel.ForAddress("http://localhost:5279"); + var client = channel.CreateGrpcService(); + + var reply = client.SayHello( + new HelloRequest { Name = "GreeterClient" }); + + System.Console.WriteLine($"Greeting: {reply.Message}"); + System.Console.WriteLine("Press any key to exit..."); + System.Console.ReadKey(); + } + + private static bool ValidateServerCertificate( + object sender, + X509Certificate? certificate, + X509Chain? chain, + SslPolicyErrors sslPolicyErrors) + { + ArgumentNullException.ThrowIfNull(sender); + + return true; // Always accept + } + } +} diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj new file mode 100644 index 0000000..2f951ff --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + dc5d175f-9c1c-40b2-89c0-f238d5251c25 + Linux + ..\..\.. + + + + + + + + + + + + + + diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj.user b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj.user new file mode 100644 index 0000000..983ecfc --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj.user @@ -0,0 +1,9 @@ + + + + http + + + ProjectDebugger + + \ No newline at end of file diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Dockerfile b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Dockerfile new file mode 100644 index 0000000..b400605 --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Dockerfile @@ -0,0 +1,25 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj", "Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/"] +RUN dotnet restore "./Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/./BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj" +COPY . . +WORKDIR "/src/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service" +RUN dotnet build "./BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./BSN.Commons.GrpcIntegrationTest.Sample.Service.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "BSN.Commons.GrpcIntegrationTest.Sample.Service.dll"] \ No newline at end of file diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Program.cs b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Program.cs new file mode 100644 index 0000000..5e22d53 --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Program.cs @@ -0,0 +1,22 @@ +using BSN.Commons.GrpcIntegrationTest.Sample.Service.Services; +using ProtoBuf.Grpc.Server; + +namespace BSN.Commons.GrpcIntegrationTest.Sample.Service +{ + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + var startup = new Startup(); + startup.ConfigureServices(builder.Services); + + var app = builder.Build(); + + startup.Configure(app, builder.Environment); + + app.Run(); + } + } +} \ No newline at end of file diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Properties/launchSettings.json b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Properties/launchSettings.json new file mode 100644 index 0000000..2a6fa7b --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5279" + }, + "https": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7056;http://localhost:5279" + }, + "Docker": { + "commandName": "Docker", + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "environmentVariables": { + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json" +} \ No newline at end of file diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Services/GreeterService.cs b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Services/GreeterService.cs new file mode 100644 index 0000000..6d87843 --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Services/GreeterService.cs @@ -0,0 +1,26 @@ +using BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract; +using BSN.Commons.PresentationInfrastructure; +using BSN.Commons.Responses; +using ProtoBuf.Grpc; + +namespace BSN.Commons.GrpcIntegrationTest.Sample.Service.Services +{ + public class GreeterService : IGreeterService + { + public Response SayHello(HelloRequest request, CallContext context = default) + { + if (string.IsNullOrEmpty(request.Name)) + return new ErrorResponse() + { + InvalidItems = new List { new InvalidItem() { Name = nameof(request.Name), Reason = "Name is required" } }, + Message = "Invalid request", + StatusCode = ResponseStatusCode.BadRequest + }; + + return new Response() + { + Message = $"Hello {request.Name}" + }; + } + } +} diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Startup.cs b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Startup.cs new file mode 100644 index 0000000..66be4aa --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/Startup.cs @@ -0,0 +1,47 @@ +using BSN.Commons.GrpcIntegrationTest.Sample.AppService.Contract; +using BSN.Commons.GrpcIntegrationTest.Sample.Service.Services; +using BSN.Commons.PresentationInfrastructure; +using BSN.Commons.Responses; +using BSN.Commons.Utilities; +using ProtoBuf.Grpc.Server; + +namespace BSN.Commons.GrpcIntegrationTest.Sample.Service +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + // Add services to the container. + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + + services.AddCodeFirstGrpc(); + } + + public void Configure(TApp app, IWebHostEnvironment env) where TApp : IApplicationBuilder + { + // Configure the HTTP request pipeline. + app.UseRouting(); + + + app.UseEndpoints(endpoints => + { + GrpcPolymorphismActivator.Enable(typeof(Startup).Assembly, new (Type, Type)[] + { + (typeof(Response), typeof(ErrorResponse)), + (typeof(Response), typeof(Response)) + }); + //GrpcPolymorphismActivator.Enable(typeof(Response).Assembly); + //ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(Response), false) + // .Add(1, nameof(Response.StatusCode)) + // .Add(2, nameof(Response.Message)) + // .Add(3, nameof(Response.InvalidItems)) + // .AddSubType(100, typeof(ErrorResponse)) + // .AddSubType(101, typeof(Response)); + + endpoints.MapGrpcService(); + endpoints.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. " + + "To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + }); + } + } +} diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/appsettings.Development.json b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/appsettings.json b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/appsettings.json new file mode 100644 index 0000000..0ef54df --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2", + "Url": "https://localhost:5279" + } + } +} diff --git a/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/libman.json b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/libman.json new file mode 100644 index 0000000..ceee271 --- /dev/null +++ b/Test/GrpcIntegrationTest/BSN.Commons.GrpcIntegrationTest.Sample.Service/libman.json @@ -0,0 +1,5 @@ +{ + "version": "1.0", + "defaultProvider": "cdnjs", + "libraries": [] +} \ No newline at end of file diff --git a/Test/GrpcIntegrationTest/README.md b/Test/GrpcIntegrationTest/README.md new file mode 100644 index 0000000..b926d8b --- /dev/null +++ b/Test/GrpcIntegrationTest/README.md @@ -0,0 +1,6 @@ +# ITNOA + +This is a simple example of using the gRPC based on the [ASP.NET Core gRPC code first](https://learn.microsoft.com/en-us/aspnet/core/grpc/code-first?view=aspnetcore-8.0). + +## TODO +* [] Add [Aspire project](https://learn.microsoft.com/en-us/dotnet/aspire/get-started/build-your-first-aspire-app?tabs=visual-studio), when Visual Studio 2022 17.10 is released. \ No newline at end of file