In the process of looking for a way to read large images from a file or database, and displaying them as thumbnails, while all the while doing this using the MVC framework and TDD; I came accross two really good blog samples. One by Josh Holmes and the other approach by Tom Janssens. I took the latter example, and it worked like a charm.
The only I change I made was to refactor the singleton function that retrieves the MemoryStream so that I can test it. I used StructureMap in this case so I could inject it into the ThumbNail class. Therefore the GetMemoryStream function which was static so it could used as a singleton was moved to it's own class.
Here is the interface:
7 public interface IImageMemoryManager
8 {
9 MemoryStream GetStream(Image image, int width, int height, ImageFormat imageFormat);
10 }
The function in the sample that converts the image into a MemoryStream will then located in the concreate class ImageMemoryManager which impliments the above function.
On the Application_Start event of my application, I can call out to a bootstrapper class that loads up StructureMap mappings and saves it so it can be used later.
28 protected void Application_Start()
29 {
30 RegisterRoutes(RouteTable.Routes);
31 BootStrapper.ConfigureStructureMap();
32 ControllerBuilder.Current.SetControllerFactory(new GalleryControllerFactory());
33 }
The Bootstrapper class configures the StructureMap mappings.
6 public class BootStrapper
7 {
8 public static void ConfigureStructureMap()
9 {
10 StructureMapConfiguration.AddRegistry(new StructureMapSiteRegistry());
11 }
12 }
And here is mappings:
9 internal class StructureMapSiteRegistry : Registry
10 {
11 protected override void configure()
12 {
13 ForRequestedType<IContentType>().TheDefaultIsConcreteType<ContentType>();
14 ForRequestedType<IPictureRespository>().TheDefaultIsConcreteType<SqlPictureRespository>();
15 ForRequestedType<IImageMemoryManager>().TheDefaultIsConcreteType<ImageMemoryManager>().CacheBy(
16 InstanceScope.Singleton);
17 }
18 }
Notice the IImageMemoryManager maps to the contrete class ImageMemoryManager, and since initially it was a singleton class; I want to designate the mapping's InstanceScope as Singleton by using the CacheBy function.
I can then inject the these mappings into all my controller by inheriting from the DefaultControllerFactory class calling the GetInstance function in StructureMap to build out the injections.
7 public class GalleryControllerFactory : DefaultControllerFactory
8 {
9 protected override IController GetControllerInstance(System.Type controllerType)
10 {
11 IController result = null;
12 if (controllerType != null)
13 {
14 try
15 {
16 result = ObjectFactory.GetInstance(controllerType) as Controller;
17 }
18 catch(StructureMapException ex)
19 {
20 Debug.WriteLine(ObjectFactory.WhatDoIHave());
21 }
22 }
23 return result;
24 }
25
26 }
By the way, you can get a real good sample code to this by taking a look at the MvcContrib samples.
Now I that the function is isolated I can test it.
72 [TestMethod()]
73 public void GetStreamTest()
74 {
75 ImageMemoryManager target = new ImageMemoryManager(); // TODO: Initialize to an appropriate value
76 Image image = Image.FromFile(@"C:\Projects\Gallery\source\Gallery.Tests\Images\shuttle5-768.jpg");
77 int width = 100;
78 int height = 100;
79 ImageFormat imageFormat = ImageFormat.Bmp;
80
81 MemoryStream actual = target.GetStream(image, width, height, imageFormat);
82 Assert.IsNotNull(actual);
83 }
I could probably put together a more rigorous test here, but its testable.
The ThumbNailResults class which is returned to the view now looks like this.
10 public class ThumbnailResult : FileStreamResult
11 {
12
13
14 public ThumbnailResult(Image input) : this(input, input.Width, input.Height)
15 {
16 }
17
18 public ThumbnailResult(Image input, int width, int height) :
19 base(
20 ((IImageMemoryManager) StructureMap.ObjectFactory.GetInstance(typeof (IImageMemoryManager))).GetStream(
21 input, width, height, ImageFormat.Png),
22 "image/png")
23 {
24 }
25
26
27
28
29 }
Thanks to Tom and Josh in providing really nice examples. It's nice to see the community contributing with really good examples to such a new framework!