Download The Code
If you need to resize an image or create a thumbnail on the fly there are number of ways to do so in ASP.NET MVC. After a quick scout around on Google I decided to create some reusable code. I needed three main classes to get this up and running - ImageController, ThumbnailResult and some html helpers to generate the mark-up for the view.
First I created the ThumbnailResult class which is derived from ActionResult. This is where all the work is carried out to create the thumbnail / resized image. I decided to force a convention here and state that all images should reside in the Content\Images folder of the web application.
using System;
using System.Web.Mvc;
using System.Drawing.Imaging;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
namespace MVC.Image.Resize.Helpers
{
public class ThumbnailResult : ActionResult
{
public int Width { get; set; }
public int Height { get; set; }
public string Filename { get; set; }
public ThumbnailResult(int Width, int Height, string Filename)
{
this.Width = Width;
this.Height = Height;
this.Filename = Filename;
}
public override void ExecuteResult(ControllerContext context)
{
if (string.IsNullOrEmpty(Filename))
throw new NullReferenceException("parameter Image cannot be null or empty");
//A little convention - place all images in the path Content/Images
string FilePath = context.HttpContext.Server.MapPath(context.HttpContext.Request.ApplicationPath)
+ @"Content\Images\" + Filename;
if(!File.Exists(FilePath))
throw new FileNotFoundException("Image does not exist at " + FilePath);
Bitmap bitmap = new Bitmap(FilePath);
try
{
if (bitmap.Width < Width && bitmap.Height < Height)
{
context.HttpContext.Response.ContentType = "image/gif";
bitmap.Save(context.HttpContext.Response.OutputStream, ImageFormat.Jpeg);
return;
}
}
catch (Exception e)
{
throw new Exception(e.Message);
}
finally
{
bitmap.Dispose();
}
Bitmap FinalBitmap = null;
try
{
bitmap = new Bitmap(FilePath);
int BitmapNewWidth;
decimal Ratio;
int BitmapNewHeight;
if (bitmap.Width > bitmap.Height)
{
Ratio = (decimal)Width / bitmap.Height;
BitmapNewWidth = Width;
decimal temp = bitmap.Height * Ratio;
BitmapNewHeight = (int)temp;
}
else
{
Ratio = (decimal)Height / bitmap.Height;
BitmapNewHeight = Height;
decimal temp = bitmap.Width * Ratio;
BitmapNewWidth = (int)temp;
}
FinalBitmap = new Bitmap(BitmapNewWidth, BitmapNewHeight);
Graphics graphics = Graphics.FromImage(FinalBitmap);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.FillRectangle(Brushes.White, 0, 0, BitmapNewWidth, BitmapNewHeight);
graphics.DrawImage(bitmap, 0, 0, BitmapNewWidth, BitmapNewHeight);
context.HttpContext.Response.ContentType = "image/gif";
FinalBitmap.Save(context.HttpContext.Response.OutputStream, ImageFormat.Jpeg);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
finally
{
if (FinalBitmap != null) FinalBitmap.Dispose();
}
}
}
I then created the ImageController that is derived from Controller. The ImageController exposes the ThumbnailResult action. When creating a controller that serves resized images / thumbnails you must make sure the controller derives from ImageController rather than Controller.
using System.Web.Mvc;
namespace MVC.Image.Resize.Helpers
{
public class ImageController : Controller
{
protected internal virtual ThumbnailResult Thumbnail(int Width, int Height, string Filename)
{
return new ThumbnailResult(Width, Height, Filename);
}
}
}
I created some html helpers to facilitate the creation of mark-up for a thumbnail / resized image in the view. (Please note: there is probably a better way to do this!!! If anyone can suggest anything please e-mail me
dan@dotnetguy.co.uk).
namespace MVC.Image.Resize.Helpers
{
public static class ImageHelper
{
//0 - Image Source
//1 - Alt Tag
//2 - Styles
//3 - Class
const string ImageTag = "<img src=\"{0}\" alt=\"{1}\" {2} {3} />";
public static string Thumbnail(this HtmlHelper Helper,
string Controllername,
string Action,
int Width,
int Height,
string File)
{
string Imagelocation = ThumbnailLocation(Helper,Controllername, Action, Width, Height, File);
//WC3 - ALT tag always needed so when not set then set to the filename.
return string.Format(ImageTag, Imagelocation, File, null, null);
}
public static string Thumbnail(this HtmlHelper Helper,
string Controllername,
string Action,
int Width,
int Height,
string File,
string Alt)
{
string Imagelocation = ThumbnailLocation(Helper, Controllername, Action, Width, Height, File);
return string.Format(ImageTag, Imagelocation, Alt, null, null);
}
public static string Thumbnail(this HtmlHelper Helper,
string Controllername,
string Action,
int Width,
int Height,
string File,
string Alt,
IDictionary<string, object> styleAttributes)
{
string Imagelocation = string.Format("/{0}/{1}/{2}/{3}/{4}", Controllername, Action, Width, Height, File);
StringBuilder builder = new StringBuilder();
string Class = string.Empty;
if (styleAttributes != null && styleAttributes.Count > 0)
{
builder = new StringBuilder("style=\"");
foreach (KeyValuePair<string, object> styleAttribute in styleAttributes)
{
if (styleAttribute.Key == "class")
{
Class = string.Format("class=\"{0}\"", styleAttribute.Value);
}
else
{
builder.Append(styleAttribute.Key + ":" + styleAttribute.Value + ";");
}
}
builder.Append("\"");
}
return string.Format(ImageTag, Imagelocation, Alt, builder, Class);
}
public static string ThumbnailLocation(this HtmlHelper Helper,
string Controllername,
string Action,
int Width,
int Height,
string File)
{
return string.Format("/{0}/{1}/{2}/{3}/{4}", Controllername, Action, Width, Height, File);
}
}
}
Below is the code for registering the route for image resizing in the global.asax. I decided to make a Service route which then had an action to resize the images setting default image, width and height.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Service", // Route name
"Service/{action}/{width}/{height}/{file}", // URL with parameters
new { controller = "Service", action = "ResizeImage", width = 500, height = 500, file = "geekhard.jpg" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
When creating the ServiceController remember to derive from the ImageController to allow access to the Thumbnail result.
public class ServiceController : ImageController
{
public ThumbnailResult ResizeImage(int width, int height, string file)
{
return Thumbnail(width, height, file);
}
}
Below is how to call the html helper from the view. I feel that the html helpers have only 50% of the functionality needed allowing you to set inline styles as well as classes but missing is the ability to set attributes and events. (Everything rendered is wc3 compliant).
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
<%=Html.Thumbnail("Service", "ResizeImage", 300, 399, "geekinside.jpg", "Geek",
new Dictionary<string, object> {
{ "border", "solid 1px black" },
{ "margin-top", "0px" },
{ "float", "left" } })%>
<%=Html.Thumbnail("Service", "ResizeImage", 250, 399, "geekinside.jpg", "Geek",
new Dictionary<string, object> {
{ "float", "left" },
{ "margin", "20px" },
{ "margin-top", "0px" },
{ "border", "solid 1px red" } })%>
<%=Html.Thumbnail("Service", "ResizeImage", 200, 399, "geekinside.jpg", "Geek",
new Dictionary<string, object> {
{ "float", "left" },
{ "margin", "20px" },
{ "margin-top", "0px" },
{ "border", "solid 1px red" } })%>
<%=Html.Thumbnail("Service", "ResizeImage", 150, 399, "geekinside.jpg", "Geek",
new Dictionary<string, object>())%>
<%=Html.Thumbnail("Service", "ResizeImage", 100, 399, "geekinside.jpg")%>
<div style="clear:both; height:50px; padding-top:25px;"><hr /></div>
<%=Html.Thumbnail("Service", "ResizeImage", 350, 111, "geekhard.jpg", "lkzf",
new Dictionary<string, object> {
{ "margin-left", "20px" },
{ "border", "solid 1px red" },
{ "class", "Left" } })%>
<img src="<%=Html.ThumbnailLocation("Service", "ResizeImage", 100, 300, "geekhard.jpg")%>"
alt="my image" class="Left" />
<img src="
<%=Url.Action("ResizeImage", "Service", new {width = 500, height = 100, file="geekhard.jpg" }) %>"
alt="New" class="Left" />
<div style="clear:both;"></div>
</asp:Content>
Here is the output! This is just the beginning of my ImageController implementation. I have plans to introduce more ActionResults including:-DropShadowResult, RotateResult and RemoveGammaResult (anyone who has worked with IE will love this one!).
Download The Code
Download The Code
Ok the title of the post may be a little misleading because technically you cannot fluently map stored procedures, but you can still use the NHibernate configuration files to complement your fluent mappings with named query’s (calls on stored procedures). A while ago I investigated how much work would be involved when making a migration from an ADO.NET to a NHibernate data access implementation whilst slowly swapping out stored procedures to use LINQ 2 NHibernate. Although this example only shows a few simple read operation’s even more complex operations are relatively straight forwards to migrate.
Here is an example of how do fluently map a class as well as map stored procedures.
Below is a method that creates an ISessionFactory you can see the usual call to create the mappings fluently from the specified assembly, and you can see we can also add Hbm Mappings for the QuestionGroup class.
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using Domain;
namespace Data.Helpers
{
public class SessionFactory
{
public ISessionFactory CreateSessionFactory(string ConnectionString)
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.ConnectionString(
C => C.Is(ConnectionString)))
.Mappings(M => M.FluentMappings.AddFromAssemblyOf<SessionFactory>())
.Mappings(M => M.HbmMappings.AddClasses(typeof(QuestionGroup)))
.BuildSessionFactory();
}
}
}
I have a QuestionGroupMap class that takes care of mapping the properties and entities of the QuestionGroup class but I have an additional QuestionGroup.hbm file which defines two stored procedures which then can be called by NHibernate.
using Domain;
using FluentNHibernate.Mapping;
namespace Data.Mappings
{
public class QuestionGroupMap : ClassMap<QuestionGroup>
{
public QuestionGroupMap()
{
Id(X => X.QuestionGroupId).GeneratedBy.Identity();
Map(X => X.QuestionGroupDescription);
Map(X => X.IsActive);
HasMany(X => X.Questions).Cascade.All().Inverse().KeyColumnNames.Add("QuestionGroupId");
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<sql-query name="Select_All_Question_Groups">
<return alias="QuestionGroup" class="Domain.QuestionGroup, Domain">
<return-property name="QuestionGroupId" column="QuestionGroupId"></return-property>
<return-property name="QuestionGroupDescription" column="QuestionGroupDescription"></return-property>
<return-property name="IsActive" column="IsActive"></return-property>
</return>
exec Select_All_Question_Groups
</sql-query>
<sql-query name="Select_Question_Group">
<return alias="QuestionGroup" class="Domain.QuestionGroup, Domain"></return>
exec Select_Question_Group @QuestionGroupId=:QuestionGroupId
</sql-query>
</hibernate-mapping>
Below shows how the stored procedures are then called in the data access layer (I have added the commented out LINQ 2 NHibernate implementation as well for reference). This is great because as soon as you swap out the stored procedure and use LINQ you get testability for free (see my other post ).
public IQueryable<QuestionGroup> SelectAll()
{
//return from questionGroup in Session.Linq<QuestionGroup>()
// select questionGroup;
return Session
.GetNamedQuery("Select_All_Question_Groups")
.List<QuestionGroup>().AsQueryable();
}
Download The Code (you will need VS2008 Standard / Pro / Team to run this or if your über cool use VS2010 beta).