Skip to main content

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:

ComponentRole
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" }
);
}
}