Writing Helpers in ASP.NET Web Pages (Beta)

I’ve been seeing some people asking about how to write their own helpers in ASP.NET Web Pages.  If you’re not familiar with ASP.NET Web Pages or Razor be sure to check out Scott Guthrie’s post announcing them.  I’m the PM for them.  :)  You can use WebMatrix (or Notepad or any text editor of your choice) to build web apps using ASP.NET Web Pages.

First, let me overview how writing your own helper will be in the next version of ASP.NET Web Pages, then I’ll go over how you would go about writing them as of when this blog post went up.

Future @helper Usage (not in Beta)

In the next release of ASP.NET Web Pages you will be able to use our new @helper syntax to easily build new helpers.  The main scenario we’re trying to cover with this is simple.  Say you have a page with a little code/markup that you would like to reuse on other pages.

<!DOCTYPE html>
<html>
  <head>
       <title>Gravatar</title>
   </head>
   <body>
       <img src="@Gravatar.GetUrl("someone@somewhere.com")" alt="Gravatar" />
   </body>
</html>

In the next version you will be able to simply do the following:

  • Create a new file under App_Code in your app named Utilities.cshtml (this can be named anything)
  • Cut the code/markup you’d like to reuse and place it into a method in the new file and replace any hard-coded values with any incoming parameters you have in your method.
  • Call the new helper by the name of the file (Utilities in this example) and the name of the method

Here is what the Utilities.cshtml file would look like:

@helper GetGravatarImage(string email) {
   <img src="@Gravatar.GetUrl(email)" alt="Gravatar" />
}

And here is what the original page would now look like using the new helper:

<!DOCTYPE html>
<html>
  <head>
       <title>Gravatar</title>
   </head>
   <body>
       @Utilities.GetGravatarImage("someone@somewhere.com")
   </body>
</html>

Pretty straightforward and simple. Unfortunately, we’re still working on this feature.  It will be in the next release.

Writing a Helper Today in Beta

For the time being you can convert your code/markup into a static class, which is how we currently build helpers internally.  Here is an example:

using System.Web;
using Microsoft.WebPages.Helpers;

public static class Utilities {
    public static IHtmlString GetGravatarImage(string email) {
        return new HtmlString("<img src=\"" + @Gravatar.GetUrl(email) + "\" alt=\"Gravatar\" />");
    }
}

The most important thing to note here is that your method should return IHtmlString for things to get rendered properly.  How you build it internally doesn’t really matter.  In my simple example I just build a string and then wrap it up into a new instance of HtmlString (which is an implementation of HtmlString).  One other thing to note here is that I’m importing the Microsoft.WebPages.Helpers namespace because that’s where the Gravatar helper that I’m using exists.

One other important thing to note is that while I’m building a string here because it’s such a simple solution, the better/safer way to do this is to build it using the TagBuilder, which is in MVC (and available in the Beta).

The usage for this helper would be exactly the same as the new way stated above:

<!DOCTYPE html>
<html>
  <head>
       <title>Gravatar</title>
   </head>
   <body>
       @Utilities.GetGravatarImage("someone@somewhere.com")
   </body>
</html>

So it’s still pretty easy to build your own, but unfortunately today you have to start learning some OOP concepts, which goes against our goal of simplicity and staying relatively procedural.  Can you see why we’re working on a newer syntax to help simplify this?  🙂

Summary

Writing helpers isn’t too difficult but can be a lot of work if you’re trying to compartmentalize a lot of code/markup and requires you to understand more advanced concepts.  In the next release of ASP.NET Web Pages you will be able to simply cut and paste your code/markup that you want to reuse into a new file and use it.

Do you like the new syntax?  What sorts of helpers are you building?

.NET Web Roadmap for 3.5 (ASP.NET, Silverlight, IIS 7)

Scott Guthrie posted some fantastic info on ASP.NET Extensions 3.5 to match up with Visual Studio 2008 and .NET Framework 3.5.

http://weblogs.asp.net/scottgu/archive/2007/11/29/net-web-product-roadmap-asp-net-silverlight-iis7.aspx

Highlights:

  • Silverlight 1.1 is now Silverlight 2.0
  • Silverlight 2.0 will start to get some of the great features that WPF has
  • Some of the ASP.NET AJAX Futures (like the browser history control) will be moved into ASP.NET AJAX
  • IIS 7 will ship early next year
  • There will be new ways to deploy to IIS 7 and do things like versioning and rolling back on single servers and web farms

Great stuff…check it out!

