diff --git a/README_DERIVATIVE.md b/README_DERIVATIVE.md
index 51fd0463..9a728be9 100644
--- a/README_DERIVATIVE.md
+++ b/README_DERIVATIVE.md
@@ -9,10 +9,14 @@
You will need the following development tools to build, run, and test this project:
* Windows or MacOS.
-* Jetbrains Rider or Visual Studio (There is most support for JetBrains dotUltimate)
+* JetBrains Rider (recommended) or Visual Studio
+ * Note: Using JetBrains Rider is recommended since the codebase includes more comprehensive customized tooling (coding standards, live templates, etc.)
+ * Note: If using Visual Studio, you will need to install the additional component `.NET Compiler Platform SDK` in order to run the built-in Roslyn source generators.
+ * Note: if using Visual Studio, the built-in Roslyn analyzers will not work (due to .netstandard2.0 [restrictions between Visual Studio and Roslyn](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview))
+
* Install the .NET7.0 SDK (specifically version 7.0.14). Available for [download here](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)
-> We have ensured that you wont need any other infrastructure running on your local machine (i.e. SQLServer database), unless you want to run infrastructure specific integration tests.
+> We have ensured that you won't need any other infrastructure running on your local machine (i.e., a Microsoft SQLServer database) unless you want to run infrastructure-specific integration tests.
# Setup Environment
diff --git a/src/Infrastructure.Web.Api.Interfaces/RouteAttribute.cs b/src/Infrastructure.Web.Api.Interfaces/RouteAttribute.cs
index 3af33618..066ab9f0 100644
--- a/src/Infrastructure.Web.Api.Interfaces/RouteAttribute.cs
+++ b/src/Infrastructure.Web.Api.Interfaces/RouteAttribute.cs
@@ -1,15 +1,21 @@
using System.ComponentModel;
+#if !NETSTANDARD2_0
using System.Diagnostics.CodeAnalysis;
+#endif
namespace Infrastructure.Web.Api.Interfaces;
///
-/// Provides a declarative way to define a REST route and service operation
+/// Provides a declarative way to define a REST route service operation, and configuration
///
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class RouteAttribute : Attribute
{
- public RouteAttribute([StringSyntax("Route")] string routeTemplate, ServiceOperation operation,
+ public RouteAttribute(
+#if !NETSTANDARD2_0
+ [StringSyntax("Route")]
+#endif
+ string routeTemplate, ServiceOperation operation,
AccessType access = AccessType.Anonymous, bool isTestingOnly = false)
{
if (!Enum.IsDefined(typeof(ServiceOperation), operation))
@@ -23,10 +29,10 @@ public RouteAttribute([StringSyntax("Route")] string routeTemplate, ServiceOpera
IsTestingOnly = isTestingOnly;
}
- public bool IsTestingOnly { get; }
-
public AccessType Access { get; }
+ public bool IsTestingOnly { get; }
+
public ServiceOperation Operation { get; }
public string RouteTemplate { get; }
diff --git a/src/Tools.Generators.WebApi.UnitTests/MinimalApiMediatRGeneratorSpec.cs b/src/Tools.Generators.WebApi.UnitTests/MinimalApiMediatRGeneratorSpec.cs
index 451668f3..73e5d31a 100644
--- a/src/Tools.Generators.WebApi.UnitTests/MinimalApiMediatRGeneratorSpec.cs
+++ b/src/Tools.Generators.WebApi.UnitTests/MinimalApiMediatRGeneratorSpec.cs
@@ -5,42 +5,47 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
-using WebApi_MinimalApiMediatRGenerator = Generators::Tools.Generators.WebApi.MinimalApiMediatRGenerator;
+using MinimalApiMediatRGenerator = Generators::Tools.Generators.WebApi.MinimalApiMediatRGenerator;
namespace Tools.Generators.WebApi.UnitTests;
[UsedImplicitly]
public class MinimalApiMediatRGeneratorSpec
{
+ private static readonly string[]
+ AdditionalCompilationAssemblies =
+ { "System.Runtime.dll", "netstandard.dll" }; //HACK: required to analyze custom attributes
+
private static CSharpCompilation CreateCompilation(string sourceCode)
{
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
+ var references = new List
+ {
+ MetadataReference.CreateFromFile(typeof(MinimalApiMediatRGenerator).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location)
+ };
+ AdditionalCompilationAssemblies.ToList()
+ .ForEach(item => references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, item))));
var compilation = CSharpCompilation.Create("compilation",
new[]
{
CSharpSyntaxTree.ParseText(sourceCode)
},
- new[]
- {
- MetadataReference.CreateFromFile(typeof(WebApi_MinimalApiMediatRGenerator).Assembly.Location),
- MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location),
- MetadataReference.CreateFromFile(Path.Combine(assemblyPath,
- "System.Runtime.dll")) //HACK: this is required to make custom attributes work
- },
+ references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
return compilation;
}
[Trait("Category", "Unit")]
- public class GivenAServiceCLass
+ public class GivenAServiceClass
{
private GeneratorDriver _driver;
- public GivenAServiceCLass()
+ public GivenAServiceClass()
{
- var generator = new WebApi_MinimalApiMediatRGenerator();
+ var generator = new MinimalApiMediatRGenerator();
_driver = CSharpGeneratorDriver.Create(generator);
}
diff --git a/src/Tools.Generators.WebApi.UnitTests/WebApiAssemblyVisitorSpec.cs b/src/Tools.Generators.WebApi.UnitTests/WebApiAssemblyVisitorSpec.cs
index c648ba27..394c1567 100644
--- a/src/Tools.Generators.WebApi.UnitTests/WebApiAssemblyVisitorSpec.cs
+++ b/src/Tools.Generators.WebApi.UnitTests/WebApiAssemblyVisitorSpec.cs
@@ -16,23 +16,26 @@ namespace Tools.Generators.WebApi.UnitTests;
public class WebApiAssemblyVisitorSpec
{
private const string CompilationSourceCode = "";
+ private static readonly string[]
+ AdditionalCompilationAssemblies =
+ { "System.Runtime.dll", "netstandard.dll" }; //HACK: required to analyze use custom attributes
private static CSharpCompilation CreateCompilation(string sourceCode)
{
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
-
+ var references = new List
+ {
+ MetadataReference.CreateFromFile(typeof(WebApiAssemblyVisitor).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location)
+ };
+ AdditionalCompilationAssemblies.ToList()
+ .ForEach(item => references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, item))));
var compilation = CSharpCompilation.Create("compilation",
new[]
{
CSharpSyntaxTree.ParseText(sourceCode)
},
- new[]
- {
- MetadataReference.CreateFromFile(typeof(WebApiAssemblyVisitor).Assembly.Location),
- MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location),
- MetadataReference.CreateFromFile(Path.Combine(assemblyPath,
- "System.Runtime.dll")) //HACK: this is required to make custom attributes work
- },
+ references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
return compilation;
@@ -371,7 +374,7 @@ namespace ANamespace;
public class AResponse : IWebResponse
{
}
- [Route("aroute", ServiceOperation.Get)]
+ [Infrastructure.Web.Api.Interfaces.RouteAttribute("aroute", ServiceOperation.Get)]
public class ARequest : IWebRequest
{
}
diff --git a/src/Tools.Generators.WebApi/Extensions/StringExtensions.cs b/src/Tools.Generators.WebApi/Extensions/StringExtensions.cs
index 6d403682..88e46b00 100644
--- a/src/Tools.Generators.WebApi/Extensions/StringExtensions.cs
+++ b/src/Tools.Generators.WebApi/Extensions/StringExtensions.cs
@@ -5,7 +5,7 @@ public static class StringExtensions
///
/// Whether the string value contains no value: it is either: null, empty or only whitespaces
///
- public static bool HasNoValue(this string? value)
+ public static bool HasNoValue(this string value)
{
return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
}
@@ -13,7 +13,7 @@ public static bool HasNoValue(this string? value)
///
/// Whether the string value contains any value except: null, empty or only whitespaces
///
- public static bool HasValue(this string? value)
+ public static bool HasValue(this string value)
{
return !string.IsNullOrEmpty(value) && !string.IsNullOrWhiteSpace(value);
}
diff --git a/src/Tools.Generators.WebApi/Extensions/SymbolExtensions.cs b/src/Tools.Generators.WebApi/Extensions/SymbolExtensions.cs
index a241c562..5dce9af1 100644
--- a/src/Tools.Generators.WebApi/Extensions/SymbolExtensions.cs
+++ b/src/Tools.Generators.WebApi/Extensions/SymbolExtensions.cs
@@ -5,13 +5,13 @@ namespace Tools.Generators.WebApi.Extensions;
public static class SymbolExtensions
{
- public static AttributeData? GetAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
+ public static AttributeData GetAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
{
return symbol.GetAttributes()
.FirstOrDefault(attribute => attribute.AttributeClass!.IsOfType(attributeType));
}
- public static INamedTypeSymbol? GetBaseType(this ITypeSymbol symbol, INamedTypeSymbol baseType)
+ public static INamedTypeSymbol GetBaseType(this ITypeSymbol symbol, INamedTypeSymbol baseType)
{
return symbol.AllInterfaces.FirstOrDefault(@interface => @interface.IsOfType(baseType));
}
@@ -45,7 +45,7 @@ public static IEnumerable GetUsingNamespaces(this INamedTypeSymbol symbo
return usingSyntaxes.Select(us => us.Name!.ToString())
.Distinct()
- .OrderDescending()
+ .OrderByDescending(s => s)
.ToList();
}
@@ -61,7 +61,6 @@ public static bool IsConcreteInstanceClass(this INamedTypeSymbol symbol)
public static bool IsDerivedFrom(this ITypeSymbol symbol, INamedTypeSymbol baseType)
{
- ArgumentNullException.ThrowIfNull(baseType);
return symbol.AllInterfaces.Any(@interface => @interface.IsOfType(baseType));
}
diff --git a/src/Tools.Generators.WebApi/MinimalApiMediatRGenerator.cs b/src/Tools.Generators.WebApi/MinimalApiMediatRGenerator.cs
index 5792d31b..83031411 100644
--- a/src/Tools.Generators.WebApi/MinimalApiMediatRGenerator.cs
+++ b/src/Tools.Generators.WebApi/MinimalApiMediatRGenerator.cs
@@ -54,7 +54,7 @@ public void Execute(GeneratorExecutionContext context)
return;
- static string BuildFile(string? assemblyNamespace, string allUsingNamespaces, string allEndpointRegistrations,
+ static string BuildFile(string assemblyNamespace, string allUsingNamespaces, string allEndpointRegistrations,
string allHandlerClasses)
{
return $@"//
@@ -221,7 +221,7 @@ private static void BuildHandlerClasses(
handlerClasses.AppendLine();
}
- private static string BuildInjectorConstructorAndFields(string? handlerClassName,
+ private static string BuildInjectorConstructorAndFields(string handlerClassName,
List constructors)
{
var handlerClassConstructorAndFields = new StringBuilder();
diff --git a/src/Tools.Generators.WebApi/Tools.Generators.WebApi.csproj b/src/Tools.Generators.WebApi/Tools.Generators.WebApi.csproj
index b33ec330..c18c0328 100644
--- a/src/Tools.Generators.WebApi/Tools.Generators.WebApi.csproj
+++ b/src/Tools.Generators.WebApi/Tools.Generators.WebApi.csproj
@@ -1,7 +1,9 @@
- net7.0
+ netstandard2.0
+ latest
+ disable
true
true
true
diff --git a/src/Tools.Generators.WebApi/WebApiAssemblyVisitor.cs b/src/Tools.Generators.WebApi/WebApiAssemblyVisitor.cs
index cc093ab7..faa2f1ce 100644
--- a/src/Tools.Generators.WebApi/WebApiAssemblyVisitor.cs
+++ b/src/Tools.Generators.WebApi/WebApiAssemblyVisitor.cs
@@ -21,7 +21,7 @@ namespace Tools.Generators.WebApi;
public class WebApiAssemblyVisitor : SymbolVisitor
{
internal static readonly string[] IgnoredNamespaces =
- { "System", "Microsoft", "MediatR", "MessagePack", "NerdBank*" };
+ ["System", "Microsoft", "MediatR", "MessagePack", "NerdBank*"];
private readonly CancellationToken _cancellationToken;
private readonly INamedTypeSymbol _cancellationTokenSymbol;
@@ -205,24 +205,24 @@ TypeName GetServiceName()
return new TypeName(symbol.ContainingNamespace.ToDisplayString(), symbol.Name);
}
- static ServiceOperation FromOperationVerb(string? operation)
+ static ServiceOperation FromOperationVerb(string operation)
{
if (operation is null)
{
return ServiceOperation.Get;
}
- return Enum.Parse(operation, true);
+ return (ServiceOperation)Enum.Parse(typeof(ServiceOperation), operation, true);
}
- static AccessType FromAccessType(string? access)
+ static AccessType FromAccessType(string access)
{
if (access is null)
{
return AccessType.Anonymous;
}
- return Enum.Parse(access, true);
+ return (AccessType)Enum.Parse(typeof(AccessType), access, true);
}
// We assume that the request type derives from IWebRequest
@@ -336,7 +336,7 @@ bool HasWrongSetOfParameters(IMethodSymbol method)
}
// We assume that the request DTO it is decorated with a RouteAttribute
- bool HasRouteAttribute(IMethodSymbol method, out AttributeData? routeAttribute)
+ bool HasRouteAttribute(IMethodSymbol method, out AttributeData routeAttribute)
{
var parameters = method.Parameters;
if (parameters.Length == 0)
@@ -353,35 +353,33 @@ bool HasRouteAttribute(IMethodSymbol method, out AttributeData? routeAttribute)
public record ServiceOperationRegistration
{
- public required ApiServiceClassRegistration Class { get; init; }
+ public ApiServiceClassRegistration Class { get; set; }
- public required bool HasCancellationToken { get; init; }
+ public bool HasCancellationToken { get; set; }
- public required bool IsAsync { get; init; }
+ public bool IsAsync { get; set; }
- public required bool IsTestingOnly { get; init; }
+ public bool IsTestingOnly { get; set; }
- public string? MethodBody { get; set; }
+ public string MethodBody { get; set; }
- public required string MethodName { get; init; }
+ public string MethodName { get; set; }
- public required AccessType OperationAccess { get; init; }
+ public AccessType OperationAccess { get; set; }
- public required ServiceOperation OperationType { get; init; }
+ public ServiceOperation OperationType { get; set; }
- public required TypeName RequestDtoType { get; init; }
+ public TypeName RequestDtoType { get; set; }
- public required TypeName ResponseDtoType { get; init; }
+ public TypeName ResponseDtoType { get; set; }
- public required string RoutePath { get; init; }
+ public string RoutePath { get; set; }
}
public record TypeName
{
public TypeName(string @namespace, string name)
{
- ArgumentException.ThrowIfNullOrEmpty(@namespace);
- ArgumentException.ThrowIfNullOrEmpty(name);
Namespace = @namespace;
Name = name;
}
@@ -392,7 +390,7 @@ public TypeName(string @namespace, string name)
public string Namespace { get; }
- public virtual bool Equals(TypeName? other)
+ public virtual bool Equals(TypeName other)
{
if (ReferenceEquals(null, other))
{
@@ -409,32 +407,39 @@ public virtual bool Equals(TypeName? other)
public override int GetHashCode()
{
+#if NETSTANDARD2_0
+ var hash = 17;
+ hash = hash * 23 + Namespace.GetHashCode();
+ hash = hash * 23 + Name.GetHashCode();
+ return hash;
+#else
return HashCode.Combine(Namespace, Name);
+#endif
}
}
public record ApiServiceClassRegistration
{
- public IEnumerable Constructors { get; init; } = new List();
+ public IEnumerable Constructors { get; set; } = new List();
- public required TypeName TypeName { get; init; }
+ public TypeName TypeName { get; set; }
- public IEnumerable UsingNamespaces { get; init; } = new List();
+ public IEnumerable UsingNamespaces { get; set; } = new List();
}
public record Constructor
{
- public IEnumerable CtorParameters { get; init; } = new List();
+ public IEnumerable CtorParameters { get; set; } = new List();
- public required bool IsInjectionCtor { get; init; }
+ public bool IsInjectionCtor { get; set; }
- public string? MethodBody { get; set; }
+ public string MethodBody { get; set; }
}
public record ConstructorParameter
{
- public required TypeName TypeName { get; init; }
+ public TypeName TypeName { get; set; }
- public required string VariableName { get; init; }
+ public string VariableName { get; set; }
}
}
\ No newline at end of file