Archive for December, 2007

MVC: ViewData vs. TempData

// December 20th, 2007 // 5 Comments » // MVC

NOTE:
This article was written for the December CTP release of the MVC framework. Unfortunately, it does not entirely apply to the Preview 2 release or subsequent releases.

A commenter (oVan) recently left a comment on one of my posts with a suggestion to redirect to a different URL rather than display a different view. He cautioned that if I do so to make certain I use store data in the TempData dictionary rather than ViewData. I had not yet heard of the TempData dictionary, so I set off into Reflector to find out what exactly it is.

Initially it appeared that TempData was simply a Dictionary<string,object> stored in session. This is close to the truth, but not the whole truth. In fact the internal declaration of TempData's storage device is:

Pair<Dictionary<string, object>, HashSet<string>>

 

Why such a complicated declaration? Further digging revealed that the second element of the pair (the HashSet) is used as a kind of key synchronization device. When an item is added to the internal storage, the key is placed in the HashSet. At some point that I have been unable to determine, the key is removed from the HashSet. Then when the TempData object is built again, the discrepancy is noticed and the Dictionary entry that has a key not contained in the HashSet is removed from the Dictionary.

The end result is that any data stored in TempData will be around for the life the current request and the next request only, or until the item is removed explicitly. This is useful when you want to pass data to another view that you will be redirecting to, rather than rendering to.

    1 [ControllerAction]

    2 public void Index()

    3 {

    4     TempData["text"] = "lorem ipsum";

    5     RedirectToAction( "Test1" );

    6     //RenderView("Index");

    7 }

    8 

    9 [ControllerAction]

   10 public void Test1()

   11 {

   12     string text = TempData["text"] as string;

   13     RenderView("Test1");

   14 }

   15 

   16 [ControllerAction]

   17 public void Test2()

   18 {

   19     string text = TempData["text"] as string;

   20     RenderView("Test2");

   21 }

 

In the code above if you navigate to the Index page, you are automatically redirected to the Test1 action's URL. The Test1 action then executes and is able to access the data stored for it in TempData. If you then navigate to the Test2 action, that action is unable to retrieve the data previously stored in TempData.

To retrieve data from TempData inside of a view, do the following:

<% =ViewContext.TempData["text"] %>

 

One suggestion for an improvement in the next CTP would be to have all data available in TempData copied over to ViewData at the beginning of a request (after the old TempData has been cleared). This would allow us to simply say:

<% =ViewData["text"] %>

 

Otherwise the view has to be very aware of the controller and how it plans to pass data into the view. Currently I cannot have a view that supports being passed data both ways without doing the following:

<% =ViewContext.TempData["text"] ?? ViewData["text"] %>

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

MVC Bug: Broken Codebehind

// December 11th, 2007 // 4 Comments » // MVC

Update (Jan 2): I have posted a permanent fix in the form of modified Visual Studio templates in another blog post. 

Update (Dec 11): ScottGu was kind enough to post a fix for this particular problem. It is unfortunate that you have to repeat the fix for every view you add to your project, but I guess that's why it is called a CTP! ScottGu said…

There is a bug in the file template when you create a new page – and the .designer.cs file isn't generated.
To fix this, right click on the file and choose the "Convert to Web Application" menu item. This will generate the .designer.cs file that contains your control declarations. From that point on the code-behind will be kept up to date as you make changes.


Hope this helps,


Scott


Original Post: 

It may not be a popular choice, but I'm perfectly okay with in-line code in my views as long as it doesn't contain business logic. One of the developers on my project prefers tag-based views (<asp:Blah runat="server" />) and came to me yesterday with a curious issue. I've spent some of the morning investigating the issue and it does appear that there is a bug in the newly released ASP.Net MVC Framework.

The issue? Controls declared in an ASPX are not visible to that page's codebehind.

