From 09f2d362808547c0c07346ed53e783caee4d6cc1 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Carle <29762210+jscarle@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:14:08 -0500 Subject: [PATCH 1/6] Refactor constants into attribute-specific files (#76) --- .../Common/Constants.AcceptsAttribute.cs | 100 +++ .../Constants.AttributesInitialization.cs | 31 + .../Constants.DisableAntiforgeryAttribute.cs | 29 + ...onstants.DisableRequestTimeoutAttribute.cs | 29 + .../Constants.DisableValidationAttribute.cs | 31 + .../Constants.EndpointFilterAttribute.cs | 55 ++ .../Common/Constants.GeneratedSources.cs | 626 ------------------ .../Common/Constants.MapGroupAttribute.cs | 47 ++ .../Common/Constants.OrderAttribute.cs | 42 ++ .../Constants.ProducesProblemAttribute.cs | 56 ++ .../Constants.ProducesResponseAttribute.cs | 104 +++ ...ants.ProducesValidationProblemAttribute.cs | 56 ++ .../Constants.RequestTimeoutAttribute.cs | 49 ++ ...Constants.RequireAuthorizationAttribute.cs | 49 ++ .../Common/Constants.RequireCorsAttribute.cs | 47 ++ .../Common/Constants.RequireHostAttribute.cs | 41 ++ .../Constants.RequireRateLimitingAttribute.cs | 41 ++ .../Common/Constants.ShortCircuitAttribute.cs | 29 + .../Common/Constants.SummaryAttribute.cs | 42 ++ src/GeneratedEndpoints/Common/Constants.cs | 70 +- 20 files changed, 879 insertions(+), 695 deletions(-) create mode 100644 src/GeneratedEndpoints/Common/Constants.AcceptsAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.AttributesInitialization.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.DisableAntiforgeryAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.DisableRequestTimeoutAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.DisableValidationAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.EndpointFilterAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.MapGroupAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.OrderAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.ProducesProblemAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.ProducesResponseAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.ProducesValidationProblemAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.RequestTimeoutAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.RequireAuthorizationAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.RequireCorsAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.RequireHostAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.RequireRateLimitingAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.ShortCircuitAttribute.cs create mode 100644 src/GeneratedEndpoints/Common/Constants.SummaryAttribute.cs diff --git a/src/GeneratedEndpoints/Common/Constants.AcceptsAttribute.cs b/src/GeneratedEndpoints/Common/Constants.AcceptsAttribute.cs new file mode 100644 index 0000000..c07e4d7 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.AcceptsAttribute.cs @@ -0,0 +1,100 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string AcceptsAttributeName = "AcceptsAttribute"; + internal const string AcceptsAttributeHint = $"{AcceptsAttributeFullyQualifiedName}.gs.cs"; + + private const string AcceptsAttributeFullyQualifiedName = $"{AttributesNamespace}.{AcceptsAttributeName}"; + + internal static readonly SourceText AcceptsAttributeSourceText; + + private static SourceText CreateAcceptsAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies the request type and content types accepted by the annotated endpoint or class. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] + internal sealed class {{AcceptsAttributeName}} : global::System.Attribute + { + /// + /// Gets the CLR type of the endpoint filter. + /// + public global::System.Type Type { get; } + + /// + /// Gets the primary content type accepted by the endpoint. + /// + public string ContentType { get; } + + /// + /// Gets the additional content types accepted by the endpoint. + /// + public string[] AdditionalContentTypes { get; } + + /// + /// Gets a value indicating whether the request body is optional. + /// + public bool IsOptional { get; init; } + + /// + /// Initializes a new instance of the class. + /// + /// The CLR type of the request body. + /// The primary content type accepted by the endpoint. + /// Additional content types accepted by the endpoint. + public {{AcceptsAttributeName}}(global::System.Type type, string contentType = "application/json", params string[] additionalContentTypes) + { + Type = type; + ContentType = contentType; + AdditionalContentTypes = additionalContentTypes; + } + } + + /// + /// Specifies the request type using a generic argument and the content types accepted by the annotated endpoint or class. + /// + /// The CLR type of the request body. + [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] + internal sealed class {{AcceptsAttributeName}} : global::System.Attribute + { + /// + /// Gets the CLR type of the endpoint filter. + /// + public global::System.Type Type => typeof(TRequest); + + /// + /// Gets the primary content type accepted by the endpoint. + /// + public string ContentType { get; } + + /// + /// Gets the additional content types accepted by the endpoint. + /// + public string[] AdditionalContentTypes { get; } + + /// + /// Gets a value indicating whether the request body is optional. + /// + public bool IsOptional { get; init; } + + /// + /// Initializes a new instance of the generic Accepts attribute class. + /// + /// The primary content type accepted by the endpoint. + /// Additional content types accepted by the endpoint. + public {{AcceptsAttributeName}}(string contentType = "application/json", params string[] additionalContentTypes) + { + ContentType = contentType; + AdditionalContentTypes = additionalContentTypes; + } + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.AttributesInitialization.cs b/src/GeneratedEndpoints/Common/Constants.AttributesInitialization.cs new file mode 100644 index 0000000..41461ee --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.AttributesInitialization.cs @@ -0,0 +1,31 @@ +#pragma warning disable S3963 +#pragma warning disable CA1810 + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + static Constants() + { + RequireAuthorizationAttributeSourceText = CreateRequireAuthorizationAttributeSourceText(); + RequireCorsAttributeSourceText = CreateRequireCorsAttributeSourceText(); + RequireRateLimitingAttributeSourceText = CreateRequireRateLimitingAttributeSourceText(); + RequireHostAttributeSourceText = CreateRequireHostAttributeSourceText(); + DisableAntiforgeryAttributeSourceText = CreateDisableAntiforgeryAttributeSourceText(); + ShortCircuitAttributeSourceText = CreateShortCircuitAttributeSourceText(); + DisableRequestTimeoutAttributeSourceText = CreateDisableRequestTimeoutAttributeSourceText(); + DisableValidationAttributeSourceText = CreateDisableValidationAttributeSourceText(); + RequestTimeoutAttributeSourceText = CreateRequestTimeoutAttributeSourceText(); + OrderAttributeSourceText = CreateOrderAttributeSourceText(); + MapGroupAttributeSourceText = CreateMapGroupAttributeSourceText(); + SummaryAttributeSourceText = CreateSummaryAttributeSourceText(); + EndpointFilterAttributeSourceText = CreateEndpointFilterAttributeSourceText(); + AcceptsAttributeSourceText = CreateAcceptsAttributeSourceText(); + ProducesResponseAttributeSourceText = CreateProducesResponseAttributeSourceText(); + ProducesProblemAttributeSourceText = CreateProducesProblemAttributeSourceText(); + ProducesValidationProblemAttributeSourceText = CreateProducesValidationProblemAttributeSourceText(); + } +} + +#pragma warning restore CA1810 +#pragma warning restore S3963 diff --git a/src/GeneratedEndpoints/Common/Constants.DisableAntiforgeryAttribute.cs b/src/GeneratedEndpoints/Common/Constants.DisableAntiforgeryAttribute.cs new file mode 100644 index 0000000..4a10f94 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.DisableAntiforgeryAttribute.cs @@ -0,0 +1,29 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string DisableAntiforgeryAttributeName = "DisableAntiforgeryAttribute"; + internal const string DisableAntiforgeryAttributeHint = $"{DisableAntiforgeryAttributeFullyQualifiedName}.gs.cs"; + + private const string DisableAntiforgeryAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableAntiforgeryAttributeName}"; + + internal static readonly SourceText DisableAntiforgeryAttributeSourceText; + + private static SourceText CreateDisableAntiforgeryAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Disables antiforgery protection for the annotated endpoint or class. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{DisableAntiforgeryAttributeName}} : global::System.Attribute + { + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.DisableRequestTimeoutAttribute.cs b/src/GeneratedEndpoints/Common/Constants.DisableRequestTimeoutAttribute.cs new file mode 100644 index 0000000..1ab9d08 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.DisableRequestTimeoutAttribute.cs @@ -0,0 +1,29 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string DisableRequestTimeoutAttributeName = "DisableRequestTimeoutAttribute"; + internal const string DisableRequestTimeoutAttributeHint = $"{DisableRequestTimeoutAttributeFullyQualifiedName}.gs.cs"; + + private const string DisableRequestTimeoutAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableRequestTimeoutAttributeName}"; + + internal static readonly SourceText DisableRequestTimeoutAttributeSourceText; + + private static SourceText CreateDisableRequestTimeoutAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Disables the request timeout for the annotated endpoint or class. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{DisableRequestTimeoutAttributeName}} : global::System.Attribute + { + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.DisableValidationAttribute.cs b/src/GeneratedEndpoints/Common/Constants.DisableValidationAttribute.cs new file mode 100644 index 0000000..8ac0c6c --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.DisableValidationAttribute.cs @@ -0,0 +1,31 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string DisableValidationAttributeName = "DisableValidationAttribute"; + internal const string DisableValidationAttributeHint = $"{DisableValidationAttributeFullyQualifiedName}.gs.cs"; + + private const string DisableValidationAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableValidationAttributeName}"; + + internal static readonly SourceText DisableValidationAttributeSourceText; + + private static SourceText CreateDisableValidationAttributeSourceText() => SourceText.From($$""" + #if NET10_0_OR_GREATER + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Disables request validation for the annotated endpoint or class when targeting .NET 10 or later. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{DisableValidationAttributeName}} : global::System.Attribute + { + } + #endif + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.EndpointFilterAttribute.cs b/src/GeneratedEndpoints/Common/Constants.EndpointFilterAttribute.cs new file mode 100644 index 0000000..97bb1c3 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.EndpointFilterAttribute.cs @@ -0,0 +1,55 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string EndpointFilterAttributeName = "EndpointFilterAttribute"; + internal const string EndpointFilterAttributeHint = $"{EndpointFilterAttributeFullyQualifiedName}.gs.cs"; + + private const string EndpointFilterAttributeFullyQualifiedName = $"{AttributesNamespace}.{EndpointFilterAttributeName}"; + + internal static readonly SourceText EndpointFilterAttributeSourceText; + + private static SourceText CreateEndpointFilterAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies an endpoint filter type to apply to the annotated endpoint or class. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] + internal sealed class {{EndpointFilterAttributeName}} : global::System.Attribute + { + /// + /// Gets the CLR type of the endpoint filter. + /// + public global::System.Type Type { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The CLR type of the endpoint filter. + public {{EndpointFilterAttributeName}}(global::System.Type type) + { + Type = type; + } + } + + /// + /// Specifies an endpoint filter type using a generic argument. + /// + /// The CLR type of the endpoint filter. + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] + internal sealed class {{EndpointFilterAttributeName}} : global::System.Attribute + { + /// + /// Gets the CLR type of the endpoint filter. + /// + public global::System.Type Type => typeof(TFilter); + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.GeneratedSources.cs b/src/GeneratedEndpoints/Common/Constants.GeneratedSources.cs index 1b90733..81ac968 100644 --- a/src/GeneratedEndpoints/Common/Constants.GeneratedSources.cs +++ b/src/GeneratedEndpoints/Common/Constants.GeneratedSources.cs @@ -38,632 +38,6 @@ internal static partial class Constants internal static readonly ImmutableDictionary HttpAttributeDefinitionsByName = HttpAttributeDefinitions.ToImmutableDictionary(static definition => definition.Name); - internal static readonly SourceText RequireAuthorizationAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies that authorization is required for the annotated endpoint or class. - /// Optionally restricts access to the specified authorization policies. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{RequireAuthorizationAttributeName}} : global::System.Attribute - { - /// - /// Gets the policy names that the endpoint or class requires. - /// - public string[] PolicyNames { get; } - - /// - /// Marks the endpoint or class as requiring authorization. - /// - public {{RequireAuthorizationAttributeName}}() - { - PolicyNames = []; - } - - /// - /// Marks the endpoint or class as requiring authorization with one or more policies. - /// - public {{RequireAuthorizationAttributeName}}(params string[] policyNames) - { - PolicyNames = policyNames ?? []; - } - } - """, Encoding.UTF8 - ); - - internal static readonly SourceText RequireCorsAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies that the annotated endpoint requires a configured CORS policy. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{RequireCorsAttributeName}} : global::System.Attribute - { - /// - /// Gets the optional CORS policy name. - /// - public string? PolicyName { get; } - - /// - /// Marks the endpoint or class as requiring the default CORS policy. - /// - public {{RequireCorsAttributeName}}() - { - } - - /// - /// Marks the endpoint or class as requiring the specified named CORS policy. - /// - public {{RequireCorsAttributeName}}(string policyName) - { - PolicyName = policyName; - } - } - """, Encoding.UTF8 - ); - - internal static readonly SourceText RequireRateLimitingAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies that the annotated endpoint requires the provided rate limiting policy. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{RequireRateLimitingAttributeName}} : global::System.Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The rate limiting policy to apply. - public {{RequireRateLimitingAttributeName}}(string policyName) - { - PolicyName = policyName; - } - - /// - /// Gets the rate limiting policy name. - /// - public string PolicyName { get; } - } - """, Encoding.UTF8 - ); - - internal static readonly SourceText RequireHostAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies the allowed hosts for the annotated endpoint or class. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{RequireHostAttributeName}} : global::System.Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The hosts that are allowed to access the endpoint. - public {{RequireHostAttributeName}}(params string[] hosts) - { - Hosts = hosts ?? []; - } - - /// - /// Gets the allowed hosts. - /// - public string[] Hosts { get; } - } - """, Encoding.UTF8 - ); - - internal static readonly SourceText DisableAntiforgeryAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Disables antiforgery protection for the annotated endpoint or class. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{DisableAntiforgeryAttributeName}} : global::System.Attribute - { - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText ShortCircuitAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Marks the annotated endpoint or class to short-circuit the request pipeline. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{ShortCircuitAttributeName}} : global::System.Attribute - { - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText DisableRequestTimeoutAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Disables the request timeout for the annotated endpoint or class. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{DisableRequestTimeoutAttributeName}} : global::System.Attribute - { - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText DisableValidationAttributeSourceText = SourceText.From($$""" - #if NET10_0_OR_GREATER - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Disables request validation for the annotated endpoint or class when targeting .NET 10 or later. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{DisableValidationAttributeName}} : global::System.Attribute - { - } - #endif - - """, Encoding.UTF8 - ); - - internal static readonly SourceText RequestTimeoutAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Applies the request timeout metadata to the annotated endpoint or class. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{RequestTimeoutAttributeName}} : global::System.Attribute - { - /// - /// Gets the optional request timeout policy name. - /// - public string? PolicyName { get; init; } - - /// - /// Applies the default request timeout behavior. - /// - public {{RequestTimeoutAttributeName}}() - { - } - - /// - /// Applies the specified request timeout policy. - /// - /// The request timeout policy name. - public {{RequestTimeoutAttributeName}}(string policyName) - { - PolicyName = policyName; - } - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText OrderAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies the order for the annotated endpoint when building conventions. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{OrderAttributeName}} : global::System.Attribute - { - /// - /// Gets the order that will be applied to the endpoint. - /// - public int Order { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The order value to apply to the endpoint. - public {{OrderAttributeName}}(int order) - { - Order = order; - } - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText MapGroupAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies the route group for the annotated class. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class, Inherited = false, AllowMultiple = false)] - internal sealed class {{MapGroupAttributeName}} : global::System.Attribute - { - /// - /// Gets the route group pattern. - /// - public string Pattern { get; } - - /// - /// Gets or sets the endpoint group name. - /// - public string? Name { get; init; } - - /// - /// Initializes a new instance of the class. - /// - /// The route group pattern to apply. - public {{MapGroupAttributeName}}(string pattern) - { - Pattern = pattern; - } - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText SummaryAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies the summary metadata for the annotated endpoint. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - internal sealed class {{SummaryAttributeName}} : global::System.Attribute - { - /// - /// Gets the summary value for the endpoint. - /// - public string Summary { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The summary to apply to the endpoint. - public {{SummaryAttributeName}}(string summary) - { - Summary = summary; - } - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText AcceptsAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies the request type and content types accepted by the annotated endpoint or class. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - internal sealed class {{AcceptsAttributeName}} : global::System.Attribute - { - /// - /// Gets the CLR type of the endpoint filter. - /// - public global::System.Type Type { get; } - - /// - /// Gets the primary content type accepted by the endpoint. - /// - public string ContentType { get; } - - /// - /// Gets the additional content types accepted by the endpoint. - /// - public string[] AdditionalContentTypes { get; } - - /// - /// Gets a value indicating whether the request body is optional. - /// - public bool IsOptional { get; init; } - - /// - /// Initializes a new instance of the class. - /// - /// The CLR type of the request body. - /// The primary content type accepted by the endpoint. - /// Additional content types accepted by the endpoint. - public {{AcceptsAttributeName}}(global::System.Type type, string contentType = "application/json", params string[] additionalContentTypes) - { - Type = type; - ContentType = contentType; - AdditionalContentTypes = additionalContentTypes; - } - } - - /// - /// Specifies the request type using a generic argument and the content types accepted by the annotated endpoint or class. - /// - /// The CLR type of the request body. - [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - internal sealed class {{AcceptsAttributeName}} : global::System.Attribute - { - /// - /// Gets the CLR type of the endpoint filter. - /// - public global::System.Type Type => typeof(TRequest); - - /// - /// Gets the primary content type accepted by the endpoint. - /// - public string ContentType { get; } - - /// - /// Gets the additional content types accepted by the endpoint. - /// - public string[] AdditionalContentTypes { get; } - - /// - /// Gets a value indicating whether the request body is optional. - /// - public bool IsOptional { get; init; } - - /// - /// Initializes a new instance of the generic Accepts attribute class. - /// - /// The primary content type accepted by the endpoint. - /// Additional content types accepted by the endpoint. - public {{AcceptsAttributeName}}(string contentType = "application/json", params string[] additionalContentTypes) - { - ContentType = contentType; - AdditionalContentTypes = additionalContentTypes; - } - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText EndpointFilterAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies an endpoint filter type to apply to the annotated endpoint or class. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - internal sealed class {{EndpointFilterAttributeName}} : global::System.Attribute - { - /// - /// Gets the CLR type of the endpoint filter. - /// - public global::System.Type Type { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The CLR type of the endpoint filter. - public {{EndpointFilterAttributeName}}(global::System.Type type) - { - Type = type; - } - } - - /// - /// Specifies an endpoint filter type using a generic argument. - /// - /// The CLR type of the endpoint filter. - [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - internal sealed class {{EndpointFilterAttributeName}} : global::System.Attribute - { - /// - /// Gets the CLR type of the endpoint filter. - /// - public global::System.Type Type => typeof(TFilter); - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText ProducesResponseAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies a response type, status code, and content types produced by the annotated endpoint or class. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - internal sealed class {{ProducesResponseAttributeName}} : global::System.Attribute - { - /// - /// Gets the response type produced by the endpoint. - /// - public global::System.Type Type { get; } - - /// - /// Gets the HTTP status code returned by the endpoint. - /// - public int StatusCode { get; } - - /// - /// Gets the primary content type produced by the endpoint. - /// - public string? ContentType { get; } - - /// - /// Gets the additional content types produced by the endpoint. - /// - public string[] AdditionalContentTypes { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The CLR type of the response body. - /// The HTTP status code returned by the endpoint. - /// The primary content type produced by the endpoint. - /// Additional content types produced by the endpoint. - public {{ProducesResponseAttributeName}}(global::System.Type type, int statusCode = global::Microsoft.AspNetCore.Http.StatusCodes.Status200OK, string? contentType = null, params string[] additionalContentTypes) - { - Type = type; - StatusCode = statusCode; - ContentType = contentType; - AdditionalContentTypes = additionalContentTypes ?? []; - } - } - - /// - /// Specifies a response type using a generic argument along with status code and content types produced by the annotated endpoint or class. - /// - /// The CLR type of the response body. - [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - internal sealed class {{ProducesResponseAttributeName}} : global::System.Attribute - { - /// - /// Gets the response type produced by the endpoint. - /// - public global::System.Type Type => typeof(TResponse); - - /// - /// Gets the HTTP status code returned by the endpoint. - /// - public int StatusCode { get; } - - /// - /// Gets the primary content type produced by the endpoint. - /// - public string? ContentType { get; } - - /// - /// Gets the additional content types produced by the endpoint. - /// - public string[] AdditionalContentTypes { get; } - - /// - /// Initializes a new instance of the generic Produces attribute class. - /// - /// The HTTP status code returned by the endpoint. - /// The primary content type produced by the endpoint. - /// Additional content types produced by the endpoint. - public {{ProducesResponseAttributeName}}(int statusCode = global::Microsoft.AspNetCore.Http.StatusCodes.Status200OK, string? contentType = null, params string[] additionalContentTypes) - { - StatusCode = statusCode; - ContentType = contentType; - AdditionalContentTypes = additionalContentTypes ?? []; - } - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText ProducesProblemAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies that the endpoint produces a problem details payload. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - internal sealed class {{ProducesProblemAttributeName}} : global::System.Attribute - { - /// - /// Gets the HTTP status code returned by the endpoint. - /// - public int StatusCode { get; } - - /// - /// Gets the primary content type produced by the endpoint. - /// - public string? ContentType { get; } - - /// - /// Gets the additional content types produced by the endpoint. - /// - public string[] AdditionalContentTypes { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The HTTP status code returned by the endpoint. - /// The primary content type produced by the endpoint. - /// Additional content types produced by the endpoint. - public {{ProducesProblemAttributeName}}(int statusCode = global::Microsoft.AspNetCore.Http.StatusCodes.Status500InternalServerError, string? contentType = null, params string[] additionalContentTypes) - { - StatusCode = statusCode; - ContentType = contentType; - AdditionalContentTypes = additionalContentTypes ?? []; - } - } - - """, Encoding.UTF8 - ); - - internal static readonly SourceText ProducesValidationProblemAttributeSourceText = SourceText.From($$""" - {{FileHeader}} - - namespace {{AttributesNamespace}}; - - /// - /// Specifies that the endpoint produces a validation problem details payload. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - internal sealed class {{ProducesValidationProblemAttributeName}} : global::System.Attribute - { - /// - /// Gets the HTTP status code returned by the endpoint. - /// - public int StatusCode { get; } - - /// - /// Gets the primary content type produced by the endpoint. - /// - public string? ContentType { get; } - - /// - /// Gets the additional content types produced by the endpoint. - /// - public string[] AdditionalContentTypes { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The HTTP status code returned by the endpoint. - /// The primary content type produced by the endpoint. - /// Additional content types produced by the endpoint. - public {{ProducesValidationProblemAttributeName}}(int statusCode = global::Microsoft.AspNetCore.Http.StatusCodes.Status400BadRequest, string? contentType = null, params string[] additionalContentTypes) - { - StatusCode = statusCode; - ContentType = contentType; - AdditionalContentTypes = additionalContentTypes ?? []; - } - } - - """, Encoding.UTF8 - ); - private static HttpAttributeDefinition CreateHttpAttributeDefinition(string attributeName, string verb, bool allowOptionalPattern = false) { var fullyQualifiedName = $"{AttributesNamespace}.{attributeName}"; diff --git a/src/GeneratedEndpoints/Common/Constants.MapGroupAttribute.cs b/src/GeneratedEndpoints/Common/Constants.MapGroupAttribute.cs new file mode 100644 index 0000000..cc29b68 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.MapGroupAttribute.cs @@ -0,0 +1,47 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string MapGroupAttributeName = "MapGroupAttribute"; + internal const string MapGroupAttributeHint = $"{MapGroupAttributeFullyQualifiedName}.gs.cs"; + + private const string MapGroupAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapGroupAttributeName}"; + + internal static readonly SourceText MapGroupAttributeSourceText; + + private static SourceText CreateMapGroupAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies the route group for the annotated class. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + internal sealed class {{MapGroupAttributeName}} : global::System.Attribute + { + /// + /// Gets the route group pattern. + /// + public string Pattern { get; } + + /// + /// Gets or sets the endpoint group name. + /// + public string? Name { get; init; } + + /// + /// Initializes a new instance of the class. + /// + /// The route group pattern to apply. + public {{MapGroupAttributeName}}(string pattern) + { + Pattern = pattern; + } + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.OrderAttribute.cs b/src/GeneratedEndpoints/Common/Constants.OrderAttribute.cs new file mode 100644 index 0000000..64e06d5 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.OrderAttribute.cs @@ -0,0 +1,42 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string OrderAttributeName = "OrderAttribute"; + internal const string OrderAttributeHint = $"{OrderAttributeFullyQualifiedName}.gs.cs"; + + private const string OrderAttributeFullyQualifiedName = $"{AttributesNamespace}.{OrderAttributeName}"; + + internal static readonly SourceText OrderAttributeSourceText; + + private static SourceText CreateOrderAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies the order for the annotated endpoint when building conventions. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{OrderAttributeName}} : global::System.Attribute + { + /// + /// Gets the order that will be applied to the endpoint. + /// + public int Order { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The order value to apply to the endpoint. + public {{OrderAttributeName}}(int order) + { + Order = order; + } + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.ProducesProblemAttribute.cs b/src/GeneratedEndpoints/Common/Constants.ProducesProblemAttribute.cs new file mode 100644 index 0000000..269541f --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.ProducesProblemAttribute.cs @@ -0,0 +1,56 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string ProducesProblemAttributeName = "ProducesProblemAttribute"; + internal const string ProducesProblemAttributeHint = $"{ProducesProblemAttributeFullyQualifiedName}.gs.cs"; + + private const string ProducesProblemAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesProblemAttributeName}"; + + internal static readonly SourceText ProducesProblemAttributeSourceText; + + private static SourceText CreateProducesProblemAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies that the endpoint produces a problem details payload. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] + internal sealed class {{ProducesProblemAttributeName}} : global::System.Attribute + { + /// + /// Gets the HTTP status code returned by the endpoint. + /// + public int StatusCode { get; } + + /// + /// Gets the primary content type produced by the endpoint. + /// + public string? ContentType { get; } + + /// + /// Gets the additional content types produced by the endpoint. + /// + public string[] AdditionalContentTypes { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The HTTP status code returned by the endpoint. + /// The primary content type produced by the endpoint. + /// Additional content types produced by the endpoint. + public {{ProducesProblemAttributeName}}(int statusCode = global::Microsoft.AspNetCore.Http.StatusCodes.Status500InternalServerError, string? contentType = null, params string[] additionalContentTypes) + { + StatusCode = statusCode; + ContentType = contentType; + AdditionalContentTypes = additionalContentTypes ?? []; + } + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.ProducesResponseAttribute.cs b/src/GeneratedEndpoints/Common/Constants.ProducesResponseAttribute.cs new file mode 100644 index 0000000..191f29f --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.ProducesResponseAttribute.cs @@ -0,0 +1,104 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string ProducesResponseAttributeName = "ProducesResponseAttribute"; + internal const string ProducesResponseAttributeHint = $"{ProducesResponseAttributeFullyQualifiedName}.gs.cs"; + + private const string ProducesResponseAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesResponseAttributeName}"; + + internal static readonly SourceText ProducesResponseAttributeSourceText; + + private static SourceText CreateProducesResponseAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies a response type, status code, and content types produced by the annotated endpoint or class. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] + internal sealed class {{ProducesResponseAttributeName}} : global::System.Attribute + { + /// + /// Gets the response type produced by the endpoint. + /// + public global::System.Type Type { get; } + + /// + /// Gets the HTTP status code returned by the endpoint. + /// + public int StatusCode { get; } + + /// + /// Gets the primary content type produced by the endpoint. + /// + public string? ContentType { get; } + + /// + /// Gets the additional content types produced by the endpoint. + /// + public string[] AdditionalContentTypes { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The CLR type of the response body. + /// The HTTP status code returned by the endpoint. + /// The primary content type produced by the endpoint. + /// Additional content types produced by the endpoint. + public {{ProducesResponseAttributeName}}(global::System.Type type, int statusCode = global::Microsoft.AspNetCore.Http.StatusCodes.Status200OK, string? contentType = null, params string[] additionalContentTypes) + { + Type = type; + StatusCode = statusCode; + ContentType = contentType; + AdditionalContentTypes = additionalContentTypes ?? []; + } + } + + /// + /// Specifies a response type using a generic argument along with status code and content types produced by the annotated endpoint or class. + /// + /// The CLR type of the response body. + [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] + internal sealed class {{ProducesResponseAttributeName}} : global::System.Attribute + { + /// + /// Gets the response type produced by the endpoint. + /// + public global::System.Type Type => typeof(TResponse); + + /// + /// Gets the HTTP status code returned by the endpoint. + /// + public int StatusCode { get; } + + /// + /// Gets the primary content type produced by the endpoint. + /// + public string? ContentType { get; } + + /// + /// Gets the additional content types produced by the endpoint. + /// + public string[] AdditionalContentTypes { get; } + + /// + /// Initializes a new instance of the generic Produces attribute class. + /// + /// The HTTP status code returned by the endpoint. + /// The primary content type produced by the endpoint. + /// Additional content types produced by the endpoint. + public {{ProducesResponseAttributeName}}(int statusCode = global::Microsoft.AspNetCore.Http.StatusCodes.Status200OK, string? contentType = null, params string[] additionalContentTypes) + { + StatusCode = statusCode; + ContentType = contentType; + AdditionalContentTypes = additionalContentTypes ?? []; + } + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.ProducesValidationProblemAttribute.cs b/src/GeneratedEndpoints/Common/Constants.ProducesValidationProblemAttribute.cs new file mode 100644 index 0000000..b1f1708 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.ProducesValidationProblemAttribute.cs @@ -0,0 +1,56 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string ProducesValidationProblemAttributeName = "ProducesValidationProblemAttribute"; + internal const string ProducesValidationProblemAttributeHint = $"{ProducesValidationProblemAttributeFullyQualifiedName}.gs.cs"; + + private const string ProducesValidationProblemAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesValidationProblemAttributeName}"; + + internal static readonly SourceText ProducesValidationProblemAttributeSourceText; + + private static SourceText CreateProducesValidationProblemAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies that the endpoint produces a validation problem details payload. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] + internal sealed class {{ProducesValidationProblemAttributeName}} : global::System.Attribute + { + /// + /// Gets the HTTP status code returned by the endpoint. + /// + public int StatusCode { get; } + + /// + /// Gets the primary content type produced by the endpoint. + /// + public string? ContentType { get; } + + /// + /// Gets the additional content types produced by the endpoint. + /// + public string[] AdditionalContentTypes { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The HTTP status code returned by the endpoint. + /// The primary content type produced by the endpoint. + /// Additional content types produced by the endpoint. + public {{ProducesValidationProblemAttributeName}}(int statusCode = global::Microsoft.AspNetCore.Http.StatusCodes.Status400BadRequest, string? contentType = null, params string[] additionalContentTypes) + { + StatusCode = statusCode; + ContentType = contentType; + AdditionalContentTypes = additionalContentTypes ?? []; + } + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.RequestTimeoutAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequestTimeoutAttribute.cs new file mode 100644 index 0000000..c77fbaf --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.RequestTimeoutAttribute.cs @@ -0,0 +1,49 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string RequestTimeoutAttributeName = "RequestTimeoutAttribute"; + internal const string RequestTimeoutAttributeHint = $"{RequestTimeoutAttributeFullyQualifiedName}.gs.cs"; + + private const string RequestTimeoutAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequestTimeoutAttributeName}"; + + internal static readonly SourceText RequestTimeoutAttributeSourceText; + + private static SourceText CreateRequestTimeoutAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Applies the request timeout metadata to the annotated endpoint or class. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{RequestTimeoutAttributeName}} : global::System.Attribute + { + /// + /// Gets the optional request timeout policy name. + /// + public string? PolicyName { get; init; } + + /// + /// Applies the default request timeout behavior. + /// + public {{RequestTimeoutAttributeName}}() + { + } + + /// + /// Applies the specified request timeout policy. + /// + /// The request timeout policy name. + public {{RequestTimeoutAttributeName}}(string policyName) + { + PolicyName = policyName; + } + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.RequireAuthorizationAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequireAuthorizationAttribute.cs new file mode 100644 index 0000000..7d35f79 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.RequireAuthorizationAttribute.cs @@ -0,0 +1,49 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string RequireAuthorizationAttributeName = "RequireAuthorizationAttribute"; + internal const string RequireAuthorizationAttributeHint = $"{RequireAuthorizationAttributeFullyQualifiedName}.gs.cs"; + + private const string RequireAuthorizationAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireAuthorizationAttributeName}"; + + internal static readonly SourceText RequireAuthorizationAttributeSourceText; + + private static SourceText CreateRequireAuthorizationAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies that authorization is required for the annotated endpoint or class. + /// Optionally restricts access to the specified authorization policies. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{RequireAuthorizationAttributeName}} : global::System.Attribute + { + /// + /// Gets the policy names that the endpoint or class requires. + /// + public string[] PolicyNames { get; } + + /// + /// Marks the endpoint or class as requiring authorization. + /// + public {{RequireAuthorizationAttributeName}}() + { + PolicyNames = []; + } + + /// + /// Marks the endpoint or class as requiring authorization with one or more policies. + /// + public {{RequireAuthorizationAttributeName}}(params string[] policyNames) + { + PolicyNames = policyNames ?? []; + } + } + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.RequireCorsAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequireCorsAttribute.cs new file mode 100644 index 0000000..766ee22 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.RequireCorsAttribute.cs @@ -0,0 +1,47 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string RequireCorsAttributeName = "RequireCorsAttribute"; + internal const string RequireCorsAttributeHint = $"{RequireCorsAttributeFullyQualifiedName}.gs.cs"; + + private const string RequireCorsAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireCorsAttributeName}"; + + internal static readonly SourceText RequireCorsAttributeSourceText; + + private static SourceText CreateRequireCorsAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies that the annotated endpoint requires a configured CORS policy. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{RequireCorsAttributeName}} : global::System.Attribute + { + /// + /// Gets the optional CORS policy name. + /// + public string? PolicyName { get; } + + /// + /// Marks the endpoint or class as requiring the default CORS policy. + /// + public {{RequireCorsAttributeName}}() + { + } + + /// + /// Marks the endpoint or class as requiring the specified named CORS policy. + /// + public {{RequireCorsAttributeName}}(string policyName) + { + PolicyName = policyName; + } + } + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.RequireHostAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequireHostAttribute.cs new file mode 100644 index 0000000..d8966e0 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.RequireHostAttribute.cs @@ -0,0 +1,41 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string RequireHostAttributeName = "RequireHostAttribute"; + internal const string RequireHostAttributeHint = $"{RequireHostAttributeFullyQualifiedName}.gs.cs"; + + private const string RequireHostAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireHostAttributeName}"; + + internal static readonly SourceText RequireHostAttributeSourceText; + + private static SourceText CreateRequireHostAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies the allowed hosts for the annotated endpoint or class. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{RequireHostAttributeName}} : global::System.Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The hosts that are allowed to access the endpoint. + public {{RequireHostAttributeName}}(params string[] hosts) + { + Hosts = hosts ?? []; + } + + /// + /// Gets the allowed hosts. + /// + public string[] Hosts { get; } + } + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.RequireRateLimitingAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequireRateLimitingAttribute.cs new file mode 100644 index 0000000..ad74b5d --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.RequireRateLimitingAttribute.cs @@ -0,0 +1,41 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string RequireRateLimitingAttributeName = "RequireRateLimitingAttribute"; + internal const string RequireRateLimitingAttributeHint = $"{RequireRateLimitingAttributeFullyQualifiedName}.gs.cs"; + + private const string RequireRateLimitingAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireRateLimitingAttributeName}"; + + internal static readonly SourceText RequireRateLimitingAttributeSourceText; + + private static SourceText CreateRequireRateLimitingAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies that the annotated endpoint requires the provided rate limiting policy. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{RequireRateLimitingAttributeName}} : global::System.Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The rate limiting policy to apply. + public {{RequireRateLimitingAttributeName}}(string policyName) + { + PolicyName = policyName; + } + + /// + /// Gets the rate limiting policy name. + /// + public string PolicyName { get; } + } + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.ShortCircuitAttribute.cs b/src/GeneratedEndpoints/Common/Constants.ShortCircuitAttribute.cs new file mode 100644 index 0000000..dc5104d --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.ShortCircuitAttribute.cs @@ -0,0 +1,29 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string ShortCircuitAttributeName = "ShortCircuitAttribute"; + internal const string ShortCircuitAttributeHint = $"{ShortCircuitAttributeFullyQualifiedName}.gs.cs"; + + private const string ShortCircuitAttributeFullyQualifiedName = $"{AttributesNamespace}.{ShortCircuitAttributeName}"; + + internal static readonly SourceText ShortCircuitAttributeSourceText; + + private static SourceText CreateShortCircuitAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Marks the annotated endpoint or class to short-circuit the request pipeline. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{ShortCircuitAttributeName}} : global::System.Attribute + { + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.SummaryAttribute.cs b/src/GeneratedEndpoints/Common/Constants.SummaryAttribute.cs new file mode 100644 index 0000000..ae0c537 --- /dev/null +++ b/src/GeneratedEndpoints/Common/Constants.SummaryAttribute.cs @@ -0,0 +1,42 @@ +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace GeneratedEndpoints.Common; + +internal static partial class Constants +{ + internal const string SummaryAttributeName = "SummaryAttribute"; + internal const string SummaryAttributeHint = $"{SummaryAttributeFullyQualifiedName}.gs.cs"; + + private const string SummaryAttributeFullyQualifiedName = $"{AttributesNamespace}.{SummaryAttributeName}"; + + internal static readonly SourceText SummaryAttributeSourceText; + + private static SourceText CreateSummaryAttributeSourceText() => SourceText.From($$""" + {{FileHeader}} + + namespace {{AttributesNamespace}}; + + /// + /// Specifies the summary metadata for the annotated endpoint. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + internal sealed class {{SummaryAttributeName}} : global::System.Attribute + { + /// + /// Gets the summary value for the endpoint. + /// + public string Summary { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The summary to apply to the endpoint. + public {{SummaryAttributeName}}(string summary) + { + Summary = summary; + } + } + + """, Encoding.UTF8); +} diff --git a/src/GeneratedEndpoints/Common/Constants.cs b/src/GeneratedEndpoints/Common/Constants.cs index d7cb149..1c393b6 100644 --- a/src/GeneratedEndpoints/Common/Constants.cs +++ b/src/GeneratedEndpoints/Common/Constants.cs @@ -7,57 +7,6 @@ internal static partial class Constants internal const string NameAttributeNamedParameter = "Name"; internal const string IsOptionalAttributeNamedParameter = "IsOptional"; - internal const string RequireAuthorizationAttributeName = "RequireAuthorizationAttribute"; - internal const string RequireAuthorizationAttributeHint = $"{RequireAuthorizationAttributeFullyQualifiedName}.gs.cs"; - - internal const string RequireCorsAttributeName = "RequireCorsAttribute"; - internal const string RequireCorsAttributeHint = $"{RequireCorsAttributeFullyQualifiedName}.gs.cs"; - - internal const string RequireRateLimitingAttributeName = "RequireRateLimitingAttribute"; - internal const string RequireRateLimitingAttributeHint = $"{RequireRateLimitingAttributeFullyQualifiedName}.gs.cs"; - - internal const string RequireHostAttributeName = "RequireHostAttribute"; - internal const string RequireHostAttributeHint = $"{RequireHostAttributeFullyQualifiedName}.gs.cs"; - - internal const string DisableAntiforgeryAttributeName = "DisableAntiforgeryAttribute"; - internal const string DisableAntiforgeryAttributeHint = $"{DisableAntiforgeryAttributeFullyQualifiedName}.gs.cs"; - - internal const string ShortCircuitAttributeName = "ShortCircuitAttribute"; - internal const string ShortCircuitAttributeHint = $"{ShortCircuitAttributeFullyQualifiedName}.gs.cs"; - - internal const string DisableRequestTimeoutAttributeName = "DisableRequestTimeoutAttribute"; - internal const string DisableRequestTimeoutAttributeHint = $"{DisableRequestTimeoutAttributeFullyQualifiedName}.gs.cs"; - - internal const string DisableValidationAttributeName = "DisableValidationAttribute"; - internal const string DisableValidationAttributeHint = $"{DisableValidationAttributeFullyQualifiedName}.gs.cs"; - - internal const string RequestTimeoutAttributeName = "RequestTimeoutAttribute"; - internal const string RequestTimeoutAttributeHint = $"{RequestTimeoutAttributeFullyQualifiedName}.gs.cs"; - - internal const string OrderAttributeName = "OrderAttribute"; - internal const string OrderAttributeHint = $"{OrderAttributeFullyQualifiedName}.gs.cs"; - - internal const string MapGroupAttributeName = "MapGroupAttribute"; - internal const string MapGroupAttributeHint = $"{MapGroupAttributeFullyQualifiedName}.gs.cs"; - - internal const string SummaryAttributeName = "SummaryAttribute"; - internal const string SummaryAttributeHint = $"{SummaryAttributeFullyQualifiedName}.gs.cs"; - - internal const string EndpointFilterAttributeName = "EndpointFilterAttribute"; - internal const string EndpointFilterAttributeHint = $"{EndpointFilterAttributeFullyQualifiedName}.gs.cs"; - - internal const string AcceptsAttributeName = "AcceptsAttribute"; - internal const string AcceptsAttributeHint = $"{AcceptsAttributeFullyQualifiedName}.gs.cs"; - - internal const string ProducesResponseAttributeName = "ProducesResponseAttribute"; - internal const string ProducesResponseAttributeHint = $"{ProducesResponseAttributeFullyQualifiedName}.gs.cs"; - - internal const string ProducesProblemAttributeName = "ProducesProblemAttribute"; - internal const string ProducesProblemAttributeHint = $"{ProducesProblemAttributeFullyQualifiedName}.gs.cs"; - - internal const string ProducesValidationProblemAttributeName = "ProducesValidationProblemAttribute"; - internal const string ProducesValidationProblemAttributeHint = $"{ProducesValidationProblemAttributeFullyQualifiedName}.gs.cs"; - internal const string RoutingNamespace = $"{BaseNamespace}.Routing"; internal const string AddEndpointHandlersClassName = "EndpointServicesExtensions"; @@ -74,24 +23,7 @@ internal static partial class Constants internal const string GlobalPrefix = "global::"; private const string BaseNamespace = "Microsoft.AspNetCore.Generated"; - private const string AttributesNamespace = $"{BaseNamespace}.Attributes"; - private const string RequireAuthorizationAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireAuthorizationAttributeName}"; - private const string RequireCorsAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireCorsAttributeName}"; - private const string RequireRateLimitingAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireRateLimitingAttributeName}"; - private const string RequireHostAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireHostAttributeName}"; - private const string DisableAntiforgeryAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableAntiforgeryAttributeName}"; - private const string ShortCircuitAttributeFullyQualifiedName = $"{AttributesNamespace}.{ShortCircuitAttributeName}"; - private const string DisableRequestTimeoutAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableRequestTimeoutAttributeName}"; - private const string DisableValidationAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableValidationAttributeName}"; - private const string RequestTimeoutAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequestTimeoutAttributeName}"; - private const string OrderAttributeFullyQualifiedName = $"{AttributesNamespace}.{OrderAttributeName}"; - private const string MapGroupAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapGroupAttributeName}"; - private const string SummaryAttributeFullyQualifiedName = $"{AttributesNamespace}.{SummaryAttributeName}"; - private const string EndpointFilterAttributeFullyQualifiedName = $"{AttributesNamespace}.{EndpointFilterAttributeName}"; - private const string AcceptsAttributeFullyQualifiedName = $"{AttributesNamespace}.{AcceptsAttributeName}"; - private const string ProducesResponseAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesResponseAttributeName}"; - private const string ProducesProblemAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesProblemAttributeName}"; - private const string ProducesValidationProblemAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesValidationProblemAttributeName}"; + internal const string AttributesNamespace = $"{BaseNamespace}.Attributes"; private const string AddEndpointHandlersMethodFullyQualifiedName = $"{RoutingNamespace}.{AddEndpointHandlersMethodName}"; private const string UseEndpointHandlersMethodFullyQualifiedName = $"{RoutingNamespace}.{UseEndpointHandlersMethodName}"; } From a719d1299bbee03643f59551710299cd4a403cba Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Carle <29762210+jscarle@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:40:30 -0500 Subject: [PATCH 2/6] Inline attribute initialization (#77) --- .../Common/Constants.AcceptsAttribute.cs | 2 +- .../Constants.AttributesInitialization.cs | 31 ------------------- .../Constants.DisableAntiforgeryAttribute.cs | 2 +- ...onstants.DisableRequestTimeoutAttribute.cs | 2 +- .../Constants.DisableValidationAttribute.cs | 2 +- .../Constants.EndpointFilterAttribute.cs | 2 +- .../Common/Constants.GeneratedSources.cs | 13 -------- .../Common/Constants.MapGroupAttribute.cs | 2 +- .../Common/Constants.OrderAttribute.cs | 2 +- .../Constants.ProducesProblemAttribute.cs | 2 +- .../Constants.ProducesResponseAttribute.cs | 2 +- ...ants.ProducesValidationProblemAttribute.cs | 2 +- .../Constants.RequestTimeoutAttribute.cs | 2 +- ...Constants.RequireAuthorizationAttribute.cs | 2 +- .../Common/Constants.RequireCorsAttribute.cs | 2 +- .../Common/Constants.RequireHostAttribute.cs | 2 +- .../Constants.RequireRateLimitingAttribute.cs | 2 +- .../Common/Constants.ShortCircuitAttribute.cs | 2 +- .../Common/Constants.SummaryAttribute.cs | 2 +- src/GeneratedEndpoints/Common/Constants.cs | 13 ++++++++ 20 files changed, 30 insertions(+), 61 deletions(-) delete mode 100644 src/GeneratedEndpoints/Common/Constants.AttributesInitialization.cs diff --git a/src/GeneratedEndpoints/Common/Constants.AcceptsAttribute.cs b/src/GeneratedEndpoints/Common/Constants.AcceptsAttribute.cs index c07e4d7..25c413a 100644 --- a/src/GeneratedEndpoints/Common/Constants.AcceptsAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.AcceptsAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string AcceptsAttributeFullyQualifiedName = $"{AttributesNamespace}.{AcceptsAttributeName}"; - internal static readonly SourceText AcceptsAttributeSourceText; + internal static readonly SourceText AcceptsAttributeSourceText = CreateAcceptsAttributeSourceText(); private static SourceText CreateAcceptsAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.AttributesInitialization.cs b/src/GeneratedEndpoints/Common/Constants.AttributesInitialization.cs deleted file mode 100644 index 41461ee..0000000 --- a/src/GeneratedEndpoints/Common/Constants.AttributesInitialization.cs +++ /dev/null @@ -1,31 +0,0 @@ -#pragma warning disable S3963 -#pragma warning disable CA1810 - -namespace GeneratedEndpoints.Common; - -internal static partial class Constants -{ - static Constants() - { - RequireAuthorizationAttributeSourceText = CreateRequireAuthorizationAttributeSourceText(); - RequireCorsAttributeSourceText = CreateRequireCorsAttributeSourceText(); - RequireRateLimitingAttributeSourceText = CreateRequireRateLimitingAttributeSourceText(); - RequireHostAttributeSourceText = CreateRequireHostAttributeSourceText(); - DisableAntiforgeryAttributeSourceText = CreateDisableAntiforgeryAttributeSourceText(); - ShortCircuitAttributeSourceText = CreateShortCircuitAttributeSourceText(); - DisableRequestTimeoutAttributeSourceText = CreateDisableRequestTimeoutAttributeSourceText(); - DisableValidationAttributeSourceText = CreateDisableValidationAttributeSourceText(); - RequestTimeoutAttributeSourceText = CreateRequestTimeoutAttributeSourceText(); - OrderAttributeSourceText = CreateOrderAttributeSourceText(); - MapGroupAttributeSourceText = CreateMapGroupAttributeSourceText(); - SummaryAttributeSourceText = CreateSummaryAttributeSourceText(); - EndpointFilterAttributeSourceText = CreateEndpointFilterAttributeSourceText(); - AcceptsAttributeSourceText = CreateAcceptsAttributeSourceText(); - ProducesResponseAttributeSourceText = CreateProducesResponseAttributeSourceText(); - ProducesProblemAttributeSourceText = CreateProducesProblemAttributeSourceText(); - ProducesValidationProblemAttributeSourceText = CreateProducesValidationProblemAttributeSourceText(); - } -} - -#pragma warning restore CA1810 -#pragma warning restore S3963 diff --git a/src/GeneratedEndpoints/Common/Constants.DisableAntiforgeryAttribute.cs b/src/GeneratedEndpoints/Common/Constants.DisableAntiforgeryAttribute.cs index 4a10f94..7973b79 100644 --- a/src/GeneratedEndpoints/Common/Constants.DisableAntiforgeryAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.DisableAntiforgeryAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string DisableAntiforgeryAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableAntiforgeryAttributeName}"; - internal static readonly SourceText DisableAntiforgeryAttributeSourceText; + internal static readonly SourceText DisableAntiforgeryAttributeSourceText = CreateDisableAntiforgeryAttributeSourceText(); private static SourceText CreateDisableAntiforgeryAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.DisableRequestTimeoutAttribute.cs b/src/GeneratedEndpoints/Common/Constants.DisableRequestTimeoutAttribute.cs index 1ab9d08..99225ef 100644 --- a/src/GeneratedEndpoints/Common/Constants.DisableRequestTimeoutAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.DisableRequestTimeoutAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string DisableRequestTimeoutAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableRequestTimeoutAttributeName}"; - internal static readonly SourceText DisableRequestTimeoutAttributeSourceText; + internal static readonly SourceText DisableRequestTimeoutAttributeSourceText = CreateDisableRequestTimeoutAttributeSourceText(); private static SourceText CreateDisableRequestTimeoutAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.DisableValidationAttribute.cs b/src/GeneratedEndpoints/Common/Constants.DisableValidationAttribute.cs index 8ac0c6c..f98194e 100644 --- a/src/GeneratedEndpoints/Common/Constants.DisableValidationAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.DisableValidationAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string DisableValidationAttributeFullyQualifiedName = $"{AttributesNamespace}.{DisableValidationAttributeName}"; - internal static readonly SourceText DisableValidationAttributeSourceText; + internal static readonly SourceText DisableValidationAttributeSourceText = CreateDisableValidationAttributeSourceText(); private static SourceText CreateDisableValidationAttributeSourceText() => SourceText.From($$""" #if NET10_0_OR_GREATER diff --git a/src/GeneratedEndpoints/Common/Constants.EndpointFilterAttribute.cs b/src/GeneratedEndpoints/Common/Constants.EndpointFilterAttribute.cs index 97bb1c3..71fd661 100644 --- a/src/GeneratedEndpoints/Common/Constants.EndpointFilterAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.EndpointFilterAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string EndpointFilterAttributeFullyQualifiedName = $"{AttributesNamespace}.{EndpointFilterAttributeName}"; - internal static readonly SourceText EndpointFilterAttributeSourceText; + internal static readonly SourceText EndpointFilterAttributeSourceText = CreateEndpointFilterAttributeSourceText(); private static SourceText CreateEndpointFilterAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.GeneratedSources.cs b/src/GeneratedEndpoints/Common/Constants.GeneratedSources.cs index 81ac968..9748a57 100644 --- a/src/GeneratedEndpoints/Common/Constants.GeneratedSources.cs +++ b/src/GeneratedEndpoints/Common/Constants.GeneratedSources.cs @@ -6,19 +6,6 @@ namespace GeneratedEndpoints.Common; internal static partial class Constants { - internal static readonly string FileHeader = $""" - //----------------------------------------------------------------------------- - // - // This code was generated by {nameof(MinimalApiGenerator)} which can be found - // in the {typeof(MinimalApiGenerator).Namespace} namespace. - // - // Changes to this file may cause incorrect behavior - // and will be lost if the code is regenerated. - // - //----------------------------------------------------------------------------- - - #nullable enable - """; internal static readonly ImmutableArray HttpAttributeDefinitions = [ diff --git a/src/GeneratedEndpoints/Common/Constants.MapGroupAttribute.cs b/src/GeneratedEndpoints/Common/Constants.MapGroupAttribute.cs index cc29b68..4b61e0b 100644 --- a/src/GeneratedEndpoints/Common/Constants.MapGroupAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.MapGroupAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string MapGroupAttributeFullyQualifiedName = $"{AttributesNamespace}.{MapGroupAttributeName}"; - internal static readonly SourceText MapGroupAttributeSourceText; + internal static readonly SourceText MapGroupAttributeSourceText = CreateMapGroupAttributeSourceText(); private static SourceText CreateMapGroupAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.OrderAttribute.cs b/src/GeneratedEndpoints/Common/Constants.OrderAttribute.cs index 64e06d5..a52e2fe 100644 --- a/src/GeneratedEndpoints/Common/Constants.OrderAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.OrderAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string OrderAttributeFullyQualifiedName = $"{AttributesNamespace}.{OrderAttributeName}"; - internal static readonly SourceText OrderAttributeSourceText; + internal static readonly SourceText OrderAttributeSourceText = CreateOrderAttributeSourceText(); private static SourceText CreateOrderAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.ProducesProblemAttribute.cs b/src/GeneratedEndpoints/Common/Constants.ProducesProblemAttribute.cs index 269541f..80a6d5f 100644 --- a/src/GeneratedEndpoints/Common/Constants.ProducesProblemAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.ProducesProblemAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string ProducesProblemAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesProblemAttributeName}"; - internal static readonly SourceText ProducesProblemAttributeSourceText; + internal static readonly SourceText ProducesProblemAttributeSourceText = CreateProducesProblemAttributeSourceText(); private static SourceText CreateProducesProblemAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.ProducesResponseAttribute.cs b/src/GeneratedEndpoints/Common/Constants.ProducesResponseAttribute.cs index 191f29f..a7bee4b 100644 --- a/src/GeneratedEndpoints/Common/Constants.ProducesResponseAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.ProducesResponseAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string ProducesResponseAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesResponseAttributeName}"; - internal static readonly SourceText ProducesResponseAttributeSourceText; + internal static readonly SourceText ProducesResponseAttributeSourceText = CreateProducesResponseAttributeSourceText(); private static SourceText CreateProducesResponseAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.ProducesValidationProblemAttribute.cs b/src/GeneratedEndpoints/Common/Constants.ProducesValidationProblemAttribute.cs index b1f1708..784e5c0 100644 --- a/src/GeneratedEndpoints/Common/Constants.ProducesValidationProblemAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.ProducesValidationProblemAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string ProducesValidationProblemAttributeFullyQualifiedName = $"{AttributesNamespace}.{ProducesValidationProblemAttributeName}"; - internal static readonly SourceText ProducesValidationProblemAttributeSourceText; + internal static readonly SourceText ProducesValidationProblemAttributeSourceText = CreateProducesValidationProblemAttributeSourceText(); private static SourceText CreateProducesValidationProblemAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.RequestTimeoutAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequestTimeoutAttribute.cs index c77fbaf..7c7831e 100644 --- a/src/GeneratedEndpoints/Common/Constants.RequestTimeoutAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.RequestTimeoutAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string RequestTimeoutAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequestTimeoutAttributeName}"; - internal static readonly SourceText RequestTimeoutAttributeSourceText; + internal static readonly SourceText RequestTimeoutAttributeSourceText = CreateRequestTimeoutAttributeSourceText(); private static SourceText CreateRequestTimeoutAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.RequireAuthorizationAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequireAuthorizationAttribute.cs index 7d35f79..bc54ccc 100644 --- a/src/GeneratedEndpoints/Common/Constants.RequireAuthorizationAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.RequireAuthorizationAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string RequireAuthorizationAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireAuthorizationAttributeName}"; - internal static readonly SourceText RequireAuthorizationAttributeSourceText; + internal static readonly SourceText RequireAuthorizationAttributeSourceText = CreateRequireAuthorizationAttributeSourceText(); private static SourceText CreateRequireAuthorizationAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.RequireCorsAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequireCorsAttribute.cs index 766ee22..52fd3e8 100644 --- a/src/GeneratedEndpoints/Common/Constants.RequireCorsAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.RequireCorsAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string RequireCorsAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireCorsAttributeName}"; - internal static readonly SourceText RequireCorsAttributeSourceText; + internal static readonly SourceText RequireCorsAttributeSourceText = CreateRequireCorsAttributeSourceText(); private static SourceText CreateRequireCorsAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.RequireHostAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequireHostAttribute.cs index d8966e0..7a040a9 100644 --- a/src/GeneratedEndpoints/Common/Constants.RequireHostAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.RequireHostAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string RequireHostAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireHostAttributeName}"; - internal static readonly SourceText RequireHostAttributeSourceText; + internal static readonly SourceText RequireHostAttributeSourceText = CreateRequireHostAttributeSourceText(); private static SourceText CreateRequireHostAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.RequireRateLimitingAttribute.cs b/src/GeneratedEndpoints/Common/Constants.RequireRateLimitingAttribute.cs index ad74b5d..fa4f88d 100644 --- a/src/GeneratedEndpoints/Common/Constants.RequireRateLimitingAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.RequireRateLimitingAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string RequireRateLimitingAttributeFullyQualifiedName = $"{AttributesNamespace}.{RequireRateLimitingAttributeName}"; - internal static readonly SourceText RequireRateLimitingAttributeSourceText; + internal static readonly SourceText RequireRateLimitingAttributeSourceText = CreateRequireRateLimitingAttributeSourceText(); private static SourceText CreateRequireRateLimitingAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.ShortCircuitAttribute.cs b/src/GeneratedEndpoints/Common/Constants.ShortCircuitAttribute.cs index dc5104d..37e2b5c 100644 --- a/src/GeneratedEndpoints/Common/Constants.ShortCircuitAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.ShortCircuitAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string ShortCircuitAttributeFullyQualifiedName = $"{AttributesNamespace}.{ShortCircuitAttributeName}"; - internal static readonly SourceText ShortCircuitAttributeSourceText; + internal static readonly SourceText ShortCircuitAttributeSourceText = CreateShortCircuitAttributeSourceText(); private static SourceText CreateShortCircuitAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.SummaryAttribute.cs b/src/GeneratedEndpoints/Common/Constants.SummaryAttribute.cs index ae0c537..ab45af4 100644 --- a/src/GeneratedEndpoints/Common/Constants.SummaryAttribute.cs +++ b/src/GeneratedEndpoints/Common/Constants.SummaryAttribute.cs @@ -10,7 +10,7 @@ internal static partial class Constants private const string SummaryAttributeFullyQualifiedName = $"{AttributesNamespace}.{SummaryAttributeName}"; - internal static readonly SourceText SummaryAttributeSourceText; + internal static readonly SourceText SummaryAttributeSourceText = CreateSummaryAttributeSourceText(); private static SourceText CreateSummaryAttributeSourceText() => SourceText.From($$""" {{FileHeader}} diff --git a/src/GeneratedEndpoints/Common/Constants.cs b/src/GeneratedEndpoints/Common/Constants.cs index 1c393b6..b170d78 100644 --- a/src/GeneratedEndpoints/Common/Constants.cs +++ b/src/GeneratedEndpoints/Common/Constants.cs @@ -2,6 +2,19 @@ namespace GeneratedEndpoints.Common; internal static partial class Constants { + internal const string FileHeader = + "//-----------------------------------------------------------------------------\n" + + "// \n" + + "// This code was generated by MinimalApiGenerator which can be found\n" + + "// in the GeneratedEndpoints namespace.\n" + + "//\n" + + "// Changes to this file may cause incorrect behavior\n" + + "// and will be lost if the code is regenerated.\n" + + "// \n" + + "//-----------------------------------------------------------------------------\n" + + "\n" + + "#nullable enable"; + internal const string FallbackHttpMethod = "__FALLBACK__"; internal const string NameAttributeNamedParameter = "Name"; From 697b8521716f24e012f05fe56436221caf08f5e8 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Carle <29762210+jscarle@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:37:49 -0500 Subject: [PATCH 3/6] Fix Accepts attribute handling (#78) --- .../Common/EndpointConfigurationFactory.cs | 11 ++++++++--- .../GeneratedEndpoints.Tests/Common/SourceFactory.cs | 2 +- ...trix_0BE3EC6390D4_MapEndpointHandlers.verified.txt | 1 + ...trix_2F85DF1A025B_MapEndpointHandlers.verified.txt | 1 + ...trix_9F5FE6E1F139_MapEndpointHandlers.verified.txt | 1 + ....AcceptsAttribute_MapEndpointHandlers.verified.txt | 3 ++- ...tipleContentTypes_MapEndpointHandlers.verified.txt | 3 ++- 7 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs b/src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs index f8dccd5..242772a 100644 --- a/src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs +++ b/src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs @@ -207,8 +207,10 @@ public static EndpointConfiguration Create(ISymbol symbol) private static void TryAddAcceptsMetadata(AttributeData attribute, INamedTypeSymbol attributeClass, ref List? accepts) { + var isGenericAttribute = attributeClass is { IsGenericType: true, TypeArguments.Length: 1 }; + string? requestType; - if (attributeClass is { IsGenericType: true, TypeArguments.Length: 1 }) + if (isGenericAttribute) requestType = attributeClass.TypeArguments[0] .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); else @@ -218,8 +220,11 @@ private static void TryAddAcceptsMetadata(AttributeData attribute, INamedTypeSym if (requestType is null) return; - var contentType = attribute.GetConstructorStringValue() ?? ApplicationJsonContentType; - var additionalContentTypes = attribute.GetConstructorStringArray(1); + var contentTypeIndex = isGenericAttribute ? 0 : 1; + var additionalContentTypesIndex = isGenericAttribute ? 1 : 2; + + var contentType = attribute.GetConstructorStringValue(contentTypeIndex) ?? ApplicationJsonContentType; + var additionalContentTypes = attribute.GetConstructorStringArray(additionalContentTypesIndex); var isOptional = attribute.GetNamedBoolValue(IsOptionalAttributeNamedParameter); var acceptMetadata = new AcceptsMetadata(requestType, contentType, additionalContentTypes, isOptional); diff --git a/tests/GeneratedEndpoints.Tests/Common/SourceFactory.cs b/tests/GeneratedEndpoints.Tests/Common/SourceFactory.cs index 035ce8d..a9f928f 100644 --- a/tests/GeneratedEndpoints.Tests/Common/SourceFactory.cs +++ b/tests/GeneratedEndpoints.Tests/Common/SourceFactory.cs @@ -370,7 +370,7 @@ public static string BuildContractsAndBindingSource( if (includeAccepts) { var secondContentType = string.IsNullOrWhiteSpace(acceptsContentType2) ? "" : $", \"{acceptsContentType2}\""; - builder.AppendLine($" [Accepts(\"{acceptsContentType1 ?? "application/json"}\"{secondContentType})]"); + builder.AppendLine($" [Accepts(typeof(RequestRecord), \"{acceptsContentType1 ?? "application/json"}\"{secondContentType})]"); } if (includeGenericAccepts) diff --git a/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_0BE3EC6390D4_MapEndpointHandlers.verified.txt b/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_0BE3EC6390D4_MapEndpointHandlers.verified.txt index c9e81bb..33f7099 100644 --- a/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_0BE3EC6390D4_MapEndpointHandlers.verified.txt +++ b/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_0BE3EC6390D4_MapEndpointHandlers.verified.txt @@ -26,6 +26,7 @@ internal static class EndpointRouteBuilderExtensions .WithName("Handle") .WithDisplayName("Contract endpoint") .WithTags("Contracts", "Bindings") + .Accepts("application/custom") .Produces(200, "application/problem+json") .ProducesProblem(500, "application/problem+json") .AllowAnonymous(); diff --git a/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_2F85DF1A025B_MapEndpointHandlers.verified.txt b/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_2F85DF1A025B_MapEndpointHandlers.verified.txt index d535349..ee24747 100644 --- a/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_2F85DF1A025B_MapEndpointHandlers.verified.txt +++ b/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_2F85DF1A025B_MapEndpointHandlers.verified.txt @@ -26,6 +26,7 @@ internal static class EndpointRouteBuilderExtensions .WithName("Handle") .WithDisplayName("Contract endpoint") .ExcludeFromDescription() + .Accepts("application/xml") .Produces(200, "application/json") .ProducesProblem(500, "application/json") .RequireAuthorization("ContractsPolicy"); diff --git a/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_9F5FE6E1F139_MapEndpointHandlers.verified.txt b/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_9F5FE6E1F139_MapEndpointHandlers.verified.txt index 74ba401..0930bfa 100644 --- a/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_9F5FE6E1F139_MapEndpointHandlers.verified.txt +++ b/tests/GeneratedEndpoints.Tests/GeneratedSourceTests.ContractsAndBindingMatrix_9F5FE6E1F139_MapEndpointHandlers.verified.txt @@ -28,6 +28,7 @@ internal static class EndpointRouteBuilderExtensions .WithSummary("Gets detailed content.") .WithDescription("Shows binding and contract combinations.") .WithTags("Contracts", "Bindings") + .Accepts("application/xml", "text/xml") .Accepts("application/xml") .Produces(200, "application/json", "text/json") .Produces(200, "application/json") diff --git a/tests/GeneratedEndpoints.Tests/IndividualTests.AcceptsAttribute_MapEndpointHandlers.verified.txt b/tests/GeneratedEndpoints.Tests/IndividualTests.AcceptsAttribute_MapEndpointHandlers.verified.txt index d8a37e9..be1b5fd 100644 --- a/tests/GeneratedEndpoints.Tests/IndividualTests.AcceptsAttribute_MapEndpointHandlers.verified.txt +++ b/tests/GeneratedEndpoints.Tests/IndividualTests.AcceptsAttribute_MapEndpointHandlers.verified.txt @@ -23,7 +23,8 @@ internal static class EndpointRouteBuilderExtensions internal static IEndpointRouteBuilder MapEndpointHandlers(this IEndpointRouteBuilder builder) { builder.MapGet("/contracts/{id:int}", global::GeneratedEndpointsTests.ContractEndpoints.Handle) - .WithName("Handle"); + .WithName("Handle") + .Accepts("application/custom"); return builder; } diff --git a/tests/GeneratedEndpoints.Tests/IndividualTests.AcceptsMultipleContentTypes_MapEndpointHandlers.verified.txt b/tests/GeneratedEndpoints.Tests/IndividualTests.AcceptsMultipleContentTypes_MapEndpointHandlers.verified.txt index d8a37e9..104e71d 100644 --- a/tests/GeneratedEndpoints.Tests/IndividualTests.AcceptsMultipleContentTypes_MapEndpointHandlers.verified.txt +++ b/tests/GeneratedEndpoints.Tests/IndividualTests.AcceptsMultipleContentTypes_MapEndpointHandlers.verified.txt @@ -23,7 +23,8 @@ internal static class EndpointRouteBuilderExtensions internal static IEndpointRouteBuilder MapEndpointHandlers(this IEndpointRouteBuilder builder) { builder.MapGet("/contracts/{id:int}", global::GeneratedEndpointsTests.ContractEndpoints.Handle) - .WithName("Handle"); + .WithName("Handle") + .Accepts("application/json", "text/json"); return builder; } From 1f180a1efc8d9cb90869141f89b3fbe583171e9b Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Carle <29762210+jscarle@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:45:17 -0500 Subject: [PATCH 4/6] Use pooled builders for declaration names (#79) --- src/GeneratedEndpoints/Common/DeclarationExtensions.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/GeneratedEndpoints/Common/DeclarationExtensions.cs b/src/GeneratedEndpoints/Common/DeclarationExtensions.cs index aa020a6..5b65c18 100644 --- a/src/GeneratedEndpoints/Common/DeclarationExtensions.cs +++ b/src/GeneratedEndpoints/Common/DeclarationExtensions.cs @@ -1,5 +1,3 @@ -using System.Text; - namespace GeneratedEndpoints.Common; /// Provides extension methods for working with declarations. @@ -10,7 +8,7 @@ internal static class DeclarationExtensions /// The namespace represented by the declarations. public static string ToNamespace(this EquatableImmutableArray declarations) { - var builder = new StringBuilder(); + var builder = StringBuilderPool.Get(); for (var index = 0; index < declarations.Count; index++) { @@ -23,7 +21,7 @@ public static string ToNamespace(this EquatableImmutableArray decla builder.Append(declaration.Name); } - return builder.ToString(); + return StringBuilderPool.ToStringAndReturn(builder); } /// Converts a list of declarations to their fully qualified name. @@ -31,7 +29,7 @@ public static string ToNamespace(this EquatableImmutableArray decla /// The fully qualified name represented by the declarations. public static string ToFullyQualifiedName(this EquatableImmutableArray declarations) { - var builder = new StringBuilder(); + var builder = StringBuilderPool.Get(); for (var index = 0; index < declarations.Count; index++) { @@ -48,7 +46,7 @@ public static string ToFullyQualifiedName(this EquatableImmutableArray Where(this EquatableImmutableArray declarations, Func predicate) From da713d5b37629883b3b5b4232676b0d51f0eee24 Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Carle <29762210+jscarle@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:01:27 -0500 Subject: [PATCH 5/6] Skip instance endpoint generation for abstract classes (#80) --- .../AddEndpointHandlersGenerator.cs | 2 +- .../Common/RequestHandlerClass.cs | 1 + .../Common/RequestHandlerClassHelper.cs | 2 ++ src/GeneratedEndpoints/MinimalApiGenerator.cs | 3 ++ .../Common/SourceFactory.cs | 14 +++++++++ ...eHandlers_AddEndpointHandlers.verified.txt | 23 ++++++++++++++ ...eHandlers_MapEndpointHandlers.verified.txt | 30 +++++++++++++++++++ .../IndividualTests.cs | 12 ++++++++ 8 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/GeneratedEndpoints.Tests/IndividualTests.AbstractClassSkipsInstanceHandlers_AddEndpointHandlers.verified.txt create mode 100644 tests/GeneratedEndpoints.Tests/IndividualTests.AbstractClassSkipsInstanceHandlers_MapEndpointHandlers.verified.txt diff --git a/src/GeneratedEndpoints/AddEndpointHandlersGenerator.cs b/src/GeneratedEndpoints/AddEndpointHandlersGenerator.cs index 6a6551f..67e9631 100644 --- a/src/GeneratedEndpoints/AddEndpointHandlersGenerator.cs +++ b/src/GeneratedEndpoints/AddEndpointHandlersGenerator.cs @@ -18,7 +18,7 @@ public static void GenerateSource(SourceProductionContext context, ImmutableSort context.CancellationToken.ThrowIfCancellationRequested(); var nonStaticClassNames = grouped.Keys - .Where(x => !x.IsStatic) + .Where(x => !x.IsStatic && !x.IsAbstract) .Select(x => x.Name) .ToList(); var source = new StringBuilder(); diff --git a/src/GeneratedEndpoints/Common/RequestHandlerClass.cs b/src/GeneratedEndpoints/Common/RequestHandlerClass.cs index e3c8336..17ac27a 100644 --- a/src/GeneratedEndpoints/Common/RequestHandlerClass.cs +++ b/src/GeneratedEndpoints/Common/RequestHandlerClass.cs @@ -4,6 +4,7 @@ namespace GeneratedEndpoints.Common; { public required string Name { get; init; } public required bool IsStatic { get; init; } + public required bool IsAbstract { get; init; } public required bool HasConfigureMethod { get; init; } public required bool ConfigureMethodAcceptsServiceProvider { get; init; } public required EndpointConfiguration Configuration { get; init; } diff --git a/src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs b/src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs index 806a254..08e6d93 100644 --- a/src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs +++ b/src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs @@ -15,6 +15,7 @@ internal static class RequestHandlerClassHelper var name = classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var isStatic = classSymbol.IsStatic; + var isAbstract = classSymbol.IsAbstract; var configureMethodDetails = GetConfigureMethodDetails(classSymbol, cancellationToken); var classConfiguration = EndpointConfigurationFactory.Create(classSymbol); @@ -22,6 +23,7 @@ internal static class RequestHandlerClassHelper { Name = name, IsStatic = isStatic, + IsAbstract = isAbstract, HasConfigureMethod = configureMethodDetails.HasConfigureMethod, ConfigureMethodAcceptsServiceProvider = configureMethodDetails.ConfigureMethodAcceptsServiceProvider, Configuration = classConfiguration, diff --git a/src/GeneratedEndpoints/MinimalApiGenerator.cs b/src/GeneratedEndpoints/MinimalApiGenerator.cs index 873acfd..b6051b7 100644 --- a/src/GeneratedEndpoints/MinimalApiGenerator.cs +++ b/src/GeneratedEndpoints/MinimalApiGenerator.cs @@ -105,6 +105,9 @@ private static bool RequestHandlerFilter(SyntaxNode syntaxNode, CancellationToke var requestHandlerMethod = RequestHandlerMethodHelper.Create(methodSymbol, cancellationToken); + if (requestHandlerClass.Value.IsAbstract && !requestHandlerMethod.IsStatic) + return null; + var (httpMethod, pattern, name) = GetRequestHandlerAttribute(methodSymbol, attribute, cancellationToken); var requestHandler = new RequestHandler diff --git a/tests/GeneratedEndpoints.Tests/Common/SourceFactory.cs b/tests/GeneratedEndpoints.Tests/Common/SourceFactory.cs index a9f928f..5611197 100644 --- a/tests/GeneratedEndpoints.Tests/Common/SourceFactory.cs +++ b/tests/GeneratedEndpoints.Tests/Common/SourceFactory.cs @@ -29,6 +29,20 @@ public static string BuildFallbackSource(bool includeDefault, bool includeCustom return builder.ToString(); } + public static string BuildAbstractEndpointsSource() + { + return """ + internal abstract class AbstractEndpoints + { + [MapGet("/abstract/static")] + public static Ok Static() => TypedResults.Ok(); + + [MapPost("/abstract/instance")] + public Ok Instance() => TypedResults.Ok(); + } + """; + } + public static string BuildAuthorizationMatrixSource( bool classAllowAnonymous, bool methodAllowAnonymous, diff --git a/tests/GeneratedEndpoints.Tests/IndividualTests.AbstractClassSkipsInstanceHandlers_AddEndpointHandlers.verified.txt b/tests/GeneratedEndpoints.Tests/IndividualTests.AbstractClassSkipsInstanceHandlers_AddEndpointHandlers.verified.txt new file mode 100644 index 0000000..e27630b --- /dev/null +++ b/tests/GeneratedEndpoints.Tests/IndividualTests.AbstractClassSkipsInstanceHandlers_AddEndpointHandlers.verified.txt @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// +// This code was generated by MinimalApiGenerator which can be found +// in the GeneratedEndpoints namespace. +// +// Changes to this file may cause incorrect behavior +// and will be lost if the code is regenerated. +// +//----------------------------------------------------------------------------- + +#nullable enable + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.AspNetCore.Generated.Routing; + +internal static class EndpointServicesExtensions +{ + internal static void AddEndpointHandlers(this IServiceCollection services) + { + } +} diff --git a/tests/GeneratedEndpoints.Tests/IndividualTests.AbstractClassSkipsInstanceHandlers_MapEndpointHandlers.verified.txt b/tests/GeneratedEndpoints.Tests/IndividualTests.AbstractClassSkipsInstanceHandlers_MapEndpointHandlers.verified.txt new file mode 100644 index 0000000..eacb7f5 --- /dev/null +++ b/tests/GeneratedEndpoints.Tests/IndividualTests.AbstractClassSkipsInstanceHandlers_MapEndpointHandlers.verified.txt @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// +// This code was generated by MinimalApiGenerator which can be found +// in the GeneratedEndpoints namespace. +// +// Changes to this file may cause incorrect behavior +// and will be lost if the code is regenerated. +// +//----------------------------------------------------------------------------- + +#nullable enable + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Generated.Routing; + +internal static class EndpointRouteBuilderExtensions +{ + internal static IEndpointRouteBuilder MapEndpointHandlers(this IEndpointRouteBuilder builder) + { + builder.MapGet("/abstract/static", global::GeneratedEndpointsTests.AbstractEndpoints.Static) + .WithName("Static"); + + return builder; + } +} diff --git a/tests/GeneratedEndpoints.Tests/IndividualTests.cs b/tests/GeneratedEndpoints.Tests/IndividualTests.cs index bd0d6c3..4cf7e63 100644 --- a/tests/GeneratedEndpoints.Tests/IndividualTests.cs +++ b/tests/GeneratedEndpoints.Tests/IndividualTests.cs @@ -172,6 +172,13 @@ public async Task OrderMetadata() await VerifyIndividualAsync(source, nameof(OrderMetadata)); } + [Fact] + public async Task AbstractClassSkipsInstanceHandlers() + { + var source = AbstractEndpoints(); + await VerifyIndividualAsync(source, nameof(AbstractClassSkipsInstanceHandlers)); + } + [Fact] public async Task ClassMapGroup() { @@ -472,6 +479,11 @@ private static string FallbackScenario(bool includeDefault = false, bool include return SourceFactory.BuildFallbackSource(includeDefault, includeCustom, customRoute); } + private static string AbstractEndpoints() + { + return SourceFactory.BuildAbstractEndpointsSource(); + } + private static string AuthorizationScenario( bool classAllowAnonymous = false, bool methodAllowAnonymous = false, From 8888591be500294047db67f4f7347078557e2efa Mon Sep 17 00:00:00 2001 From: Jean-Sebastien Carle <29762210+jscarle@users.noreply.github.com> Date: Sun, 21 Dec 2025 21:04:10 -0500 Subject: [PATCH 6/6] Bump version. --- .../Common/EndpointConfigurationFactory.cs | 9 ++------- src/GeneratedEndpoints/GeneratedEndpoints.csproj | 8 ++++---- .../GeneratedEndpoints.Tests.csproj | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs b/src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs index 242772a..a03f476 100644 --- a/src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs +++ b/src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs @@ -170,12 +170,7 @@ public static EndpointConfiguration Create(ISymbol symbol) RequestTimeoutPolicyName = requestTimeoutPolicyName, Order = order, Group = groupIdentifier is not null && groupPattern is not null - ? new EndpointGroup - { - Identifier = groupIdentifier, - Pattern = groupPattern, - Name = groupName, - } + ? new EndpointGroup { Identifier = groupIdentifier, Pattern = groupPattern, Name = groupName } : null, }; } @@ -240,7 +235,7 @@ private static void TryAddProducesMetadata(AttributeData attribute, INamedTypeSy { var responseType = attributeClass.TypeArguments[0] .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - var statusCode = attribute.GetConstructorIntValue(0) ?? 200; + var statusCode = attribute.GetConstructorIntValue() ?? 200; var contentType = attribute.GetConstructorStringValue(1); var additionalContentTypes = attribute.GetConstructorStringArray(2); producesMetadata = new ProducesMetadata(responseType, statusCode, contentType, additionalContentTypes); diff --git a/src/GeneratedEndpoints/GeneratedEndpoints.csproj b/src/GeneratedEndpoints/GeneratedEndpoints.csproj index efddba0..a5c4bae 100644 --- a/src/GeneratedEndpoints/GeneratedEndpoints.csproj +++ b/src/GeneratedEndpoints/GeneratedEndpoints.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -36,9 +36,9 @@ GeneratedEndpoints - 10.0.2 - 10.0.2.0 - 10.0.2.0 + 10.0.3 + 10.0.3.0 + 10.0.3.0 en-US false diff --git a/tests/GeneratedEndpoints.Tests/GeneratedEndpoints.Tests.csproj b/tests/GeneratedEndpoints.Tests/GeneratedEndpoints.Tests.csproj index 1bd4374..8d28c01 100644 --- a/tests/GeneratedEndpoints.Tests/GeneratedEndpoints.Tests.csproj +++ b/tests/GeneratedEndpoints.Tests/GeneratedEndpoints.Tests.csproj @@ -14,7 +14,7 @@ - + all