Wednesday, January 09, 2008

A twist on Bill Cosby's humerous show,  the other day I said the following to my daughter:

I'm sorry, donkeys don't stick to the refrigerator.

Taken out of context, Becky thought it was pretty funny. Here's the context: I was playing with Jenna in the kitchen the other day, and she was playing with 2 plastic donkeys as well as some refrigerator magnets.  After seeing how the magnets stuck to the refrigerator, she tried to do the same with the plastic donkeys, which didn't work of course.

posted on Wednesday, January 09, 2008 10:40:08 AM (Pacific Standard Time, UTC-08:00)  #    Comments [2]
 Thursday, January 03, 2008

The CTP for the MVC framework includes support for master-page, page, and user-control based views.  I thought it might be interesting to enable .ashx-based views for things like RSS generation via System.Xml.Linq, or other more "raw" view output.

As it turns out, this is fairly trivial.  The place we need to extend is the IViewFactory returned by Controller.ViewFactory.  This is the component that is responsible for creating the view when a call to RenderView is made.

The default view factory is the WebFormViewFactory, which knows how to generate views based on .master, .aspx, and .ascx views.  Since we want to add support for .ashx, we'll use WebFormViewFactory as a starting place.  We'll inherit from WebFormViewFactory and override CreateView to supply our extra .ashx lookup.

using System;
using System.Globalization;
using System.IO;
using System.Web;
using System.Web.Compilation;
using System.Web.Mvc;

public class SpecialViewFactory : WebFormViewFactory {

    static readonly string[] ViewLocationFormats = new string[] { "~/Views/{1}/{0}.ashx", "~/Views/Shared/{0}.ashx" };

    ControllerContext _ControllerContext;

    #region IViewFactory Members

    protected override IView CreateView(ControllerContext controllerContext, string viewName, string masterName, object viewData) {
        _ControllerContext = controllerContext;
        //check to see if there is an ashx that matches here.
        object value = null;
        controllerContext.RouteData.Values.TryGetValue("controller", out value);
        string controllerName = value as string;
        if (controllerName == null) {
            throw new InvalidOperationException("No route data value available for controller.");
        }

        Type viewType = null;
        foreach (var loc in ViewLocationFormats) {
            var path = string.Format(CultureInfo.InvariantCulture, loc, viewName, controllerName);
            viewType = GetCompiledType(path);
            if (viewType != null) break;
        }
        if (viewType == null) {
            return base.CreateView(controllerContext, viewName, masterName, viewData);
        }

        if (!typeof(IView).IsAssignableFrom(viewType)) {
            //TODO: better exception
            throw new InvalidOperationException("Type not a view");
        }
        var view = (IView)Activator.CreateInstance(viewType);
        var viewHandler = view as ViewHandler;
        if (viewHandler != null) viewHandler.ViewData = viewData;

        _ControllerContext = null;
        return view;
    }

    private Type GetCompiledType(string path) {
        Type compiledType = null;
        try {
            if (File.Exists(_ControllerContext.HttpContext.Request.MapPath(path))) {
                compiledType = BuildManager.GetCompiledType(path);
            }
        }
        catch (HttpCompileException) {
            throw;
        }
        catch (HttpParseException) {
            throw;
        }
        catch (HttpException) {
        }
        return compiledType;
    }

    #endregion
}

GetCompiledType had to be replicated as it isn't exposed in the base class.  Note, I added a File.Exists check before I attempt to get the compiled type from the BuildManager. This was really to avoid having to deal with a bunch of first chance exceptions in the debug, although it seems likely that avoiding the exception is a good thing.  It wouldn't catch handlers that are mapped in the app dynamically or via web.config.

As you can see, I also added a ViewHandler class that my handlers can inherit from that gives them the same goodies that the other views get, but I'll leave that as an exercise for the reader to implement.

So, now the only thing remaining is to inject our special view factory into the pipeline instead of the default.  A simple way to do this is to set the ViewFactory property in the constructor of any controller that needs .ashx support. Now, you can create .ashx files and use them as views!

Next time, I'll show you how to add support for routing controller actions based on data not in the URL.

