Archive

Archive for the ‘MVC’ Category

Reusable ‘Controls’ with Sprites, CSS, and Spark Partials

I came across SilkSprite during some of my work with BluePrintCSS and fell in love.  I’ve used the FamFamSilk icons for ages and, with a current project, wanted to implement some instant CSS buttons and menu items using those icons.

So, with CSS-fu in hand, here’s how the button turned out.

First, creating a simple CSS button style:

.button

{

    display: inline-block;

    font-weight: bold;

    font: bold .85em/2.5em Verdana, Helvetica, sans-serif;

    text-decoration: none;

    text-indent: 10px;

    width: 150px;

    height: 2.5em;

    color: #555555;

    background-color: #EAEAD7;

    border: #CCCCCC solid 1px;

    -moz-border-radius: 5px;

    -webkit-border-radius: 5px;

}

 

.button:hover

{

    background-color: #F3F3E9;

    color: #737373;

    cursor: pointer;

}

This button is pretty simple and standard—nothing fancy.  The most important tag in there is display: inline-block as this allows our buttons to share the same row and not stack on top of each other (like float:left would cause).

Hover Off: CSS Button - Hover Off

Hover On: CSS Button - Hover On

Second, because I needed to include the icon INSIDE another container (the button), I modified the original SilkSprite ss_sprite and removed a bit of the extra padding.

.sprite

{

    display: inline-block;

    overflow: hidden;

    background-repeat: no-repeat;

    background-image: url(/content/img/sprites.png);

    padding-left: 25px;

    padding-top: 2px;

    height: 16px;

    max-height: 16px;

    vertical-align: middle;

}

The important thing to note here is the padding-top and the height attributes.  Until I’m lead into the light and improve my CSS-fu, I’m compensating for the font height differences by using padding-top.  e.g. I want the icon to truly show up in the “middle”… and vertical-align just wasn’t pushing it far enough.

We’ll come back to actually working with SilkSprite in a moment, let’s work up a quick Spark partial view to display our results.  Now, you could do this without the partial views—just replace my Spark variables with the actual text we’re passing to them.  I’ll provide both examples.

Our button needs three elements to be useful:

  1. An ID so that we can call it from jQuery or your client-side query engine of choice,
  2. A Sprite Name, such as ss_add, etc.  These are provided by SilkSprite.
  3. The button text.

So, knowing that, a basic button could be rendered using:

<div id=”my-button” class=”button”>

    <span class=”sprite ss_add” />Add Activity

</div>

We can simply substitute out the variable elements (id, sprite class, and text) and create a Spark partial view (named _styledButton.spark and located in the /Shared view folder).

<div id=”${id}class=”button”>

    <span class=”sprite ${sprite}“></span>${text}

</div>

In our views, it’s easy to create a button:

<styledButton text=”‘Add Activity‘” id=”‘my-button‘” sprite=”‘ss_page_add‘” />

Note: You need to surround your text with single quotes (‘text’) to inform the Spark engine that the information contained in your variables is a string.

Now, we have shiny, icon’d buttons:

Hover Off:

Hover On:

Categories: HTML, MVC, Spark View Engine

Populating Select Lists in ASP.NET MVC and jQuery

September 25, 2009 David Longnecker Leave a comment

I’ve been working the last bit to find the best way to create/populate select (option) lists using a mixture of ASP.NET MVC and jQuery.  What I’ve run into is that the “key” and “value” tags are not passed along when using Json(data).

Here’s what I’m trying to pull off in jQuery: building a simple select drop down list.

var dd_activities = “<select id=’dd_activities’>”;
var count = data.length;
for (var i = 0; i < count; i++) {
 dd_activities += “<option value=’” + data[i].Key + “‘>” + data[i].Value + “</option>”;
}
dd_activities += “</select>”;

