Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC Dynamically generated image URLs

I have an ASP.NET MVC application where I am displaying images.

These images could be located on the file system or inside a database. This is fine as I can use Url.Action in my image, call the action on my controller and return the image from the relevant location.

However, I want to be able to support images stored in Amazon S3. In this case, I don't want my controller action to return the image, it should instead generate an image URL for Amazon S3.

Although I could just perform this logic inside my view e.g.

<%if (Model.Images[0].ImageLocation == ImageLocation.AmazonS3) {%> // render amazon image

I need to ensure that the image exists first.

Essentially I need to pass a size value to my controller so that I can check that the image exists in that size (whether it be in the database, file system or amazon s3). Once I am sure that the image exists, then I return the URL to it.

Hope that makes sense,

Ben

like image 986
Ben Foster Avatar asked Nov 05 '22 17:11

Ben Foster


2 Answers

Try the following approach.

A model class for an image tag.

public class ImageModel
{
    public String Source { get; set; }
    public String Title { get; set; }
}

Helper

public static String Image(this HtmlHelper helper, String source, String title)
{
    var builder = new TagBuilder("img");
    builder.MergeAttribute("src", source);
    builder.MergeAttribute("title", title);
    return builder.ToString();
}

View with Model.Images of type IEnumerable<ImageModel>

...    
<%= Html.Image(Model.Images[0].Source, Model.Images[0].Title) %>

Action

public ActionResult ActionName(/*whatever*/)
{
    // ...
    var model = ...;
    //...

    var model0 = ImageModel();
    if (Image0.ImageLocation == ImageLocation.AmazonS3)
        model0.Source = "an amazon url";
    else
        model0.Source = Url.Action("GetImageFromDatabaseOrFileSystem", "MyController", new { Id = Image0.Id });
    model0.Title = "some title";
    model.Images.Add(model0);
    // ...
    return View(model);
}

An action is a kind of a pseudo code, however the idea should be clear.

like image 121
Anthony Serdyukov Avatar answered Nov 11 '22 16:11

Anthony Serdyukov


After several iterations I have come up with a workable solution, although I'm still not convinced its the best solution.

Originally I followed Anton's suggestion and just set the image url accordingly within my controller action. This was simple enough with the following code:

        products.ForEach(p =>
        {
            p.Images[0].Url = _mediaService.GetImageUrl(p.Images[0], 200);
        });

However, I soon found that this approach did not give me the flexibility I needed. Often I will need to display images of different sizes and I don't want to use properties of my model for this such as Product.FullSizeImageUrl, Product.ThumbnailImageUrl.

As far as "Product" is concerned it only knows about the images that were originally uploaded. It doesn't need to know about how we manipulate and display them, or whether we are caching them in Amazon S3.

In web forms I might use a user control to display product details and then use a repeater control to display images, setting the image urls programatically in code behind.

I found that the use of RenderAction in ASP.NET MVC gave me similar flexibility:

Controller Action:

    [ChildActionOnly]
    public ActionResult CatalogImage(CatalogImage image, int targetSize)
    {
        image.Url = _mediaService.GetImageUrl(image, targetSize);
        return PartialView(image);
    }

Media Service:

         public MediaCacheLocation CacheLocation { get; set; }

     public string GetImageUrl(CatalogImage image, int targetSize)
     {
         string imageUrl;

         // check image exists
         // if not exist, load original image from store (fs or db)
         // resize and cache to relevant cache location

         switch (this.CacheLocation) {
             case MediaCacheLocation.FileSystem:
                 imageUrl = GetFileSystemImageUrl(image, targetSize);
                 break;
             case MediaCacheLocation.AmazonS3:
                 imageUrl = GetAmazonS3ImageUrl(image, targetSize);
                 break;
             default:
                 imageUrl = GetDefaultImageUrl();
                 break;
         }

         return imageUrl;
     }

Html helper:

        public static void RenderCatalogImage(this HtmlHelper helper, CatalogImage src, int size) {
        helper.RenderAction("CatalogImage", "Catalog", new { image = src, targetSize = size });
    }

Usage:

<%Html.RenderCatalogImage(Model.Images[0], 200); %>

This now gives me the flexibility I require and will support both caching the resized images to disk or saving to Amazon S3.

Could do with some url utility methods to ensure that the generated image URL supports SSL / virtual folders - I am currently using VirtualPathUtility.

Thanks Ben

like image 24
Ben Foster Avatar answered Nov 11 '22 14:11

Ben Foster