.NET Code Analysis with Roslyn Analyzers

NET compiler platform (Roslyn) analyzers inspect your C# or Visual Basic code for style, quality, maintainability, design, and other issues. This inspection or analysis happens during design time in all open files.

Here are the key take aways;

Maintainability index range and meaning

For the thresholds, we decided to break down this 0-100 range 80-20 to keep the noise level low and we only flagged code that was suspicious. We’ve used the following thresholds:

Index value Color Meaning
0-9 Red Low maintainability of code
10-19 Yellow Moderate maintainability of code
20-100 Green Good maintainability of code

Code metrics – Class coupling

“Module cohesion was introduced by Yourdon and Constantine as ‘how tightly bound or related the internal elements of a module are to one another’ YC79. A module has a strong cohesion if it represents exactly one task […], and all its elements contribute to this single task. They describe cohesion as an attribute of design, rather than code, and an attribute that can be used to predict reusability, maintainability, and changeability.”

The Magic Number
As with cyclomatic complexity, there is no limit that fits all organizations. However, S2010 does indicate that a limit of 9 is optimal:

“Therefore, we consider the threshold values […] as the most effective. These threshold values (for a single member) are CBO = 9[…].” (emphasis added)

Code metrics – Cyclomatic complexity

https://learn.microsoft.com/en-us/visualstudio/code-quality/code-metrics-cyclomatic-complexity?view=vs-2022

Cyclomatic complexity is defined as measuring “the amount of decision logic in a source code function” NIST235. Simply put, the more decisions that have to be made in code, the more complex it is.

The Magic Number
As with many metrics in this industry, there’s no exact cyclomatic complexity limit that fits all organizations. However, NIST235 does indicate that a limit of 10 is a good starting point:

“The precise number to use as a limit, however, remains somewhat controversial. The original limit of 10 as proposed by McCabe has significant supporting evidence, but limits as high as 15 have been used successfully as well. Limits over 10 should be reserved for projects that have several operational advantages over typical projects, for example experienced staff, formal design, a modern programming language, structured programming, code walkthroughs, and a comprehensive test plan. In other words, an organization can pick a complexity limit greater than 10, but only if it’s sure it knows what it’s doing and is willing to devote the additional testing effort required by more complex modules.” NIST235

As described by the Software Assurance Technology Center (SATC) at NASA:

“The SATC has found the most effective evaluation is a combination of size and (Cyclomatic) complexity. The modules with both a high complexity and a large size tend to have the lowest reliability. Modules with low size and high complexity are also a reliability risk because they tend to be very terse code, which is difficult to change or modify.” SATC

Putting It All Together
The bottom line is that a high complexity number means greater probability of errors with increased time to maintain and troubleshoot. Take a closer look at any functions that have a high complexity and decide whether they should be refactored to make them less complex.

Code metrics – Depth of inheritance (DIT)

Depth of Inheritance. Depth of inheritance, also called depth of inheritance tree (DIT), is defined as “the maximum length from the node to the root of the tree”.

High values for DIT mean the potential for errors is also high, low values reduce the potential for errors. High values for DIT indicate a greater potential for code reuse through inheritance, low values suggest less code reuse though inheritance to use. Due to lack of sufficient data, there is no currently accepted standard for DIT values.

You can read full article here;

https://learn.microsoft.com/en-us/visualstudio/code-quality/code-metrics-values?view=vs-2022

Add following nuget package in your project;

Microsoft.CodeAnalysis.NetAnalyzers

Integration in Azure DevOps

To integrate in Azure DevOps, follow this article;

https://secdevtools.azurewebsites.net/helpRoslynAnalyzers.html

Convert Enum to List in C#

We can use LINQ for this;

public class EnumModel
{
    public int Value { get; set; }
    public string Name { get; set; }
}

public enum MyEnum
{
    Name1=1,
    Name2=2,
    Name3=3
}