$(“#activities”).before(dd_activities);

Using some very basic key/value data:

[
 {"3","Text Value"},
 {"4","Another Text Value"},
 {"1","More boring values..."},
 {"2","Running out of values"},
 {"5","Last value..."}
]

Without any sort of name, I was at a loss on how to access the information, how to get it’s length, or anything.  FireBug was happy to order it up… but that didn’t help.
 
My first attempt was to use a custom object, but that just felt dirty—creating NEW objects simply to return Json data.
 
My second attempt matched the mentality of newing new anonymous Json objects and seemed to work like a champ:
 

[Authorize]

[CacheFilter(Duration = 20)]

public ActionResult GetActivitiesList()

{

    try

    {

        var results =

        _activityRepository

            .GetAll()

            .OrderBy(x => x.Target.Name).OrderBy(x => x.Name)

            .Select(x => new

                {

                    Key = x.Id.ToString(),

                    Value = string.Format(“[{0}] {1}”, x.Target.Name, x.Name)

                })

            .ToList();

 

        return Json(results);

    }

    catch (Exception ex)

    {

        return Json(ex.Message);

    }

}

 
Well, not beautiful, but returns a sexy Key/Value list that Json expects—and that populates our select list.
[
 {"Key":"3","Value":"Text Value"},
 {"Key":"4","Value":"Another Text Value"},
 {"Key":"1","Value":"More boring values..."},
 {"Key":"2","Value":"Running out of values"},
 {"Key":"5","Value":"Last value..."}
]
The next step was to get that out of the controller and into the data repository… pushing some of that logic back down to the database.
 

var criteria =

    Session.CreateCriteria<Activity>()

    .CreateAlias(“Target”, “Target”)

    .Add(Restrictions.Eq(“IsValid”, true))

    .AddOrder(Order.Asc(“Target.Name”))

    .AddOrder(Order.Asc(“Name”))

    .SetMaxResults(100);

 

var data = criteria.List<Activity>();

var result =

    data

        .Select(x => new

            {

                Key = x.Id.ToString(),

                Value = string.Format(“[{0}] {1}”, x.Target.Name, x.Name)

            })

        .ToList();

tx.Commit();

return result;

 
A bit of formatting, restrictions, push the ordering back to the database, and a tidy SQL statement is created.
 
The last touch is the return type.  Since we’re returning a “List” of anonymous types, the return type of GetActivitiesList() must be an IList.
 
That shrinks down my ActionResult to a single call.
 

try

 {

     return Json(_activityRepository.GetActivitiesList());

 }

 catch (Exception ex)

 {

     return Json(ex.Message);

 }

 
That works… and will work for now.  Though, I’ve marked it as a HACK in my code.  Why?  I’m honestly not sure yet.  Just a feeling.

Html.Grid Rendering as Plain Text?

September 23, 2009 David Longnecker Leave a comment

Notice: Stupid, stupid moment described ahead.  Proceed with caution.

I spent a good half hour trying to figure out why my MVCContrib Html.Grid<T> wasn’t rendering.  It wasn’t throwing an error, it was simply returning the HTML code as Plain Text.

  • The AutoMapper code looked good,
  • The Html.Grid<T> code looked good (it’d be templated off another page anyway and that page was working),
  • The view model code looked good.

So why was I being greeted with garble junk?

${Html.Grid(Model.Details) .Attributes(Id => “RoutineDetails”) .Columns(column => { column.For(c => this.Button(“EditDetail”).Value(“Edit”).Id(string.Format(“edit_{0}”, c.Id))).DoNotEncode(); column.For(c => c.Activity.Target.Name).Named(“Target Area”); column.For(c => c.Activity.Name).Named(“Activity”); column.For(c => c.Sets); column.For(c => c.Weight); column.For(c => c.Repetitions); column.For(c => c.Duration); })}

Html.Grid that is not… well, at least not properly.

Encoding issue? Maybe. Data issue? Perhaps.

No, the issue was typing too quick and not paying attention.

public ActionResult New()

 {

     var viewModel = BuildRoutineNewViewModel(new Routine());

     return View();

 }

Yeah, that’s the problem… right there.  I’d forgotten to pass the view model into the View.  Apparently the Html.Grid<T> helper simply panics if the model you’re reading from is empty or null—rather than throwing an error.

Oddly enough, this is one of those times I’d wish the screen would have lit up red.  Lessons learned.

Using xUnit.net in ReSharper 4.1 and MVC

September 11, 2008 David Longnecker 1 comment

Ben Taylor has saved us all a HUGE load of frustration by releasing a patched version of the xUnit.net installer and libraries that works with ReSharper 4.1.

There’s a final step, however, if you want it to work with the MVC Framework (this is for Preview 5).

The default xUnit.net MVC template package includes a 3rdParty directory for the xUnit.dll file.  The Test project also references that same library for the context of the tests.  To take advantage of Ben’s new 4.1’esque library, you need to update the 3rdParty file with his or you’ll be seeing red.

Steps:

1. Open your MVC Test project.

2. Remove (delete) the 3rdParty/xunit.dll.

3. Add an Existing File to the 3rdParty directory, pointing to the new library from Ben.

4. Under the Test project’s References, refresh the xunit reference and be sure it’s picking up the new file (the caution icon should disappear).

5. Run your tests in ReSharper and be very happy. :)