posted on Thursday, January 03, 2008 3:46:58 PM (Pacific Standard Time, UTC-08:00)  #    Comments [1]
 Tuesday, December 11, 2007

markandjasonWell, my old-school buddy Jason turns 32 today.  Jason and I have been friends since pre-school.  We pretty much attended every year of school together, all the way through our undergraduate years.  Here's a picture of me and him (Jason on the left... I don't know what the heck I'm doing) at one of my brother's birthday parties.  He was the friend I got to invite.

Happy birthday, man.  Go play some slaughterball today for me.

posted on Tuesday, December 11, 2007 4:22:30 PM (Pacific Standard Time, UTC-08:00)  #    Comments [4]
 Monday, December 10, 2007

I've been loving playing around with the CTP of the MVC framework for ASP.net.  On several occasions in the past, I've rolled my own hybrid MVC implementations on top of ASP.net, so the prospect of a high-quality, "official" implementation is pretty exciting.

Note that I'm NOT on the ASP.net team.  I don't have any "inside" information about the framework, other than the privilege of getting to play with early bits.  But, since my previous jobs have involved alot of web development, I feel like I have some interesting insights.  I've been doing alot of app-building with the framework, and I found myself missing a few things that I'm sure are in the planning phases, but they are missing nonetheless.  Nikhil has some great AJAX support prototyped, so I was able to build on that, but I have some other samples I'll share that may be of some use.

First, users will no doubt be frustrated by trying to implement something simple like shared components.  Think of a blogroll, or list of categories on a blog.  These items don't really depend on input from the route (URL), they show the same data for every page.  Currently, each controller action has to put this data into the ViewData (which makes it more difficult to dynamically add shared components, especially if you're using strongly-typed ViewData).

You could solve this in your controllers by inheriting from a common controller type that prepared that data for you, but then you likely have a controller pulling data that is totally unrelated to it.

I think a better approach is to allow Views to render actions directly.  At first, this may seem like a violation of general MVC practices, but think of it this way...

  • A view can already generate a url to an action that would be rendered if the user clicked on it.
  • With proper AJAX support, you could make a callback and have the results of that action render into part of the page, either onclick or onload.
  • Allowing views to render actions using the same input that they would use to create a URL to that action simply eliminates the round-trip.

So, as Nikhil adds "RenderPartial" support to views, here's my RenderAction implementation:

First are some extension methods for the different view types.  These would go in an appropriate static class.

        /// <summary>

        /// Common logic for rendering actions as part of a view.

        /// </summary>

        static void RenderAction(string actionUrl, IHttpContext currentContext) {

            //create a fake IHttpContext that will fool the route collection

            //into creating RouteData for our action

            var fakeContext = new FakeContext(actionUrl, currentContext);

            var routeData = RouteTable.Routes.GetRouteData(fakeContext);

            //Make a new MvcHandler that will process our fake request

            var handler = new MvcHandler() {

                RequestContext = new RequestContext(fakeContext, routeData)

            };

            ((IHttpHandler)handler).ProcessRequest(HttpContext.Current);

        }

 

        /// <summary>

        /// Allows a page-based view to render actions

        /// </summary>

        public static void RenderAction(this ViewPage page, object values) {

            RenderAction(page.Url.Action(values), page.ViewContext.HttpContext);

        }

 

        /// <summary>

        /// Allows a master-based view to render actions

        /// </summary>

        public static void RenderAction(this ViewMasterPage page, object values) {

            RenderAction(page.Url.Action(values), page.ViewContext.HttpContext);

        }

 

        /// <summary>

        /// Allows a usercontrol-based view to render actions

        /// </summary>

        public static void RenderAction(this ViewUserControl control, object values) {

            RenderAction(control.Url.Action(values), control.ViewContext.HttpContext);

        }

You could imagine adding a similar extension to enable this for any kind of view.

