ASP.NET MVC and Razor Pages ship with some very sensible defaults. That doesn’t mean that all of our problems are solved. We may want to add new HTML helpers, navigation extension methods, or change the folder structure of our ASP.NET project to suit our needs better. While ASP.NET can handle all these changes, our IDEs might not understand our dramatic changes and display errors.

In this post, we’ll see how we can apply JetBrains.Annotations to give hints to our favorite JetBrains .NET products, ReSharper and Rider.

What Is JetBrains.Annotations

JetBrains.Annotations is a NuGet package that contains varying attributes designed to give hints to the suite of products developed by JetBrains. The library can create an enhanced experience for JetBrains users and ultimately lead to better development outcomes.

Annotations help reduce false positive warnings, explicitly declare purity and nullability in your code, deal with implicit usages of members, support special semantics of APIs in ASP.NET and XAML frameworks and otherwise increase the accuracy of inspections. JetBrains

To install the package, we can execute the following dotnet command targetting our ASP.NET project.

> dotnet add package JetBrains.Annotations

Favorite Annotations

As of writing this post, there are a total of 77 attributes in JetBrains.Annotations. We are going to look at some of the most helpful annotations and the behavior that they can enable in our IDEs.

Regex Anyone?

The RegexPatternAttribute can decorate parameters of any method and hints to our IDE that this particular parameter is a regular expression. The decoration can immediately light up features of our IDE and make working with regular expressions more helpful.

public static Match Find([RegexPattern] string pattern, string text)
{
    var regex = new Regex(pattern);
    return regex.Match(text);
}

regex pattern attribute help

regex pattern attribute help literal

Rider and Resharper allow us to mark any string literal as a regular expression. Still, the advantage of using this annotation is that it enables this behavior for all JetBrains users across your team automatically.

You can read more about regular expressions on the official JetBrains blog in a post written by Rachel Appel.

Stopping Nulls

Building off of the previous example, we may want to warn our team members that our method is not expecting null inputs. Our IDE can give preemptive warnings and catch bugs before our code deploys.

public static Match Find([RegexPattern] string pattern, [NotNull] string text)
{
    var regex = new Regex(pattern);
    return regex.Match(text);
}

null warning

Actions, Controllers, & Areas

We often find ourselves trying to clean up our Razor views. I just wrote such a post (check it out). We can sit around and wait to have extension methods written for us, or we can take the initiative and solve it ourselves. Using JetBrains.Annotations, we can mark our extension methods with hints noting which parameters are actions, controllers, and areas.

public static HtmlString Example<T>(
    this IHtmlHelper<T> html, 
    [AspMvcAction] string action,
    [AspMvcController] string controller,
    [AspMvcArea] string area)
{
    return HtmlString.Empty;
}

The attributes AspMvcAction, AspMvcController, and AspMvcArea in our parameter list enable our IDE to autocomplete our parameters for us and warn us when we have no matches.

mvc action lookup

mvc action lookup

Value Ranges

When building extension methods for Razor, we find ourselves writing UI centric code. Since all UIs have limits according to the user’s screen, we may want to catch logical mistakes. For example, we may add a width extension method and understand the values may range between 1 and 100 percent.

public static HtmlString Box<T>(
    this IHtmlHelper<T> html,
    [ValueRange(1,100)] int width)
{
    if (width > 100)
    {
        throw new Exception("Oops");
    }
    
    return HtmlString.Empty;
}

value range

Our IDE tells us we’ve have a potential logic flaw. The value being passed into our method will never be outside of that range.

View Location Attributes

I recently wrote a post about Razor view locations and mentioned that they were configurable. The changes may be easy to make, but we also need to tell our IDE to look for our new location formats.

If we use a Feature Folders approach, we can add the following to our Startup.cs file or create an AssemblyInfo.cs file.

[assembly: AspMvcViewLocationFormat(@"~\Features\{1}\{0}.cshtml")]
[assembly: AspMvcViewLocationFormat(@"~\Features\{0}.cshtml")]
[assembly: AspMvcViewLocationFormat(@"~\Features\Shared\{0}.cshtml")]

[assembly: AspMvcAreaViewLocationFormat(@"~\Areas\{2}\{1}\{0}.cshtml")]
[assembly: AspMvcAreaViewLocationFormat(@"~\Areas\{2}\Features\{1}\{0}.cshtml")]
[assembly: AspMvcAreaViewLocationFormat(@"~\Areas\{2}\{0}.cshtml")]
[assembly: AspMvcAreaViewLocationFormat(@"~\Areas\{2}\Shared\{0}.cshtml")]

The addition of these attributes should clear up all IDE irregularities around missing views.

So Much Razor

ASP.NET MVC isn’t the only thing that benefits from JetBrains.Annotations. There are also many attributes used to hint at Razor Pages constructs.

  • RazorDirectiveAttribute
  • RazorHelperCommonAttribute
  • RazorImportNamespaceAttribute
  • RazorInjectionAttribute
  • RazorLayoutAttribute
  • RazorPageBaseTypeAttribute
  • RazorSectionAttribute
  • RazorWriteLiteralMethodAttribute
  • RazorWriteMethodAttribute
  • RazorWriteMethodParameterAttribute

Each of these attributes can help the IDE understand our usage of Razor. From parameters, automatically importing namespaces, and understanding our Razor page base type.

Like the MVC attributes, these can enhance the experience of working with Razor pages dramatically and help reduce issues.

Using Return Values

How often are we left scratching our heads because we forgot to use the return value? The problem can happen innocently within Razor pages.

[MustUseReturnValue]
public static HtmlString Fancy(this IHtmlHelper helper)
{
    return HtmlString.Empty;
}

Let’s look at a mistake a developer may make when working with Razor.

<h1>@{ Html.Fancy(); }</h1>

Can we see the issue? The use of @{} has created a code block, and the resulting value from the Fancy method is never used. By using the MustUseReturnValue attribute, our IDE immediately knows we’ve made a mistake.

must use return value in action

We can now fix our code and avoid some frustration.

<h1>@Html.Fancy();</h1>

Conclusion

There is a lot of goodness squirreled away inside of JetBrains.Annotations and specifically a lot of great features for ASP.NET developers. If you’re a JetBrains user, give these features a shot, and they should improve your development experience dramatically. Thanks for reading, and I hope you found this post helpful. Leave a comment below.