Working MVC tests with xUnit and Ben's Patch.

VS2008 and .NET 3.5 SP1 Success

August 12, 2008 David Longnecker Comments off

Danger, danger!Well, I’m 1/4 now upgrading the VMs to SP1.  The “one” that worked is the one I least expected.

Machine 1:

A simple VM used for a basic web project.  C#, Web Developer Tools installed—not even joined to the network.  No “hotfixes” were installed, but I ran the tool anyway just to be sure.  It chugged for a while and then threw an exception.  Feeling daring, I went ahead and tried to upgrade to SP1 and it didn’t even detect Visual Studio or .NET 3.5.  Hah.

Machine 2:

A prototyping VM that I copy web apps onto to run and test—nothing major, just VS2008, C#, web test tools, etc.  This one did have SP1 beta on it that I was dinking with as well as various Silverlight tools.  The patch removal tool worked just fine.  After a reboot, the SP1 installation got about 20% complete then McAfee (mcshield.exe) threw about 70 exceptions all at once and flooded the task bar.  The system then BSOD’d and reboot.  I tried another couple times to install SP1 (I can’t disable McAfee, ePO prevents it) with no success.

Machine 3:

This VM provides a test system for our customers to connect to, evaluate applications, and such.  VS2008 and .NET 3.5 (no SP1 beta) was installed more for convenience than anything else.  The system isn’t in use for the next few weeks, so worth a shot on this one.  I didn’t run the patch removal tool (perhaps that was my mistake) on this workstation and proceeded with installing SP1.  About 80% through the installation, devenv.exe crashed and asked to be debugged—of course, when I tried to debug, it crashed.  Hah.

Machine 4:

This is the one that worked; however, it’s the oddest, most broken of the bunch.  It’s had SP1 beta on it, various MVC preview builds, Astoria, bunches of Silverlight tools as well as various libraries and code bunches I have for newsgroup and forum posts.  The patch removal tool found a bunch of stuff and remove it without any issues.  After the reboot, SP1 installed (took about 45 minutes) and the system reboot.

And it works…

It works!

Seriously… why?

I still have a few VMs left to dink with and plan to throw it on my development workstation later this week after I wrap up a few projects and have some down time (I had planned to rebuild the entire workstation anyway—so good opportunity).

I hope, after I find some basic installation method, to have time to dig in to the features…

Visual Studio 2008 and .NET 3.5 SP1 Beta

The blogs are abuzz this morning after the first beta release of the VS2008 and .NET 3.5 SP1.   Download it here.

In my opinion, this isn’t a service pack—this is a new version!

There are quite a few bug fixes (what you normally associate with a service pack), but also a huge list of new additions and improvements.

From Somasegar:

