For creating a local diskcache of resized images, I am trying to figure out how I can create a URL rewrite during runtime.

Something like this:

[Route("(images/{id}.jpg")]
public IActionResult ResizeImage(int id, int height, int width)
{
    string webRoot = _env.ContentRootPath;
    var file = System.IO.Path.Combine(webRoot, $"resizedimage{id}.jpg");
    //Pseudocode:
    UrlRewriter.DoMagic($"images/{id}.jpg?height={height}&width={width}", "/resizedimage{id}.jpg")
    return File(file, "image/jpeg");
}

A client requests /images/123.jpg?height=100&width=100 ...

I can create a static url rewrite, that rewrites /images/123.jpg?height=100&width=100 to /images/resizedimage.jpg (resized image on disk), bypassing the action method (presumably)..

How can I do the same thing on the fly, with something like the above pseudocode?

Note:

  • I do not care about the first request, which WILL hit the actionmethod and be served the image via a fileresult (as shown above), only subsequent requests to the same url.

  • I am aware of methods to create dynamic url rewrites at startup, but not runtime (which is what I am asking about)

  • Yes, I could just return a redirect to the image file, which is also pretty efficient - but it is still two synchrounous requests from the client.

  • ASP.Net Core 2+ required

2 Answers

3
cem On Best Solutions

Easiest way to do this is that injecting RewriteOptions to controller and then adding rules to it but RewriteOptions.Rules is not thread-safe.

You need custom rule and thread-safe collection. Something like this should work:

Startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ImageRewriteCollection>();
    // ...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseRewriter(new RewriteOptions().Add(new ImageRewriteRule(app.ApplicationServices.GetService<ImageRewriteCollection>())));
    // ...
}

ImageRewriteCollection:

public class ImageRewriteCollection
{
    private ConcurrentDictionary<(int id, int width, int height), string> imageRewrites
        = new ConcurrentDictionary<(int id, int width, int height), string>();

    public bool TryAdd(int id, int width, int height, string path)
        => imageRewrites.TryAdd((id, width, height), path);

    public bool TryGetValue(int id, int width, int height, out string path)
        => imageRewrites.TryGetValue((id, width, height), out path);
}

ImageRewriteRule:

public class ImageRewriteRule : IRule
{
    private readonly ImageRewriteCollection rewriteCollection;
    private readonly Regex pathRegex = new Regex("^/images/(\\d+).jpg");

    public ImageRewriteRule(ImageRewriteCollection rewriteCollection)
    {
        this.rewriteCollection = rewriteCollection;
    }

    public void ApplyRule(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        var pathMatch = pathRegex.Match(request.Path.Value);
        if (pathMatch.Success)
        {
            bool isValidPath = true;
            int id = int.Parse(pathMatch.Groups[1].Value);

            int width = 0;
            int height = 0;
            string widthQuery = request.Query["width"];
            string heightQuery = request.Query["height"];

            if (widthQuery == null || !int.TryParse(widthQuery, out width))
                isValidPath = false;

            if (heightQuery == null || !int.TryParse(heightQuery, out height))
                isValidPath = false;

            if (isValidPath && rewriteCollection.TryGetValue(id, width, height, out string path))
            {
                request.Path = path;
                context.Result = RuleResult.SkipRemainingRules;
            }
        }
    }
}

Controller:

private readonly ImageRewriteCollection rewriteCollection;

public HomeController(ImageRewriteCollection rewriteCollection)
{
    this.rewriteCollection = rewriteCollection;
}

[Route("images/{id}.jpg")]
public IActionResult ResizeImage(int id, int width, int height)
{
    rewriteCollection.TryAdd(id, width, height, "/resizedimagepath...");
    return File();
}
0
christopher_hustman On