Community Coding Contest 2010 - Looking for Input and Prizes

4. January 2010

Now that it's 2010, and over a year since the first Community Coding Contest came to an end, I'm thinking about running the contest again in 2010.

Last time it ran for 3 months and we had 6 really great entries. This time around I'm thinking that it may be better to accept entries for 6 months, and have the voting run for a full month. This will give much more time for entries to be submitted, and for people to work on their entries before submitting and/or voting begins.

These are just some initial thoughts, and I'm going to need input on everything from YOU (the community) before I can make a final decision as to when and how to run the contest again. I'd really appreciate if you could fill out the following survey and tell us what you think.

Community Coding Contest 2010 Pre-Contest Survey

Also, we will need some prizes donated before the contest will be able to go on. If you or your company are interested in donating prizes or monetary support (web hosting fees, mailing expenses, etc.) for the contest, please contact the contest directly here: http://communitycodingcontest.org/contact.aspx

You can view the official contest website here: http://communitycodingcontest.org

Thanks, and I look forward to hearing more about what everyone thinks!

asp.net, ASP.NET MVC, Bing Maps, C#, General, Silverlight, vb.net , , , ,

Simplovation Web.Maps.VE v3.0 Now With FREE Edition!

26. August 2009

Simplovation Web.Maps.VE v3.0!Today, I just posted the latest Web.Maps.VE v3.0 release. The coolest thing about this new version is that is has a FREE Edition for non-commercial use!

Download Web.Maps.VE v3.0 FREE Edition!

There are a few new things in this latest release, but the most significant are the following performance updates:

  • Microsoft CDN (Content Delivery Network) Support Added. This helps improve the Bing Maps content delivery speed by up to 82%
  • JavaScript Performance Optimizations. All the JavaScript code internally within the control has been optimized to increase the overall speed of the Web.Maps.VE Map controls functionality.


Check out the following link to see what has all been included in this release:
https://simplovation.com/page/webmapsve30/roadmap.aspx

Also, this new version release is a completely FREE upgrade to all existing customers who have already purchased Web.Maps.VE v2.0. If you have previously purchased v2.0, then there is nothing required to obtain your v3.0 license other than going to the "My Licenses" page to download the DLL and License Activation File. If for some reason you have trouble obtaining your Free Upgrade to v3.0, please let us know and we'll get it activated as soon as possible.

asp.net, Bing Maps, JavaScript , , , , ,

Microsoft Killed the Virtual Earth ASP.NET Control

22. August 2009

Yesterday, Chris Pendleton officially announced that the Microsoft Virtual Earth ASP.NET Control is now Dead. Frankly, I’ve considered it “dead” for a long time now since it didn’t get updated much, didn’t have a completely full feature support, has a few bugs AND you couldn’t use it within your applications because it was just a Preview (CTP) anyway.

There are however a few good things to point out in the light of Microsoft killing its Virtual Earth ASP.NET Control:

If you were previously a fan of Microsoft’s control, then I recommend you check out the Simplovation Web.Maps.VE control. The licensing cost of the Simplovation control is fairly low and sold on a “per developer” basis. A single developer can purchase a single license and then use the Simplovation Web.Maps.VE control within as many (unlimited) applications as he wants/needs. Plus, the Simplovation Web.Maps.VE component can be redistributed along with any of those applications royalty free.

Go check it out: http://simplovation.com

Update 8/23/2009: John has created the CodePlex project to contain the source code for Microsoft’s control. http://bingmapsasp.codeplex.com/

asp.net, Bing Maps , ,

ASP.NET MVC: Implement Theme Folders using a Custom ViewEngine

26. March 2009

One of the things that ASP.NET MVC 1.0 is missing is the ability to easily implement Themes. The older, more mature standard ASP.NET framework includes theme support via the App_Themes folder; however limited it can be, it’s still more than ASP.NET MVC currently has. Well, at least until I wrote this little custom ViewEngine and ControllerBase class to help out and allow us to very easily implement Themes within our ASP.NET MVC applications.

A little history: A few months back I wrote up a post on “How To Setup Custom Theme Support In ASP.NET MVC Preview 4 using a Custom ViewEngine”, then a couple weeks later I posted an updated version that added Custom Themes to ASP.NET MVC Preview 5. So I’ve decided to update the code from Preview 5 and make it all work with ASP.NET MVC v1.0 Final Release.

Also, I took a tip from Michael Ryan and modified my previous theme implementation from Preview 5 to include both Views and Content folders within the Theme folder.

Download the Code

I know some of you may want to download the code and look at it before reading on, so here's the download link:

Source Code: AspNetMvc1CustomThemeImplementation.zip (262.99 kb)

 

Create “~/Themes” Folder and a “Default” View Theme

ASPNETMVC_1_CustomThemeFolderLayoutFirst, we’ll make some changes to the Views contained within the default ASP.NET MVC Template.

Here’s a brief summary of what changes are needed:

  1. Create a sub-folder named “Themes” within the website root folder
  2. Create a sub-folder names “Default” within the “Themes” folder
  3. Cut and Paste the “Views” folder into the new “~/Themes/Default” folder
  4. Cut and Past the “Content” folder into the new “~/Themes/Default” folder
  5. Modify the Pages (.aspx) to point correctly to the Master Page (.master) for the Theme their in, now that we moved the files.
  6. Modify the Master Pages (.master) to point to the CSS file within the “Content” folder correctly, now that we moved the files.
  7. To create additional Themes, just copy the “Default” folder and name it what you want for the desired Theme, and repeat Steps 5 and 6 above.

To the right is a screenshot displaying the layout of the Themes folder as described above.

Apply the Custom ViewEngine

Now that we have our Themes folder created, we can go ahead and implement our Custom ViewEngine. The Custom ViewEngine in this example was created by inheriting the WebFormViewEngine and just overriding/changing the necessary functionality.

