tags:

views:

356

answers:

4

Hi,

I have an MVC app and I want on the top of the master page to have a series of random images from the folder.

To do this I have to write code but not where to write the code?? It has to be done in one place.

I will probably just set the images at the beginning of the session so they are cached by the browser and improve performance of the site.

Malcolm

+2  A: 

Write a helper function that would get a random filename from an array of filenames which is generated at application startup.

Anton Gogolev
A helper function that would get *N* random filename. To avoid duplicates ;)
François
+2  A: 

Well, to get the images:

string[] get_images(string folder) {
     string[] files = Directory.GetFiles(folder, "*.jpg"/* or whatever */);
     List<string> rand = new List<string>();
     Random r = new Random();
     for ( int i = 0; i < numImages; i++ ) {
         rand.Add(Path.GetFileName(files[r.Next(files.Length-1)]));
     }
     return rand.ToArray();
}

And then in the master page:

<% PrintImages(); %>

Where PrintImages() is:

string[] img = get_images(Server.MapPath("~/Content/RandomImages"));
foreach (string i in img) { Response.Write("<img src=\"/Content/RandomImages/"+i+"\" />"); }

That is a rough solution, and caching would be good - that would really thrash a disk.

Lucas Jones
Thanks but where does PrintImages() go. There is not code behind for Site.Master???
Malcolm
PrintImages goes in the site.master FRONT end .. somewhere inside the <body></body> html. we do not have ANY CODE in the codebehind, anymore. There's no need (thank gawd!).eg.<body><h1>Welcome to ASP.NET</h1>Random image: <% PrintImages(); %><br/><br/>end of doc</body>.. etc.
Pure.Krome
No No where does the method PrintImages live, the code for it?????
Malcolm
Is there not a code nehind file? Try putting it in another file, in a static class, called "ImagePrinter". Then call <% ImagePrinter.PrintImages() %>. You will need to change Server to HttpContext.Current.Server, likewise for Response. VS may also tell you theres an error, just do the default thing.
Lucas Jones
A: 

I'd want to make sure that simply reading the directory each time and generating the file names is a real bottleneck before I'd do anything more complicated. It's certainly less efficient, but the code is arguably simpler. If it isn't a bottleneck, then the controller action responsible for rendering the view is the right place for the code. Note that you'll need to turn of output caching for the action (if it isn't already) if you want the image to change each time.

If it turns out that reading the file names and constructing the links really is a bottleneck -- it may take much less time than reading the actual file -- AND the files in the directory can change while the application is running, then constructing a collection of them and storing them in the session on login is a reasonable way to handle it. The caveat is that if the application is also responsible for uploading the images (so that it knows when they change), you might be able to load them at application start and keep them in a global instance which is updated by the application as images are uploaded (or removed).

Once you have your collection of images (by reading each time, from the session, or a global instance), use a random number generator to pick the image that you want to display or a set that you want to rotate through using something like the jQuery Cycle plugin, pass it (them) to the view and have it render the image tags.

tvanfosson
+2  A: 

Caching is Key

As the others have said, you've got to use caching since you are performing repetitive disk I/O on data that doesn't change often.

My example creates and caches a List<T> of the image file paths you will need for each subsequent request. System.Web.Caching is perfect for this because you can create a CacheDependency object directly on your image directory -- if a file gets changed or added, your cache is automatically invalidated. It is then recreated the next time it is requested.

Avoiding Duplicates with the HashSet<T>

I bet you don't want two of the same pictures ever showing up in your header!

Randomizing using Random.Next does not exclude previously generated duplicates. I used a HashSet<T> as a poor man's unique randomizer since the HashSet<T> will only allow you to add unique values.

The Model

This operation should be part of your model in MVC. You change it to go along with your other data fetching classes as you see fit.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Caching;

public class RandomImage
{
    public static string[] GetImages(string folder, int count)
    {
        HttpContext context = HttpContext.Current;
        string virtualFolderPath = string.Format("/content/{0}/", folder);
        string absoluteFolderPath = context.Server.MapPath(virtualFolderPath);

        Cache cache = context.Cache;
        var images = cache[folder + "_images"] as List<string>;

        // cache string array if it does not exist
        if (images == null)
        {
            var di = new DirectoryInfo(absoluteFolderPath);
            images = (from fi in di.GetFiles()
                            where fi.Extension.ToLower() == ".jpg" || fi.Extension.ToLower() == ".gif"
                            select string.Format("{0}{1}", virtualFolderPath, fi.Name))
                            .ToList();


            // create cach dependency on image randomFolderName
            cache.Insert(folder + "_images", images, new CacheDependency(absoluteFolderPath));
        }

        Random random = new Random();
        var imageSet = new HashSet<string>();
        if (count > images.Count())
        {
            throw new ArgumentOutOfRangeException("count");
        }

        while (imageSet.Count() < count)
        {
            //using an hashset will ensure a random set with unique values.
            imageSet.Add(images[random.Next(count)]);
        }

        return imageSet.ToArray();
    }
}

The Controller

Access the method in your controller something like....

string[] images = Models.RandomImage.GetImages("myPictures", 4);
Ray Womack