Saturday, July 30, 2011

Image Upload, Crop and Resize with ASP.NET MVC jQuery Uploadify and jCrop

I need a slick way for my customers to upload, crop and resize photos within my Church Management Software. After doing some research it seems that a bunch of existing libraries and utilities need to be combined to create a functional and slick user experience for this seemingly mundane task. Specifically the platform I want to use is ASP.NET MVC (although this code will work equally well in Java with a few changes) and the best tools for uploading and cropping images seem to be Uploadify and jCrop powered by jQuery (of course.)

I found a lot of examples of each different library used in ASP.NET (and a few in MVC) but the examples where rather simplistic and didn’t really provide an elegant end to end solution tying together these great libraries. So I went ahead and created a nice ASP.NET MVC based sample application that accomplishes the following…

  • Uploads images with Uploadify
  • Stores uploaded images in Cache
  • Crops images using jCrop
  • Resizing images
  • All interactions handled by jQuery AJAX for a smooth UX

image

Uploading Images

I used a great blog post by James McCormack as the base for uploading images with Uploadify and ASP.NET MVC. Basically Uploadify is doing the heavy lifting and I just tied the onComplete event to an ASP.NET MVC Controller method.

$("#fuFileUploader").uploadify({
'hideButton': true, // We use a trick below to overlay a fake html upload button with this hidden flash button
'wmode': 'transparent',
'uploader': '<%= Url.Content("~/Uploadify/uploadify.swf") %>',
'cancelImg': '<%= Url.Content("~/Uploadify/cancel.png") %>',
'buttonText': 'Upload File',
'script': '<%= Url.Action("FileUpload", "Media") %>',
'multi': true,
'auto': true,
'fileExt': '*.jpg;*.gif;*.png;*.jpeg',
'fileDesc' : 'Image Files',
'scriptData': { RequireUploadifySessionSync: true, SecurityToken: UploadifyAuthCookie, SessionId: UploadifySessionId },
'onComplete': function (event, ID, fileObj, response, data) {
response = $.parseJSON(response);
if (response.Status == 'OK') {
$("#pnlUpload").hide();
$("#pnlUploadedImage").show();
$("#imgUploadedImage").attr("src", imageHandler + response.Id);
$('#imgUploadedImage').Jcrop({
onChange: setCoords,
onSelect: setCoords
});
}
}
});


The controller method grabs the image that was uploaded…


public ActionResult FileUpload(MediaAssetUploadModel uploadedFileMeta)
{
Guid newImageId = new Guid();
try
{
newImageId = ProcessUploadedImage(uploadedFileMeta);
}
catch (Exception ex)
{
string errorMsg = string.Format("Error processing image: {0}", ex.Message);
Response.StatusCode = 500;
Response.Write(errorMsg);
return Json(string.Empty);
}

return Json(new { Id = newImageId, Status = "OK" });
}


And then processes it….


private Guid ProcessUploadedImage(MediaAssetUploadModel uploadedFileMeta)
{
// Get the file extension
WorkingImageExtension = Path.GetExtension(uploadedFileMeta.Filename).ToLower();
string[] allowedExtensions = { ".png", ".jpeg", ".jpg", ".gif" }; // Make sure it is an image that can be processed
if (allowedExtensions.Contains(WorkingImageExtension))
{
WorkingImageId = Guid.NewGuid();
Image workingImage = new Bitmap(uploadedFileMeta.fileData.InputStream);
WorkingImage = ImageHelper.ImageToByteArray(workingImage);
}
else
{
throw new Exception("Cannot process files of this type.");
}

return WorkingImageId;
}


And stores the image converted to a byte array in Cache…


#region cached properties

private byte[] WorkingImage
{
get
{
byte[] img = null;

if (HttpContext.Cache[WorkingImageCacheKey] != null)
img = (byte[])HttpContext.Cache[WorkingImageCacheKey];

return img;
}
set
{
HttpContext.Cache.Add(WorkingImageCacheKey,
value,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new TimeSpan(0, 40, 0),
System.Web.Caching.CacheItemPriority.Low,
null);
}
}

private byte[] ModifiedImage
{
get
{
byte[] img = null;
if (HttpContext.Cache[ModifiedImageCacheKey] != null)
img = (byte[])HttpContext.Cache[ModifiedImageCacheKey];
return img;
}
set
{
HttpContext.Cache.Add(ModifiedImageCacheKey,
value,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new TimeSpan(0, 40, 0),
System.Web.Caching.CacheItemPriority.Low,
null);
}
}