Now, the "FakeContext" type is an implementation of IHttpContext that does the trickiness we need to get the routes to work, and the MvcHandler to execute the request.  Here's the relevant parts, the rest of the implementation just throws so that it is clear when something breaks that we haven't really thought through.

        class FakeContext : IHttpContext {

 

            //This is a similar fake object to help

            class FakeRequest : IHttpRequest {

                FakeContext _FakeContext;

 

                public FakeRequest(FakeContext context) {

                    _FakeContext = context;

                }

 

                public string AppRelativeCurrentExecutionFilePath {

                    get { return VirtualPathUtility.ToAppRelative(_FakeContext._ActionUrl); }

                }

 

                public string PathInfo {

                    get { return string.Empty; }

                }

 

                /// <summary>

                /// This is here to allow Nikhil's IsAjaxRequest method to continue to work

                /// </summary>

                public System.Collections.Specialized.NameValueCollection Headers {

                    get { return new System.Collections.Specialized.NameValueCollection(); }

                }

 

                //the rest of the implementation just throws.

                //that way, we know if something needs to be implemented later.

            }

 

            string _ActionUrl;

            IHttpContext _Context;

 

            public FakeContext(string actionUrl, IHttpContext context) {

                _ActionUrl = actionUrl;

                _Context = context;

            }

 

            public IHttpRequest Request {

                get { return new FakeRequest(this); }

            }

 

            public IHttpSessionState Session {

                get { return _Context.Session; }

            }

 

            //the rest of the implementation just throws.

            //that way, we know if something needs to be implemented later.

        }

This is a testament to the flexibility of the interface-based approach.  This would be MUCH more difficult if we had to deal with the concrete HttpContext type.

So, if we want to inject the results of an action in part of a view, you would just do something similar to the following in your view, and you can pass other data, just as if you were creating an Action.Link:

<% this.RenderAction(new { Controller = "Tags", Action = "Cloud" }); %>

Bingo, the system processes the request (almost) as if it were an isolated request.  There are obviously some limitations here, as well as some dependency on using Controller-based controllers and the MvcHandler, but that's certainly the common scenario.

Next time, I'll show you how to add support for ".ashx"-based views.

posted on Monday, December 10, 2007 12:44:29 PM (Pacific Standard Time, UTC-08:00)  #    Comments [3]
 Sunday, December 02, 2007

CRW_6366It's Jenna's 2nd birthday!  Wow, I can't believe it's already been 2 years.  Here's one of the pictures we took yesterday for our Christmas cards.

That was really fun.  I don't have a nice flash, so I had to resort to auxiliary lighting.  All things considered, I think they turned out pretty well.

Jenna constantly amazes me with what she knows.  She was playing with some blocks this morning and I asked her how many blocks she had.  She said, "Let's count.  One... Two... Three... Four."  Wow.

She's counted as high as 20, and can say the entire alphabet.  She loves to play with toy animals and make them talk with each other.  She loves to play "knock knock", where she'll close a door, we'll knock and she'll let us in, grab our hand and pull us in for a "visit".  Fun times.

CRW_3639

posted on Sunday, December 02, 2007 3:47:37 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Monday, November 19, 2007

This release was exciting me before I made the move to Microsoft, and getting to be a part of it has made me even more excited about what we're giving our customers in this release.  If you're not familiar with Visual Studio, it is a suite of software development tools for creating just about any application or library you can think of.  I'll be writing more about this release in the coming weeks, but for now, go to www.microsoft.com/vstudio and check it out!

posted on Monday, November 19, 2007 9:29:03 AM (Pacific Standard Time, UTC-08:00)  #    Comments [1]
 Wednesday, October 31, 2007

Vista's search rocks, but sometimes you're not interested in searching through the gigabytes of file contents, you just want to search on filename.  Good ol "dir /s".  But, you don't need to give up the indexed goodness for this.  Just use some of the special search syntax: "name:[string you're searching for]" in the search box.

posted on Wednesday, October 31, 2007 3:03:16 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Monday, October 15, 2007

I certainly don't want this to turn into a video blog of fish, but I got some more video of the fish in our stream, and it's so unbelievable to me as someone new to this area that I simply must post it.  I've also done some tweaks to my custom Silverlight player as well as used some different encoding techniques (I haven't decided whether I like them yet).

Again, enjoy me sounding like an idiot.

[UPDATE:] The way I have embedded the player this time seems to prevent it from showing in most RSS aggregators. click through to my blog to see the video.

posted on Monday, October 15, 2007 2:02:31 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]