Skip to content
Back to skills

Dotnet Coding Standards

ASecurity

Reviews ASP.NET Core / C# code for baseline coding-standard violations. Flags disabled or suppressed nullable reference types, sync-over-async blocking calls, exceptions used for control flow or broad swallowed catches, unstructured string-interpolated logging, and scattered IConfiguration reads instead of the Options pattern. Outputs findings with pilot-dotnet coding-standards standard IDs.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 6, 2026
developmentgoc#apifullstack

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 6, 2026

npx -y skills add AgenticPawan/FullStack-Pilot --skill dotnet-coding-standards --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dotnet Coding Standards?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dotnet Coding Standards
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/agenticpawan-dotnet-coding-standards/badge)](https://www.skillsdirectory.com/skills/agenticpawan-dotnet-coding-standards)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dotnet-coding-standards
description: Reviews ASP.NET Core / C# code for baseline coding-standard violations. Flags disabled or suppressed nullable reference types, sync-over-async blocking calls, exceptions used for control flow or broad swallowed catches, unstructured string-interpolated logging, and scattered IConfiguration reads instead of the Options pattern. Outputs findings with pilot-dotnet coding-standards standard IDs.
when_to_use: coding standards, nullable reference types, NRT, sync over async, .Result, .Wait(), GetAwaiter().GetResult(), exception control flow, catch Exception, swallowed exception, structured logging, ILogger, message template, Options pattern, IOptions, IConfiguration, file-scoped namespace, ImplicitUsings
---

## Standard IDs

| ID | Severity | What it checks |
|----|----------|----------------|
| CS-001 | P1 | Nullable reference types disabled or warnings suppressed project-wide |
| CS-002 | P0 | Blocking async code via `.Result`/`.Wait()`/`.GetAwaiter().GetResult()` |
| CS-003 | P1 | Exceptions used for control flow, or `catch (Exception)` swallowed silently |
| CS-004 | P2 | Unstructured logging — string interpolation into `ILogger` calls |
| CS-005 | P2 | Hardcoded config reads via `IConfiguration["Key"]` instead of Options pattern |
| CS-006 | P4 | Not using file-scoped namespaces / `ImplicitUsings` disabled (advisory) |

---

## Check A — Nullable reference types

### Detection

1. Open every `.csproj` in the solution and check for `<Nullable>enable</Nullable>`.
2. If `<Nullable>disable</Nullable>`, missing entirely, or set to `annotations` only (warnings not enforced) → CS-001.
3. Also grep for `#nullable disable` at the top of `.cs` files that re-disable it locally without a comment explaining why (e.g. generated code).

### BAD — nullable disabled project-wide

```xml
<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <Nullable>disable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
```

```csharp
public class CustomerService
{
    // No compiler help — this silently accepts null and blows up at runtime
    public string GetDisplayName(Customer customer)
    {
        return customer.FirstName + " " + customer.LastName;
    }
}
```

### GOOD — nullable enabled, nullability made explicit

```xml
<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
```

```csharp
public class CustomerService
{
    public string GetDisplayName(Customer customer)
    {
        ArgumentNullException.ThrowIfNull(customer);

        // Compiler flags this at build time if FirstName/LastName can be null
        return $"{customer.FirstName} {customer.LastName}".Trim();
    }
}

public class Customer
{
    public required string FirstName { get; init; }
    public string? MiddleName { get; init; }
    public required string LastName { get; init; }
}
```

---

## Check B — Sync-over-async blocking

### Detection

1. Grep for `.Result`, `.Wait()`, and `.GetAwaiter().GetResult()` across `**/*.cs`.
2. Exclude usages inside `Main(string[] args)` synchronous entry points that have no async alternative and test helper `Setup`/`TearDown` methods where the framework mandates sync signatures.
3. Any remaining hit inside a request-handling path (controller, minimal API handler, service consumed by ASP.NET Core) → CS-002. This pattern can deadlock under a synchronization context and starves the thread pool.

### BAD — blocking on async work

```csharp
[HttpGet("{id:int}")]
public IActionResult GetOrder(int id)
{
    // Blocks the request thread; can deadlock under load
    var order = _orderService.GetOrderAsync(id).Result;
    if (order is null)
    {
        return NotFound();
    }

    return Ok(order);
}
```

### GOOD — async all the way

```csharp
[HttpGet("{id:int}")]
public async Task<IActionResult> GetOrder(int id, CancellationToken cancellationToken)
{
    var order = await _orderService.GetOrderAsync(id, cancellationToken);
    if (order is null)
    {
        return NotFound();
    }

    return Ok(order);
}
```

---

## Check C — Exceptions for control flow / swallowed catches

### Detection

1. Grep for `catch (Exception` (or `catch (Exception ex)`) blocks whose body is empty, contains only a `// TODO`, or only logs at `Debug`/`Trace` level without rethrow.
2. Grep for `throw new` inside logic that is reachable via a normal, expected code path (e.g., validating user input) rather than truly exceptional conditions — a strong signal is a `try { … } catch (SomeException) { return SomeDefault; }` pattern used to implement branching instead of `TryParse`/`if` checks.
3. Both patterns → CS-003.

### BAD — exception used for control flow, and swallowed catch

```csharp
public decimal ParseDiscount(string input)
{
    try
    {
        return decimal.Parse(input);
    }
    catch (Exception)
    {
        // Swallowed — caller has no idea parsing failed, and this is expected input, not exceptional
        return 0m;
    }
}

public async Task ProcessBatchAsync(IEnumerable<Order> orders)
{
    foreach (var order in orders)
    {
        try
        {
            await _paymentGateway.ChargeAsync(order);
        }
        catch (Exception)
        {
            // Silently continues — a failed charge is now invisible
        }
    }
}
```

### GOOD — expected outcomes modeled explicitly, unexpected ones surfaced

```csharp
public bool TryParseDiscount(string input, out decimal discount)
{
    return decimal.TryParse(input, NumberStyles.Number, CultureInfo.InvariantCulture, out discount);
}

public async Task ProcessBatchAsync(IEnumerable<Order> orders)
{
    foreach (var order in orders)
    {
        try
        {
            await _paymentGateway.ChargeAsync(order);
        }
        catch (PaymentGatewayException ex)
        {
            _logger.LogError(ex, "Charge failed for order {OrderId}", order.Id);
            await _failedChargeQueue.EnqueueAsync(order.Id);
        }
    }
}
```

---

## Check D — Unstructured logging

### Detection

1. Grep for `_logger.Log(Information|Warning|Error|Debug|Trace)\(\$"` — interpolated string literals (`$"..."`) passed directly to `ILogger` calls.
2. Also flag `string.Format` or `+` concatenation used to build the log message.
3. Any match → CS-004. Structured logging providers (e.g. Application Insights, Seq) cannot query/aggregate on interpolated text.

### BAD — string interpolation destroys structure

```csharp
_logger.LogInformation($"Order {order.Id} shipped to {order.Address} at {DateTime.UtcNow}");

_logger.LogError("Payment failed for customer " + customerId + " amount " + amount);
```

### GOOD — message template with named parameters

```csharp
_logger.LogInformation(
    "Order {OrderId} shipped to {ShippingAddress} at {ShippedAtUtc}",
    order.Id, order.Address, DateTime.UtcNow);

_logger.LogError(
    "Payment failed for customer {CustomerId} amount {Amount:C}",
    customerId, amount);
```

---

## Check E — Missing Options pattern

### Detection

1. Grep for `IConfiguration` injected into non-`Program.cs`/non-startup classes (services, controllers, handlers).
2. Grep for `_configuration["..."]` or `_configuration.GetValue<T>("...")` used more than once for the same logical setting across different files — a sign settings are duplicated instead of bound once.
3. Either pattern in business-logic classes → CS-005.

### BAD — raw IConfiguration reads scattered through business logic

```csharp
public class EmailSenderService
{
    private readonly IConfiguration _configuration;

    public EmailSenderService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public async Task SendWelcomeEmailAsync(string toAddress)
    {
        var smtpHost = _configuration["Email:SmtpHost"];
        var smtpPort = int.Parse(_configuration["Email:SmtpPort"]!);
        var fromAddress = _configuration["Email:FromAddress"];

        using var client = new SmtpClient(smtpHost, smtpPort);
        await client.SendMailAsync(new MailMessage(fromAddress!, toAddress, "Welcome", "Hello!"));
    }
}
```

### GOOD — strongly-typed Options bound once at startup

```csharp
public sealed class EmailOptions
{
    public const string SectionName = "Email";

    public required string SmtpHost { get; init; }
    public required int SmtpPort { get; init; }
    public required string FromAddress { get; init; }
}

// Program.cs
builder.Services
    .AddOptions<EmailOptions>()
    .Bind(builder.Configuration.GetSection(EmailOptions.SectionName))
    .ValidateDataAnnotations()
    .ValidateOnStart();

public class EmailSenderService
{
    private readonly EmailOptions _options;

    public EmailSenderService(IOptions<EmailOptions> options)
    {
        _options = options.Value;
    }

    public async Task SendWelcomeEmailAsync(string toAddress)
    {
        using var client = new SmtpClient(_options.SmtpHost, _options.SmtpPort);
        await client.SendMailAsync(
            new MailMessage(_options.FromAddress, toAddress, "Welcome", "Hello!"));
    }
}
```

---

## Check F — File-scoped namespaces / ImplicitUsings (advisory)

### Detection

1. Grep new/modern (net8+) `.cs` files for block-scoped `namespace Foo.Bar { ... }` instead of `namespace Foo.Bar;`.
2. Check `.csproj` for missing `<ImplicitUsings>enable</ImplicitUsings>`.
3. Emit as CS-006, P4 advisory — style-only, no functional risk.

### BAD — block-scoped namespace, extra indentation noise

```csharp
using System;

namespace FullStack.Pilot.Services
{
    public class OrderNumberGenerator
    {
        public string Generate()
        {
            return $"ORD-{DateTime.UtcNow:yyyyMMdd}-{Guid.NewGuid():N}";
        }
    }
}
```

### GOOD — file-scoped namespace, implicit usings enabled

```csharp
namespace FullStack.Pilot.Services;

public class OrderNumberGenerator
{
    public string Generate()
        => $"ORD-{DateTime.UtcNow:yyyyMMdd}-{Guid.NewGuid():N}";
}
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…