Dynamically serving images with extension from database

04 Sep 2021

Most websites maintain images as static files within their source code repository. So, application is deployed to multiple servers along with these static images.

Consider, that you have a requirement to receive and save images from users. If you plan to save those as files within a folder, you will end up with images sitting in particular instance or machine that processed the user request. Requests routed to any other instances in that web farm will return 404 error for that image.

There are few ways to solve this problem

  1. In on-premise datacenters, you will probably use a Distributed File System (DFS)
  2. In Cloud, you'll select a shared storage space which will be used by all web server instances
  3. Else, you can persist the images in database and every instance can fetch that resource & serve it dynamically

In this post, I will cover the third option.

ASP.NET MVC's default request handler, handles all extensionless URLs and skips requests with the extensions like .jpg, .css, .pdf etc., These skipped URL requests are then served by the static file handler registered in the IIS.

You can see following extensionless URL handler in all your ASP .NET MVC projects

<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

When you write an action controller to capture/handle such dynamic URLs with the extension e.g., https://davidsekar.com/images/1/vsts-banner.png. Your requests will end-up in 404 (resource not found) error.

So, you can't capture the URLs with extensions in MVC action (in this case, dynamic image URLs that ends with .jpg).

If you search for solution, you'll first end-up with following suggestion

1. To route everything through MVC pipeline like below

<modules runAllManagedModulesForAllRequests="true" />

This is the quickest and dirtiest way to do. This implementation has performance impact as both static or dynamic content requests are handled by server like dynamic content. This puts strain on your ASP.NET MVC application handlers.

There is rather an effective way to achieve the requirements without affecting performance of rest of the URL routes in your application.

2. To add a handler path in your web.config, to handle the URL patterns in addition to the extensionless handler as shown below

<add name="DynamicImagesHandler" path="/images/*" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

Above handler should be added in web.config before the default extensionless handler. This will route all the requests with path matching/starting "/images/*" through our MVC handler. So, we can then add our custom action to read the ID from the URL and serve the images resource accordingly. Sample controller action shown below for your reference

public class ImagesController : Controller
{
    #region Fields

    private readonly ILoggingService _loggingService;
    private readonly IRepositoryService _repo;

    #endregion Fields

    #region Constructors

    public ImagesController(IRepositoryService repo, ILoggingService loggingService)
    {
        _repo = repo;
        _loggingService = loggingService;
    }

    #endregion Constructors

    #region Methods

    [Route("images/{id}/{filename}")]
    [OutputCache(CacheProfile = CacheProfileName.DynamicImages)]
    public async Task<ActionResult> Index(int id, string filename)
    {
        var image = await _repo.GetImageById(id).ConfigureAwait(false);
        if (image == null) return new HttpNotFoundResult();

        if (image.Name.Equals(filename, System.StringComparison.Ordinal))
        {
            return new FileContentResult(image.Data, image.ContentType);
        }
        return new RedirectResult($"/images/{id}/{image.Name}", true);
    }

    #endregion Methods
}

You might be wondering, why we would need to go through the pain of having extension for images,.. As images without the extensions will work fine with default extensionless URL handler.

Well to answer that;
Following benefits can be achieved through images or files with extension

  1. CDNs like Cloudflare and other proxies will cache your files with extension using their default cache policy (In most case, this is the desired behaviour - when you are in their free plan wink with limited page cache rules). Where as, extensionless URLs are always considered as dynamic content and are cached only based on explicit rules.
  2. Appending the SEO content as part of the image URL with the file extension, will provide a clear indication to Search engines that the contents are meant to be crawled and indexed. As they see these images, as just yet another static file.
  3. File extensions based content-type identification logics have been part of most applications to process the files (yeah, I know that is a lazy work from those app developers). But, we can make our URLs / files compatible with those apps too.
  4. Image or PDF URLs with extensions added to their filenames looks familiar, as the extensionless files URLs appears like an alien object, as we don't know what to expect on loading it foot-in-mouth
  5. Save as file from most application works fine for URLs with extensions, where as extensionless URLs needs the user to resolve the extension

Hope this post helps you to handle your dynamic images with SEO and easy caching ability.

Happy coding!

Related Posts