Traditionally our service packs address a range of issues found both through customer and partner feedback as well as our own internal testing.  While this service pack holds true to that theme and delivers updates for these types of issues, it also builds on the tremendous value that Visual Studio 2008 and .NET Framework 3.5 deliver today and enables an improved developer experience by adding a number of additional components that cover a range of highly requested customer features. For example, the service pack is the first release for Visual Studio 2008 that delivers full support for SQL Server 2008 and the ADO.NET Entity Framework.

I’ve posted a few links at the end of the post to the more extensive sources right now, take a look and get ready for the plunge.

So, what am I most excited about?

  • ADO.NET Entity Framework – I’m hoping that the “real” release motivates Oracle to develop provides for the entity framework and my dream of LINQ-esque connections to Oracle will be realized.
  • ASP.NET Routing Engine – As the MVC framework gets closer to a production reality, it’s very motivating to see the underpinnings already in place.
  • VS2008 Performance Improvements – Anything has to be an improvement. :(
  • JavaScript Code Formatting – Sweet, now if I can only get JavaScript intellisense to work. :(
  • LINQ Debug Support – Very nice, love seeing the generated SQL right there at debug time.

There are also lots of updates to WCF and WPF.  Hopefully this summer I’ll have more time to use these .NET 3.0 technologies and maybe be a bit more excited. ;)

Visual Studio 2008 GUI/Tools

The Web Developer Tools team has released a comprehensive list of designer bug fixes, IIS templates and modules, formatting changes, intellisense upgrades, and more on their blog.

MVC and URL Routing

Phil Haack details the effects of the URL routing changes on the MVC Preview releases as well as how it affects the upcoming Preview 3.

Everything

ScottGu, as always does an excellent job tying everything up together—designer, framework, and tooling.

Now, if ReSharper 4.0 would EVER get to RTW before we’re ready to VS2009, it’d be super!

Adding Parameters to the Generic BindListControl Method

March 25, 2008 David Longnecker Comments off

Yesterday, I blogged about a generic method I created that could take a series of parameters and bind most any type of IEnumerable data source to most any type of DataBoundControl control.  I needed a simple and portable method (so that I could eventually toss it into the base helper library I use).

Unfortunately, the post didn’t cover something quite common—what happens when your data methods have parameters?  Integers, strings, even objects (passing a User object, etc).

For this, I’ll be modifying our “third attempt,” which, for now is, is the final version of the code.  Here’s the change. 

protected void BindDataControl

<TDataControlType, TEnumerableType, TDataSourceClass>

       (bool hasBeenModified, string sessionVariable,

              TDataControlType dataControl,

string dataSourceMethod,

              TDataSourceClass dataSourceClass,

object[] methodArguments)

where TDataControlType : DataBoundControl,

              new() where TEnumerableType : IEnumerable,

             new() where TDataSourceClass : class

{

// If session is null or has been modified

       // (thus invalidated), update the session state.

       if (Session[sessionVariable] == null || hasBeenModified)

       {

              // Invoke the specified method that

//creates our data source.

             var data = dataSourceClass.GetType().InvokeMember(

                     dataSourceMethod,

                    BindingFlags.InvokeMethod |

                    BindingFlags.NonPublic |

                    BindingFlags.Public |

                    BindingFlags.Instance,

                    null,

dataSourceClass, methodArguments);

// Add it to session.

             Session.Add(sessionVariable, data);

}

 

// Read the data from session and bind the data control.

dataControl.DataSource =

(TEnumerableType)Session[sessionVariable];

 

dataControl.DataBind();

}

Pay close attention to the new methodArguments object array.  I’ve bolded them in the code above.  The InvokeMember reflection method allows for arguments to be passed, so I simply opened that up to the calling method.

To use this, here’s an example that takes the Page.User property and passes it to a LINQ data context method:

BindDataControl<ListView, List<Gallery>, WebGalleryDataContext>

(true,

       “GalleryList”,

       GalleriesList,

       “GetGalleriesByRole”,

       db,

       new [] { Page.User });

or, pulling from the QueryString:

BindDataControl<ListView, List<WebFile>, WebGalleryDataContext>

(true,

       “CurrentGallery”,

       lv,

       “GetWebFilesByGalleryName”,

       db,

new [] {Request.QueryString["id"]});

and passing multiple arguments with different base types:

BindDataControl

<ListView, List<IGrouping<Gallery, WebFile>>,

WebGalleryDataContext>

       (true,

       “ChangesSinceLastVisit”,

       listViewTest,

       “GetAllSinceLastVisit”,

       db,

       new object[]

{Convert.ToDateTime(Session["LastLoginDate"]),

Page.User});

Cool.

Using Generics to Update DataBoundControls – A Prototype

March 24, 2008 David Longnecker Comments off

NOTE: This is a prototype, an idea, a random thought expressed aloud (well, in type).  The code explains a concept and isn’t “tested” or production worthy (in my opinion). 

Feedback is always appreciated. :)   This is also what happens when I have a week off work and come back with ‘ideas.’

