Přeskočit na hlavní obsah

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