#endregion


The image is assigned in id, which is used as the Cache key and is returned back out via json. Once the call is successfully completed an img src attribute is updated to point to a ASHX image handler I wrote that accepts the id as a querystring parameter.


$("#imgUploadedImage").attr("src", imageHandler + response.Id);


The image handler grabs the image out of the cache and then displays it.


public void ProcessRequest (HttpContext context) {
byte[] image = GetImage(context.Request.QueryString["id"], context);
context.Response.Clear();
context.Response.ContentType = "image/pjpeg";
context.Response.BinaryWrite(image);
context.Response.End();
}


Cropping Images


Once the image has been uploaded, jCrop is initialized to allow for image crop selection.


$('#imgUploadedImage').Jcrop({
onChange: setCoords,
onSelect: setCoords
});


If the user selects a region to crop and clicks the crop button a jQuery AJAX call is made to the server. The x, y and width, height crop values are posted to the server and then the server crops the image as specified. A new modified image id is returned via json to the view and then the new cropped image is displayed via the image handler ashx once again.


function cropImage() {
$.ajax({
url: "/Media/CropImage",
type: "POST",
data: { x: x, y: y, w: w, h: h },
success: function (data) {
$('#lblMethodError').hide();
$("#pnlNewImage").show();
$("#imgNewImage").attr("src", imageHandler + data);
},
error: function (xhr, status, error) {
// Show the error
$('#lblMethodError').text(xhr.responseText);
$('#lblMethodError').show();
}
});

}


public JsonResult CropImage(int x, int y, int w, int h)
{
try
{
if (w == 0 && h == 0) // Make sure the user selected a crop area
throw new Exception("A crop selection was not made.");

string imageId = ModifyImage(x, y, w, h, ImageModificationType.Crop);
return Json(imageId);
}
catch (Exception ex)
{
string errorMsg = string.Format("Error cropping image: {0}", ex.Message);
Response.StatusCode = 500;
Response.Write(errorMsg);
return Json(string.Empty);
}
}


Resizing Images


Resizing images follows the same basic pattern. I have hard coded the new image dimensions in the example, but you could easily add a couple of inputs to the view and allow the user to specify the new dimensions.


Download the Example


I put together a full working example that includes all of the code above and is the basis for what I will end up implemented in my SaaS. I hope you find this helpful.


Aaron Schnieder
http://www.churchofficeonline.com

Tuesday, November 30, 2010

Azure SDK 1.3 Beta - System.ServiceModel Exception on Debug

I started using the new Windows Azure 1.3 SDK (BETA) yesterday and after I installed it I began getting a strange error when I tried to run Debug from Visual Studio 2010.


System.ServiceModel.CommunicationObjectFaultedException was unhandled
Message=The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.

This issue drove me crazy for the last day and after much weeping and gnashing of teeth, I figured out what the fix is:

Make sure your web role web.config is writable (i.e. not locked in read-only by source control or the read-only file bit.)

It looks like there is a bug in the Azure SDK 1.3 that is causing the web.config to be written to during the instantiation of the Azure dev fabric roles. If the web.config is read-only, Azure is blocked from editing the file (no actual edits take place that I can find) and the bizarro System.ServiceModel.CommunicationObjectFailedException message bubbles up.

Secondarily.... Make sure that any declarations are moved to . This was a minor issue causing a few hiccups (not the main problem), but I thought it was worth mentioning.

Hope that helps everyone.

Aaron
http://www.churchofficeonline.com

Wednesday, June 2, 2010

jQuery AJAX Validation Using The Validity Plugin

Books I recommend on this subject

The following book is the best reference I have found when working with jQuery. I highly recommend it!

jQuery in Action

jQuery Validity

Input validation is one of those areas that most developers view as a necessary evil. We know that it is necessary and we really do want to ensure that we get good input from our users. But most of us are lazy (me included) and input validation is one of those things that gets done but usually is a quick and dirty implementation. This is partly due to laziness and partly do to input validation being painful.

Thanks to the amazing jQuery Validity plug in, input validation can be really slick, easy and robust enough to work any any scenario. I specifically like the Validity plugin because it supports jQuery AJAX input validation. Other input validation implementations that I have worked with require a form post to take place. However, if you are using jQuery.ajax methods then there isn’t a form and you need to validate the formless input.

Here is the download link for the jQuery Validity plugin: http://code.google.com/p/validity/downloads/list

Here is the documentation for the jQuery Validity plugin: http://validity.thatscaptaintoyou.com/

What It Looks Like

The default UI for the validation feedback is pretty nice, take a look:

jQueryValidityScreenshot

HTML Input

Here is the HTML markup that I used to create the inputs to validate:

<fieldset id="SignUpFields">
<legend>Sign Up</legend>
<label>Name</label>
<input type="text" id="txtbxName" />
<label>Email</label>
<input type="text" id="txtbxEmail" />
<label>Password</label>
<input type="password" id="txtbxPassword" />
<label>Confirm Password</label>
<input type="password" id="txtbxConfirmPassword" />
<label>How Did You Hear About Us?</label>
<input type="text" id="txtbxSource" />
</fieldset>
<button id="btnSubmit">Submit</button>


jQuery and Validity Functions


The jQuery Validity code is extremely straight forward and minimal.


$(function () {
// Handle the click event for the submit button
$('#btnSubmit').click(function () {
// Validate the fields
if (ValidateFields()) {
// All fields are valid, $.ajax POST would go here
$('#SignUpFields').hide();
$('#btnSubmit').hide();
$('#lblResponse').show();
}
});
});

function ValidateFields() {
// Start validation:
$.validity.start();

// Validate fields
$("#txtbxName").require();
$("#txtbxEmail")
.require()
.match("email");

// Validate password strength & match
$("input[type='password']")
.require()
.match(/^.{8,20}$/, "Passwords must be at least 8 characters.")
.equal("Passwords do not match.");

// All of the validator methods have been called:
// End the validation session:
var result = $.validity.end();

// Return whether it's okay to proceed with the Ajax:
return result.valid;
}


That's It!


That's all it takes, very simple and straight forward. Just that tiny bit of Javascript gives you rich input validation and a nice user experience!


Demo Download


Here is a little ASP.NET MVC demo that I threw together using jQuery Validity so you can see it in action and play with it.



- Aaron Schnieder
http://www.churchofficeonline.com

Windows Live Writer Code Snippet Plugin

I love Windows Live Writer as a blogging application and use it pretty much exclusively for writing my blog posts. The only downside is that I have found it difficult to get code snippets formatted correctly in my posts. Luckily a friend of mine, Tyson Swing, turned me on to a great code snippet plugin for Windows Live writer.

CodeSnippet

The Windows Live Writer code snippet plugin is aptly named “CodeSnippet” and you can download it here: http://wlwplugincollection.codeplex.com/ 

This Windows Live Writer code snippet plugin is provided by Leo Vidosola. http://lvildosola.blogspot.com/

Settings

Personally, I have found that the following settings work well for the “CodeSnippet” plug in:

  • Be sure to select the correct code language
  • No Line Numbers
  • No Alternate Lines
  • No Container

Aaron Schnieder
http://www.churchofficeonline.com

Monday, May 3, 2010

jQuery Ajax Error Handling – How To Show Custom Error Messages

Books I recommend on this subject

The following books are the best references I have found when working with jQuery and ASP.NET MVC. I highly recommend both.

jQuery in Action

Pro ASP.NET MVC Framework

So you want to make your error feedback nice for your users…

Kind of an ironic statement isn’t it? We obviously want to avoid errors if at all possible in our applications, but when errors do occur then we want to provide some nice feedback to our users. The worst thing that can happen is to blow up a huge server exception page when something goes wrong or equally bad is not providing any feedback at all and leaving the user in the dark. Although I do not recommend displaying actual .NET Framework exception messages or stack traces to the user in most instances; they are usually not helpful to the user and can be a security concern.

Using the jQuery Ajax methods ($.ajax(), $.get(), $.post(), etc.) to call your controller (in ASP.NET MVC) or web service (in ASP.NET) to perform server side actions (GET, POST, etc.) is a great way to provide a great user experience in ASP.NET applications while also interacting with a server to get or update data. However, if an error occurs on the server, or an exception takes place in the server side methods, then the problem can often be hidden from the user. Because a Postback is not taking place, if you do not handle errors on the jQuery Ajax methods and display some feedback then the user will be left in a state where they have no idea that a problem has occurred. Luckily, we can use the error callback on the jQuery Ajax methods along with some controller information to display some nice error feedback to the user.

Setting up layers to handle and communicate problems

I have defined an Entities assembly that contains an object to be used to provide operation feedback between assemblies and methods.

namespace jQueryCustomErrorHandling.Entities
{
public class OperationResult
{
public bool Success { get; set; }
public string Message { get; set; }
}
}

My persistence and logic methods pass an OperationResult as a return type.

Persistence Layer 

public interface IMyDao
{
OperationResult MyDaoMethod(bool fail);
}
public class MyDao : IMyDao
{
public OperationResult MyDaoMethod(bool fail)
{
var result = new OperationResult() { Success = true };
try
{
if (fail)
throw new Exception("Persistance Error");
}
catch (Exception ex)
{
result.Success = false;
result.Message = ex.Message;
}
return result;
}
}

Logic Layer

public interface IMyLogic
{
OperationResult MyLogicMethod(bool fail);
}
public class MyLogic : IMyLogic
{
private IMyDao _myDao;
public IMyDao MyDao
{
get { return _myDao ?? (_myDao = new MyDao()); }
set { _myDao = value; }
}
public OperationResult MyLogicMethod(bool fail)
{
return MyDao.MyDaoMethod(fail);
}
}

And now communicating the errors to the presentation layer

I have created a controller method that calls the logic layer. Notice that if the OperationResult.Success property is set to false, I throw an exception in the controller method. The catch block handles that exception and sets the Response.StatusCode to an error and then also does a Response.Write of the error message. This is how we communicate with the jQuery error callback that there was an error on the server side.

[AcceptVerbs(HttpVerbs.Post)]
public JsonResult MyPostMethod(bool fail)
{
try
{
// Call the logic layer to make the updates
OperationResult result = MyLogic.MyLogicMethod(fail);
// If the result is a failure, throw an exception
if (!result.Success)
throw new Exception(result.Message);
return Json(result.Message);
}
catch (Exception ex)
{
string errorMsg = string.Format("Error posting data: {0}", ex.Message);
Response.StatusCode = 500;
Response.Write(errorMsg);
return Json(string.Empty);
}
}

The jQuery.ajax configuration looks like this:

function CallAjaxMethod(fail) {
$('#lblMethodError').hide();
$('#lblMethodSuccess').hide();
$.ajax({
type: "POST",
url: "/Home/MyPostMethod",
data: "fail=" + fail,
dataType: "json",
error: function(xhr, status, error) {
// Show the error
$('#lblMethodError').text(xhr.responseText);
$('#lblMethodError').show();
},
success: function(data, textSuccess) {
// show the success message
$('#lblMethodSuccess').show();
}
});
}

The presentation

In the view I simply have two hidden labels that I show or hide depending on whether or not the jQuery.ajax call was successful. I populate the error label with the custom error message from the server in the jQuery.ajax error callback if a problem occurred on the server side.

This provides a nice way to give feedback to the user when problems occur on the server side and also establishes a nice error handling pattern within your projects.

Demo download

I put together a working demo with full source code that you can download below.

- Aaron Schnieder
http://www.churchofficeonline.com

Thursday, April 22, 2010

Best jQuery Libraries, Plug-Ins and Controls

Worried About The Loss Of ASP.NET Controls in MVC? Don’t Be

If you are hesitant of moving to ASP.NET MVC because you are worried about losing all of the awesome ASP.NET controls that you are so used to using, don’t be. Wonderful client side controls already exist to replace most, if not all, of the most used ASP.NET controls (and these controls provide a MUCH BETTER user experience.)

First off, if you are looking for an excellent jQuery book I HIGHLY recommend jQuery in Action

Here are the best jQuery libraries, plug-ins and controls that I have found to date.

What is a CDN (Content Delivery Network)?
http://en.wikipedia.org/wiki/Content_delivery_network

jQuery

The best client site Javascript library to date. Client side development without jQuery just doesn’t make sense.

Information, Documentation & Download http://jquery.com/
Google CDN http://code.google.com/apis/ajaxlibs/documentation/index.html#jquery
Microsoft CDN http://www.asp.net/ajaxlibrary/cdn.ashx

jQueryUI

Excellent user interface controls, interactions and effects that are built on top of the jQuery framework. Provides themes for customized look and feel and out of the box excellent user experience.

  • Calendar control
  • Dialog / Modal control
  • Tab control
  • Accordion control
  • Animation effects
  • Draggable / Droppable interaction
  • Much, much, much more….

Information, Documentation & Download http://jqueryui.com/
Google CDN http://code.google.com/apis/ajaxlibs/documentation/index.html#jqueryUI

Date.js

Sweet library that makes working with dates on the client side infinitely easier. Great for date calculations, date ranges and date validation.

  • Text date references; ‘last week’ will return the date for 7 days ago
  • ‘yesterday’ returns yesterday’s date
  • Date.parse('1 month ago').moveToFirstDayOfMonth() returns the first day of last month
  • Much, much more…

Information, Documentation & Download http://www.datejs.com/

Validity Validation Plug In

The jQuery Validity plug-in is an extremely flexible, lightweight and easy to use validation library. You can validate anything on the fly in any instance. It doesn’t require a form or anything else, just setup the validation rules (there are a bunch of common validations built in) and go.

Information, Documentation & Download http://validity.thatscaptaintoyou.com/

DateRangePicker

The DateRangePicker plug-in from the Filament Group is an awesome little user experience element that makes selecting dates so much nicer. You can select predefined dates or date ranges easily (such as last week, last month, yesterday, etc.) and is the best user experience have seen selecting a date range.

Information, Documentation & Download http://www.filamentgroup.com/lab/date_range_picker_using_jquery_ui_16_and_jquery_ui_css_framework/

jQuery Tools

jQuery Tools is an excellent library of user interface elements. It is fairly lightweight and can add some very nice user experience elements to your applications.

  • Tooltips
  • Overlays
  • Scrollable Galleries
  • Flash Embedding

Information, Documentation & Download http://flowplayer.org/tools/index.html

jqGrid

Ah jqGrid, how I love thee jqGrid. This little beauty is a wonderful Gridview alternative in ASP.NET MVC. I wrote up an overview of using it here: http://www.schnieds.com/2010/01/gridview-in-aspnet-mvc.html

Information, Documentation & Download http://www.trirand.com/blog/

jQuery Templates

jQuery Templates are a way to accomplish DataBinding in ASP.NET MVC. Basically you pre-define some HTML elements and then when the page is rendering you use a jQuery AJAX call to get some data from the server, create instances of the HTML elements, populate the values and then append the items into the DOM. jQuery Templates makes this process very easy. Here is a nice tutorial on using this plug-in: http://publicityson.blogspot.com/2009/11/dynamic-content-using-html-templates.html

Information, Documentation & Download http://plugins.jquery.com/project/jquerytemplate

Monday, January 25, 2010

Using Ionics Isapi Rewrite for ASP.NET MVC in IIS 6

If you are running IIS6 and want to deploy an ASP.NET MVC app it is a royal pain in the behind. Pretty URLs (www.mysite.com/prices instead of www.mysite.com/prices.aspx) aren’t supported out of the box. There are several solutions to this problem. First, you can still use MVC with IIS6 out of the box and just append .mvc to all of your files. This approach works, but you lose the nice URLs. Second, you can add a wildcard mapping that processes EVERY resource in a website through the ASP.NET ISAPI dll (images, css files, javascript, everything!) There is a known performance hit with this method and I found it to be quite noticeable when I tried it. Third, you can configure a URL re-writing module that will enable pretty URLs and do so in a well performing manner.

I wanted to go with option 3, which is a bit more work but I feel the best overall solution. Unfortunately, most of the information and “How To” posts I found out on the internet didn’t work, were horribly outdated or just didn’t make sense. So with that in mind I download the latest version of Ionic’s open source Isapi Rewrite module, read through their documentation and then worked out a configuration in IIS 6 that got everything working. Here is a step by step process that you can follow to easily (and freely) get ASP.NET MVC pretty URLs running under IIS 6 using Ionic’s Isapi Rewrite module.

Ionics Isapi Rewrite Installation and Configuration

  1. Download Ionic Isapi Rewrite http://iirf.codeplex.com/
  2. Unzip the contents
  3. Create the following folder c:\inetpub\IonicRewriter (or a location of your choice)
  4. Copy IonicIsapiRewriter-2.0-Release-bin\bin\IIRF.dll (from the contents of the download you unzipped) to c:\inetpub\IonicRewrite
  5. Create a new text document, name it IirfGlobal.ini
  6. Select both files, right click and choose Properties
  7. Select the Security tab
  8. Ensure IIS_WPG has read & execute permissions on both IIRF.dll and IirfGlobal.ini
  9. Edit IirfGlobal.ini and include the following:
    # IsapiRewrite4.ini
    ## Turn off logging, enable if you need to debug routing
    #RewriteLog  c:\_Logs\iirfLog.out
    #RewriteLogLevel 3
    RewriteFilterPriority HIGH
    IterationLimit 1
    RewriteEngine ON
    StatusUrl /iirfStatus
    RewriteRule ^/Default\.aspx /Home.mvc [I,L]
    RewriteRule ^/$ /Home.mvc [I,L]
    RewriteRule ^/([\w]+)$ /$1.mvc [I,L]
    RewriteRule ^/(?!Content|Scripts|App_Data|Images)([\w]*)/(.*) /$1.mvc/$2 [I,L]

    ** The last rule filters out any files in the Content, Scripts, App_Data and Images folder from the URL rewrite. This prevents a .MVC extension from being appended to root folder of the resource being requested (i.e. mysite.com/Content.mvc/site.css) To add another other folders you want to exclude, just add another |FolderName to the regular expression.


  10. Copy the IirfGlobal.ini file to your website directory and rename it to IIRF.ini
  11. Select IIRF.ini, right click and choose Properties
  12. Ensure IIS_WPG has read & execute permissions on IIRF.ini

Internet Information Services (IIS) 6 Configuration

** These configuration instructions are for a single website. If you want to configure the Ionics Isapi Rewrite module for all of IIS 6, perform these steps on the Websites root directory.

  1. Open IIS Manager
  2. Expand the Web Sites node
  3. Right click on the web site you want to configure and select Properties
  4. Select the ISAPI Filters tab
  5. Click the Add button
  6. Enter "Ionic Rewriter" for the filter name
  7. Browse to c:\inetpub\IonicRewriter\IIRF.ddl and select the file (or the location that you placed IIRF.dll into in Step 3)
  8. Click OK
  9. Select the Home Directory tab
  10. Click Configuration
  11. Click the Add... button under "Application extentions"
  12. Click the Browse button and select C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll for the executable
  13. Enter .mvc for the extension
  14. Limit verbs to: GET,HEAD,POST,DEBUG
  15. UNCHECK "Verify that file exists"
  16. Click OK
  17. Click OK, OK to close the main properties dialog
  18. In IIS Manager, go to Application Pools and Start/Stop the application pool that your MVC application is running under
  19. In IIS Manager –> Web Sites, select your MVC web site and stop/start the web site

IIRF Status and Troubleshooting

  1. On the server, open Internet Explorer and browse to: http://yourmvcwebsiteurl/iirfStatus
  2. You should see the IIRF Status Report if you followed the steps properly
  3. Specifically check the INI file status for both the Global and Site Specific sections, if there are any problems double check your file locations and file permissions as stated above
  4. If IIRF still isn’t working correctly, consult the IIRF v2.0 Operator’s Guide located in the contents of the zip file you downloaded under AdminGuide/Help. There is a section in the chm titled “Verifying and Troubleshooting Installation”
  5. You can also enable logging in the ini files for IIRF and take a look at the IIRF log output to diagnose problems and errors. If there isn’t a log being generated and un-commenting the logging options that means that IIRF isn’t routing properly.

ASP.NET MVC Application Configuration

  1. Modify your Global.asax route code to handle .mvc routes as follows
    routes.MapRoute(
        "Default.mvc",                                          // IIS 6 Ionic Isapi Rewrite support
        "{controller}.mvc/{action}/{id}",                       // URL with parameters
        new { controller = "Home", action = "Index", id = "" }, // Parameter defaults
        new { controller = @"[^\.]*" }                          // Don't look for a controller for non mvc files (ico, images, etc.)
    );

  2. Build & publisher your MVC application
  3. Copy your MVC application to your server, restart the application pool once more and your nice routes should now work

Download

Here is a zip file containing the IIRF.dll, IirfGlobal.ini and IIRF.ini files as well as a sample ASP.NET MVC application with the Global.asax routing setup. It is everything you need and you can just copy/paste these files into your web application using the instructions above.




Aaron Schnieder
http://www.churchofficeonline.com