First we need to tell our application to use the new “WebFormThemeViewEngine” ViewEngine class. To do this we’ll need to clear any existing ViewEngines and add a new instance of the ‘WebFormThemeViewEngine”. This needs to be done within the Application_Start method in the Global.asax. Below is what the Application_Start method will look like after we make the necessary changes.

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);

    // Replace the Default WebFormViewEngine with our custom WebFormThemeViewEngine
    System.Web.Mvc.ViewEngines.Engines.Clear();
    System.Web.Mvc.ViewEngines.Engines.Add(new WebFormThemeViewEngine());
}

Also, for reference here’s the complete code for the “WebFormThemeViewEngine” class that’s used in this example. I wont be discussing the steps necessary when creating your own custom ViewEngines; that would be a little more involved than I would like to get within the scope of this article. A link to download the entire code for the project I created when writing this article is located at the top of the article.

public class WebFormThemeViewEngine : WebFormViewEngine
{
    public WebFormThemeViewEngine()
    {
        base.MasterLocationFormats = new string[] {
            "~/Themes/{2}/Views/{1}/{0}.master", 
            "~/Themes/{2}/Views/Shared/{0}.master"
        };
        base.ViewLocationFormats = new string[] { 
            "~/Themes/{2}/Views/{1}/{0}.aspx", 
            "~/Themes/{2}/Views/{1}/{0}.ascx", 
            "~/Themes/{2}/Views/Shared/{0}.aspx", 
            "~/Themes/{2}/Views/Shared/{0}.ascx" 
        };
        base.PartialViewLocationFormats = new string[] {
            "~/Themes/{2}/Views/{1}/{0}.aspx",
            "~/Themes/{2}/Views/{1}/{0}.ascx",
            "~/Themes/{2}/Views/Shared/{0}.aspx",
            "~/Themes/{2}/Views/Shared/{0}.ascx"
        };
    }

    protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
    {
        try
        {
            return System.IO.File.Exists(controllerContext.HttpContext.Server.MapPath(virtualPath));
        }
        catch (HttpException exception)
        {
            if (exception.GetHttpCode() != 0x194)
            {
                throw;
            }
            return false;
        }
        catch
        {
            return false;
        }
    }

    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {
        string[] strArray;
        string[] strArray2;

        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(viewName))
        {
            throw new ArgumentException("viewName must be specified.", "viewName");
        }

        string themeName = this.GetThemeToUse(controllerContext);

        string requiredString = controllerContext.RouteData.GetRequiredString("controller");

        string viewPath = this.GetPath(controllerContext, this.ViewLocationFormats, "ViewLocationFormats",
                viewName, themeName, requiredString, "View", useCache, out strArray);

        string masterPath = this.GetPath(controllerContext, this.MasterLocationFormats, "MasterLocationFormats",
                masterName, themeName, requiredString, "Master", useCache, out strArray2);

        if (!string.IsNullOrEmpty(viewPath) && (!string.IsNullOrEmpty(masterPath) || string.IsNullOrEmpty(masterName)))
        {
            return new ViewEngineResult(this.CreateView(controllerContext, viewPath, masterPath), this);
        }
        return new ViewEngineResult(strArray.Union<string>(strArray2));
    }

    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
    {
        string[] strArray;
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(partialViewName))
        {
            throw new ArgumentException("partialViewName must be specified.", "partialViewName");
        }

        string themeName = this.GetThemeToUse(controllerContext);

        string requiredString = controllerContext.RouteData.GetRequiredString("controller");
        string partialViewPath = this.GetPath(controllerContext, this.PartialViewLocationFormats,
                "PartialViewLocationFormats", partialViewName, themeName, requiredString, "Partial", useCache, out strArray);
        if (string.IsNullOrEmpty(partialViewPath))
        {
            return new ViewEngineResult(strArray);
        }
        return new ViewEngineResult(this.CreatePartialView(controllerContext, partialViewPath), this);

    }

    private string GetThemeToUse(ControllerContext controllerContext)
    {
        string themeName = controllerContext.HttpContext.Items["themeName"] as string;
        if (themeName == null) themeName = "Default";
        return themeName;
    }

    private static readonly string[] _emptyLocations;

    private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName,
        string name, string themeName, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
    {
        searchedLocations = _emptyLocations;
        if (string.IsNullOrEmpty(name))
        {
            return string.Empty;
        }
        if ((locations == null) || (locations.Length == 0))
        {
            throw new InvalidOperationException("locations must not be null or emtpy.");
        }

        bool flag = IsSpecificPath(name);
        string key = this.CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, themeName);
        if (useCache)
        {
            string viewLocation = this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
            if (viewLocation != null)
            {
                return viewLocation;
            }
        }
        if (!flag)
        {
            return this.GetPathFromGeneralName(controllerContext, locations, name, controllerName, themeName, key, ref searchedLocations);
        }
        return this.GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
    }

    private static bool IsSpecificPath(string name)
    {
        char ch = name[0];
        if (ch != '~')
        {
            return (ch == '/');
        }
        return true;
    }

    private string CreateCacheKey(string prefix, string name, string controllerName, string themeName)
    {
        return string.Format(CultureInfo.InvariantCulture,
            ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}",
            new object[] { base.GetType().AssemblyQualifiedName, prefix, name, controllerName, themeName });
    }

    private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name,
        string controllerName, string themeName, string cacheKey, ref string[] searchedLocations)
    {
        string virtualPath = string.Empty;
        searchedLocations = new string[locations.Length];
        for (int i = 0; i < locations.Length; i++)
        {
            string str2 = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { name, controllerName, themeName });

            if (this.FileExists(controllerContext, str2))
            {
                searchedLocations = _emptyLocations;
                virtualPath = str2;
                this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
                return virtualPath;
            }
            searchedLocations[i] = str2;
        }
        return virtualPath;
    }

    private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
    {
        string virtualPath = name;
        if (!this.FileExists(controllerContext, name))
        {
            virtualPath = string.Empty;
            searchedLocations = new string[] { name };
        }
        this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
        return virtualPath;
    }
}

 

