ATK.Command.AspNet.ModelBinder
0.0.5
dotnet add package ATK.Command.AspNet.ModelBinder --version 0.0.5
NuGet\Install-Package ATK.Command.AspNet.ModelBinder -Version 0.0.5
<PackageReference Include="ATK.Command.AspNet.ModelBinder" Version="0.0.5" />
<PackageVersion Include="ATK.Command.AspNet.ModelBinder" Version="0.0.5" />
<PackageReference Include="ATK.Command.AspNet.ModelBinder" />
paket add ATK.Command.AspNet.ModelBinder --version 0.0.5
#r "nuget: ATK.Command.AspNet.ModelBinder, 0.0.5"
#:package ATK.Command.AspNet.ModelBinder@0.0.5
#addin nuget:?package=ATK.Command.AspNet.ModelBinder&version=0.0.5
#tool nuget:?package=ATK.Command.AspNet.ModelBinder&version=0.0.5
How to Use Model Binder in ASP.NET
This guide explains how to use the Model Binder to automatically bind JSON command objects from HTTP requests based on a command interface.
Installation
dotnet add package ATK.Command.AspNet.ModelBinder
Core Concept
The model binder works on the basis of a command interface that all commands implement. This enables:
- ✅ Polymorphic binding of multiple command types
- ✅ Centralized authentication and validation
- ✅ Type-safe deserialization based on
$typein JSON
Step-by-Step Guide
Step 1: Define Command Interface
Create an interface that all commands will implement:
namespace MyApp.Commands
{
public interface IUserCommand
{
// All commands implementing IUserCommand can be bound
}
}
Step 2: Create Concrete Commands
Create concrete command classes that implement the interface:
using System.ComponentModel.DataAnnotations;
public class CreateUserCommand : IUserCommand
{
[Required]
[StringLength(50)]
public string Username { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
public string FullName { get; set; }
}
public class UpdateUserCommand : IUserCommand
{
[Required]
public int UserId { get; set; }
[StringLength(50)]
public string Username { get; set; }
[EmailAddress]
public string Email { get; set; }
}
public class DeleteUserCommand : IUserCommand
{
[Required]
public int UserId { get; set; }
}
Step 3: Register Model Binder
Register the model binder for the interface in Program.cs:
using ATK.Command.AspNet.ModelBinder;
using ATK.Command.AspNet.ModelBinder.CommandAuthentications;
var builder = WebApplicationBuilder.CreateBuilder(args);
// Setup authentication
builder.Services.AddAuthentication("Bearer");
builder.Services.AddAuthorization();
// Register model binder with the command interface
var commandAuthentications = new List<ICommandAuthentication>
{
new DefaultCommandAuthentication()
};
builder.Services.AddControllers(options =>
{
// IMPORTANT: Register the INTERFACE, not individual command classes!
options.ModelBinderProviders.Insert(0,
new RequestCommandModelBinderProvider<IUserCommand>(commandAuthentications));
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Step 4: Use in Controller
The controller accepts the interface as parameter. The model binder automatically deserializes to the correct command type:
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpPost]
public IActionResult ProcessUserCommand(IUserCommand command)
{
// Check validation
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Use pattern matching to check the concrete type
return command switch
{
CreateUserCommand createCmd => HandleCreateUser(createCmd),
UpdateUserCommand updateCmd => HandleUpdateUser(updateCmd),
DeleteUserCommand deleteCmd => HandleDeleteUser(deleteCmd),
_ => BadRequest("Unknown command type")
};
}
private IActionResult HandleCreateUser(CreateUserCommand command)
{
// Create new user
return Ok(new { message = $"User {command.Username} created" });
}
private IActionResult HandleUpdateUser(UpdateUserCommand command)
{
// Update user
return Ok(new { message = $"User {command.UserId} updated" });
}
private IActionResult HandleDeleteUser(DeleteUserCommand command)
{
// Delete user
return Ok(new { message = $"User {command.UserId} deleted" });
}
}
Step 5: Send JSON Request
The JSON must include the $type information so the model binder deserializes to the correct command type:
# CreateUserCommand
curl -X POST https://localhost:5001/api/user \
-H "Content-Type: application/json" \
-d '{
"$type": "MyApp.Commands.CreateUserCommand, MyApp",
"username": "john.doe",
"email": "john@example.com",
"fullName": "John Doe"
}'
# UpdateUserCommand
curl -X POST https://localhost:5001/api/user \
-H "Content-Type: application/json" \
-d '{
"$type": "MyApp.Commands.UpdateUserCommand, MyApp",
"userId": 1,
"username": "jane.doe",
"email": "jane@example.com"
}'
# DeleteUserCommand
curl -X POST https://localhost:5001/api/user \
-H "Content-Type: application/json" \
-d '{
"$type": "MyApp.Commands.DeleteUserCommand, MyApp",
"userId": 1
}'
Authentication & Authorization
Allow Anonymous Commands
To allow a command without authentication, use the [AllowAnonymous] attribute:
[AllowAnonymous]
public class PublicSearchCommand : IUserCommand
{
public string Query { get; set; }
}
Require Roles
Use the [Authorize] attribute to require specific roles:
[Authorize(Roles = "Admin")]
public class DeleteUserCommand : IUserCommand
{
[Required]
public int UserId { get; set; }
}
Require Claims
Use the [ClaimRequirement] attribute for claim-based authorization:
[ClaimRequirement("department", "hr")]
public class PromoteEmployeeCommand : IHRCommand
{
public int EmployeeId { get; set; }
public string NewPosition { get; set; }
}
Custom Authentication
Implement ICommandAuthentication for custom authentication logic:
public class ApiKeyCommandAuth : ICommandAuthentication
{
public bool Execute(ModelBindingContext bindingContext, object model)
{
var httpContext = bindingContext.ActionContext.HttpContext;
if (!httpContext.Request.Headers.TryGetValue("X-API-Key", out var apiKey))
{
bindingContext.ModelState.TryAddModelError("Unauthorized", "Missing API Key");
return false;
}
if (!ValidateApiKey(apiKey.ToString()))
{
bindingContext.ModelState.TryAddModelError("Unauthorized", "Invalid API Key");
return false;
}
bindingContext.Result = ModelBindingResult.Success(model);
return true;
}
private bool ValidateApiKey(string apiKey)
{
// Your validation logic
return !string.IsNullOrEmpty(apiKey);
}
}
// Register in Program.cs
var authentications = new List<ICommandAuthentication>
{
new ApiKeyCommandAuth(),
new DefaultCommandAuthentication()
};
builder.Services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0,
new RequestCommandModelBinderProvider<IUserCommand>(authentications));
});
Data Validation
Use DataAnnotations for automatic validation:
public class CreateUserCommand : IUserCommand
{
[Required(ErrorMessage = "Username is required")]
[StringLength(50, MinimumLength = 3,
ErrorMessage = "Username must be between 3 and 50 characters")]
public string Username { get; set; }
[Required]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string Email { get; set; }
[MinLength(5, ErrorMessage = "FullName must be at least 5 characters")]
public string FullName { get; set; }
}
Validation in Controller
[HttpPost]
public IActionResult ProcessCommand(IUserCommand command)
{
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage);
return BadRequest(new { errors = errors });
}
// Process command
return Ok();
}
Multiple Command Interfaces
You can register multiple command interfaces:
builder.Services.AddControllers(options =>
{
var defaultAuth = new List<ICommandAuthentication>
{
new DefaultCommandAuthentication()
};
// Register different command interfaces
options.ModelBinderProviders.Insert(0,
new RequestCommandModelBinderProvider<IUserCommand>(defaultAuth));
options.ModelBinderProviders.Insert(1,
new RequestCommandModelBinderProvider<IOrderCommand>(defaultAuth));
options.ModelBinderProviders.Insert(2,
new RequestCommandModelBinderProvider<IProductCommand>(defaultAuth));
});
Error Handling
The model binder provides informative error messages in ModelState:
| Error | Meaning |
|---|---|
| "no command." | Request body is empty |
| "not valid json." | Request body is not valid JSON |
| "Cant parse to object." | JSON does not match the command type |
| "Unauthorized" | User is not authenticated |
Handle errors in your controller:
[HttpPost]
public IActionResult ProcessCommand(IUserCommand command)
{
if (!ModelState.IsValid)
{
return BadRequest(new
{
errors = ModelState
.Where(ms => ms.Value.Errors.Count > 0)
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage))
});
}
// Process command
return Ok();
}
Complete Configuration Example
using ATK.Command.AspNet.ModelBinder;
using ATK.Command.AspNet.ModelBinder.CommandAuthentications;
using MyApp.Commands;
var builder = WebApplicationBuilder.CreateBuilder(args);
// Setup authentication
builder.Services.AddAuthentication("Bearer");
builder.Services.AddAuthorization();
// Configure model binder
var commandAuthentications = new List<ICommandAuthentication>
{
new DefaultCommandAuthentication()
};
builder.Services.AddControllers(options =>
{
// Register the command interface, not individual classes!
options.ModelBinderProviders.Insert(0,
new RequestCommandModelBinderProvider<IUserCommand>(commandAuthentications));
options.ModelBinderProviders.Insert(1,
new RequestCommandModelBinderProvider<IOrderCommand>(commandAuthentications));
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Best Practices
Always register the interface - Not individual command classes
// ✅ Correct new RequestCommandModelBinderProvider<IUserCommand>(auth) // ❌ Wrong new RequestCommandModelBinderProvider<CreateUserCommand>(auth)Include $type in JSON - The model binder needs this information
{ "$type": "MyApp.Commands.CreateUserCommand, MyApp", "username": "john" }Use pattern matching - For dispatching to the right handlers
return command switch { CreateUserCommand cmd => Handle(cmd), UpdateUserCommand cmd => Handle(cmd), _ => BadRequest() };Always validate ModelState - Before processing the command
if (!ModelState.IsValid) return BadRequest(ModelState);Authentication is required - Commands require authentication unless
[AllowAnonymous]is set[AllowAnonymous] public class PublicCommand : IUserCommand { }
Requirements
- .NET 10.0 or higher
- ASP.NET Core 9.0 or higher
- Dependencies: Newtonsoft.Json, Microsoft.AspNetCore.Mvc.Core
Made with ❤️ for ASP.NET Core developers
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Authorization (>= 9.0.2)
- Microsoft.AspNetCore.Mvc.Abstractions (>= 2.3.0)
- Microsoft.AspNetCore.Mvc.Core (>= 2.3.0)
- Newtonsoft.Json (>= 13.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.0.5 | 141 | 4/20/2026 |