I find that a few of my projects, like the WebGallery2, all have a similar functionality.  On pages with GridViews, ListViews (which, I’m slowly replacing all my GridViews with), or other DataBoundControls, I follow a common theme for data binding:

If the data set will be cached or in session, is that session/cache null OR has the data set been explicitly modified?

  1. true – regenerate the data set and repopulate session/cache.
  2. false – read the current data set from session/cache to the control.

In a single instance, the code to do this might look like:

private void BindList(bool hasBeenModified, string sessionVariable)

{

       // If our session variable is null or the data has

// been explicitly modified, then rebuild the session variable.

if (Session[sessionVariable] == null || hasBeenModified)

              Session[sessionVariable] =

db.GetWebFilesByGalleryName(this.Id);

 

resultsListView.DataSource =

Session[sessionVariable] as List<WebFile>;

 

resultsListView.DataBind();

}

This would then be called with:

BindList(true, “CurrentGallery”);

However, this code really bothers me. 

The data source (the db.GetWebFilesByGalleryName method), data bound control (the resultsListView), and the type of the data source (List<WebFile>) are all hard coded.

How could this helper method use generics to add a bit of resuability?  What about when I want to use a GridView instead of a ListView, or have a List<Gallery>, List<String>, string[] of information?

First Attempt

The first attempt works.  It takes the generics as anticipated and is rather easy to use.

protected void BindDataControl<TDataControlType, TEnumerableType>

(bool hasBeenModified, string sessionVariable,

              TDataControlType dataControl, TEnumerableType dataSource)

where TDataControlType : DataBoundControl,

              new() where TEnumerableType : IEnumerable

{

// Add the dataSource to session.

       if (Session[sessionVariable] == null || hasBeenModified)

              Session.Add(sessionVariable, dataSource);

 

// Read the data from session and bind the data control.

       dataControl.DataSource =

(TEnumerableType)Session[sessionVariable];

       dataControl.DataBind();

}

The constructor here has both generic parameters and standard parameters.

  • TDataControlType has a generic constraint that requires it to be part of or a subclass of DataBoundControl (GridView, ListView, etc).
  • TEnumerableType requires the source to inherit from IEnumerable (List, Array, etc).

Here are a few examples of using it this bit of code:

var gv = new GridView();

this.Page.Controls.Add(gv);

BindDataControl<GridView, Array>(

true,   // this is new, so build a session variable.

       “test”, // the session variable

       gv,     // the Id of our GridView

       new[]   // The data source, an array.

              { “hello”, “world” });

When rendered, we have a simple GridView with our two data items.

GridView and Array data source.

What about a more complicated example using collections and a ListView? On the current build of the WebStorage2 project, the galleries are built in a similar method (see this post for more details).  I could just as easily replace the logic in Show.aspx’s Page_PreRender with:

BindDataControl<ListView, List<WebFile>>(

true,

       “CurrentGallery”,

       lv,

       db.GetWebFilesByGalleryName(Request.QueryString["id"]));

So what’s the downfall to this method? 

Downfall #1: It’s a performance nightmare. AFAIK, when passing a method (which GetWebFilesByGalleryName is a method from my LINQ DataContext), it is evaluated immediately.  So, with that in mind, the hasBeenModified is irrelevant—it may not NEED to update the session, but the method will still go out, search the database, and return the results.  That’s a bad deal.