Create a ThemeControllerBase Class to Initially Set the Theme to Use

One additional step that’s necessary when using the above WebFormThemeViewEngine is to create a custom Controller Base Class, and inherit from it with all your Controllers. This ThemeControllerBase class needs to set the Theme to use within the “HttpContext.Items[“themeName”]”. If you miss or skip this step you will get an exception and will not be able to run the website.

The ThemeControllerBase class used in this example includes code that allows you to specify the Theme to use by passing it in using the QueryString like this: “http://localhost/Default.aspx?theme=Red”

Here’s the code for the ThemeControllerBase class:

public abstract class ThemeControllerBase : Controller
{
    protected override void Execute(System.Web.Routing.RequestContext requestContext)
    {
        // Add code here to set the Theme based on your database or some other storage
        requestContext.HttpContext.Items["themeName"] = "Red";


        
        // Allow the Theme to be overriden via the querystring
        // If a Theme Name is Passed in the querystring then use it and override the previously set Theme Name
        // http://localhost/Default.aspx?theme=Red
        var previewTheme = requestContext.HttpContext.Request.QueryString["theme"];
        if (!string.IsNullOrEmpty(previewTheme))
        {
            requestContext.HttpContext.Items["themeName"] = previewTheme;
        }

        base.Execute(requestContext);
    }
}

Then you need to have all your Controller’s inherit from the ThemeControllerBase like the following:

public class HomeController : ThemeControllerBase { }

 

Conclusion

I’m sure some kind of Themes support is on the list for Microsoft to eventually implement into ASP.NET MVC some time down the road, but I have yet to see anything mentioned. Also, since the Official v1.0 release has already been released, I don’t really expect anything to change until presumably after .NET 4.0 ships; this is assuming the ASP.NET MVC 1.0 bits are what make it into .NET 4.0 and not a newer version of ASP.NET MVC. So, for now if you want to implement Themes into your ASP.NET MVC Website, you’ll just have to use the code I post here, or some other custom implementation.

asp.net, ASP.NET MVC ,

Develop Software for the Microsoft Windows Platform for FREE

11. December 2008

I've been lucky enough to get 2 free MSDN subscriptions over the last few years, so I've never needed to purchase Visual Studio, Office or SQL Server. The first MSDN subscription I won in a blogging contest back in 2006, and the second I got when I won the Microsoft MVP award earlier this year (2008). Before you start to think that I'm braging; just let me point out that I am very greatful for this. Below is a list/guide that I would follow if I wasn't so fortunate, and I'm not talking about softare piracy (that really is stealing!).

Now the point of this post... What if I didn't win these MSDN Subscriptions? Then, how exactly would I obtain the software necessary to do my job?

No the answer isn't to borrow CD/DVD's from work or a buddy. And, No it's not to fire up Bit Torrent. The software I mention below (all of it) are all freely available to anyone.

Free Tools for Developers

There a quire a few tools out there of various types. Below, I'll focus on the ways you can obtain, for FREE, software that you may otherwise have to pay for. Also, there are so many tools of various kinds, so I'll just be focusing on the main ones that you should be aware of.

Integrated Developement Environment (IDE)

  • MS Visual Studio Express Editions - These are FREE editions of the full Visual Studio product. There is no restriction to selling the software you build using these tools, and they contain all the features necessary for you to build your software. These include Visual Basic Express, Visual C# Express, Visual C++ Express and Visual Web Developer Express.
  • SharpDevelop - This is a FREE, Open Source IDE for developing .NET applications. It's been around since before Microsoft released the Visual Studio Express editions.
  • MS Silverlight Tools for Visual Studio 2008 SP1 - This is a FREE add-on to Visual Studio (both Full Editions and Free Express editions) that enables you to build Silverlight 2 applications. You can find more info on this here.

Database

  • MS SQL Server Express Edition - This is a FREE edition of SQL Server. There are some scalability requirements with it, but it's still perfect for development purposes.

Other Tools

  • .NET Reflector - This allows you to easily view, navigate and searh through the class hierarchies of any .NET assembly; even if you don't have the source code. This tool is invaluable, since it can decompile .NET assemblies and show you the VB.NET, C# or IL code.
  • Internet Explorer Developer Toolbar - This is an add-on to IE7 that adds a much improved (and needed) set of client-side/javascript debugging tools.

There are plenty of tools you can use to develop software for the Microsoft/Windows platform that are completely FREE. All you need is a copy of Windows to begin with, and that comes with any PC you buy.

How to obtain paid tools at No Co$t

Since you can't exactly go to Microsoft.com and download the Full/Paid versions of their software for Free; there are time when they give it away. You just need to look out for those nice opportunities and take advantage of them.

Here's a couple examples in the past that I took advantage of, if I remeber them correctly:

  • Once upon a time there were some online screencasts for VB.NET from Microsoft. If you viewed a certain number of them within a certain time period, then you could enter your mailing address and Microsoft would send you a copy of Visual Basic .NET 2003 for FREE.
  • Back in 2005, Microsoft had this "Ready to Launch" tour that was put on by the MSDN Events people (it was a free conference). If you attended you got a CD/DVD pack that included the Full, RTM versions of Visual Studio 2005 Standard Edition and SQL Server 2005 Standard Edition for FREE.
  • In Dec. 2006, Microsoft gave a bunch of copies of Visual Studio 2008 Professional edition to the Wisconsin .NET Users Group to give away at a special Launch/Holiday meeting that month.
  • In early 2008, Microsoft put on the {Heroes Happen Here} events across the country, again they were free copies of Windows Vista Ultimate w/ Service Pack 1, Visual Studio 2008 Standard, SQL Server 2008 CTP, and Windows Server 2008 1 year trial.
