0

I'm currently working on a console application that essentially mimics an ASP.NET Web API.

Everything was going smoothly until I ran into an issue regarding route attributes.

What I'm trying to achieve is something similar to the ASP.NET [Route] attribute, where you can define a route like this:

[Route("testFunc/{param}")]

In ASP.NET, this route pattern supports parameter placeholders (e.g., {param}), and Visual Studio even shows suggestions or highlights for parameters that match the method's signature.

However, when I try to implement my own custom Route attribute in a console application, I encounter limitations. For example, using string interpolation like this: [MyRoute($"myFunc/{param}")

doesn’t work because attribute arguments in C# must be constant expressions, and interpolated strings are evaluated at runtime. This is a language constraint, not specific to ASP.NET.

My question is: is it even possible to replicate this kind of routing param highlighting behavior in a standard console app?

I’ve looked at the definition of the RouteAttribute in ASP.NET, but it doesn’t seem to provide much insight into how the route pattern parsing and parameter binding actually work under the hood.

Any suggestions or resources would be appreciated

Below, I’ve included the ASP.NET definition of the Route attribute for reference.

public class RouteAttribute : Attribute, IRouteTemplateProvider
{
    private int? _order;

    public RouteAttribute([StringSyntax("Route")] string template)
    {
        Template = template ?? throw new ArgumentNullException(nameof(template));
    }

    [StringSyntax("Route")]
    public string Template { get; }

    public int Order
    {
        get { return _order ?? 0; }
        set { _order = value; }
    }

    int? IRouteTemplateProvider.Order => _order;

    public string? Name { get; set; }
}
10
  • 1
    1. Why exactly do you want this? 2. Route attributes do not use interpolated strings. Commented May 14 at 7:44
  • 2
    "I'm writing a mock ASP.NET API application" answers "what are you doing" question, not "Why are you doing this?". Note that ASP.NET Core is a console application. Commented May 14 at 7:51
  • 1
    is the question really - how does [Route] work the way it does for ASP.NET Core and how do I replicate it? Cause I think that's a bit too broad Commented May 14 at 8:08
  • 2
    great project to learn about attributes, reflection, and HTTP request handling. not really. It takes a lot to make this "magic" work. Routing is handled by an entire middleware, not the attributes. Those are just markers. HTTP request handling has a lot of steps beyond routing. If you want to learn about it, check the ASP.NET Core Middleware docs. Routing is #5 in 8 different "filters" that process a request before it reaches your code Commented May 14 at 8:45
  • 1
    Attributes don't intercept or modify calls, they're metadata (eg "markers" stored in the compiled code) used by other code to do "stuff". Reflection is used by the "other" code to check a target type or method for specific name patterns, arguments or attribute "markers". ASP.NET Core uses a Pipes-and-Filters architecture. Some of those filters use attribute markers to configure themselves. Others use naming conventions. Others use runtime configuration. Commented May 14 at 8:50

2 Answers 2

0

To make this work, you would have to write your own custom middleware that intercepts a request and matches route patterns.

Yes, you can not pass interpolated strings to attributes, but you could pass a template string and use regex or a simple parsing technique to match incoming request route to the endpoint route.

for example, you would need to define attribute like this: [MyRoute("myFunc/{param}")], then, when processing a request, you can take route of the request and match it to this template.

you could define a method in your attribute class that performs route matching. Keep in mind, this custom middleware would also need to check for similar template definitions, because you may only define a single unique route.

[AttributeUsage(AttributeTargets.Method)]
public class MyRouteAttribute : Attribute
{
    public string Template { get; }

    public MyRouteAttribute(string template)
    {
        Template = template;
    }

    public bool Matches(string requestRoute)
    {
        var templateSegments = Template.Split('/');
        var routeSegments = requestRoute.Split('/');

        if (templateSegments.Length != routeSegments.Length)
            return false;

        for (int i = 0; i < templateSegments.Length; i++)
        {
            if (templateSegments[i].StartsWith("{") && templateSegments[i].EndsWith("}"))
                continue; // Placeholder matches any segment but this can be enhanced with further type checking if specified in template like: "{param:int}"
            if (!string.Equals(templateSegments[i], routeSegments[i], StringComparison.OrdinalIgnoreCase))
                return false; // Segments must match exactly, without case, I suppose
        }

        return true;
    }
}

You could also specify a type of param like {param:int} and perform further type checking in Matches method, just like asp.net does.

This can also be done with regex but I am too lazy to study regex for this, I'm sure ChatGPT will spit it out in seconds.

As for suggestions and highlights, you would have to dig into source generators and analyzers to make that work.

Sign up to request clarification or add additional context in comments.

1 Comment

I don't think this thread is about actual route matching. It's about how to change the syntax highlighting in the IDE — similar to how it's done in ASP.NET's RouteAttribute, where method parameters that match the {paramName} in the route are displayed in different colors in the IDE.
-1

For any one who wonders whats the solution imma provide below code for that.

Steps to follow:

  • Install package for VS Extensions.
  • Create project from project template - VSIX Project
  • Add new Item - Editor Classifier

After that u should get 4 new classes. a. nameof your EditorClassifierItem + .cs later called Classifer b. ClassiferClassificationDefinition.cs c. ClassiferFormat.cs d. ClassiferProvider.cs

After so only two of them will be needed - Classifer.cs and ClassiferClassificationDefinition.cs

In ClassiferClassificationDefinition.cs you have to define your classification colors.

 internal static class EditorClassifier1ClassificationDefinition
    {
        // This disables "The field is never used" compiler's warning. Justification: the field is used by MEF.
#pragma warning disable 169

        /// <summary>
        /// Defines the "EditorClassifier1" classification type.
        /// </summary>
        [Export(typeof(ClassificationTypeDefinition))]
        [Name("EditorClassifier1")]
        private static ClassificationTypeDefinition typeDefinition;

        [Export(typeof(ClassificationTypeDefinition))]
        [Name("BlueRouteText")]
        private static ClassificationTypeDefinition blueType;

        [Export(typeof(ClassificationTypeDefinition))]
        [Name("GrayRouteParam")]
        private static ClassificationTypeDefinition grayType;


#pragma warning restore 169
    }

    [Export(typeof(EditorFormatDefinition))]
    [ClassificationType(ClassificationTypeNames = "BlueRouteText")]
    [Name("BlueRouteText")]
    [UserVisible(true)]
    [Order(After = Priority.Default)]
    internal sealed class BlueRouteTextFormat : ClassificationFormatDefinition
    {
        public BlueRouteTextFormat()
        {
            this.DisplayName = "Blue Route Text";
            this.ForegroundColor = Color.FromRgb(30, 101, 255);
        }
    }

    [Export(typeof(EditorFormatDefinition))]
    [ClassificationType(ClassificationTypeNames = "GrayRouteParam")]
    [Name("GrayRouteParam")]
    [UserVisible(true)]
    [Order(After = Priority.Default)]
    internal sealed class GrayRouteParamFormat : ClassificationFormatDefinition
    {
        public GrayRouteParamFormat()
        {
            this.DisplayName = "Gray Route Param";
            this.ForegroundColor = Colors.LightGray;
        }
    }

and in Classifer.cs you basically write a logic for the highlighting.

internal class EditorClassifier1 : IClassifier
{
    private readonly IClassificationType _blueTextType;
    private readonly IClassificationType _grayParamType;

    public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;

    public EditorClassifier1(IClassificationTypeRegistryService registry)
    {
        _blueTextType = registry.GetClassificationType("BlueRouteText");
        _grayParamType = registry.GetClassificationType("GrayRouteParam");
    }

    public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
    {
        var result = new List<ClassificationSpan>();

        string text = span.GetText();

        foreach (Match attrMatch in Regex.Matches(text, @"\[Route\s*\(\s*""([^""]+)""\s*\)\s*\]"))
        {
            string routeContent = attrMatch.Groups[1].Value;
            int routeStart = attrMatch.Groups[1].Index;

            foreach (Match segment in Regex.Matches(routeContent, @"\{([^{}]+)\}"))
            {
                int braceOpenIndex = routeContent.IndexOf("{" + segment.Groups[1].Value + "}");
                if (braceOpenIndex < 0)
                    continue;

                int globalOpen = span.Start + routeStart + braceOpenIndex;
                int paramLength = segment.Groups[1].Value.Length;

                var snapshot = span.Snapshot;

                // { – czerwony
                result.Add(new ClassificationSpan(
                    new SnapshotSpan(snapshot, new Span(globalOpen, 1)),
                    _blueTextType));

                // środek – szary
                result.Add(new ClassificationSpan(
                    new SnapshotSpan(snapshot, new Span(globalOpen + 1, paramLength)),
                    _grayParamType));

                // } – czerwony
                result.Add(new ClassificationSpan(
                    new SnapshotSpan(snapshot, new Span(globalOpen + 1 + paramLength, 1)),
                    _blueTextType));
            }
        }

        return result;
    }
}

before: before vsix

results: after vsix

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.