Showing posts with label JQuery. Show all posts
Showing posts with label JQuery. Show all posts

Thursday, April 14, 2016

Issue with JQuery ajax request which uses CORS when JSONP is explicitly specified in request options

Sometime ago we faced with interesting issue: we use JSONP request via jQuery.ajax() to send data to another domain:

   1: jQuery.ajax({
   2:     url: url,
   3:     data: { ... },
   4:     dataType: "jsonp",
   5:     success: function(msg) {
   6:         ...
   7:     }
   8: });

On most sites it works as expected, i.e. it sends HTTP GET request and passes JSONP callback function name in query string parameter. But on one site because of some reason it sends HTTP OPTION verb to the same url, which as you probably know, happens when using CORS. Since our endpoint didn’t support CORS we got errors:

Chrome:

XMLHttpRequest cannot load http://example1.com. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin http://example2.com is therefore not allowed access. The response had HTTP status code 404.

FF:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://example1.com. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

IE:

XMLHttpRequest: Network Error 0x80070005, Access is denied.

In order to fix the issue, i.e. in order to force jQuery to use JSONP instead of CORS additional parameter “crossDomain: true” should be added to request options:

   1: jQuery.ajax({
   2:     url: url,
   3:     data: { ... },
   4:     dataType: "jsonp",
   5:     crossDomain: true,
   6:     success: function(msg) {
   7:         ...
   8:     }
   9: });

After that jQuery will start to use JSONP. However the question why it implicitly uses CORS on some sites even if we pass dataType: “jsonp” in request options is still open. If you will find the reason please share it in comments.

Saturday, August 3, 2013

Return JSON from asmx when call it using jquery ajax: step by step guide

Some time you still need to configure old asmx web service for working with jquery ajax. Interesting that there is still a lot of questions about different aspects of the subject (even though that people should use WCF with REST endpoints nowadays in .Net stack), so I thought that it will be good idea to summarize all configuration steps in single post.

First of all we need to create asmx service itself which returns some POCO object. Let’s create some abstract QueueService:

   1:  [WebService(Namespace = "http://example.com")]
   2:  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   3:  [ToolboxItem(false)]
   4:  [ScriptService]
   5:  public class QueueService : WebService
   6:  {
   7:      [WebMethod]
   8:      [ScriptMethod(UseHttpGet = false, ResponseFormat = ResponseFormat.Json)]
   9:      public QueueItem GetQueueItem(string itemId)
  10:      {
  11:          var queueRepository = new QueueRepository();
  12:          var queueItem = queueRepository.GetById(new Guid(itemId));
  13:          return queueItem;
  14:      }
  15:  }

QueueItem class may look like this:

   1:  [Serializable]
   2:  public class QueueItem
   3:  {
   4:      public QueueItemStatus Status { get; set; }
   5:      public int Progress { get; set; }
   6:  }

There are several important notes in the code above. First of all web service class is decorated with ScriptService attribute. Then GetQueueItem() web method is decorated with ScriptMethod attribute with UseHttpGet = false, ResponseFormat = ResponseFormat.Json properties. They say that method will be called via HTTP POST verb (you may make it work GET e.g. with JSONP). And ResponseFormat.Json says that result will be returned in JSON format. Note that in the web method we just return object, we don’t serialize it to JSON by ourselves using e.g. standard JavaScriptSerializer or JSON.Net.

The next step will be changing of the web.config, which should be located in the same folder where asmx file is, or location of asmx file should inherit it from parent folders. Here is the minimal required configuration:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <system.web>
   4:      <webServices>
   5:        <protocols>
   6:          <add name="HttpPost"/>
   7:        </protocols>
   8:      </webServices>
   9:      <httpHandlers>
  10:        <remove verb="*" path="*.asmx"/>
  11:        <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  12:      </httpHandlers>
  13:    </system.web>
  14:  </configuration>

Here we allow using of HTTP POST verb for calling of our web service (lines 4-8) and replace default handler of asmx files to ScriptHandlerFactory.

