Api controller
Overview
Extensions for .NET Native AVAplace REST API projects
Install
- ASOL.Core.ApiController - base extensions for .net core 3.1+ with support common REST API development
- ASOL.Core.ApiController.SignalR - additional extensions for support SignalR hubs with authorization.
- ASOL.Core.ApiController.Swashbuckle - additional extensions for easy setup Swagger and SwaggerUI with authorization.
- ASOL.Core.ApiController.JsonPatch - additional extension for support JSON Patch in controllers
Changelog
- 0.0.1.47854-dev
- BREAKING CHANGE: Dependency on ASOL.Core.Identity.Abstractions of the version 0.0.1.47819-dev
- 0.0.1.47830-dev
- support for JSON patch
- 0.0.1.45468-dev
- support for MutiMaster databases for testing purposes
- 0.0.1.44925-dev
- BREAKING CHANGE: Only .NET8.0+ is supported.
- 0.0.1.39271-dev
- certificate for JWT support
Authorized controller
REST API controller base class for authorized endpoints.
using Microsoft.Extensions.Logging;
/// <summary>
/// MyEntity REST API endpoint.
/// </summary>
[ApiController]
[ApiConventionType(typeof(DefaultApiConventions))]
[Route("api/v{version:apiVersion}/[controller]")]
[Produces(MediaTypeNames.Application.Json)]
[Consumes(MediaTypeNames.Application.Json)]
public class MyEntitiesController : AuthorizeControllerBase
{
public MyEntitiesController(ILogger<MyEntitiesController> logger)
: base(logger)
{
}
}
Captcha extensions
Usefull extensions for Controller implementation. Captcha error standard result
using ASOL.Core.ApiController.Captcha
[ApiController]
[ApiConventionType(typeof(DefaultApiConventions))]
[Route("api/v{version:apiVersion}/[controller]")]
[Produces(MediaTypeNames.Application.Json)]
[Consumes(MediaTypeNames.Application.Json)]
[Authorize]
public class MyEntitiesController: AuthorizeControllerBase
{
private const string CaptchaActionAnonymousData = "Anonymous data";
private readonly ICaptchaValidator _captchaValidator;
public MyEntitiesController(
ICaptchaValidator captchaValidator,
ILogger<MyEntitiesController> logger)
:base(logger)
{
_captchaValidator = captchaValidator;
}
[HttpPost]
[AllowAnonymous]
[Route("trialregister")]
public Task<IActionResult> AnonymousData()
{
if (!await _captchaValidator.ValidateUserScoreAsync(model.Captcha, CaptchaActionAnonymousData))
{
return CaptchaError(); // This setup standard Captcha Error.
}
}
}
How to use JSON patch within controllers
This part explains how to implement and use JSON Patch in your ASP.NET Core controllers using the provided patching infrastructure.
Overview
The JSON Patch pipeline consists of:
| Component | Role |
|---|---|
IPatchSource<TSource> | Loads the entity to be patched from the database. |
IPatchTarget<TTarget, TResult> | Persists the modified entity or executes an update command. |
PatchFilter<TSource, TPatchModel, TTarget, TResult> | Handles patch processing, mapping, and saving. |
All dependencies can be registered automatically via AddJsonPatch extension method.
Add registration in startup:
services.AddJsonPatch<
GetUserQueryResult, // Source query result
UserPatchModel, // Patch model
UpdateUserCommand, // Target command
UserResult, // Result type
UserPatchFilter, // Patch filter implementation
UserPatchSource, // IPatchSource<UserEntity> implementation
UserPatchTarget>(); // IPatchTarget<UpdateUserCommand, UserResult> implementation
Note: In order to use the JSON patch you need to add the following NuGet package to your project:
Newtonsoft.Json and register in extensions:
.AddControllers()
.AddNewtonsoftJson(options =>
{
new JsonSerializationConfiguration().Configure(options.SerializerSettings);
});
In case if you dont want to use Newtonsoft you can use Microsofts workaround:
builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, PatchInputFormatter.GetJsonPatchInputFormatter());
});
PatchInputFormatter is available from this nuget package.
See more details here: https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-8.0#add-support-for-json-patch-when-using-systemtextjson
Implement the patch source
public class UserPatchSource : IPatchSource<GetUserQueryResult>
{
private readonly IQueryProcessor _queryProcessor;
// ctor injection of services
public UserPatchSource(IQueryProcessor queryProcessor)
{
_queryProcessor = queryProcessor ?? throw new ArgumentNullException(nameof(queryProcessor));
}
public async Task<UserEntity?> GetEntityAsync(ActionExecutingContext context, CancellationToken ct = default)
{
// as the algorithm executes before the endpoint itself, you have to retrieve the parameters from the context
if (context.ActionArguments.TryGetValue("entityId", out var entityIdObj) && entityIdObj is string entityIdStr)
{
//you can either get the data directly from the database by repository or by use-case specific query etc..
var query = new GetUserQuery(entityIdStr);
var result = await _queryProcessor.ProcessAsync(query, ct);
return result.Value;
}
}
}
Implement the patch target
public class UserPatchTarget : IPatchTarget<UpdateUserCommand, UserResult>
{
private readonly ICommandProcessor _processor;
public RulePatchTarget(ICommandProcessor processor)
{
_processor = processor ?? throw new ArgumentNullException(nameof(processor));
}
public async Task<string> SaveAsync(UpdateUserCommand target, CancellationToken ct = default)
{
// you can either save the data directly to the database by repository or by use-case specific command etc..
var commandResult = await _processor.ProcessAsync<UpdateUserCommand, UpdateUserCommandResult>(target, ct);
return commandResult.Value;
}
}
Implement the patch filter
public class UserPatchFilter : PatchFilter<UserEntity, UserPatchModel, UpdateUserCommand, UserResult>
{
public UserPatchFilter(
IPatchSource<GetUserQueryResult> source,
IPatchTarget<UpdateUserCommand, UserResult> target,
ILogger<UserPatchFilter> logger)
: base(source, target, logger)
{ }
}
Usage in controller
To use the patching infrastructure in your controller, you need to add the ServiceFilter attribute to your patch endpoint.
[HttpPatch("{id:guid}")]
[ServiceFilter(typeof(UserPatchFilter))]
public IActionResult Patch(Guid id, [FromBody] JsonPatchDocument<UserPatchModel> patch)
{
//use patch helper for retrieving the result of the filter
var result = PatchHelper.GetResponse<string>(HttpContext);
//...
//return the result as needed
return Ok(result);
}
Patch model
The PatchModel defines the properties that can be modified via JsonPatchDocument.
It also includes validations by IValidatablePatch interface which uses new PatchValidationRule.
Note: The validation is optional, if you don't need it, use yield break in SetValidations method.
The validations are executed automatically during the patch process.
Implementation of IValidatablePatch<TPatchModel> in the patch model
public class UserPatchModel : IValidatablePatch<UserPatchModel>
{
public string Name { get; set; }
public string LastName { get; set; }
// implements the validation rules
IEnumerable<PatchValidationRule<RulePatchModel>> IValidatablePatch<RulePatchModel>.SetValidations()
{
yield return new PatchValidationRule<RulePatchModel>(
PropertyName: nameof(Name), // the property to validate
Condition: x => string.IsNullOrEmpty(x.Name), // the condition which under which the validation fails
Message: "Name must be defined.", // the validation message
AllowedOperations: new[] { "add", "replace" } // the allowed operations for this validation
);
yield return new PatchValidationRule<RulePatchModel>(
PropertyName: nameof(LastName),
Condition: x => x => string.IsNullOrEmpty(x.LastName),
Message: "Last name must be defined.",
AllowedOperations: new[] { "add", "replace" }
);
}
}