Re: ASP.NET Custom Authentication Problems

I just got a comment from an old post from over 2 years ago about some custom auth problems I had.  It's funny looking back that far to see how you were.  As with all typical developers, I look back and laugh at myself.  🙂

The question posted was if I could share my custom authentication code.  The fact of the matter is, the way I was doing authentication at the time was kind of silly and I thought I'd post how we do it now.  It's much simpler and uses everything that's already built into ASP.NET 2.0 already.  When a user logs into your site (using whatever type of authentication you want), the Context.User (same User object that shows up on the Page class, etc) is set to an IPrincipal.  Depending on what "username" you passed in for it to tack on to the cookie, you'll be ablel to access a key for looking up more detailsl about the user.  Then the same applies from the rest of my 2 year old post.  Create a BasePage class that inherits from Page and shadow the User property with your own.  Here's the code from our platform in the BasePage class.

public bool IsAuthenticated
{

get { return Request.IsAuthenticated; }
}

private string username;
public string Username
{

get

{

if (username == null)

{

if (IsAuthenticated)

username = base.User.Identity.Name;

else

username = "";

}

return username;

}
}

private bool isUserSet;
private EvNetUser user;
public new EvNetUser User
{

get

{

if (!isUserSet)

{

if (IsAuthenticated)

user = Users.Retrieve(Username);

isUserSet = true;

}

return user;

}
}

Now anywhere in our site we can say Page.User and get back an object filled with everything we need to know about the current user.  If the request is anonymous, Page.User will return null.  Hope this helps Mohammed!

ASP.NET FindControl Recursive with Generics

While working on the new version of Channel 9, part of my job this time around was to "templatize" our entire community platform.  The way everything is set up now, aspx files are read in through the Virtual Path Provider (VPP), additional settings in the Page directive are set based off of database settings and the page is rendered.  All our controls are now ascx files with the CodeFileBaseClass set to a class that implements all the code.  This allows us to easily setup new controls and templates for our different sites if new ones are needed.  Right now, Channel 9 and Channel 10 are the only two sites that will be running this code.  In the halfway near future, VisitMIX and a yet to be named Student focused site will run this code as well, so templating was very important.  My other job for this sprint was to add ASP.NET AJAX functionality to the site.  As we get closer to launching the beta you'll see more posts from me sampling some of our code and techniques especially around ASP.NET AJAX.

The new EntryList control that I created takes a List<Entry>.  The control has a setting to let us set which ascx file we want to use to represent each Entry in the list.  These are loaded using Page.LoadControl.  The same thing goes for the Pager and Filters control in the header and footer of the EntryList.  All of these controls implement different interfaces so we can throw in different ones and look them up generically.  I don't necessarily know the names of these controls and needed a way to look them up by the interfaces they implement.  So I thought, "hey, I'll bet generics could help me out here."  I got the below code written, but unfortunately couldn't figure out how to properly cast the control by the type passed in.  My next door neighbor, our resident Sampy, helped me figure out that I needed to force the type coming in to be a reference (hence the "where T : class" part).  Here's the resulting code…

public static T FindControl<T>(System.Web.UI.ControlCollection Controls) where T : class
{
     T found =
default(T);

     if (Controls != null && Controls.Count > 0)
     {
         
for (int i = 0; i < Controls.Count; i++)
          {
              
if (Controls[i] is T)
               {
                    found = Controls[i]
as T;
                   
break;
               }
              
else
                   
found = FindControl<T>(Controls[i].Controls);
          }
     }

     return found;
}

Unlike a recursive method that's not generic, you would have to pass in the type as a parameter to the method, which would get passed down through every method call, and you'd have to cast the found control on every call you made to FindControl.  That wouldn't be a huge deal, but there is something nice about simplicity.  🙂

IMyInterface myControl = FindControl<IMyInterface>(this.Controls);

Note: This only returns the first instance of a control.  You could easily pass in a references List<IMyInterface> and add to it as you find them. 

This isn't break through code or anything, but it's fun for us, the dev geeks.  Expect to see more posts soon about coding techniques in our platform.

Web Application Project (Visual Studio 2005) on Windows Vista RC1

If you've tried installing it already, you know it doesn't work.  Rich Mercer posted the answer, which is to run the msi as an administrator.  Unfortunately, you can't just right mouse click and run as Administrator.  For details on the work around (a bat file) check out Rich's blog post about it.

UPDATE: The issue with this has been fixed in Windows Vista RC2 (5744) and you do not need the workaround anymore to get it installed.