What I don’t know and am not sure how to check is whether or not the lazy/delayed loading in LINQ would balance out this at all.  Ideas?

Second Attempt

The second attempt adds in a bit more “generic” and a lot more reflective.  By adding a reference to System.Reflection, we can simply pass a string reference to a “builder” method rather than the method it self—thus saving the prefabrication of the data source when it’s not really needed.

protected void BindDataControl<TDataControlType, TEnumerableType>

(bool hasBeenModified,

              string sessionVariable,

             TDataControlType dataControl,

             string dataSourceMethod)

where TDataControlType : DataBoundControl,

              new() where TEnumerableType : IEnumerable

{

// If session is null or has been modified (thus invalidated),

       // update the session state.

       if (Session[sessionVariable] == null || hasBeenModified)

       {

              // Invoke the specified method that

// creates our data source.

             var data = Page.GetType().InvokeMember(

dataSourceMethod,.

BindingFlags.InvokeMethod |

                    BindingFlags.NonPublic |

                    BindingFlags.Instance,

                    null, this, null);

 

// Add it to session.

             Session.Add(sessionVariable, data);

}

 

// Read the data from session and bind the data control.

       dataControl.DataSource =

(TEnumerableType)Session[sessionVariable];

      

dataControl.DataBind();

}

In this method, the Page.GetType().InvokeMember method iterates through the methods on the page, finds the one that matches the string name passed to it, and executes it. 

Then, with our “data” results, the rest is the same as the previous method.

Unfortunately, I cannot pass the LINQ direct lookup anymore because the scope of InvokeMember is limited to the calling page.  I’ll need to create another little method, called GetResults in this case, to do the query for me.

protected List<WebFile> GetResults()

{

return db.GetWebFilesByGalleryName(Request.QueryString["id"]);

}

And the updated BindDataControl method:

BindDataControl<ListView, List<WebFile>>(
  false,
  “CurrentGallery”,
  lv,
  “GetResults”);

Now, since our constructor does not contain the method to fetch our data, simply a string, the results are not refetched each time the method is called—only when the requirements are met further in the code.

Downfall #1: This method requires an additional “helper” method on every page to fetch the data.  You can’t access methods outside of the page—or can you?

Downfall #2: What happens if you need to pass parameters to your InvokeMember?  You CAN, but the syntax is nasty and becomes even more difficult if the parameters are not always in the same order (which doubtfully they would be if you’re using generics).

Third Attempt

The third attempt looks more like the signature from Hell than a real method.  There had to be a way around the “stuck on this page” snafu with the second attempt… and there was, by specifying the class too using a generic.

protected void BindDataControl

<TDataControlType, TEnumerableType, TDataSourceClass>

(bool hasBeenModified, string sessionVariable,

              TDataControlType dataControl,

string dataSourceMethod,

TDataSourceClass dataSourceClass)

where TDataControlType : DataBoundControl,

              new() where TEnumerableType : IEnumerable,

             new() where TDataSourceClass : class

{

// If session is null or has been modified

// (thus invalidated), update the session state.

       if (Session[sessionVariable] == null || hasBeenModified)

       {

              // Invoke the specified method that

// creates our data source.

             var data = dataSourceClass.GetType().InvokeMember(

dataSourceMethod,

                     BindingFlags.InvokeMethod |

                    BindingFlags.NonPublic |

                    BindingFlags.Public |

                    BindingFlags.Instance,

                    null, dataSourceClass, null);

 

// Add it to session.

Session.Add(sessionVariable, data);

}

 

// Read the data from session and bind the data control.

dataControl.DataSource =  

(TEnumerableType)Session[sessionVariable];

 

dataControl.DataBind();

}

Good grief. 

This method adds a third generic to the constructor—TDataSourceClass—as well as the additional constraint requirement.  I’ve also added an additional BindingFlag—Public—since most of the methods in LINQ DataContext classes are decorated public.