As you can see, I've been able to take advantage of a few FREE opportunities to obtain the tools I need to do my job. I really encourage you to take advantage of these if you can.

 

Happy coding, now go get your Free Tools. And, remember, Do Not Pirate Steal Software; would you want someone to steal the software you write, the same software you're trying to make money on?


asp.net, General , , ,

ASP.NET AJAX: Create a JavaScript Component with Events

7. November 2008

In the spirit of my "Mixing OOP and Event Driving Programming" article I posted a couple days ago; this article discusses how to implement the same thing using the ASP.NET AJAX JavaScript Library. Using inheritence (the ASP.NET AJAX way) it is even easier to implement a little Event Driven Model into your client-side, JavaScript classes/components.

Event Driven Basics

First we'll start by creating a Person class that inherits from the Sys.Component class. Now remember, since we are doing things the ASP.NET AJAX way, we aren't using JavaScript's Prototypal Inheritence.

Person = function() {
    Person.initializeBase(this);
};
Person.prototype = {
    initialize: function() {
        Person.callBaseMethod(this, "initialize");
   },
    dispose: function() {
        Person.callBaseMethod(this, "dispose");
    }
};
Person.registerClass("Person", Sys.Component);

At first glance you may notice that there's a little more plumbing required to inherit from another object using ASP.NET AJAX. This is true, however it does make implementing Events much easier. The magic of inheritence mostly occurs within the call to the "registerClass" method that actually sets up the class/object to inherit from "Sys.Component." It also sets up the "initialize" method to be called when the object is instantiated.

If you have question (and I'm sure you do if you're not familiar with ASP.NET AJAX), please go check out the ASP.NET AJAX Documentation, as further details aren't within the scope of this article.

Now, the hash table that we implemented in the previous article is already implemented for us within the "Sys.Component" class. We can access it using the "private" "get_events" method. The "get_events" method actually returns an instance of the"Sys.EventHandlerList" class that also implements the "getHandler" method for us. This all makes implementing Events much simpler.

First, lets create a "raiseEvent" method, since this isn't created for us. This method will allow us to actually Raise the Events with only a single method call.

Person.prototype = {
    raiseEvent: function(eventName, eventArgs) {
        var handler = this.get_events().getHandler(eventName);
        if (handler) {
            if (!eventArgs) {
                eventArgs = Sys.EventArgs.Empty;
             }
            handler(this, eventArgs);
        }
    }
};

Now lets add the "Name" property (with accessors) to our Person class, and have the "set" accessor Raise the "onchangename" event:

Person.prototype = {
    get_Name: function() {
        return this._name;
    },
    set_Name: function(value) {
        // get old value
        var oldValue = this._name;

        // set new value
        this._name = value;

        // Raise the Event and send the old value as the "EventArgs"
       this.raiseEvent("onchangename", oldValue);
   }
};

Here's an example of using the Person class, and attaching a function to handle the "onchangename" event:

var p = new Person();

p.get_events().addHandler(
        "onchangename",
        function(sender, eventArgs) {
            alert("Old Value: " + eventArgs);
        }
    );

p.set_Name("Charlie");

As you can see in the example event handler that we added above, the function gets passed two parameters (sender and eventArgs). The sender is the Person object that is raising the event, and the eventArgs is this events arguments. So far we are only passing the old Name value as the event arguments, but in the next section of the article we'll see how to create our own EventArgs object that inherits from "Sys.EventArgs" that can contain anything we want to pass to the event handler.

Go Further With Sys.EventArgs

The proper way to raise events in ASP.NET AJAX, is to pass an instance of "Sys.EventArgs" when raising events. This allows you to pass any event arguments to the event handler that you want/need. This also follows suit after the way Events are handled within the .NET Framework.

