Skip to content

Extending Web

Dennis Heutinck edited this page Oct 9, 2022 · 1 revision

Custom request intercept filter

Implement a custom request intercept filter to filter out incoming requests. You can prevent particular urls from being processed by the URL Tracker, thus improving performance. Following is an example of a custom url filter that would filter out all api urls so they won't be processed by the URL Tracker:

using UrlTracker.Core.Domain.Models;
using UrlTracker.Web;
using UrlTracker.Web.Processing;

public class ApiRequestFilter : IRequestInterceptFilter
{
    public ValueTask<bool> EvaluateCandidateAsync(Url url)
    {
        // Return false to stop processing of this url. Return true to continue processing.
        //    If any filter returns false, the URL Tracker will not process the request.
        return new ValueTask<bool>(url.Path?.StartsWith("/api") is not true);
    }
}

public class MyComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        // Add the filter to the collection
        builder.RequestInterceptFilters()
            .Append<ApiRequestFilter>();

        // Remove any other filter from the collection if you want
        builder.RequestInterceptFilters()
            .Remove<UrlReservedPathFilter>();
    }
}

Custom client error filter

Implement a custom client error filter to filter out requests with client errors. You can prevent particular urls from being registered in the client error overview, so they won't be displayed on the dashboard. Following is an example of a custom url filter that would filter out all client errors to javascript source maps:

using UrlTracker.Web;
using UrlTracker.Web.Processing;

public class JsSourceMapClientErrorFilter : IClientErrorFilter
{
    public ValueTask<bool> EvaluateCandidateAsync(UrlTrackerHandled notification)
    {
        // Return false to stop prevent registration of this client error. Return true to continue registration
        //    If any filter returns false, the URL Tracker will not register the client error.
        return new ValueTask<bool>(notification.Url.Path?.EndsWith(".js.map") is not true);
    }
}

public class MyComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        // Add the filter to the collection
        builder.ClientErrorFilters()
            .Append<JsSourceMapClientErrorFilter>();

        // Remove any other filter from the collection if you want
        builder.ClientErrorFilters()
            .Remove<ConfigurationClientErrorFilter>();
    }
}

NOTE: At the time of writing, the URL Tracker only supports registration of 404 NOT FOUND results as client errors

Custom matching and handling

Should you find that the URL Tracker matching services are not enough for your needs, the URL Tracker provides means to extend matching and handling logic. The process takes two steps, though more customization is possible if required

Matching

The first step is to create a custom matching component. In the URL Tracker, these are called 'interceptors'.

using UrlTracker.Core;
using UrlTracker.Core.Domain.Models;
using UrlTracker.Core.Intercepting;
using UrlTracker.Core.Intercepting.Models;

public class MyCustomInterceptor : IInterceptor
{
    public ValueTask<ICachableIntercept?> InterceptAsync(Url url, IReadOnlyInterceptContext context)
    {
        // Read properties from the url and the context
        var culture = context.GetCulture();
        
        // Whatever you return here is what will later be handled by the handlers
        // You could:
        //  1) return a familiar model. Familiar models already have handlers and won't require you to define a handler yourself. Familiar models are: IRedirect, IClientError and CachableInterceptBase.NullIntercept
        return new ValueTask<ICachableIntercept?>(new CachableInterceptBase<IRedirect>(new MyModelThatImplementsIRedirect()));

        //  2) return your own model. You'll have to implement a handler later to handle your own model
        //     Either return CachableInterceptBase with an instance of your model or implement ICachableIntercept yourself.
        //     As the name suggests: your model must be cachable. You shouldn't return IPublishedContent here for example.
        return new ValueTask<ICachableIntercept?>(new CachableInterceptBase<MyCachableModel>(new MyCachableModel()));

        //  3) return null. return null to indicate that your handler couldn't match the incoming url. The URL Tracker will continue the search with other handlers
        return new valueTask<ICachableIntercept?>(null);
    }
}

public class MyComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        // Add the handler to the collection
        builder.Interceptors()
            .Append<MyCustomInterceptor>();

        // Remove any other handlers from the collection if you want
        builder.Interceptors()
            .Remove<RegexRedirectInterceptor>();

        // Interceptors are evaluated in order.
        //    You can also insert your handler before another
        builder.Interceptors()
            .InsertBefore<NoLongerExistsInterceptor, MyCustomInterceptor>();
    }
}

Handling

The next step is to handle the intercept with so called 'Intercept handlers'

using UrlTracker.Web.Processing;

public class MyInterceptHandler : ResponseInterceptHandlerBase<MyInterceptModel>
{
    protected override async ValueTask HandleAsync(RequestDelegate next, HttpContext context, MyInterceptModel intercept)
    {
        // From here you're acting as a middleware and can decide what to do with the request, based on the intercept model.
        // You can choose to let the request through:
        await next(context);

        // You can also do something else:
        context.Response.StatusCode = 418;

        // It's all up to you what happens now... continue, change, return, etc.
    }
}

public class MyComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        // Add the handler to the collection
        builder.ResponseInterceptHandlers()
            .Append<MyInterceptHandler>();

        // Remove any other handlers from the collection if you want
        builder.ResponseInterceptHandlers()
            .Remove<RedirectResponseInterceptHandler>();

        // Interceptors are evaluated in order.
        //    You can also insert your handler before another
        builder.ResponseInterceptHandlers()
            .InsertBefore<NullInterceptHandler, MyInterceptHandler>();
    }
}