public class Test
{
        List<EnumModel> enums = ((MyEnum[])Enum.GetValues(typeof(MyEnum))).Select(c => new EnumModel() { Value = (int)c, Name = c.ToString() }).ToList();

        // A list of Names only, does away with the need of EnumModel 
        List<string> MyNames = ((MyEnum[])Enum.GetValues(typeof(MyEnum))).Select(c => c.ToString()).ToList();

        // A list of Values only, does away with the need of EnumModel 
        List<int> myValues = ((MyEnum[])Enum.GetValues(typeof(MyEnum))).Select(c => (int)c).ToList();

        // A dictionnary of <string,int>
        Dictionary<string,int> myDic = ((MyEnum[])Enum.GetValues(typeof(MyEnum))).ToDictionary(k => k.ToString(), v => (int)v);
}

Reference

https://stackoverflow.com/questions/1167361/how-do-i-convert-an-enum-to-a-list-in-c

Blazor WebAssembly and Antiforgery token

EditForm comes with built-in anti-forgery token support. Blazor automatically secures the EditForm instances, saving you the hassle of explicitly handling CSRF protection.

Blazor WebAssembly apps run entirely in the browser and do not have a server-side processing pipeline where you would typically configure a middleware such as app.UseAntiforgery(). If your Blazor WebAssembly app interacts with server-side APIs, you should manage anti-forgery at the API level. However, if you already use token-based authentication to secure communication, anti-forgery tokens are generally not necessary. Token-based authentication, by its nature, mitigates the risks associated with CSRF, making additional anti-forgery tokens redundant.

Reference

https://learn.microsoft.com/en-us/xandr/digital-platform-api/token-based-api-authentication

Extending HttpClient With Delegating Handlers in ASP.NET Core

Delegating handlers are like ASP.NET Core middleware. Except they work with the HttpClient. The ASP.NET Core request pipeline allows you to introduce custom behavior with middleware. You can solve many cross-cutting concerns using middleware — logging, tracing, validation, authentication, authorization, etc.

But, an important aspect here is that middleware works with incoming HTTP requests to your API. Delegating handlers work with outgoing requests.

Read more here

https://www.milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore

JSON conventions and defaults

Look at the output below;

[{"levelId":1,"levelName":"Team","levelEnum":"Team","hasSave":true,"hasReset":true,"hasDelete":true,"hasGeneratePDF":false},{"levelId":2,"levelName":"Finance","levelEnum":"Finance","hasSave":true,"hasReset":true,"hasDelete":false,"hasGeneratePDF":false}]

This JSON is basically a serialization of our entity, with some built-in conventions such as;

  • CamelCase instead of PascalCase: We got levelName instead of LevelName and son, meaning that all our PascalCase .NET class names and properties will be automatically converted into camelCase when they are serialized to JSO,
  • No indentation and no line feed / carriage return (LF/CR): Everything is stacked within a single line of text.

These conventions are the default options set by .NET core when dealing with JSON outputs.

To change the default behavior for readability and no PascalCase to CamelCase switching, add these to Program.cs file;

builder.Services.AddControllersWithViews()
    .AddJsonOptions(options =>
    {
        // set this option to TRUE to indent hte JSON output
        options.JsonSerializerOptions.WriteIndented = true;
        // set this option to NULL to use PascalCase instead of camelCase (default)
        options.JsonSerializerOptions.PropertyNamingPolicy = null;
    });

Now the output would look like this;

[
  {
    "LevelId": 1,
    "LevelName": "Team",
    "LevelEnum": "Team",
    "HasSave": true,
    "HasReset": true,
    "HasDelete": true,
    "HasGeneratePDF": false
  },
  {
    "LevelId": 2,
    "LevelName": "Finance",
    "LevelEnum": "Finance",
    "HasSave": true,
    "HasReset": true,
    "HasDelete": false,
    "HasGeneratePDF": false
  }
]

Looks great.