Rather than pulling from this.Page, we’re now calling InvokeMember from the parameter class and returning the results to the calling page.  There’s one other change—rather than looking at “this” as the Binder parameter, we’re referencing the class passed along in the constructor—the dataSourceClass.

BindDataControl<ListView, List<WebFile>, WebGalleryDataContext>(
    true,
    “CurrentGallery”,
    lv,
    “GetWebFiles”,
    db);

Here we have two additional parameters, the generic parameter, TDataSourceClass, that I’ve passed the LINQ Data Context into to define the Type of the dataSourceClass parameter passed later in the constructor. 

“Verbalized”, the method’s generics read: BindDataControl to a ListView with the expected data format of a List<WebFile> using the WebGalleryDataContext class. 

The parameters (which could be reordered to make better sense) read: the data is new or has changed, so store the results in “CurrentGallery” and return them to ‘lv’ (the ListView object on the web form).  Fetch the data with GetWebFiles from the instance of db (the WebGalleryDataContext object instanciated previously in the page).

Downfall #1: Methods are still required—you cannot pass a simple data source to the BindDataControl method.

Downfall #2: Additional coding on the BindDataControl methods required to handle parameters.

This third attempt handles our most complex request—but what about the original “hello”/”world” array request?  It can be done, but, as previously mentioned, requires extracting the array list outside of the method constructor.

 

protected void Page_Load(object sender, EventArgs e)

{

var gv = new GridView();

       this.Page.Controls.Add(gv);

 

BindDataControl<GridView, ArrayList, Page>(

true, “junk”, gv, “Get”, this.Page);    

}

 

protected ArrayList Get()

{

return new ArrayList {“hello”, “world”};

}

To reference methods that exist in the same code-behind page, this.Page and the Page class offer the correct class access.

So, is this the best way to do it?  Probably not!  How would you tidy this up or rewrite it?  I’m interested!

ASP.NET MVC Source Code Now on CodePlex

March 21, 2008 David Longnecker Comments off

As promised, Microsoft has released the source for the MVC framework (and potentially others if the naming of the site is an indicator) on CodePlex.

http://www.codeplex.com/aspnet

Awesome!  Even if you have no plans to contribute or build up your own, custom libraries, reading through the source and understanding how the controllers are called, ViewPage is rendered, and such—it’s just better than Object Explorer. ;)

Exploring the ASP.NET MVC Preview 2 – Tests and Mocks

March 21, 2008 David Longnecker Comments off

The changes made around testing and mocking are a big step in the right direction from Preview 1. 

Mocking Controller Tests

In Preview 1, it was more common to create subclass testers for each Controller object—a very time consuming tasks.  Mocks “worked”—but, honest, just acted a bit odd.  Our ViewEngine still requires faking out, but Moq helps us along with the rest.

To start off, we’ll “Moq” up a Contact view and get the test to fail, then fix it (aka: implement it) to pass the test.

[TestMethod]

public void Contact_ReturnsContactView()

{

// Create a Moq instance of our HomeController.

       var controller = new Mock<HomeController>();

 

       // Create an instance of our FakeViewEngine.

       // There has got to be a way to do this without

       // ‘faking’ it. :(

var fakeViewEngine = new ViewHelper.FakeViewEngine();

 

       // Set the ViewEngine of our mock object to the fakeViewEngine.

       controller.Object.ViewEngine = fakeViewEngine;

 

       // Using the extension method from the MvcMockHelpers class,

       // set the controller context.

       controller.Object.SetFakeControllerContext();

 

       // Invoke the “Index” method.

       controller.Object.Contact();

 

       // Assert that Index() actually returned a view named Index.

       Assert.AreEqual(

              “Contact”,

             fakeViewEngine.ViewContext.ViewName);

}

This is part of the unit testing I still don’t like—that FakeViewEngine class.  The class consists of a ViewContext and get/set properties because, by default, IViewEngine can only RenderData()—it doesn’t allow direct access to the ViewContext. 