This was the server part. Now we need to consume the web service from jquery ajax. Here is the example:

   1:  $.ajax({
   2:      url: "http://example.com/QueueService.asmx/GetQueueItem",
   3:      type: "POST",
   4:      contentType: "application/json; charset=utf-8",
   5:      data: "{'itemId':'...'}",
   6:      dataType: "json",
   7:      success: function(r) {
   8:          if (!r) {
   9:              // handle error
  10:          }
  11:          var result = r.d;
  12:          if (!result) {
  13:              // handle error
  14:          }
  15:          ...
  16:      },
  17:      error: function(xhr, status, error) {
  18:          // handle error
  19:      }
  20:  });

There are several important notes here as well and without understanding them, you will continuously get “There was an error processing the request” with HTTP 500 Internal server error. First of all we call the web service using POST verb (line 3). Second – we need to specify contentType = "application/json; charset=utf-8" (line 4). Without it ASP.Net won’t treat it as JSON request and call will fail (see this article for details: JSON Hijacking and How ASP.NET AJAX 1.0 Avoids these Attacks). Next important thing is that we need to pass parameters to the web method also in JSON format (line 5). Without it you request won’t even reach the service. End the last thing which you need to note is setting dataType = "json" (line 6).

After this you should be able to consume asmx web service which returns JSON from jquery ajax. If you will check requests in fiddler you should see something like this:

request:

POST /QueueService.asmx/GetQueueItem HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: en-US,fi;q=0.5
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json; charset=utf-8
Content-Length: 52
Connection: Keep-Alive
Pragma: no-cache

{'itemId':'…'}

response:

HTTP/1.1 200 OK
Content-Length: 152
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET

{"d":{"__type":"QueueItem","Status":0,"Progress":0}}

And there are no any additional efforts needed for serializing/deserializing object to JSON neither on server nor on client. Hope this guide will help in you work.

Saturday, July 7, 2012

Use jQuery.ajax() without jQuery

Some time ago on on of the projects we needed to perform ajax calls, but can’t use jQuery (restriction came from customers). It was a bit tricky because we needed to use it with jsonp protocol and I didn’t want to implement all those things which are implemented in jQuery from scratch (including dynamic callback function which is passed in query string and executed when response is received).

If we can’t use jQuery.ajax(), we can try to copy only this part from jQuery and use it as standalone library (pure javascript without overriding ‘$’ symbol). It was actually not very hard. Just requires attention and perseverance. You need to copy jQuery.ajax() itself and all dependencies, including all methods and global variables which are used in them. It is defined as single object:

   1: var jQueryAjax = {
   2:  
   3:     noop: function() { },
   4:  
   5:     error: function(msg) {
   6:         throw msg;
   7:     },
   8:  
   9:     ...
  10: };

You can use it as general jQuery:

   1: jQueryAjax.ajax({
   2:     url: '/File/GetType',
   3:     datatype: 'json',
   4:     data: { filePath: val },
   5:     async: true,
   6:     success: function (res) {
   7:         $("#Type").val(res);
   8:     }
   9: });

I called file jQueryAjax-1.4.2.js (jQuery 1.4.2 was used) and uploaded it here. Decided to share it because it may be useful if you will need to do similar thing. It worked in our case (ajax calls with jsonp data type), but I’m not sure that it will work in all possible scenarios. Although I tried to be accurate when copy jQuery dependencies, it needs more testing. Anyway, hope that it will help someone.

Monday, April 9, 2012

jQuery sortable list plugin