To do this, we'll first create our own custom EventArgs class that inherits from "Sys.EventArgs", and we'll add "OldName" and "NewName" read-only properties to it (to do this we'll only add 'get' accessors). We'll set the "OldName" and "NewName" properties within the constructor, so we'll make the constructor accept those as well.

PersonEventArgs = function(oldNameValue, newNameValue) {
    this._oldName = oldNameValue;
    this._newName = newNameValue;
};
PersonEventArgs.prototype = {
    get_OldName: function() {
        return this._oldName;
    },
    get_NewName: function() {
        return this._newName;
    }
};
PersonEventArgs.registerClass("PersonEventArgs", Sys.EventArgs);

Now to make use of this new PersonEventArgs class, we just need to change the line of code within the Person class's "Name" properties accessor to the following:

this.raiseEvent("onchangename", new PersonEventArgs(oldValue, this._name));

Now to access the "OldName" and "NewName" properties of the PersonEventArgs when they are passed to the "onchangename" event handler, just call their property accessors. Like the following:

var p = new Person();
p.get_events().addHandler(
    "onchangename",
    function(sender, eventArgs) {
        alert("Old Name: " + eventArgs.get_OldName();
        alert("New Name: " + eventArgs.get_NewName();
   });

Simplify the Adding of Event Handlers

Having to call the "get_events" method, the calling the "addHandler" method and passing it the name of the event can be a little cumbersome. You can create "add_{eventName}" methods that accept one argument (the event handler method) that will add the event handler for you with only one method call, and never needing to pass (and type or misstype) the event name. This is actually how the objects within the ASP.NET AJAX JavaScript library handle adding events. They also implement a "remove_eventName" method that does the same, but removes the event handler passed in.

Person.prototype = {
    add_changeName: function(handler) {
        this.get_events().addHandler("onchangename", handler);
    },
    remove_changeName: function(handler) {
        this.get_events().removeHandler("onchangename", handler);
    }
};

If you compare the below example of adding an event handler for the "onchangename" event, you'll see that it is much simpler to call these methods than the example above.

var p = new Person();
p.add_changeName(function(sender, eventArgs) {
        alert("Old Name: " + eventArgs.get_OldName();
        alert("New Name: " + eventArgs.get_NewName();
   });

Complete Source Code

Heres' the complete code for the Person object created with this article.

First, the example usage code:

var p = new Person();

p.add_changeName(function(sender, eventArgs) {
    alert("Old Name: " + eventArgs.get_OldName());
    //alert("New Name: " + eventArgs.get_NewName());
});

p.set_Name("Charlie");

p.set_Name("Chris");

And, now the full Person and PersonEventArgs code:

Person = function() {
    Person.initializeBase(this);
    this._name = null;
};
Person.prototype = {
    initialize: function() {
        Person.callBaseMethod(this, "initialize");
    },
    dispose: function() {
        Person.callBaseMethod(this, "dispose");
    },

    raiseEvent: function(eventName, eventArgs) {
        var handler = this.get_events().getHandler(eventName);
        if (handler) {
            if (!eventArgs) {
                eventArgs = Sys.EventArgs.Empty;
            }
            handler(this, eventArgs);
        }
    },

    // Property Accessors
    get_Name: function() {
        return this._name;
    },
    set_Name: function(value) {
        // get old value
        var oldValue = this._name;

        // set new value
        this._name = value;

        // Raise the Event and send the old value as the "EventArgs"
        this.raiseEvent("onchangename", new PersonEventArgs(oldValue, this._name));
    },

    add_changeName: function(handler) {
        this.get_events().addHandler("onchangename", handler);
    },
    remove_changeName: function(handler) {
        this.get_events().removeHandler("onchangename", handler);
    }
};
Person.registerClass("Person", Sys.Component);

PersonEventArgs = function(oldNameValue, newNameValue) {
    this._oldName = oldNameValue;
    this._newName = newNameValue;
};
PersonEventArgs.prototype = {
    get_OldName: function() {
        return this._oldName;
    },
    get_NewName: function() {
        return this._newName;
    }
};
PersonEventArgs.registerClass("PersonEventArgs", Sys.EventArgs);

Conclusion

As you can see, it's event easier to add/handle events with your custom classes when you use the ASP.NET AJAX library, rather than implementing it completely yourself. Either way you can use inheritence to greatly simplify things.

asp.net, JavaScript ,

Place a StackOverflow badge on your website

10. October 2008

Update: StackOverflow.com has since added the ability to include StackOverflow "Flair" on your own website; basically a "badge" that contains your StackOverflow.com Name, Avatar, Score and Medal counts within a nice little box. I'm not sure when they added it, but now it's officially supported.

http://stackoverflow.com/users/flair

And, for reference, my original StackOverflow Badge code is still below:


I was thinking it would be cool if you could place a StackOverflow "badge" on your website or blog showing your current Reputation score with a link to your profile. So I look into it, and it actually wasn't that difficult to "scrape" the information off the StackOverflow.com website.

Disclaimer: I didn't see any terms of use on the site prohibiting this, but it very well may be against the terms of use. Use at your own risk.

Well, here's a small ASP.NET User Control that does it:

And here's the usage code for putting your badge on your site:

[ code:html ]

<%@ Register Src="~/StackOverflowBadge.ascx" TagPrefix="StackOverflow" TagName="Badge" %>

<style type="text/css">
    .stackoverflow-badge {border: solid 1px black; padding: 2px;}
    .stackoverflow-badge .user-info .user-gravatar32{float: left; width: 32px;}
    .stackoverflow-badge .user-info .user-gravatar32 img{border: none;}
    .stackoverflow-badge .user-info .user-details{
     float: left; margin-left: 5px; width: 138px; overflow: hidden; white-space: nowrap;
    }
    .stackoverflow-badge .user-details{color: #888; line-height:17px;}
    .stackoverflow-badge .reputation-score{font-weight: bold; color: #333; font-size: 120%; margin-right:2px;}
    .stackoverflow-badge .badge{
     color: #fff; background-color: #333; border: 1px solid #333; margin: 0 3px 3px 0;
     padding: 4px 8px 4px 3px; color: white !important; text-decoration: none; line-height: 1.9;
    }
    .stackoverflow-badge .badge:hover{border: 1px solid #555;background-color: #555;text-decoration: none;}
    .stackoverflow-badge .badge1{margin-left:3px;font-size: 120%;color: #FFCC00;}
    .stackoverflow-badge .badge2{margin-left:3px;font-size: 120%;color: #C0C0C0;}
    .stackoverflow-badge .badge3{margin-left:3px;font-size: 120%;color: #CC9966;}
    .stackoverflow-badge .badgecount{padding-left: 1px; color: #808185;}
</style>

<StackOverflow:Badge runat="server" id="sob1" DisplayName="Chris Pietschmann"></StackOverflow:Badge>

[/ code ]

See pretty simple to use, just make sure you put the CSS in your site so it's styled appropriately.

And finally, here's the source for the StackOverflowBadge.ascx user control:

StackOverflowBadge.ascx:

[ code:html ]

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="StackOverflowBadge.ascx.cs" Inherits="StackOverflowBadge" %>
<%=this.HTML%>

[/ code ]

StackOverflowBadge.ascx.cs:

using System;
using System.IO;
using System.Net;
using System.Text;

public partial class StackOverflowBadge : System.Web.UI.UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        GetStackOverflowData();
    }

    public string DisplayName { get; set; }
    public string Reputation { get; set; }
    public string UserID { get; set; }
    public string ImageURL { get; set; }
    public string HTML { get; set; }

    protected void GetStackOverflowData()
    {
        string username = this.DisplayName;
        username = username.ToLowerInvariant();


        WebRequest r = WebRequest.Create("http://stackoverflow.com/users/browser-filter");
        ((HttpWebRequest)r).UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0;)";
        r.Method = "POST";

        Byte[] byteArray = Encoding.ASCII.GetBytes("filter=" + username);

        r.ContentType = "application/x-www-form-urlencoded";
        r.ContentLength = byteArray.Length;

        Stream requestStream = r.GetRequestStream();
        requestStream.Write(byteArray, 0, byteArray.Length);
        requestStream.Close();


        WebResponse resp = r.GetResponse();

        StreamReader reader = new StreamReader(resp.GetResponseStream());

        string responseContent = reader.ReadToEnd();


        // Fix username to look for in HTML returned
        username = username.Replace(" ", "-");


        // Get UserID
        string userid = null;
        if (responseContent.IndexOf("<a href=\"/users/") > 0)
        {
            string tempUserID = responseContent.Substring(responseContent.IndexOf("<a href=\"/users/"));
            if (tempUserID.Length > 16)
            {
                tempUserID = tempUserID.Substring(16);
                if (tempUserID.IndexOf("/" + username) > 0)
                {
                    userid = tempUserID.Substring(0, tempUserID.IndexOf("/" + username));
                }
            }
            this.UserID = userid;
        }

        // Get Reputation Score
        string rep = null;
        if (responseContent.IndexOf("title=\"reputation score\">") > 0)
        {
            string tempRep = responseContent.Substring(responseContent.IndexOf("title=\"reputation score\">"));
            if (tempRep.Length > 25)
            {
                tempRep = tempRep.Substring(25);
                if (tempRep.IndexOf("</span>") > 0)
                {
                    rep = tempRep.Substring(0, tempRep.IndexOf("</span>"));
                }
            }
        }
        this.Reputation = rep;


        // Get Image URL
        string img = null;
        if (responseContent.IndexOf("\"><img src=\"") > 0)
        {
            string tempImg = responseContent.Substring(responseContent.IndexOf("\"><img src=\""));
            if (tempImg.Length > 12)
            {
                tempImg = tempImg.Substring(12);
                if (tempImg.IndexOf("\" height=32 width=32 />") > 0)
                {
                    img = tempImg.Substring(0, tempImg.IndexOf("\" height=32 width=32 />"));
                }
            }
        }
        this.ImageURL = img;

 

        // Get Full Html for Display
        string html = null;
        if (responseContent.IndexOf("; width:860px;\">") > 0)
        {
            string tempHtml = responseContent.Substring(responseContent.IndexOf("; width:860px;\">"));
            if (tempHtml.Length > 16)
            {
                tempHtml = tempHtml.Substring(16);
                if (tempHtml.IndexOf("<div style=\"float:") > 0)
                {
                    html = tempHtml.Substring(0, tempHtml.IndexOf("<div style=\"float:"));
                    html = html.Replace("<a href=\"/users", "<a href=\"http://stackoverflow.com/users");
                    html = html.Replace("<table style=\"margin: 0px 5px 5px;\">", "<table cellpadding='0' cellspacing='0' class=\"stackoverflow-badge\">");
                    html = html.Replace("<div class=\"user-action-time\"><br /></div>", "");
                    html = html.Replace("<td width=200>", "<td>");
                }
            }
        }
        this.HTML = html;
    }

}

asp.net ,

.NET 3.5 SP1: DataContractJsonSerializer Bug

27. August 2008

Update 9/16/2008: After too long, I finally tried uninstalling SP1 and reinstalling it and that fixed the issue. I'm glad it's fixed now! I haven't seen any other suggestions for fixing this, and I first tried repairing the installation, but only an uninstall and reinstall fixed it. Hope this helps solves anyone elses headache with this issue.

 

A few months ago (back in February actually) I blogged showing how to use the new DataContractJsonSerializer to serialize your .NET objects to JSON. Everything was fine until .NET 3.5 SP1 was released. It appears that the .NET 3.5 SP1 update breaks the DataContractJsonSerializer. Using the exact same code from my previous post that worked perfect on .NET 3.5 RTM, breaks with the following exceptions in .NET 3.5 SP1 RTM:

First Exception

This exception occurrs when running this code within a Console application:

System.MissingFieldException was unhandled
  Message="Field not found: 'System.Runtime.Serialization.XmlObjectSerializerContext.serializerKnownDataContracts'."
  Source="System.ServiceModel.Web"
  StackTrace:
       at System.Runtime.Serialization.Json.XmlObjectSerializerWriteContextComplexJson..ctor(DataContractJsonSerializer serializer, DataContract rootTypeDataContract)
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph)
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
       at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph)
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(Stream stream, Object graph)
       at JsonSerializer_dotNet35SP1_IssueTest.JSONHelper.Serialize[T](T obj) in D:\TEST\JsonSerializer_dotNet35SP1_IssueTest\JsonSerializer_dotNet35SP1_IssueTest\Program.cs:line 56
       at JsonSerializer_dotNet35SP1_IssueTest.Program.Main(String[] args) in D:\TEST\JsonSerializer_dotNet35SP1_IssueTest\JsonSerializer_dotNet35SP1_IssueTest\Program.cs:line 22
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

Code for Console app:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
//using System.ServiceModel.Web;

namespace JsonSerializer_dotNet35SP1_IssueTest
{
    public class Program
    {
        static void Main(string[] args)
        {

            /// Sample code using the above helper methods
            /// to serialize and deserialize the Person object

            Person myPerson = new Person("Chris", "Pietschmann", 26);

            // Serialize
            string json = JSONHelper.Serialize<Person>(myPerson);

            // Deserialize
            myPerson = JSONHelper.Deserialize<Person>(json);
        }
    }

    [DataContract]
    public class Person
    {
        public Person() { }
        public Person(string firstname, string lastname, int age)
        {
            this.FirstName = firstname;
            this.LastName = lastname;
            this.Age = age;
        }

        [DataMember]
        public string FirstName { get; set; }

        [DataMember]
        public string LastName { get; set; }

        [DataMember]
        public int Age { get; set; }
    }

    public class JSONHelper
    {
        public static string Serialize<T>(T obj)
        {
            System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
            MemoryStream ms = new MemoryStream();
            serializer.WriteObject(ms, obj);
            string retVal = Encoding.Default.GetString(ms.ToArray());
            return retVal;
        }

        public static T Deserialize<T>(string json)
        {
            T obj = Activator.CreateInstance<T>();
            MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
            System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
            obj = (T)serializer.ReadObject(ms);
            ms.Close();
            return obj;
        }
    }

Second Exception

This exception occurrs when running this code within an ASP.NET WebPage:

System.MissingMethodException was unhandled by user code
  Message="Method not found: 'Boolean System.Runtime.Serialization.DataContract.get_IsReference()'."
  Source="System.ServiceModel.Web"
  StackTrace:
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.CheckIfTypeIsReference(DataContract dataContract)
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.get_RootContract()
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph)
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
       at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph)
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
       at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(Stream stream, Object graph)
       at _Default.JSONHelper.Serialize[T](T obj) in d:\TEST\JsonSerializer_dotNet35SP1_IssueTest_ASPNET\Default.aspx.cs:line 55
       at _Default.Page_Load(Object sender, EventArgs e) in d:\TEST\JsonSerializer_dotNet35SP1_IssueTest_ASPNET\Default.aspx.cs:line 22
       at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
       at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
       at System.Web.UI.Control.OnLoad(EventArgs e)
       at System.Web.UI.Control.LoadRecursive()
       at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
  InnerException:

Code for ASP.NET app:

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Text;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

        /// Sample code using the above helper methods
        /// to serialize and deserialize the Person object

        Person myPerson = new Person("Chris", "Pietschmann", 26);

        // Serialize
        string json = JSONHelper.Serialize<Person>(myPerson);

        // Deserialize
        myPerson = JSONHelper.Deserialize<Person>(json);
    }

    [DataContract]
    public class Person
    {
        public Person() { }
        public Person(string firstname, string lastname, int age)
        {
            this.FirstName = firstname;
            this.LastName = lastname;
            this.Age = age;
        }

        [DataMember]
        public string FirstName { get; set; }

        [DataMember]
        public string LastName { get; set; }

        [DataMember]
        public int Age { get; set; }
    }

    public class JSONHelper
    {
        public static string Serialize<T>(T obj)
        {
            System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
            MemoryStream ms = new MemoryStream();
            serializer.WriteObject(ms, obj);
            string retVal = Encoding.Default.GetString(ms.ToArray());
            return retVal;
        }

        public static T Deserialize<T>(string json)
        {
            T obj = Activator.CreateInstance<T>();
            MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
            System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
            obj = (T)serializer.ReadObject(ms);
            ms.Close();
            return obj;
        }
    }
}

 

WTF??

I searched for a possible solution, but the only thing I found was this link (http://forums.msdn.microsoft.com/en-US/csharpgeneral/thread/37558154-88a9-41f0-a9d6-a2cb4052a5ce/) where someone mentions the First Exception above. The suggested solution was to use the KnownType Attribute on the object, but that doesn't help with my above code.

Does anyone know a work around for this that doesn't involve using the older JavaScriptSerializer object?

Well, for now, it looks like I'm almost the only one to experience this issue. Frown

asp.net , ,

How To Setup Custom Theme Support In ASP.NET MVC Preview 4 using a Custom ViewEngine

17. August 2008

Update 2009/03/26: There is an updated version of this code (with improvements) that targets the ASP.NET MVC 1.0 RTW located here: http://pietschsoft.com/post/2009/03/ASPNET-MVC-Implement-Theme-Folders-using-a-Custom-ViewEngine.aspx 

Update 8/29/2008: I posted a new blog post today that contains updated code for this example that works with the newly released ASP.NET MVC Preview 5. The new code is located here.

One option to do theming in ASP.NET MVC is to use the standard ASP.NET Theme functionality (App_Themes folder glory and all), but it just doesn't seem complete. Also, by doing that you don't get to generate completely custom HTML specific to each Theme. After all, the beauty of ASP.NET MVC is being able to completely control the HTML output of the application. So, I played around with things a bit and figured out how to create a custom ViewEngine that will allow you to create a seperate sub-folder within the Views folder for each Theme. This way you can have a completely different version of each View for each Theme, and maintain complete control over the HTML output on a per Theme basis.

I used the ASP.NET MVC Preview 4 release to write this article.

Create Themed Views

First, we'll make some changes to the Views contained within the default ASP.NET MVC Template.

Here's a brief summary of what changes are needed:

  1. Create a sub-folder named "Default" within the Views folder, and paste all the files and folders within the Views folder into this folder. This will be out "Default" theme.
  2. Cut the Site.css file from the Content folder, and paste it into the Shared folder within the newly created "Default" theme folder.
  3. Modify the CSS link reference in the "Shared/Site.Master" file within the "Default" theme to reference the new location for the Site.css file.
  4. Modey all the .aspx View pages to reference the new location of the MasterPage file.

Once, the above changes are made we have our first "Default" theme created. Now to create a new theme, just create a copy of this folder and make sure to update all the references within the files to reference the theme folder the files are contained within.

To the right is a screenshot displaying the layout of the Theme folders as described above.

In the code example attached to this article, I created a second theme named "Red" and I changed the background color of the Site.css file to the color Red.

Create a Custom ViewEngine

I found the following article usefull and very descriptive on creating custom ViewEngines, so I'm not going to re-write it. Just refer to the following link on how to create a custom ViewEngine in ASP.NET MVC.

http://blog.maartenballiauw.be/post/2008/05/20/Creating-a-custom-ViewEngine-for-the-ASPNET-MVC-framework.aspx

Instead, I'll just list the code I wrote for setting up Themes and give a brief description of what it does.

First, we need to create a ViewLocator object that will contain VirtualPath references that describe to ASP.NET MVC how to find a specified View. To make things simpler I'm just inheriting from the WebFormViewLocator so I don't have to re-implement it's entire logic. Also, one thing to note is the constructor of our WebFormThemeViewLocator object takes a string argument that is the name of the Theme it is to describe the VirtualPath references for.

public class WebFormThemeViewLocator : System.Web.Mvc.WebFormViewLocator
{
    public WebFormThemeViewLocator(string themeName)
    {
        base.ViewLocationFormats = new string[] {
            "~/Views/" + themeName + "/{1}/{0}.aspx",
            "~/Views/" + themeName + "/{1}/{0}.ascx",
            "~/Views/" + themeName + "/Shared/{0}.aspx",
            "~/Views/" + themeName + "/Shared/{0}.ascx"
        };

            base.MasterLocationFormats = new string[] {
            "~/Views/" + themeName + "/{1}/{0}.master",
            "~/Views/" + themeName + "/Shared/{0}.master"
        };
    }
}

Next, we need to create a custom ViewEngine. Also, in this case, I'm inheriting the WebFormViewEngine so I don't have to re-implement it's entire logic. This WebFormThemeViewEngine just overrides the RenderView method so that it can get the Theme to display from the HttpContext.Items collection and implement the new WebFormThemeViewLocator instead of the default WebFormViewLocator.

public class WebFormThemeViewEngine : System.Web.Mvc.WebFormViewEngine
{
    protected override void RenderView(System.Web.Mvc.ViewContext viewContext)
    {
        string themeName = viewContext.HttpContext.Items["themeName"] as string;
        if (themeName == null) themeName = "Default";

        base.ViewLocator = new WebFormThemeViewLocator(themeName);

        base.RenderView(viewContext);
    }
}

Next, we need to create a custom ControllerFactory that we'll setup ASP.NET MVC to use so we can use our custom ViewEngine. Again, I'm inheriting the DefaultControllerFactory so I only have to add the necessary logic to use the custom ViewEngine so I don't have to re-implement it's entire logic.

public class WebFormThemeControllerFactory : System.Web.Mvc.DefaultControllerFactory
{
    protected override System.Web.Mvc.IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        System.Web.Mvc.Controller controller = base.CreateController(requestContext, controllerName) as System.Web.Mvc.Controller;
        controller.ViewEngine = new WebFormThemeViewEngine();
        return controller;
    }
}

Finally, to tell ASP.NET MVC to use the WebFormThemeControllerFactory object, and ultimately our custom ViewEngine, the following line of code needs to be added to the Application_Start event handler:

ControllerBuilder.Current.SetControllerFactory(typeof(WebFormThemeControllerFactory));

Dynamically Determine Which Theme to Display to the User

The way I've set things up in the above code for the custom ViewEngine, I've wired it up so that you can set the Theme to display anywhere within your Controller. This gives the most flexibility I could think of to set the Theme since you'll most likely need to access a database of some kind to determine which Theme to display, and this way you can do that before, after or at the same time you get the data to display.

Here's an example of how to set the Theme to "Red", and you can do this anywhere within one of your Action methods of your Controller.

controllerContext.HttpContext.Items["themeName"]

The reason I'm storing the Theme Name using HttpContext.Items is so that we can access the Theme Name from a single place within both the custom ViewEngine and the View itself.

Also, the code example attached to this article uses a ControllerBase class that each of the Controllers inherit so it's more resuable, and it also grabs the Theme to display from the QueryString for simplicity.

Sharing UI Elements Between Themes

One thing that is done alot is sharing certain UI elements between Themes that are similar or almost identical. This can be done by creating user controls within a seperate folder than the View folder, and using/referencing them within the Pages/Views (.aspx) or Controls (.ascx) within the Themes. This way you can create shared UI elements that can be shared across multiple Themes.

In the code attached to this article, I've created a "~/Controls" folder and placed in it a ThemeLinks.ascx control that is used by both the "Default" and "Red" themes to display links (as tabs in the top navigation bar) for you to select which Theme to see.

Download the Code

Ok, after reading all the above, I'm sure you're probably a little confuses since I only gave a brief overview. Well, here's the code. I started with an ASP.NET MVC Website (as described in one of my previous posts) using the Preview 4 release and made all the changes described above. Have fun, and please post any comments you have on this, or any suggestions on making it better.

Download Code: ASPNETMVC_CustomThemeImplementation.zip (207.98 kb)

asp.net, ASP.NET MVC ,

How To Use ASP.NET MVC in Website rather than Web Application Project

15. August 2008

I recently started getting to know the ASP.NET MVC Framework (more specifically the Preview 4 release). The first thing I noticed upon installation is that it only comes with a Web Application Project Template. There is no regular ASP.NET Website template. So, I decided to try converting the Web Application Project Template to a regular ASP.NET Website. I actually prefer to use Website projects instead of Web Application projects since it gives a little more flexibility to deploy changes to the website after the website is already live. Plus, you can still throw anything you want into a DLL that goes into the Bin if you want.

I only needed to make a couple small changes when copying over the code into a new ASP.NET Website:

  1. Convert the .aspx pages from using CodeBehind to CodeFile.
  2. Place the Controllers folder into the App_Code.
  3. Change the Global.asax to be ASP.NET Website friendly (aka Not compile into DLL like in Web Application Project)

That's pretty much it, now I have a nice ASP.NET MVC Website template that I can use. To the right is an image of the file layout of the new ASP.NET MVC Website project.

The ASP.NET MVC release I used for this was the ASP.NET MVC Preview 4 release available on CodePlex.

Also, here's a link to download the code: ASP.NET MVC_Preview4_Website Project.zip (192.03 kb)

asp.net, ASP.NET MVC ,