I suppose you could override IViewEngine and implement your own—but, for testability, is there harm in exposing those as read-only?

At this point, our Contact() method call is invalid (as it doesn’t exist in the HomeController class.

For now, so our test will run, we’ll add in an empty method to the HomeController class.

public void Contact()

{

           

}

Now, we can run the test.  As expected, it fails because the object (the ViewPage) doesn’t exist yet.

Test method Contact_ReturnsContactView threw exception:  System.NullReferenceException: Object reference not set to an instance of an object..

Now, to set off to resolve the error. 

Note: The templates are very precise.  This is a content page (linked to a master page) and a view page rendering the view of a MVC controller.  Be sure to pick the right item template for your task.

In our Views > Home directory, we need to add a new MVC View Content Page item named Contact.

After the View page itself has been added, implement the RenderView method in the HomeController class by modifying the empty method added earlier.

public void Contact()

{

RenderView(“Contact”);      

}

Now, rerun our test!

Testing Routes

There isn’t any mocking (yet) in our Route tests; however, we do use some of the helper methods Scott Hanselman wrote about and that I prepackaged up (see First Glance post for downloads).  I’ve created two quick and simple tests:

  • Does RegisterRoutes work successfully register routes to the RouteTable?
  • Does the Default.aspx mapping work at the root?

Before we get started, the tests require a simple TestInitialize (or SetUp, depending on your testing tool) to initialize the RouteCollection object.

private RouteCollection routes;

 

[TestInitialize()]

public void TestInitialize()

{

routes = new RouteCollection();

}

RegisterRoutes()

Our RegisterRoutes test method is extremely easy:

[TestMethod]

public void RoutesRegistered()

{

Assert.IsTrue(routes.Count == 0);

RouteManager.RegisterRoutes(routes);

Assert.IsTrue(routes.Count > 0);

}

Simply initialized, our RouteCollection should be empty (Count == 0) and, after RegisterRoutes is called, should be populated with routes.  Easy enough and ensures that our RegisterRoutes method is doing it’s job.

ContactRoot_MapsToHomeView()

To test our routes, we must do two things: register our route and then fake the navigation to a specific URL—in this example, “~/Contact.aspx”.   This is really useful during transitions from other frameworks to MVC—especially if you have preprinted letterhead, business cards, etc. with a specific URL on it. :)

To bypass the default “controller/action/id” logic of the MVC framework, we can specify exact names, paths, etc. 

First, let’s build our test.

[TestMethod]

public void ContactRoot_MapsToHomeView()

{

RouteManager.RegisterRoutes(routes);

RouteData routeData =

              routes.GetRouteData(

MockHelper.FakeHttpContext(“~/Contact.aspx”));

           

// Check to see if a route exists for the specified URL.

Assert.IsNotNull(routeData);

      

// Check to see that the controller matches our expectation.

Assert.AreEqual(“Home”, routeData.Values["Controller"]);

 

// Check to see that the action matches our expectation.

Assert.AreEqual(“Contact”, routeData.Values["Action"]);

}

This test checks three things to pass:

  • does the URL specified exist in the route data (is there a valid route?),
  • does it call the anticipated controller,
  • and does it call the anticipated action?

All three are required to pass as a “valid route”.

When we try to run the test, the first to fail is our Assert.IsNotNull—a route does not exist for our Default.aspx page.  So let’s add a route to get this test to pass.

routes.Add(new Route(“Contact.aspx”, defaultRouteHandler)

{

Defaults =

              new RouteValueDictionary(

                     new

                    {

controller = “Home”,

action = “Contact”,

id = “”

}),

});

Success!

Working with tests for both Controllers and Routes is quite a bit easier now—both due to continued improvement to the MVC framework and the work in the community with helper and extension methods.

For more details on MVC Testing, check out Scott Hanselman’s great webcast on asp.net.

Edit: Don’t bother with the asp.net web site with IE 8–-it’s totally borked up and, even worse, Silverlight won’t load properly in FireFox 2 (it’s an invalid browser).  Break out IE7 for this venture.