Some time ago I needed simple sortable listbox (html select element). The first thing I do in such cases is check existing jQuery plugins. However surprisingly I didn’t find suitable implementation. So I wrote it by myself and then wrap it as jQuery plugin (it is published on github also: https://github.com/sadomovalex/jquery.sortablelist. Please use it as primary source, because code may be changed in future):

   1: (function ($) {
   2:  
   3:     $.fn.sortablelist = function (options) {
   4:         var self = $(this);
   5:         var opts = $.extend({}, $.fn.sortablelist.defaults, options);
   6:  
   7:         var width = self.width() || opts.width;
   8:         width += opts.arrowsColumnWidth + 10;
   9:  
  10:         var height = self.height() || opts.height;
  11:         
  12:         self.wrap('<div id="sortablelist"/>');
  13:         var parent = self.parent('#sortablelist');
  14:         parent.width(width).height(height);
  15:  
  16:         self.attr('style', 'float: left');
  17:         self.after(
  18:             '<div id="arrows" style="float: left">' +
  19:             ' <a href="#" style="text-decoration: none" id="up">' + opts.up + '</a><br />' +
  20:             ' <a href="#" style="text-decoration: none" id="down">' + opts.down + '</a>' +
  21:             '</div>');
  22:         parent.find('#up').click($.fn.sortablelist.moveUp);
  23:         parent.find('#down').click($.fn.sortablelist.moveDown);
  24:  
  25:         parent.find('#arrows').width(opts.arrowsColumnWidth).height(height);
  26:     };
  27:  
  28:     $.fn.sortablelist.moveUp = function () {
  29:         var sel = $(this).parents('#sortablelist').find('select :selected');
  30:         if (sel.length != 1) {
  31:             return;
  32:         }
  33:         sel.each(function(){
  34:             $(this).insertBefore($(this).prev());
  35:         });
  36:     };
  37:  
  38:     $.fn.sortablelist.moveDown = function () {
  39:         var sel = $(this).parents('#sortablelist').find('select :selected');
  40:         if (sel.length != 1) {
  41:             return;
  42:         }
  43:         sel.each(function(){
  44:             $(this).insertAfter($(this).next());
  45:         });
  46:     };
  47:  
  48:     $.fn.sortablelist.defaults = {
  49:         arrowsColumnWidth: 20,
  50:         up : '&uArr;',
  51:         down : '&dArr;',
  52:         width: 200,
  53:         height : 100,
  54:     };
  55:  
  56: })(jQuery);

I decided to share it – may be it will be useful for someone else. Currently it is released with version 0.1 and contains mostly features which I needed in my project, however it also contains few options which can be used for customization:

Name Description
arrowsColumnWidth

Width of the div which will be added next to the html select element. Default value is 20.

up Text (or html content) for up arrow. Default value is "⇑" (&uArr;).
down

Text (or html content) for down arrow. Default value is "⇓" (&dArr;).

width If width of html select element can't be determined, this parameter will be used for width of wrapping div. Default value is 200.
height

If height of html select element can't be determined, this parameter will be used for height of wrapping div. Default value is 100.

Living demo can be checked here: http://camlex-online.org/demo/jquery-sortable-list-plugin-demo.html. If you will find bugs or will need new features, you may leave comments here or on github.

Monday, August 9, 2010

Scale images in web applications with examples on ASP.Net MVC

Before to start I would like to save a few words. Initially I wanted to write post about scalable image action result for ASP.Net MVC. But after that I realized that it will be more useful if I will expand the post and show how to make similar tasks using several ways (client-side and server-side) . So this post will not be ASP.Net MVC  specific – it will also contain information general for web development. This is the end of little preface, lets start.

Most of web developers in their practice worked with images. And also many of developers encountered with problem of correct scaling of the images in their web sites. But what is the problem? Suppose that we have e-commerce application and want to display some products on the web site. Also there is a list of categories in left so users are able to choose products of what category they want to see. For example go to http://www.amazon.com and select some category. Probably you will see something like this (I selected more or less neutral Kids toys category, so please don’t treat it as advertisement :) ):

image

I intentionally added arrows which show size of the products. As you can see all images have approximately the same width and height. It makes overall page structure more esthetic and user friendly. But in real life product pictures may have very different proportions, and it is very is insufficient for business if company staff responsible for adding content in application will arrange all product images manually. So how this process can be automated?

In order to achieve such result you can use several approaches:

  • Static images – images are resized during upload and stored on external image server or database.
  • Client side. It means image size are arranged on client side using javascript (e.g. jQuery scale plugin) or css (using width, height, max-width, max-height properties).
  • Server side. Images are scaled dynamically on server (using query string parameters for example which specify width and height).

In this post I will briefly overview all mentioned methods. But at first lets summarize our task. We have different product images (with different aspect ratio) and we want to display product icons in similar divs for example (all parent divs have the same size, but their size is less then size of real product image) with preserving image proportions:

image

Algorithm is the following. We need to determine minimal of two ratios and then scale image using this coefficient:

image

As said above we assume that image size is greater then div size (k < 1), i.e. we consider size decreasing. This algorithm preserves aspect ratio (because width and height are multiplied on the same coefficient) and allows to adjust image size to size of parent div (as we used minimal of two ratios it ensures that image will fit div).

Ok now lets return to the methods which can be used to implement this algorithm. How it is solved on Amazon? Lets just see properties of the images using firebug for example. We will see that it is simple images located on one of the Amazon’s image server (e.g. http://ecx.images-amazon.com/images/I/41zku-TbXmL._SL120_.jpg). Also real image’s width and height are specified in <img /> attributes – this is double check in order to ensure that it will be displayed properly in all browsers:

   1: <img height="95" width="120" src="..."/>

So this is one of the approaches – every time when new product is added into catalog, its icon is automatically generated in uploaded into image server (keeping in mind their turnovers I don’t think Amazon staff makes it manually).

This is pretty good approach. It has minimal impact on performance as most of current browsers cache static images very well. Also we shouldn’t worry about images scaling as they were already scaled for proper size during product creation. But there is also another side: it is not so flexible and adaptable for layout changing. What if we will redesign of pages and will need to change images height or width? One of the solution – we can use css styles to adjust images size, i.e. specify css class for images which will keep original proportions. E.g. if original proportions were 10:8 we can specify:

   1: .icon
   2: {
   3:     width: 100px;
   4:     height: 80px;
   5: }

(Scale images using em units is out of the scope of this article). But this approach will work well when you need to decrease image size (most of browsers minimize pictures with acceptable quality):

image

For tests I used IE 8. Opera 10.6, FF 3.0.19, Chrome 5.0.375.70. As you can see almost all browsers minimized picture pretty well (IE quality is the not very good although). But what happens if you will increase image size using css. Lets see:

image

image

Here we slightly increased images using css – and quality was lost in all browsers (and it becomes worst when image size is increased). You can see typical smoothness in all pictures (from all images above I think Chrome has best quality). I think it is not very reasonable to consciously make quality worse if we have picture of big size which can be decreased (as we saw size decreasing works better). What options do we have for such situation? One options – we can resize all images to new static size again which will not save us from similar problems in future. So it is not very good approach if we have millions of pictures and layout is changed. Another approach is to make pictures scaling dynamic.

We considered approach with statically resized images. Another possible way to scale images – is to use javascript and css (client side scaling). With this approach images will not be resized during upload – they will have their original size. With this method no additional work is required – company staff who add content to web site just select product images from any sources (it can be DVD catalog or web site provided by supplier) and it will be stored as is for example in web site content database or file system. Our task – is to show all images which have different size and aspect ratio in more or less similar proportions using javascript and css.

When you use javascript (e.g. jquery scale plugin mentioned above) – at first page load users may see image size leap as browsers will load original image size and then javascript will resize it. On the next page loads browsers will show images from cache – but I think that you don’t want to show size leap to your users each time when you added new product in catalog. Also javascript approach is not very reliable. I’m not consider situation when client browser doesn’t support javascript or has javascript disabled (such users will have problems not only with your applications in this case :) ). If javascript will fail because of some reason – users will see very bad looking page with broken layout as all product images will have their original size.

Another way to implement algorithm mentioned above is to use css. You can specify static width in css and height: auto (or vice versa):

   1: .icon
   2: {
   3:     width: 100px;
   4:     height: auto;
   5: }

In this case you will have all your images with the same width, but height will be computed using original proportions:

image

Which is apparently not what we want because as described above we want to fit product images into parent divs with the same aspect ratio. There are useful css properties which we can apply: max-width and max-height. Lets see result if we will apply the following css class to our image:

   1:  
   2: .icon
   3: {
   4:     max-width: 100px;
   5:     max-height: 80px;
   6: }

image

And it works well in all browsers I checked (IE, Opera, FF, Chrome). Ok, this method is working and can be used on your site. But what if you will also need scaled images when css is not supported, e.g. you want to generate pdf or image file dynamically with catalog of your products. For this case we need to have scaled images with predefined size in order to add them in our graphic catalog. In order to do it we should consider another method: server side scaling.

First of all I will prepare some infrastructure for our example. I will assume that images are stored in content database (ProductImage table with foreign key on Product table) which is mapped on the following model (e.g. by Fluent NHibernate):

   1: public class ProductImage : PersistentObject<int>
   2: {
   3:     public virtual byte[] Image { get; set; }
   4:     public virtual string FileName { get; set; }
   5:     public virtual Product Product { get; set; }
   6: }

(PersistentObject<T> is layer super class which contains T Id property and overrides Equal method in order to equal objects by Id). Using DDD approach we will have separate repository for our images:

   1: public interface IProductImageRepository : IRepository<ProductImage, int>
   2: {
   3: }

(IRepository<TEntity, TKey>is also base interface for repositories which is instantiated by model type and key type). Ok we have a model. Now we need custom action result – ImageResult in order to be able to return images from controllers (see http://blog.maartenballiauw.be/post/2008/05/ASPNET-MVC-custom-ActionResult.aspx):

   1: public class ImageResult : ActionResult
   2: {
   3:     public byte[] Image { get; set; }
   4:     public string FileName { get; set; }
   5:  
   6:     public override void ExecuteResult(ControllerContext context)
   7:     {
   8:         if (Image == null)
   9:         {
  10:             throw new ArgumentNullException("Image");
  11:         }
  12:         if (string.IsNullOrEmpty(this.FileName))
  13:         {
  14:             throw new ArgumentNullException("FileName");
  15:         }
  16:  
  17:         // output
  18:         context.HttpContext.Response.Clear();
  19:  
  20:         var imageFormat = getContentType(this.FileName);
  21:         context.HttpContext.Response.ContentType = imageFormat;
  22:  
  23:         using (var ms = new MemoryStream(this.Image))
  24:         {
  25:             ms.WriteTo(context.HttpContext.Response.OutputStream);
  26:         }
  27:         context.HttpContext.Response.End();
  28:     }
  29:  
  30:     protected string getContentType(string name)
  31:     {
  32:         var defaultType = "image/gif";
  33:         if (string.IsNullOrEmpty(name) || !Path.HasExtension(name))
  34:         {
  35:             return defaultType;
  36:         }
  37:         string ext = Path.GetExtension(name);
  38:         if (string.IsNullOrEmpty(ext))
  39:         {
  40:             return defaultType;
  41:         }
  42:  
  43:         if (ext.Equals(".bmp", StringComparison.InvariantCultureIgnoreCase))
  44:             return "image/bmp";
  45:         if (ext.Equals(".gif", StringComparison.InvariantCultureIgnoreCase))
  46:             return "image/gif";
  47:         if (ext.Equals(".vnd.microsoft.icon", StringComparison.InvariantCultureIgnoreCase))
  48:             return "image/vnd.microsoft.icon";
  49:         if (ext.Equals(".jpeg", StringComparison.InvariantCultureIgnoreCase))
  50:             return "image/jpeg";
  51:         if (ext.Equals(".jpg", StringComparison.InvariantCultureIgnoreCase))
  52:             return "image/jpeg";
  53:         if (ext.Equals(".png", StringComparison.InvariantCultureIgnoreCase))
  54:             return "image/png";
  55:         if (ext.Equals(".tiff", StringComparison.InvariantCultureIgnoreCase))
  56:             return "image/tiff";
  57:         if (ext.Equals(".wmf", StringComparison.InvariantCultureIgnoreCase))
  58:             return "image/wmf";
  59:  
  60:         return defaultType;
  61:     }
  62: }

In order to display ImageResult on our views we can use ImageExtensions from MVC futures, but it uses non strongly-typed approach. Magic strings approach is not very good, instead it is better to have possibility to write something like this:

   1: <%
   1: = Html.Image<ProductImageController>(c => c.ViewResized(this.Model[i].Id, 215, 250))
%>

I used the same idea as in post mentioned above and added several overridden methods for convenience (including possibility to specify attributes using anonymous type):

   1: public static class ImageResultHelper
   2: {
   3:     public static string Image<T>(this HtmlHelper helper,
   4:         Expression<Action<T>> action, int? width, int? height)
   5:         where T : Controller
   6:     { ... }
   7:  
   8:     public static string Image<T>(this HtmlHelper helper,
   9:         Expression<Action<T>> action, int? width, int? height, string alt)
  10:         where T : Controller
  11:     { ... }
  12:  
  13:     public static string Image<T>(this HtmlHelper helper,
  14:         Expression<Action<T>> action, string id, string alt, string @class)
  15:         where T : Controller
  16:     { ... }
  17:  
  18:     public static string Image<T>(this HtmlHelper helper,
  19:         Expression<Action<T>> action, int? width, int? height, string alt, string @class)
  20:         where T : Controller
  21:     { ... }
  22:  
  23:     public static string Image<T>(this HtmlHelper helper,
  24:         Expression<Action<T>> action, string alt, string @class)
  25:         where T : Controller
  26:     { ... }
  27:  
  28:     public static string Image<T>(this HtmlHelper helper,
  29:         Expression<Action<T>> action, string id, int? width, int? height,
  30:         string alt, string @class)
  31:         where T : Controller
  32:     { ... }
  33:  
  34:     public static string Image<T>(this HtmlHelper helper,
  35:         Expression<Action<T>> action, string id, int? width, int? height,
  36:         string alt, string @class, object attrs)
  37:         where T : Controller
  38:     {
  39:         string url = helper.BuildUrlFromExpression<T>(action);
  40:  
  41:         var sb = new StringBuilder();
  42:  
  43:         if (!string.IsNullOrEmpty(url))
  44:         {
  45:             sb.AppendFormat("src=\"{0}\" ", url);
  46:         }
  47:         if (!string.IsNullOrEmpty(id))
  48:         {
  49:             sb.AppendFormat("id=\"{0}\" ", id);
  50:         }
  51:         if (width != null)
  52:         {
  53:             sb.AppendFormat("width=\"{0}\" ", width.Value);
  54:         }
  55:         if (height != null)
  56:         {
  57:             sb.AppendFormat("height=\"{0}\" ", height.Value);
  58:         }
  59:         if (!string.IsNullOrEmpty(alt))
  60:         {
  61:             sb.AppendFormat("alt=\"{0}\" ", alt);
  62:         }
  63:         if (!string.IsNullOrEmpty(@class))
  64:         {
  65:             sb.AppendFormat("class=\"{0}\" ", @class);
  66:         }
  67:  
  68:         addAttributes(sb, attrs);
  69:  
  70:         return string.Format("<img {0} />", sb);
  71:     }
  72:  
  73:     private static void addAttributes(StringBuilder sb, object attrs)
  74:     {
  75:         var routeValues = new RouteValueDictionary(attrs);
  76:         foreach (var kv in routeValues)
  77:         {
  78:             sb.AppendFormat("{0}=\"{1}\" ", kv.Key, kv.Value);
  79:         }
  80:     }
  81: }

We could use helper class TagBuilder from MVC futures, but for our example I decided to use simple StringBuilder in order to keep it independent.

Now we have a model and infrastructure for displaying. Lets see how we can define ProductImageController:

   1: public class ProductImageController : Controller
   2: {
   3:     private IProductImageRepository productImageRepository;
   4:  
   5:     public ProductImageController(IProductImageRepository productImageRepository)
   6:     {
   7:         this.productImageRepository = productImageRepository;
   8:     }
   9:  
  10:     [HttpGet]
  11:     public ActionResult View(int id)
  12:     {
  13:         var productImage = this.productImageRepository.GetById(id);
  14:         if (productImage == null)
  15:         {
  16:             return new EmptyResult();
  17:         }
  18:  
  19:         return new ImageResult { FileName = productImage.FileName, Image = productImage.Image };
  20:     }
  21:  
  22:     [HttpGet]
  23:     public ActionResult ViewResized(int id, int maxWidth, int maxHeight)
  24:     {
  25:         ...
  26:     }
  27: }

As you can see ProductImageController  has 2 methods: View and ViewResized. 1st method just retrieves image from database by id and returns ImageResult (custom action result, see below). 2nd method ViewResized returns scaled image by using specified maxWidth and maxHeight parameters.

In order to implement ViewResized method we can use approach described here:

   1: [HttpGet]
   2: public ActionResult ViewResized(int id, int maxWidth, int maxHeight)
   3: {
   4:     var productImage = this.productImageRepository.GetById(id);
   5:     if (productImage == null)
   6:     {
   7:         return new EmptyResult();
   8:     }
   9:  
  10:     return this.getResizedImageResult(productImage.FileName,
  11:         productImage.Image, maxWidth, maxHeight);
  12: }
  13:  
  14: private ActionResult getResizedImageResult(string fileName, byte[] image,
  15:     int maxWidth, int maxHeight)
  16: {
  17:     using (var ms = new MemoryStream(image))
  18:     {
  19:         using (var img = Image.FromStream(ms))
  20:         {
  21:             var originalSize = img.Size;
  22:             if (originalSize.Width <= maxWidth && originalSize.Height <= maxHeight)
  23:             {
  24:                 return new ImageResult { FileName = fileName, Image = image };
  25:             }
  26:             var widthtRatio = (double)maxWidth / originalSize.Width;
  27:             var heightRatio = (double)maxHeight/originalSize.Height;
  28:             var ratio = Math.Min(widthtRatio, heightRatio);
  29:             var newSize = new Size((int)(originalSize.Width*ratio),
  30:                 (int)(originalSize.Height*ratio));
  31:  
  32:             using (var btm = new Bitmap(newSize.Width, newSize.Height))
  33:             {
  34:                 btm.MakeTransparent(Color.White);
  35:                 using (var gr = Graphics.FromImage(btm))
  36:                 {
  37:                     gr.Clear(Color.White);
  38:                     gr.CompositingQuality = CompositingQuality.HighSpeed;
  39:                     gr.InterpolationMode = InterpolationMode.Default;
  40:                     gr.DrawImage(img, 0, 0, newSize.Width, newSize.Height);
  41:                     gr.Flush();
  42:                     using (var outputMs = new MemoryStream())
  43:                     {
  44:                         btm.Save(outputMs, img.RawFormat);
  45:                         var result = new ImageResult { FileName = fileName,
  46:                             Image = outputMs.ToArray() };
  47:                         return result;
  48:                     }
  49:                 }
  50:             }
  51:         }
  52:     }
  53: }

As I said earlier we are interesting currently only in size decreasing. That’s why we have a check that specified maxWidth and maxHeight are less than original size and if not – return original image. It will also help us to prevent malicious users from requesting action with big width and height specified in order to occupy a lot of memory on web server.

Having this infrastructure we can add images into views by the following code:

   1: <%= Html.Image<ProductImageController>(c => c.ViewResized(this.Model.Id, 110, 120), "", "")%>

In real world applications you can consider adding caching into this code in order to avoid database querying each time when image is requested from view. Also if you use http modules don’t forget that modules are initialized and used per web application. It means that by default your http modules will be executed each time when any resource (including images) are requested. In order to avoid performance impact consider use separate web server or at least web application (by converting regular folder in IIS with your images into virtual folder with separate web.config).

This is all I wanted to say about scaling images in ASP.Net MVC applications. I also hope that this information will be useful for web developers who work with other technologies.