csharpsquid:S6965 false positive - "REST API actions should be annotated with an HTTP verb attribute

This rule explicitly looks for an [Http<Method>] attribute attributes on C# when the method is also annotated with [Route] attribute. An exception is granted for when the method is annotated with the [AcceptsVerbs] attribute.

The false positive comes from when you use a custom attribute that implements IActionHttpMethodProvider and IRouteTemplateProvider, which is what the rule really ought to be searching for.

For me, this is coming from Community Build v26.2.0.119303, MQR Mode, as well as the Visual Studio extension linter.

My opinion is that if your C# method is annotated with an attribute that implements IRouteTemplateProvider, then it should also be annotated with an attribute that implements IActionHttpMethodProvider, even if they’re the same attribute. This is in accordance with the spirit of the rule. The rule itself seems to be unaware of the interfaces that make the [AcceptsVerbs] attribute special to the runtime.

I am using .Net 10, although this also applies to older (and ancient) versions of .Net.

A self-contained example for this is a little tricky for my particular case, as the salient code is generated from a source generator. My particular attribute looks like this:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class HttpRouteAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
    public HttpRouteAttribute(Type routeType)
    {
        var routeSpecification = routeType
            .GetProperty(nameof(RouteSpecification), BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?
            .GetValue(null) as RouteSpecification
            ?? throw new ArgumentException($"Type \"{routeType.FullName}\" lacks a public static RouteSpecification property.", nameof(routeType));

        Template    = routeSpecification.RouteTemplate;
        Name        = routeSpecification.RouteName;
        HttpMethods = [routeSpecification.HttpMethod];
    }

    public string  Template { get; }
    public string? Name     { get; }
    public int?    Order    { get; set; }
    public IEnumerable<string> HttpMethods { get; }
}

where RouteSpecification is simply

public record RouteSpecification(string HttpMethod, string RouteTemplate, string RouteName);

and populated by the generated source. The usage looks like

    [HttpRoute(typeof(Routes.MyFeature.MyEndpoint))]
    public async Task<ActionResult<MyResult>> MyEndpoint([FromBody] MyEndpointArguments dto)
    { ... }

and MyEndpoint, which is the generated code, resembles the following:

public static partial class Routes
{
    public static partial class MyFeature
    {
        public static partial class MyEndpoint
        {
            public const string RouteTemplate = "api/my-feature/my-endpoint";
            public const string RouteName     = "MyCompany.MyWhatever.MyFeature.MyEndpoint";
            public const string HttpMethod    = "POST";

            public static RouteSpecification RouteSpecification => new(HttpMethod, RouteTemplate, RouteName);
        }
    }
}

Hello @Sros

This rule explicitly looks for an [Http<Method>] attribute attributes on C# when the method is also annotated with [Route] attribute.

It’s not the case exactly. The rule checks if your controller is annotated with the ApiController attribute (or an attribute that derives from it) and that there’s no attribute present in the controller that indicates that the API explorer should be ignored (see docs).
If the Route attribute is there or not does not really matter.

However, I confirm this is an FP.
I added a ticket to our backlog (internal only).

Thanks a lot for the report!