ActionFilter – Manipuliere Daten bevor diese ins System kommen

Tobias Janssen

Backend Software-Developer und GIT Enthusiast bei traperto GmbH

Es gibt Anwendungsfälle, bei denen es Sinn ergibt Daten zu manipulieren bevor diese in der Businesslogik weiterverarbeitet werden. Zum Beispiel um ein Datum immer im korrekten Format zu verwenden.

Der Einfachheit halber werden wir in diesem Beispiel lediglich den Wert eines Strings spiegeln. Vorteil ist,  dass sich um die weitere Verarbeitung keine Sorgen mehr gemacht werden muss, da die Daten zentral manipuliert werden.

Konkret sprechen wir über Daten, die durch eine Schnittstelle an das Backend übergeben werden. In ASP.NET sind Schnittstellen-Methoden sogenannte Actions. Um die Daten zu manipulieren verwenden wir sogenannte ActionFilter. ActionFilter gehören zu den Filtertypen innerhalb von ASP.NET Anwendung. ActionFilter können zentral, sprich für alle Schnittstellen innerhalb der Anwendung, für eine Reihe an Schnittstellen oder für eine bestimmte Schnittstelle registriert werden.

Die Anforderungen an unseren ActionFilter: Spiegel alle Strings, die einer Schnittstelle übergeben werden. Zunächst implementieren wir eine einfache Schnittstelle:

using Microsoft.AspNetCore.Mvc;
 
namespace ActionFilter.Controllers;
 
public class TestController : Controller
{
    [HttpGet("test")]
    public async Task<IActionResult> Foo(
        [FromQuery] string foo,
        CancellationToken cancellationToken)
    {
        return Json(foo);
    }
}

Als nächstes implementieren wir eine Klasse, die von dem Interface IActionFilter erbt. Dieses Interface bietet zwei Methoden:

  • OnActionExecuting (ActionExecutingContext context)
    • Führe Code aus während auf die Action zugegriffen wird
  • OnActionExecuted (ActionExecutedContext context)
    • Führe Code aus nachdem auf die Action zugegriffen wurde

Wir wollen während des Aufrufs auf die Daten zugreifen und implementieren unsere Logik in die Methode OnActionExecuting.

Unsere Logik soll Folgendes beinhalten:

  • Hole alle Parameter die an die Action übergeben werden
  • ist der Parameter ein String, bzw. ist der Wert zu einem String zu­or­d­bar
  • Wenn ein Wert vorhanden ist, spiegel diesen und überschreibe den ursprünglichen Wert

Der Code dazu sieht wie folgt aus:
(Die Erläuterungen sind Kommentare im Code)

using ActionFilter.Extensions;
using Microsoft.AspNetCore.Mvc.Filters;
 
namespace ActionFilter.Filter;
 
public class ActionParamFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        /*
         * Ermittle alle verfügbaren Parameter über den Context 
         */
        var parameterDescriptors = context.ActionDescriptor.Parameters.ToList();
        var @params = parameterDescriptors.Where(p => p.ParameterType.IsAssignableTo(typeof(string))).ToList();
 
        /*
         * Durchlaufe alle Parameter
         */
        foreach (var param in @params)
        {
 
           /*
            * Ist der Wert des Parameter null so wird dieser ignoriert
            */ 
            if (context.ActionArguments[param.Name] is null)
            {
                continue;
            }
 
           /*
            * Speichere den Wert in eine Variable und verarbeite diesen weiter sofern der Wert nicht null ist oder Leerzeichen beinhaltet
            */ 
            var value = (string)context.ActionArguments[param.Name];
 
            if (!string.IsNullOrWhiteSpace(value))
            {
                /*
                 * Spiegel den Wert (ReserveIt) und überschreibe den ursprünglichen Wert innerhalb des Contexts
                 */  
                context.ActionArguments[param.Name] = value.ReverseIt();
            }
        }
    }
...
}

Zu guter Letzt muss der ActionFilter in der Program.cs registriert werden.

using ActionFilter.Filter;
 
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(o =>
                                {
                                    //Register filter
                                    o.Filters.Add(new ActionParamFilter());
                                });
 
var app = builder.Build();
 
app.MapDefaultControllerRoute();
app.Run();

Nachdem die Anwendung gestartet wurde, kann die Schnittstelle aufgerufen werden. Wir übergeben z.b. den String Query „traperto“ und erwarten das Ergebnis: otrepart

Der cURL zu unserem Aufruf:

curl 'https://localhost:1337/test?foo=traperto' \
  -H ...
  ....
  --compressed
 
"otrepart"%

Das Ergebnis ist wie erwartet gespiegelt. 

Kann davon ausgegangen, bzw. sollte sichergestellt werden, dass bestimmte Daten immer in einem korrekten Format an das Backend übermittelt werden, kann das eine einfache und zentrale Möglichkeit sein, diese Anforderungen zu bedienen.

Den kompletten Code zu diesem Beispiel findest du hier: github