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 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 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 Response() { }
/// 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