diff --git a/Svrnty.CQRS.Grpc.Generators/ProtoFileGenerator.cs b/Svrnty.CQRS.Grpc.Generators/ProtoFileGenerator.cs
new file mode 100644
index 0000000..7eac118
--- /dev/null
+++ b/Svrnty.CQRS.Grpc.Generators/ProtoFileGenerator.cs
@@ -0,0 +1,338 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Svrnty.CQRS.Grpc.Generators;
+
+///
+/// Generates Protocol Buffer (.proto) files from C# Command and Query types
+///
+internal class ProtoFileGenerator
+{
+ private readonly Compilation _compilation;
+ private readonly HashSet _requiredImports = new HashSet();
+ private readonly HashSet _generatedMessages = new HashSet();
+ private readonly StringBuilder _messagesBuilder = new StringBuilder();
+
+ public ProtoFileGenerator(Compilation compilation)
+ {
+ _compilation = compilation;
+ }
+
+ public string Generate(string packageName, string csharpNamespace)
+ {
+ var commands = DiscoverCommands();
+ var queries = DiscoverQueries();
+
+ var sb = new StringBuilder();
+
+ // Header
+ sb.AppendLine("syntax = \"proto3\";");
+ sb.AppendLine();
+ sb.AppendLine($"option csharp_namespace = \"{csharpNamespace}\";");
+ sb.AppendLine();
+ sb.AppendLine($"package {packageName};");
+ sb.AppendLine();
+
+ // Imports (will be added later if needed)
+ var importsPlaceholder = sb.Length;
+
+ // Command Service
+ if (commands.Any())
+ {
+ sb.AppendLine("// Command service for CQRS operations");
+ sb.AppendLine("service CommandService {");
+ foreach (var command in commands)
+ {
+ var methodName = command.Name.Replace("Command", "");
+ var requestType = $"{command.Name}Request";
+ var responseType = $"{command.Name}Response";
+
+ sb.AppendLine($" // {GetXmlDocSummary(command)}");
+ sb.AppendLine($" rpc {methodName} ({requestType}) returns ({responseType});");
+ sb.AppendLine();
+ }
+ sb.AppendLine("}");
+ sb.AppendLine();
+ }
+
+ // Query Service
+ if (queries.Any())
+ {
+ sb.AppendLine("// Query service for CQRS operations");
+ sb.AppendLine("service QueryService {");
+ foreach (var query in queries)
+ {
+ var methodName = query.Name.Replace("Query", "");
+ var requestType = $"{query.Name}Request";
+ var responseType = $"{query.Name}Response";
+
+ sb.AppendLine($" // {GetXmlDocSummary(query)}");
+ sb.AppendLine($" rpc {methodName} ({requestType}) returns ({responseType});");
+ sb.AppendLine();
+ }
+ sb.AppendLine("}");
+ sb.AppendLine();
+ }
+
+ // Generate messages for commands
+ foreach (var command in commands)
+ {
+ GenerateRequestMessage(command);
+ GenerateResponseMessage(command);
+ }
+
+ // Generate messages for queries
+ foreach (var query in queries)
+ {
+ GenerateRequestMessage(query);
+ GenerateResponseMessage(query);
+ }
+
+ // Append all generated messages
+ sb.Append(_messagesBuilder);
+
+ // Insert imports if any were needed
+ if (_requiredImports.Any())
+ {
+ var imports = new StringBuilder();
+ foreach (var import in _requiredImports.OrderBy(i => i))
+ {
+ imports.AppendLine($"import \"{import}\";");
+ }
+ imports.AppendLine();
+ sb.Insert(importsPlaceholder, imports.ToString());
+ }
+
+ return sb.ToString();
+ }
+
+ private List DiscoverCommands()
+ {
+ return _compilation.GetSymbolsWithName(
+ name => name.EndsWith("Command"),
+ SymbolFilter.Type)
+ .OfType()
+ .Where(t => !HasGrpcIgnoreAttribute(t))
+ .Where(t => t.TypeKind == TypeKind.Class || t.TypeKind == TypeKind.Struct)
+ .ToList();
+ }
+
+ private List DiscoverQueries()
+ {
+ return _compilation.GetSymbolsWithName(
+ name => name.EndsWith("Query"),
+ SymbolFilter.Type)
+ .OfType()
+ .Where(t => !HasGrpcIgnoreAttribute(t))
+ .Where(t => t.TypeKind == TypeKind.Class || t.TypeKind == TypeKind.Struct)
+ .ToList();
+ }
+
+ private bool HasGrpcIgnoreAttribute(INamedTypeSymbol type)
+ {
+ return type.GetAttributes().Any(attr =>
+ attr.AttributeClass?.Name == "GrpcIgnoreAttribute");
+ }
+
+ private void GenerateRequestMessage(INamedTypeSymbol type)
+ {
+ var messageName = $"{type.Name}Request";
+ if (_generatedMessages.Contains(messageName))
+ return;
+
+ _generatedMessages.Add(messageName);
+
+ _messagesBuilder.AppendLine($"// Request message for {type.Name}");
+ _messagesBuilder.AppendLine($"message {messageName} {{");
+
+ var properties = type.GetMembers()
+ .OfType()
+ .Where(p => p.DeclaredAccessibility == Accessibility.Public)
+ .ToList();
+
+ int fieldNumber = 1;
+ foreach (var prop in properties)
+ {
+ if (ProtoFileTypeMapper.IsUnsupportedType(prop.Type))
+ {
+ // Skip unsupported types and add a comment
+ _messagesBuilder.AppendLine($" // Skipped: {prop.Name} - unsupported type {prop.Type.Name}");
+ continue;
+ }
+
+ var protoType = ProtoFileTypeMapper.MapType(prop.Type, out var needsImport, out var importPath);
+ if (needsImport && importPath != null)
+ {
+ _requiredImports.Add(importPath);
+ }
+
+ var fieldName = ProtoFileTypeMapper.ToSnakeCase(prop.Name);
+ _messagesBuilder.AppendLine($" {protoType} {fieldName} = {fieldNumber};");
+
+ // If this is a complex type, generate its message too
+ if (IsComplexType(prop.Type))
+ {
+ GenerateComplexTypeMessage(prop.Type as INamedTypeSymbol);
+ }
+
+ fieldNumber++;
+ }
+
+ _messagesBuilder.AppendLine("}");
+ _messagesBuilder.AppendLine();
+ }
+
+ private void GenerateResponseMessage(INamedTypeSymbol type)
+ {
+ var messageName = $"{type.Name}Response";
+ if (_generatedMessages.Contains(messageName))
+ return;
+
+ _generatedMessages.Add(messageName);
+
+ _messagesBuilder.AppendLine($"// Response message for {type.Name}");
+ _messagesBuilder.AppendLine($"message {messageName} {{");
+
+ // Determine the result type from ICommandHandler or IQueryHandler
+ var resultType = GetResultType(type);
+
+ if (resultType != null)
+ {
+ var protoType = ProtoFileTypeMapper.MapType(resultType, out var needsImport, out var importPath);
+ if (needsImport && importPath != null)
+ {
+ _requiredImports.Add(importPath);
+ }
+
+ _messagesBuilder.AppendLine($" {protoType} result = 1;");
+ }
+ // If no result type, leave message empty (void return)
+
+ _messagesBuilder.AppendLine("}");
+ _messagesBuilder.AppendLine();
+
+ // Generate complex type message after closing the response message
+ if (resultType != null && IsComplexType(resultType))
+ {
+ GenerateComplexTypeMessage(resultType as INamedTypeSymbol);
+ }
+ }
+
+ private void GenerateComplexTypeMessage(INamedTypeSymbol? type)
+ {
+ if (type == null || _generatedMessages.Contains(type.Name))
+ return;
+
+ // Don't generate messages for system types or primitives
+ if (type.ContainingNamespace?.ToString().StartsWith("System") == true)
+ return;
+
+ _generatedMessages.Add(type.Name);
+
+ _messagesBuilder.AppendLine($"// {type.Name} entity");
+ _messagesBuilder.AppendLine($"message {type.Name} {{");
+
+ var properties = type.GetMembers()
+ .OfType()
+ .Where(p => p.DeclaredAccessibility == Accessibility.Public)
+ .ToList();
+
+ int fieldNumber = 1;
+ foreach (var prop in properties)
+ {
+ if (ProtoFileTypeMapper.IsUnsupportedType(prop.Type))
+ {
+ _messagesBuilder.AppendLine($" // Skipped: {prop.Name} - unsupported type {prop.Type.Name}");
+ continue;
+ }
+
+ var protoType = ProtoFileTypeMapper.MapType(prop.Type, out var needsImport, out var importPath);
+ if (needsImport && importPath != null)
+ {
+ _requiredImports.Add(importPath);
+ }
+
+ var fieldName = ProtoFileTypeMapper.ToSnakeCase(prop.Name);
+ _messagesBuilder.AppendLine($" {protoType} {fieldName} = {fieldNumber};");
+
+ // Recursively generate nested complex types
+ if (IsComplexType(prop.Type))
+ {
+ GenerateComplexTypeMessage(prop.Type as INamedTypeSymbol);
+ }
+
+ fieldNumber++;
+ }
+
+ _messagesBuilder.AppendLine("}");
+ _messagesBuilder.AppendLine();
+ }
+
+ private ITypeSymbol? GetResultType(INamedTypeSymbol commandOrQueryType)
+ {
+ // Scan for handler classes that implement ICommandHandler or IQueryHandler
+ var handlerInterfaceName = commandOrQueryType.Name.EndsWith("Command")
+ ? "ICommandHandler"
+ : "IQueryHandler";
+
+ // Find all types in the compilation
+ var allTypes = _compilation.GetSymbolsWithName(_ => true, SymbolFilter.Type)
+ .OfType();
+
+ foreach (var type in allTypes)
+ {
+ // Check if this type implements the handler interface
+ foreach (var @interface in type.AllInterfaces)
+ {
+ if (@interface.Name == handlerInterfaceName && @interface.TypeArguments.Length >= 1)
+ {
+ // Check if the first type argument matches our command/query
+ var firstArg = @interface.TypeArguments[0];
+ if (SymbolEqualityComparer.Default.Equals(firstArg, commandOrQueryType))
+ {
+ // Found the handler! Return the result type (second type argument) if it exists
+ if (@interface.TypeArguments.Length == 2)
+ {
+ return @interface.TypeArguments[1];
+ }
+ // If only one type argument, it's a void command (ICommandHandler)
+ return null;
+ }
+ }
+ }
+ }
+
+ return null; // No handler found
+ }
+
+ private bool IsComplexType(ITypeSymbol type)
+ {
+ // Check if it's a user-defined class/struct (not a primitive or system type)
+ if (type.TypeKind != TypeKind.Class && type.TypeKind != TypeKind.Struct)
+ return false;
+
+ var fullName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ return !fullName.Contains("System.");
+ }
+
+ private string GetXmlDocSummary(INamedTypeSymbol type)
+ {
+ var xml = type.GetDocumentationCommentXml();
+ if (string.IsNullOrEmpty(xml))
+ return $"{type.Name} operation";
+
+ // Simple extraction - could be enhanced
+ // xml is guaranteed non-null after IsNullOrEmpty check above
+ var summaryStart = xml!.IndexOf("");
+ var summaryEnd = xml.IndexOf("");
+ if (summaryStart >= 0 && summaryEnd > summaryStart)
+ {
+ var summary = xml.Substring(summaryStart + 9, summaryEnd - summaryStart - 9).Trim();
+ return summary;
+ }
+
+ return $"{type.Name} operation";
+ }
+}
diff --git a/Svrnty.CQRS.Grpc.Generators/ProtoFileSourceGenerator.cs b/Svrnty.CQRS.Grpc.Generators/ProtoFileSourceGenerator.cs
new file mode 100644
index 0000000..272fadf
--- /dev/null
+++ b/Svrnty.CQRS.Grpc.Generators/ProtoFileSourceGenerator.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Svrnty.CQRS.Grpc.Generators;
+
+///
+/// Incremental source generator that generates .proto files from C# commands and queries
+///
+[Generator]
+public class ProtoFileSourceGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Register a post-initialization output to generate the proto file
+ context.RegisterPostInitializationOutput(ctx =>
+ {
+ // Generate a placeholder - the actual proto will be generated in the source output
+ });
+
+ // Collect all command and query types
+ var commandsAndQueries = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ predicate: static (s, _) => IsCommandOrQuery(s),
+ transform: static (ctx, _) => GetTypeSymbol(ctx))
+ .Where(static m => m is not null)
+ .Collect();
+
+ // Combine with compilation to have access to it
+ var compilationAndTypes = context.CompilationProvider.Combine(commandsAndQueries);
+
+ // Generate proto file when commands/queries change
+ context.RegisterSourceOutput(compilationAndTypes, (spc, source) =>
+ {
+ var (compilation, types) = source;
+
+ if (types.IsDefaultOrEmpty)
+ return;
+
+ try
+ {
+ // Get build properties for configuration
+ var packageName = GetBuildProperty(spc, "RootNamespace") ?? "cqrs";
+ var csharpNamespace = GetBuildProperty(spc, "RootNamespace") ?? "Generated.Grpc";
+
+ // Generate the proto file content
+ var generator = new ProtoFileGenerator(compilation);
+ var protoContent = generator.Generate(packageName, csharpNamespace);
+
+ // Output as an embedded resource that can be extracted
+ var protoFileName = "cqrs_services.proto";
+
+ // Generate a C# class that contains the proto content
+ // This allows build tools to extract it if needed
+ var csContent = $$"""
+ //
+ #nullable enable
+
+ namespace Svrnty.CQRS.Grpc.Generated
+ {
+ ///
+ /// Contains the auto-generated Protocol Buffer definition
+ ///
+ internal static class GeneratedProtoFile
+ {
+ public const string FileName = "{{protoFileName}}";
+
+ public const string Content = @"{{protoContent.Replace("\"", "\"\"")}}";
+ }
+ }
+ """;
+
+ spc.AddSource("GeneratedProtoFile.g.cs", csContent);
+
+ // Report that we generated the proto content
+ var descriptor = new DiagnosticDescriptor(
+ "CQRSGRPC002",
+ "Proto file generated",
+ "Generated proto file content in GeneratedProtoFile class",
+ "Svrnty.CQRS.Grpc",
+ DiagnosticSeverity.Info,
+ isEnabledByDefault: true);
+
+ spc.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None));
+ }
+ catch (Exception ex)
+ {
+ // Report diagnostic if generation fails
+ var descriptor = new DiagnosticDescriptor(
+ "CQRSGRPC001",
+ "Proto file generation failed",
+ "Failed to generate proto file: {0}",
+ "Svrnty.CQRS.Grpc",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ spc.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None, ex.Message));
+ }
+ });
+ }
+
+ private static bool IsCommandOrQuery(SyntaxNode node)
+ {
+ if (node is not TypeDeclarationSyntax typeDecl)
+ return false;
+
+ var name = typeDecl.Identifier.Text;
+ return name.EndsWith("Command") || name.EndsWith("Query");
+ }
+
+ private static INamedTypeSymbol? GetTypeSymbol(GeneratorSyntaxContext context)
+ {
+ var typeDecl = (TypeDeclarationSyntax)context.Node;
+ var symbol = context.SemanticModel.GetDeclaredSymbol(typeDecl) as INamedTypeSymbol;
+
+ // Skip if it has GrpcIgnore attribute
+ if (symbol?.GetAttributes().Any(a => a.AttributeClass?.Name == "GrpcIgnoreAttribute") == true)
+ return null;
+
+ return symbol;
+ }
+
+ private static string? GetBuildProperty(SourceProductionContext context, string propertyName)
+ {
+ // Try to get build properties from the compilation options
+ // This is a simplified approach - in practice, you might need analyzer config
+ return null; // Will use defaults
+ }
+}
diff --git a/Svrnty.CQRS.Grpc.Generators/ProtoTypeMapper.cs b/Svrnty.CQRS.Grpc.Generators/ProtoTypeMapper.cs
new file mode 100644
index 0000000..38dbcb1
--- /dev/null
+++ b/Svrnty.CQRS.Grpc.Generators/ProtoTypeMapper.cs
@@ -0,0 +1,191 @@
+using System;
+using Microsoft.CodeAnalysis;
+
+namespace Svrnty.CQRS.Grpc.Generators;
+
+///
+/// Maps C# types to Protocol Buffer types for proto file generation
+///
+internal static class ProtoFileTypeMapper
+{
+ public static string MapType(ITypeSymbol typeSymbol, out bool needsImport, out string? importPath)
+ {
+ needsImport = false;
+ importPath = null;
+
+ // Handle special name (fully qualified name)
+ var fullTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ var typeName = typeSymbol.Name;
+
+ // Nullable types - unwrap
+ if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated && typeSymbol is INamedTypeSymbol namedType && namedType.TypeArguments.Length > 0)
+ {
+ return MapType(namedType.TypeArguments[0], out needsImport, out importPath);
+ }
+
+ // Basic types
+ switch (typeName)
+ {
+ case "String":
+ return "string";
+ case "Int32":
+ return "int32";
+ case "UInt32":
+ return "uint32";
+ case "Int64":
+ return "int64";
+ case "UInt64":
+ return "uint64";
+ case "Int16":
+ return "int32"; // Proto has no int16
+ case "UInt16":
+ return "uint32"; // Proto has no uint16
+ case "Byte":
+ return "uint32"; // Proto has no byte
+ case "SByte":
+ return "int32"; // Proto has no sbyte
+ case "Boolean":
+ return "bool";
+ case "Single":
+ return "float";
+ case "Double":
+ return "double";
+ case "Byte[]":
+ return "bytes";
+ }
+
+ // Special types that need imports
+ if (fullTypeName.Contains("System.DateTime"))
+ {
+ needsImport = true;
+ importPath = "google/protobuf/timestamp.proto";
+ return "google.protobuf.Timestamp";
+ }
+
+ if (fullTypeName.Contains("System.TimeSpan"))
+ {
+ needsImport = true;
+ importPath = "google/protobuf/duration.proto";
+ return "google.protobuf.Duration";
+ }
+
+ if (fullTypeName.Contains("System.Guid"))
+ {
+ // Guid serialized as string
+ return "string";
+ }
+
+ if (fullTypeName.Contains("System.Decimal"))
+ {
+ // Decimal serialized as string (no native decimal in proto)
+ return "string";
+ }
+
+ // Collections
+ if (typeSymbol is INamedTypeSymbol collectionType)
+ {
+ // List, IEnumerable, Array, etc.
+ if (collectionType.TypeArguments.Length == 1)
+ {
+ var elementType = collectionType.TypeArguments[0];
+ var protoElementType = MapType(elementType, out needsImport, out importPath);
+ return $"repeated {protoElementType}";
+ }
+
+ // Dictionary
+ if (collectionType.TypeArguments.Length == 2 &&
+ (typeName.Contains("Dictionary") || typeName.Contains("IDictionary")))
+ {
+ var keyType = MapType(collectionType.TypeArguments[0], out var keyNeedsImport, out var keyImportPath);
+ var valueType = MapType(collectionType.TypeArguments[1], out var valueNeedsImport, out var valueImportPath);
+
+ // Set import flags if either key or value needs imports
+ if (keyNeedsImport)
+ {
+ needsImport = true;
+ importPath = keyImportPath;
+ }
+ if (valueNeedsImport)
+ {
+ needsImport = true;
+ importPath = valueImportPath; // Note: This only captures last import, may need improvement
+ }
+
+ return $"map<{keyType}, {valueType}>";
+ }
+ }
+
+ // Enums
+ if (typeSymbol.TypeKind == TypeKind.Enum)
+ {
+ return typeName; // Use the enum name directly
+ }
+
+ // Complex types (classes/records) become message types
+ if (typeSymbol.TypeKind == TypeKind.Class || typeSymbol.TypeKind == TypeKind.Struct)
+ {
+ return typeName; // Reference the message type by name
+ }
+
+ // Fallback
+ return "string"; // Default to string for unknown types
+ }
+
+ ///
+ /// Converts C# PascalCase property name to proto snake_case field name
+ ///
+ public static string ToSnakeCase(string pascalCase)
+ {
+ if (string.IsNullOrEmpty(pascalCase))
+ return pascalCase;
+
+ var result = new System.Text.StringBuilder();
+ result.Append(char.ToLowerInvariant(pascalCase[0]));
+
+ for (int i = 1; i < pascalCase.Length; i++)
+ {
+ var c = pascalCase[i];
+ if (char.IsUpper(c))
+ {
+ // Handle sequences of uppercase letters (e.g., "APIKey" -> "api_key")
+ if (i + 1 < pascalCase.Length && char.IsUpper(pascalCase[i + 1]))
+ {
+ result.Append(char.ToLowerInvariant(c));
+ }
+ else
+ {
+ result.Append('_');
+ result.Append(char.ToLowerInvariant(c));
+ }
+ }
+ else
+ {
+ result.Append(c);
+ }
+ }
+
+ return result.ToString();
+ }
+
+ ///
+ /// Checks if a type should be skipped/ignored for proto generation
+ ///
+ public static bool IsUnsupportedType(ITypeSymbol typeSymbol)
+ {
+ var fullTypeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ // Skip these types - they should trigger a warning/error
+ if (fullTypeName.Contains("System.IO.Stream") ||
+ fullTypeName.Contains("System.Threading.CancellationToken") ||
+ fullTypeName.Contains("System.Threading.Tasks.Task") ||
+ fullTypeName.Contains("System.Collections.Generic.IAsyncEnumerable") ||
+ fullTypeName.Contains("System.Func") ||
+ fullTypeName.Contains("System.Action") ||
+ fullTypeName.Contains("System.Delegate"))
+ {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/Svrnty.CQRS.Grpc.Generators/Svrnty.CQRS.Grpc.Generators.csproj b/Svrnty.CQRS.Grpc.Generators/Svrnty.CQRS.Grpc.Generators.csproj
index dda5820..d2d6d01 100644
--- a/Svrnty.CQRS.Grpc.Generators/Svrnty.CQRS.Grpc.Generators.csproj
+++ b/Svrnty.CQRS.Grpc.Generators/Svrnty.CQRS.Grpc.Generators.csproj
@@ -29,11 +29,17 @@
-
-
+
+
+
+
+
+
+
+
diff --git a/Svrnty.CQRS.Grpc.Generators/build/Svrnty.CQRS.Grpc.Generators.targets b/Svrnty.CQRS.Grpc.Generators/build/Svrnty.CQRS.Grpc.Generators.targets
new file mode 100644
index 0000000..6f77d41
--- /dev/null
+++ b/Svrnty.CQRS.Grpc.Generators/build/Svrnty.CQRS.Grpc.Generators.targets
@@ -0,0 +1,22 @@
+
+
+
+ true
+ $(MSBuildProjectDirectory)\Protos
+ cqrs_services.proto
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(MSBuildProjectDirectory)
+
+
diff --git a/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj b/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj
index ab45392..50b1100 100644
--- a/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj
+++ b/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj
@@ -13,13 +13,13 @@
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Svrnty.CQRS.Grpc/Svrnty.CQRS.Grpc.csproj b/Svrnty.CQRS.Grpc/Svrnty.CQRS.Grpc.csproj
index 21c01c3..190c1bb 100644
--- a/Svrnty.CQRS.Grpc/Svrnty.CQRS.Grpc.csproj
+++ b/Svrnty.CQRS.Grpc/Svrnty.CQRS.Grpc.csproj
@@ -27,8 +27,8 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive