My Technical Notes

Thursday, 22 October 2015

Implementing an Images Controller in MVC

Aim - Given a request of the form `/Images/<path>`, for e.g. `/Images/path/to/garden.jpg`, pass control to a controller-action method `ImagesController.SendFile`.

In your `RouteConfig` class, `RegisterRoutes` method, put the below code immediately


routes.MapRoute(
    name: "ImagesRoute",
    url: "Images/{*path}",
    defaults: new { controller = "Images", action = "SendFile" }
);

In the `system.webServer/handlers` section we add a handler for paths `Images/*` to prevent IIS from attempting to fetch an image at that file location, which would result in a `404`.



Above, I have set the type to `System.Web.Mvc.MvcHandler`, but even if the `type` attribute was set to a newly-created, "blank" `IHttpHandler` class, control would still be passed to our `ImagesController` class.

Finally we have the `ImagesController` which handles the image request:


public class ImagesController : Controller
{
    [HttpGet]
    [HttpCache(ExpiresInDays = 1)] // required for Firefox
    public ActionResult SendFile(string path)
    {
        var fullPath = Path.GetFullPath(Path.Combine(Config.ImageDirectory, path));

        if (fullPath.StartsWith(Config.ImageDirectory) && System.IO.File.Exists(fullPath))
        {
            return File(fullPath, ContentTypeForImage(path));
        }
        else
        { 
            return HttpNotFound(string.Format("Image {0} does not exist", path));
        }
    }

    private string ContentTypeForImage(string path)
    {
        var extensionCapture = System.Text.RegularExpressions.Regex.Match(path, @"^.*\.([^.]*)$");

        if (!extensionCapture.Success)
            return "image";

        var extension = extensionCapture.Groups[1].Value.ToLower();

        return "image/" + ((extension == "jpg") ? "jpeg" : extension);
    }
}

Here is the definition of `HttpCache` attribute. If we don't set the HTTP `Expires` header to a time in the future, Firefox (version 41.0.2 at time of writing) will reissue the request, so, in effect, the image is downloaded twice.


public class HttpCacheAttribute : ActionFilterAttribute
{
    public int ExpiresInDays { get; set; }

    public HttpCacheAttribute()
    {
        this.ExpiresInDays = -1;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (this.ExpiresInDays != -1)
        {
            filterContext.HttpContext.Response.Cache.SetExpires(DateTime.Now.AddDays(this.ExpiresInDays));
        }
    }
}

References

No comments: