# Tuesday, 22 December 2009

I made a small upgrade to an application that had a text box for date input to use the masked edit and calendar extender found in the asp.net control toolkit.  The two extenders are great together for allowing a user to select a date off the calendar or type it in without having to worry about the formatting.  The masked edit even has a nice auto complete feature that allows you to type the month and day without the year and the extender will add the year for you (if you want the current year of course).

Everything works well except for one small bug or feature when using these two extenders together.  If you set up your mask to be ''99/99/9999" but leave the calendar’s format to it’s default then on post backs with dates having one digit month or days the calendar control will reappear. 

In order to correct this, you have to set the format for the calendar to match your mask.  In the example above you would need to set the format to "MM/dd/yyyy" for the calendar extender.

Hopefully, someone will find this post before spending too long trying to figure out what is happening (like I did).

:-)

Posted 12.22.2009  #    Comments [0]  | 
# Saturday, 12 December 2009

Testing the routes in .Net MVC can be a bit tedious without some help.  MVCContrib’s TestHelper library eases the load and makes testing routes a breeze.

To demonstrate, I created an application to manage recipes with their ingredients and directions. Below is how I wanted the routing to work.

What I want the URL to look like What it will do
/recipe List all recipes
/recipe/blueberrypie Display blueberry pie recipe
/recipe/edit/blueberrypie Edit blueberry pie recipe
/recipe/blueberrypie/ingredients Show the ingredients for blueberry pie
/recipe/blueberrypie/directions Show directions for blueberry pie
/recipe/blueberrypie/ingredients/delete/1234 Delete ingredient with ID 1234 for blueberry pie & go back to the list of ingredients

I edited the RegisterRoutes method in the Global.asax.cs file to match the table:

// recipe/BlueberryPie/ingredients/edit/1234
routes.MapRoute(
    "Ingredients",
    "recipe/{nameKey}/ingredients/{action}/{id}",
    new { controller = "Ingredients", action = "Index" },
    new { id = @"\d+" }
    );

// recipe/BlueberryPie/ingredients/
routes.MapRoute(
    "IngredientsForRecipe",
    "recipe/{nameKey}/ingredients/{action}",
    new { controller = "Ingredients", action = "List" }
    );

// recipe/BlueberryPie/directions/edit/1234
routes.MapRoute(
    "Directions",
    "recipe/{nameKey}/directions/{action}/{id}",
    new { controller = "directions", action = "Index" },
    new { id = @"\d+" }
    );

// recipe/BlueberryPie/directions/
routes.MapRoute(
    "DirectionsForRecipe",
    "recipe/{nameKey}/directions/{action}",
    new { controller = "directions", action = "List" }
    );

// recipe/edit/BlueberryPie
routes.MapRoute(
    null,
    "recipe/{action}/{nameKey}",
    new { controller = "Recipe", action = "List", nameKey = "" },
    new { action = "create|edit|index|list" }
    );

// recipe/BlueberryPie
routes.MapRoute(
    "recipe",
    "recipe/{nameKey}",
     new { controller = "Recipe", action = "Index" }
    );

Now for the magic with MVCContrib’s TestHelper library and the extension ShouldMapTo:

[TestFixture]
public class RouteTests
{
    [TestFixtureSetUp]
    public void SetUp()
    {
        RouteTable.Routes.Clear();
        MvcApplication.RegisterRoutes(RouteTable.Routes); 
    }

    [Test]
    public void RootMatchesHome()
    {
        "~/".ShouldMapTo(x => x.Index());
    }

    [Test]
    public void RecipeRootMatchesList()
    {
        "~/recipe".ShouldMapTo(x => x.List());
    }

    [Test]
    public void RecipeNameShouldDisplayIndex()
    {
        "~/recipe/BlueberryPie".ShouldMapTo(x => x.Index("BlueberryPie")); 
    }

    [Test]
    public void CreateRecipeShouldDisplayCreateAction()
    {
        "~/recipe/create/".ShouldMapTo(x => x.Create());
    }

    [Test]
    public void EditRecipeShouldDisplayEditAction()
    {
        "~/recipe/edit/BlueberryPie".ShouldMapTo(x => x.Edit("BlueberryPie"));
    }
    [Test]
    public void DeleteRecipeShouldDisplayDeleteAction()
    {
        "~/recipe/delete/1".ShouldMapTo(x => x.Delete(1));
    }
    [Test]
    public void IngredientsForRecipeShouldDisplayIngredientsListAction()
    {
        "~/recipe/BlueberryPie/ingredients".ShouldMapTo(i => i.List("BlueberryPie")); 
    }
    [Test]
    public void DirectionsForRecipeShouldDisplayIngredientsListAction()
    {
        "~/recipe/BlueberryPie/directions".ShouldMapTo(i => i.List("BlueberryPie"));
    }
    [Test]
    public void DeleteIngredientShouldDisplayDeleteAction()
    {
        "~/recipe/BlueberryPie/ingredients/delete/1".ShouldMapTo(i => i.Delete(1));
    }
    [Test]
    public void DeleteDirectionShouldDisplayDeleteAction()
    {
        "~/recipe/BlueberryPie/directions/delete/1".ShouldMapTo(i => i.Delete(1));
    }
}

No need to mock/fake any context, setup route data, etc.  The code is short and sweet, easy to read, understand and matches the URL that would be in the browser making it very intuitive.

Please download the code.  The sample is using NUnit 2.5.

MVCContribTestRoutes.zip (603.49 KB)

Happy coding!

Posted 12.12.2009  #    Comments [0]  | 
# Friday, 11 December 2009

In an earlier post I added character counting functionality to the ASP.Net AJAX HTML Editor with jQuery.  Soon after posting the entry I noticed that cutting and pasting text into the editor via the context menu did not change the character count immediately.  I enhanced the jQuery code in my last post to do this.

var Editor1 = '#Editor1';
var Editor1CountLimit = 50
var Editor1InfoArea = '#Info';

$(document).ready(function() {
    TrackCharacterCount(Editor1, Editor1CountLimit, Editor1InfoArea);
});

function TrackCharacterCount(ctl, limit, info) {
    var editor = GetEditor();
    $(editor).load(function() {

    var body = $(this).contents().find('body');
    var txt = $(body).text();
    $(info).html(txt.length); //set initial value 

    $(body).bind('cut paste', function(e) {
            setTimeout(function() {
                var txt = $(body).text();
                UpdateTextCounter(txt,limit,info);
            }, 150)
        });
        
        $(this).contents().keyup(function() {
            var txt = $(this).contents().find('body').text();
            UpdateTextCounter(txt,limit,info);
        });
    });
}

function UpdateTextCounter(txt,limit,counter) {
    if (txt.length > limit)
        $(counter).html(txt.length).css("color", "red");
    else
        $(counter).html(txt.length).css("color", "");
} 

I added the bind code on line 17 to handle the cut and paste functionality.  I went through a couple iterations of the cut & paste functionality to count the characters correctly.  I was running into an issue where the textbox value was not being updated immediately with what was pasted.  Then I stumbled upon a nice stackoverflow answer that used a timeout to wait for the textbox to be updated.  This little jewel allowed me to simplify the code to what it is now.

I refactored the code to decrease duplication of code by creating the UpdateTextCounter function. 

I also noticed a subtle difference between IE7 and IE8 that stopped the counter from working and have corrected that in this version with a browser check.

function GetEditor() {
    if ($.browser.msie == 'true' && $.browser.version == '7.0')
        return editor = $(Editor1).contents().find('iframe').eq(1);
    else
        return editor = $(Editor1).contents().find('iframe').eq(2);
}

I verified the code runs on IE7, IE8, FF 3.5.5 & Chrome 3.0.

Enjoy!

AjaxHtmlEditorV2.zip (1.28 MB)

Read the first post.

Read the next post where I fix a bug when using a customized toolbox with the editor and find a better way to locate the IFrame that our text editor is in.

Posted 12.11.2009  #    Comments [0]  | 
# Saturday, 05 December 2009

Dynamic Data is a new project type in VS 2008.  It uses scaffolding to help code faster.  Over the past month or so I have been playing with DD and wanted to share the resources that have helped me understand and use/customize DD.

Dynamic data on codeplex: Good resource, has lots of samples on doing things beyond the basics. http://aspnet.codeplex.com/Wiki/View.aspx?title=Dynamic%20Data

Some samples include creating your own view using your own select statement.  Also in the samples is how to use Stored Procedures with DD.

Matt Berseth - http://mattberseth.com/blog/dynamic_data/

Has some nice tutorials on going beyond the basics like creating a custom meta provider that allows you to change the default way your columns are named.  For example, have the columns be Product ID instead of ProductID.

Stephen Naughton - http://csharpbits.notaclue.net/

Perhaps the best site I have found for taking DD to the next level.  Many examples of customization.  Stephen is an expert on DD in my opinion.  I used a sample from him to turn off foreign keys from showing at all for the entire application.

Craig Shoemaker also has some great examples and a book for about $10. The book isn't heavily into the underpinnings though but is worth the price for a beginner.  He also has a deep dive example which goes a little more in depth.

http://weblogs.asp.net/craigshoemaker/archive/tags/Dynamic+Data/default.aspx

http://blogs.msdn.com/rickandy/archive/2009/01/08/dynamic-data-faq.aspx - Great links to resources and perhaps my second most visited site while trying to get a handle on DD.

http://www.asp.net/learn/3.5-SP1/ - video's on DD...

http://forums.asp.net/1145.aspx - forums for dynamic data....

Depending on your level of skill with Linq2SQL or Linq2EF you may want to learn more on them as well since DD sits on top of these tools.  Knowing more about these topics and extending them will also add to your arsenal for DD.

For me, I found the basics of DD to be easy to grasp but I quickly realized two things: 

  1. DD could be really great for small tasks like creating administrative pages that allow users to add / edit lookup tables, etc….
  2. Taking DD off road, aka – going beyond the basics will require a much deeper knowledge (duh).

The three items I found that are important to understand in order to take DD off road:

  • Modify / define your own metadata providers.

http://mattberseth.com/blog/2008/08/dynamic%5Fdata%5Fand%5Fcustom%5Fmetada.html - Names all columns with a space between words... Yes, you could use the DisplayName attribute but this is a sample of how to refine the basic scaffolding project.

  • Creating your own attributes.

http://csharpbits.notaclue.net/2009/04/hiding-foreign-key-column-globally-in.html - Sample of creating an attribute to hide the foreign key column for all tables.

http://csharpbits.notaclue.net/2008/10/dynamic-data-hiding-columns-in-selected.html - Sample of another attribute to hide a column but applied at the page level.

  • Create / modify the templates.

You can create your own templates and modify the existing templates to influence the UI experience.

After this, to get DD ready for the real world you can:

Tier it:

http://weblogs.asp.net/craigshoemaker/archive/tags/Dynamic+Data/default.aspx

Mix it with Web forms, with MVC... http://www.codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=14473

Use AJAX and/or JQuery….  These are not DD specific modifications but likely necessary to be real world ready.

Hope this helps you on your journey!

Posted 12.05.2009  #    Comments [0]  |