To test this hypothesis (and make sure we hadn't somehow broken our project) I started a new "ASP.Net MVC Web Application" project (note that the bug also exists for the "ASP.Net MVC Web Application and Test" project). I then opened the Views/Home/About.aspx file and added the following line:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="About.aspx.cs" Inherits="MVCBug.Views.Home.About" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">

    <h2>Todo: Company information</h2>

    <asp:Literal ID="myLiteral" runat="server" Text="Hello World" />

</asp:Content>

 

Then I opened up the page's codebehind (Views/Home/About.aspx.cs) and added the following:

using System;

using System.Web;

using System.Web.Mvc;

namespace MVCBug.Views.Home

{

    public partial class About : ViewPage

    {

        public void Page_Load()

        {

            myLiteral.Text = "Goodbye World";

        }

    }

}

 

Ctrl+Shift+B to compile and bam, a build error: "The name 'myLiteral' does not exist in the current context."

I began to wonder if this was not supported by the MVC framework, but I took a look back at one of ScottGu's most recent articles, ASP.Net MVC Framework (Part 3): Passing ViewData from Controllers to Views, and saw that he references controls from the page's codebehind several times. I also tried using several controls besides the Literal to no avail.

To test what was going on, I commented out the reference in the Page_Load method, added a string declaration and put a breakpoint on it. When I ran the MVC application in debug mode and loaded the page I was able to see the "myLiteral" control reference. It appears that the reference is available at runtime but Visual Studio just is not able to see it at compile time. Odd.

DebugMyLiteral

For now I've told the developer to just use in-line code, but I'm well aware that many developers are loathe to do so. Thoughts? Suggestions?

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

MVC Complaint: Checkboxes

// December 11th, 2007 // 9 Comments » // MVC

NOTE:
This article was written for the December CTP release of the MVC framework. Unfortunately, it does not entirely apply to the Preview 2 release or subsequent releases.

I mentioned in my previous post on building login & registration forms in the ASP.Net MVC framework that I was not a fan of the way checkboxes are handled in the first CTP release's toolkit. To help explain why I don't care for it, consider the following… what would you expect the following helper function to output?

<% =Html.CheckBox( "rememberMe", "Remember Me", true ) %>

 

I would imagine a single checkbox and a label with the text value, which is incorrect. Instead, this is what is generated:

<input type="checkbox" id="rememberMe" name="rememberMe" value="Remember Me" checked=checked />Remember Me

 

It looks fairly reasonable to most and really isn't the end of the world, but is rather unusable in my eyes. The text is just output inline directly following the checkbox element with no element wrapping it. This small omission makes it incredibly difficult to style the output effectively using CSS. Simply wrap the text in a <label> element and everything is gravy. What I would prefer to see output is:

<input type="checkbox" id="rememberMe" name="rememberMe"  value="Remember Me" checked="checked" />

<label for="rememberMe">Remember Me</label>

 

I realize this seems like a nitpick; I'm perfectly capable of writing my own HTML checkbox and label (usually). My frustrations do not end there, though; let's look at how you retrieve data from a checkbox within a controller action that you have posted to. Taking the example of a simple login form with two text fields ("userName" and "password") and one checkbox ("rememberMe") you might expect to have the following controller action:

[ControllerAction]

public void Authenticate( string userName, string password, bool rememberMe )

{

    if( rememberMe ){ … }

}

 

Given the context of this article, you can probably guess that the above doesn't work. Unfortunately the automagic code that maps fields found in the HTTP request to parameters in a controller action does not support mapping to a boolean. This is partly the fault of the HTTP spec (submitting a checked checkbox causes the form to post with $NAME=$VALUE where $NAME is the name of the checkbox element and $VALUE is the value of the "value" attribute; submitting an unchecked checkbox causes the form to post without any reference to the checkbox — i.e.: a null value), but could easily be resolved with the following rule:

When a boolean is declared in a method marked with [ControllerAction], pass false into the parameter unless a text value is found with a field name that correlates to the name of the parameter (in which case pass true).

In other words, if you can't find a mention of "rememberMe" in the form's POST data, pass false to the parameter. If the form's POST data contains any value for a field named "rememberMe," pass true to the parameter. For the time being you should declare the checkbox parameter as a string and test it against null, like so:

[ControllerAction]

public void Authenticate( string userName, string password, string rememberMe )

{

    if( rememberMe != null ){ … }

}

 

Sure, it isn't rocket science the way it is setup right now, but a little extra work could make for a much more fluid learning curve for new MVC developers. Obviously this is just a CTP release and not the full deal; I fully expect a lot of these quirks to be worked out over the next several months. This does make me wonder, will the toolkit be posted to CodePlex so that it can benefit from community involvement?

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

ASP.Net MVC Membership Basics

// December 10th, 2007 // 36 Comments » // MVC

NOTE:
This article was written for the December CTP release of the MVC framework. Unfortunately, it does not entirely apply to the Preview 2 release or subsequent releases.

The MVC bits have finally arrived and I’ve spent a while digesting them. I’ve been waiting for the bits to be released to begin working on a side-project, so the first thing I did after downloading them last night was crank it up and start working on a new ASP.NET MVC Web Application project.

Typically, the first thing I do on a new project is set up the authentication/authorization system. For the project I am working on I want to use the ASP.Net Membership system, but most of the Membership controls do not work with the MVC framework because they require postbacks. I spent some time last night building a Security controller and views for Registration and Login that I thought would be worth sharing.

So far I have implemented basic functionality for Register, Login, and Logout. There are three files we will have to create. We will also have to change the routing table. Let’s start with the SecurityController:

    1 public class SecurityController : Controller

    2 {

    3 

    4     [ControllerAction]

    5     public void Login()

    6     {

    7         RenderView("Login");

    8     }

    9 

   10     [ControllerAction]

   11     public void Register()

   12     {

   13         RenderView( "Register" );

   14     }

   15 

   16     [ControllerAction]

   17     public void Logout()

   18     {

   19         FormsAuthentication.SignOut();

   20         Response.Redirect( "/" );

   21     }

   22 

   23     [ControllerAction]

   24     public void Authenticate( string userName, string password, string rememberMe, string returnUrl )

   25     {

   26         // figure out if username and password are correct

   27         if( Membership.ValidateUser( userName, password ) )

   28         {

   29             // everything is good, create an authticket and go

   30             FormsAuthentication.SetAuthCookie( userName, (rememberMe != null) );

   31             Response.Redirect( returnUrl );

   32         }

   33         else

   34         {

   35             // something was wrong, figure out which and pass it into view

   36             if( Membership.GetUser(userName) == null )

   37                 ViewData["ErrorMessage"] = "Incorrect username.";

   38             else

   39                 ViewData["ErrorMessage"] = "Incorrect password.";

   40             RenderView( "Login" );

   41         }

   42     }

   43 

   44     [ControllerAction]

   45     public void CreateUser( string userName, string emailAddress, string password, string returnUrl )

   46     {

   47         try

   48         {

   49             // try to create user and then login that user

   50             if( Membership.CreateUser( userName, password, emailAddress ) == null )

   51                 throw new MembershipCreateUserException( "An unspecified error occurred." );

   52             FormsAuthentication.SetAuthCookie( userName, true );

   53             Response.Redirect( returnUrl );

   54         }

   55         catch( MembershipCreateUserException e )

   56         {

   57             // something went wrong

   58             ViewData["ErrorMessage"] = e.Message;

   59             RenderView("Register");

   60             return;

   61         }

   62     }

   63 

   64 }

 

So we have two basic actions methods that only display views (Register and Login) and three action-only methods that have no views (CreateUser, Authenticate, Logout).

I have chosen to not have a logout page, but to instead redirect to the homepage. Switch out ‘Response.Redirect( "/" );’ for ‘RenderView( "Logout" );’ and create a Logout.aspx view if you would like to display a logout message.

Now let’s look at the Login view:

    1 <asp:Content ID="content" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">

    2 

    3     <h2>Login</h2>

    4 

    5     <% if( ViewData["ErrorMessage"] != null ){ %>

    6     <p><% =ViewData["ErrorMessage"] %></p>

    7     <% } %>

    8 

    9     <% using(Html.Form( "Authenticate", "Security" )){ %>

   10     <fieldset>

   11         <legend>Login</legend>

   12         <div><label for="userName">User Name:</label> <% =Html.TextBox( "userName" ) %></div>

   13         <div><label for="password">Password:</label> <% =Html.Password( "password" ) %></div>

   14         <div><label for="rememberMe">Remember Me:</label>

   15             <input type="checkbox" id="rememberMe" name="rememberMe" checked="checked" value="checked" /></div>

   16         <div><% =Html.SubmitButton() %></div>

   17         <% =Html.Hidden( "returnUrl", "/" ) %>

   18     </fieldset>

   19     <% } %>

   20 

   21 </asp:Content>

 

The toolkit’s Html.Checkbox(…) method annoys me. More on why in another post. For now I’ve instead just written the html out by hand. You’ll note I’ve also linked the label to the checkbox with JavaScript so that clicking the label toggles the checkbox.

Then the Register view:

    1 <asp:Content ID="content" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">

    2 

    3     <h2>Register</h2>

    4 

    5     <% if( ViewData["ErrorMessage"] != null ){ %>

    6     <p><% =ViewData["ErrorMessage"] %></p>

    7     <% } %>

    8 

    9     <% using(Html.Form( "CreateUser", "Security" )){ %>

   10     <fieldset>

   11         <legend>Register</legend>

   12         <div><label for="userName">User Name:</label> <% =Html.TextBox( "userName" ) %></div>

   13         <div><label for="emailAddress">Email Address:</label> <% =Html.TextBox( "emailAddress" ) %></div>

   14         <div><label for="password">Password:</label> <% =Html.Password( "password" ) %></div>

   15         <div><% =Html.SubmitButton() %></div>

   16         <% =Html.Hidden( "returnUrl", "/" ) %>

   17     </fieldset>

   18     <% } %>

   19 

   20 </asp:Content>

 

Another straightforward view. Not much to discuss here.

And finally, let’s add the new routes:

    1 RouteTable.Routes.Add( new Route

    2 {

    3     Url = "Login",

    4     Defaults = new {

    5         controller = "Security",

    6         action = "Login" },

    7     RouteHandler = typeof( MvcRouteHandler )

    8 } );

    9 RouteTable.Routes.Add( new Route

   10 {

   11     Url = "Register",

   12     Defaults = new {

   13         controller = "Security",

   14         action = "Register" },

   15     RouteHandler = typeof( MvcRouteHandler )

   16 } );

 

I personally like login to be http://website/login and register to be http://website/register, so that is how I have configured it. The other three actions (Logout, Authenticate, and CreateUser) I access via the default route (ex: /Security/Logout).

That’s it! You should now have a working registration/login system. I’ll leave making it pretty with CSS as an exercise for the reader.

I have included all of the code samples above in the below ZIP file. Just unzip it and place the controller into the Controllers directory, the views into the Views/Security directory (which you will have to create), and copy the code from Routes.txt to the appropriate area of your Global.asax.

MVCMembership_v1.2.zip (2.03 kb)

UPDATE (Dec 11): Johan and Steve Harman were kind enough to point out that I had foolishly set the "remember me" checkbox’s label’s "for" attribute to point to the password field instead of the checkbox itself. I have fixed the code above and provided a new zip file (1.1) for download. Thanks guys!

Update (Dec 19): oVan pointed out a bug in the routing rules defined in the routes.txt file. I have updated the zip file with the correct code. Thanks oVan!

Update (Jan 3): James Nail asked a very good question via a comment: what do you set for the loginUrl and defaultUrl in your web.config? Well James, here is how I’ve setup my web.config…

Assuming we’ll be using forms authentication and securing all pages except login and the homepage, place the following inside the <system.web>element:

    1 <authentication mode="Forms">

    2   <forms loginUrl="/Login" defaultUrl="/" />

    3 </authentication>

    4 <authorization>

    5   <deny users="?" />

    6 </authorization>

Then, somewhere outside the <system.web> element add:

    1 <location path="Default.aspx">

    2   <system.web>

    3     <authorization>

    4       <allow users="*" />

    5     </authorization>

    6   </system.web>

    7 </location>

    8 <location path="Security">

    9   <system.web>

   10     <authorization>

   11       <allow users="*" />

   12     </authorization>

   13   </system.web>

   14 </location>

   15 <location path="Login">

   16   <system.web>

   17     <authorization>

   18       <allow users="*" />

   19     </authorization>

   20   </system.web>

   21 </location>

Note that this will grant access to all actions within the Security controller. It is also worth pointing out that, dependent on your setup, your CSS and image files may not load unless you also create a location path for their directory and grant all users access like so:

    1 <location path="Content">

    2   <system.web>

    3     <authorization>

    4       <allow users="*" />

    5     </authorization>

    6   </system.web>

    7 </location>

I have not updated the zip file with these web.config settings; let me know if anyone would prefer that I add it. I hope this helps some of you!

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

MVC.net Release TODAY!

// December 9th, 2007 // 2 Comments » // MVC

Scott Guthrie posted a comment to his fourth entry in his ASP.Net MVC tutorial series stating:

Sunday, December 09, 2007 8:00 AM by ScottGu

Hi Panjkov,

>>>>>> is this correct link for asp.net extensions preview that will be shipped (today?) go.microsoft.com/fwlink (taken from Link)

No – that is for the ADO.NET entities bits (which is something different).  Keep an eye on www.asp.net in a few hours – the ASP.NET 3.5 Extensions Preview (which includes MVC) will be posted there shortly.

Thanks,

Scott

This is great news and rather unexpected. Time to hit up www.asp.net and F5 F5 F5…!

Update: As everyone is aware by now, I'm sure, the MVC Framework has been released. You can get it here.

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

BlogEngine.net 1.2.5 Scrapped

// December 7th, 2007 // No Comments » // Blogging

Apparently this just isn't my day when it comes to project released that I've been looking forward to. Mads Kristensen posted to the BlogEngine.net project's website stating that the 1.2.5 release of the project (a service pack, really) will no longer happen and that all of its changes will be rolled into a 1.3 release, due before the end of the year.

Dissapointing, yes, but really for the best. The 1.2.5 release fixed quite a few bugs, but none of them show stoppers (well, for me) and 1.3 looks to add some new features to the equation.

Coming in 1.3:

  • Full mono support (right out of the box)
  • Updated Metaweblog API including support for creating/editing pages in WLW.
  • Improved Blog importer/exporter, including support for easily switching between XML and SQL backends
  • New extension manager to managing extensions and their settings.
  • New events for extension writers
  • Support for mobile devices
  • New themes

 

I'm especially looking forward to the mobile device support and further Live Writer integration.

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

MVC Framework Delayed

// December 7th, 2007 // No Comments » // MVC

ScottGu has posted a comment on his latest blog post saying that a last minute bug has delayed the release of the new .Net MVC Framework, which was previously slated for release today in the form of a CTP. From the sound of things we are now looking at a Monday release if further testing does not reveal any further issues.

I'm sure I speak for almost all of us that are awaiting this highly anticipated release when I say that all of their hardwork is appreciated and while the delay is dissapointing, what is a few more days? The full text of Scott's comment is below:

"Quick update to everyone – we found a last minute bug late last night, and had to-do a rebuild of the bits.  This unfortunately means we won't go live today as origionally planned.  The team is working hard, though, to try and get it out ASAP (they will be here this weekend finishing the test pass).  

I'll do a blog post announcing it as soon as it is live.  As penance I'm also doing a long Part 4 blog post this weekend that covers editing and form posting scenarios, which combined with the first three tutorial posts should provide a pretty good foundation for working with the bits.

Many apologies for the delay – almost there now though,

Scott"

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

GraffitiCMS Beta Opens

// December 5th, 2007 // 3 Comments » // Blogging

GraffitiCMS (from Telligent, makers of Community Server) has been released in beta. Head on over to their official site to download it.

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

Add/Update/Delete with LINQPad

// December 5th, 2007 // 6 Comments » // LINQ

Danny Douglass recently posted about LINQPad, a query tool that understands LINQ. I spent some time today using it and am very impressed. It did take a while, however, to look through the numerous sample scripts and figure out exactly how to insert/update/delete data, so I thought I would share what I've found. The biggest changes between working in LINQ-To-SQL in a Visual Studio 2008 project and writing a LINQ query in LINQPad are the lack of a DataContext, the difference between C# Expressions and C# Statements, and the addition of a Dump command.

No Data Context: When you need to interact with a database via LINQ in a Visual Studio 2008 project, you do so by creating a LINQ-To-SQL DBML file that generates a data context for you. This data context is in charge of maintaining your database connection and is what you use to submit changes to the database. Because there is no data context readily available to you (and no .dbml file) in LINQPad, the way you go about this is slightly different. There is a globally-scoped subroutine, "SubmitChanges()," that should be called whenever you wish to commit an action to the database.

C# Expressions vs. C# Statements: By default LINQPad opens in "C# Expression" mode. In this mode you can type a simple query like "from r in Regions select r" and run it to see the results. As far as I can tell there is no way to insert/update/delete data in this mode. By switching to "C# Statement(s)" mode you are able to declare variables, control flow statements, and reference objects; this is the mode you need to be in to insert/update/delete data. To enter "C# Statement(s)" mode, select it from the "Type" drop-down box at the top of the window as show below:

LINQPad

object.Dump(): When you write a query in "C# Expression" mode the result of that query is automatically rendered to the Results frame (shown below). Because "C# Statement(s)" mode gives you the capability to run many queries in one execute, displaying the results of those queries must be manually invoked. To do so simply call the ".Dump()" method on the resultset of the query you want to display. ".Dump()" is implemented as an extension method available on all objects, so whether you are retrieving a single object, a list of objects, or an anonymous type the Dump method will be available to display your data.

LINQPad-Results

Below I have included examples of several ways to query data, as well as an example each for inserting, updating, and deleting data.

    1 // select (LINQ Syntax)

    2 var regions =

    3     from r in Regions

    4     where r.RegionID > 0

    5     select r;

    6 regions.Dump();

    7 

    8 // insert

    9 Region newRegion = new Region()

   10 {

   11     RegionID = 99,

   12     RegionDescription = "Lorem ipsum…"

   13 };

   14 Regions.InsertOnSubmit( newRegion );

   15 SubmitChanges();

   16 

   17 // select (LINQ Syntax, no temp variable)

   18 (from r in Regions

   19     where r.RegionID > 0

   20     select r).Dump();

   21 

   22 // update

   23 Region region =

   24     (from r in Regions

   25         where r.RegionID == 99

   26         select r).Single();

   27 region.RegionDescription = "…dolor sit amet…";

   28 SubmitChanges();

   29 

   30 // select (.Where Lambda expression)

   31 ( Regions.Where( r => r.RegionID > 0 ) ).Dump();

   32 

   33 // delete

   34 Region removeRegion =

   35     Regions.Where( r => r.RegionID == 99 ).Single();

   36 Regions.DeleteOnSubmit( removeRegion );

   37 SubmitChanges();

   38 

   39 // select (Regions & Territories, joined and

   40 //        combined by anonymous type)

   41 (from r in Regions

   42     join t in Territories

   43         on r.RegionID equals t.RegionID

   44     select new{

   45         Region = r,

   46         Territory = t

   47     }).Dump();

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com