Skip to content

Add generic versions of attributes in ASP.NET Core #37767

@DamianEdwards

Description

@DamianEdwards

Background and Motivation

Now that C# supports generic attributes as of version 10/.NET 6, we should look through ASP.NET Core and consider adding generic versions of any attributes that accept type parameters (e.g. ConsumesAttribute).

Proposed API

namespace Microsoft.AspNetCore.Mvc;

//Original: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ModelMetadataTypeAttribute.cs
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+public class ModelMetadataTypeAttribute<TType> : ModelMetadataTypeAttribute
+{
+    public ModelMetadataTypeAttribute() : base(typeof(TType))
+    { }
+}

// Original: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Filters/MiddlewareFilterAttribute.cs
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+public class MiddlewareFilterAttribute<TConfigurationType> : MiddlewareFilterAttribute
+{
+    public MiddlewareFilterAttribute() : base(typeof(TConfigurationType))
+    { }
+}

// Original: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ServiceFilterAttribute.cs
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+[System.Diagnostics.DebuggerDisplay("ServiceFilter: Type={ServiceType} Order={Order}")]
+public class ServiceFilterAttribute<TFilterType> : ServiceFilterAttribute
+ where TFilterType : IFilterMetadata
+{
+    public ServiceFilterAttribute() : base(typeof(TFilterType))
+    { }
+}

// Original: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ModelBinderAttribute.cs
+[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
+public class ModelBinderAttribute<TBinderType> : ModelBinderAttribute
+ where TBinderType : IModelBinder
+{
+    public ModelBinderAttribute() : base(typeof(TBinderType))
+    { }
+}

// Original: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ProducesAttribute.cs
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+public class ProducesAttribute<TType> : ProducesAttribute
+{
+    public ProducesAttribute() : base(typeof(TType))
+    { }
+}

// Original: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ProducesResponseTypeAttribute.cs
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+public class ProducesResponseTypeAttribute<TType> : ProducesResponseTypeAttribute
+{
+    public ProducesResponseTypeAttribute(int statusCode) : base(typeof(TType), statusCode)
+    { }
+    public ProducesResponseTypeAttribute(int statusCode, string contentType, params string[] additionalContentTypes)
+             : base(typeof(TType), statusCode, contentType, additionalContentTypes)
+    { }
+}

In addition to what listed above the original list (#37767 (comment)) contains:

src/Mvc/Mvc.Razor/src/Compilation/RazorViewAttribute.cs:
  13:     public class RazorViewAttribute : Attribute

src/Razor/Microsoft.AspNetCore.Razor.Language/src/ProvideRazorExtensionInitializerAttribute.cs:
  9:     public class ProvideRazorExtensionInitializerAttribute : Attribute

RazorViewAttribute is marked as obsolete, so, I don't think we need to do anything (cc @mkArtakMSFT in case you want to have it added).
Also, ProvideRazorExtensionInitializerAttribute is not part of the ASPNET.Core repo anymore.

Usage Examples

[ProducesResponseType<ValidationProblemDetails>(StatusCodes.Status400BadRequest)]
[ProducesResponseType<Todo>(StatusCodes.Status201Created)]
[EndpointName(nameof(AddTodo))]
[Tags("TodoApi")]
async Task<IResult> AddTodo(Todo todo, TodoDb db)
{
    if (!MiniValidator.TryValidate(todo, out var errors))
        return Results.ValidationProblem(errors);

    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todos/{todo.Id}", todo);
}

Alternative Designs

Risks

Very small, this will introduce new types derived from the non-generic ones.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Priority:2Work that is important, but not critical for the releaseapi-approvedAPI was approved in API review, it can be implementedold-area-web-frameworks-do-not-use*DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labelstriage-focusAdd this label to flag the issue for focus at triage

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions