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 , , , ,

MvcMaps Preview 1 – A Unified Bing/Google Maps API for ASP.NET MVC

2. November 2009

I spent some time lately working on bringing some of the concepts of Web.Maps.VE to ASP.NET MVC. The concepts I’m referring to are Simplicity and Ease of Development in making the implementation of mapping within ASP.NET MVC applications as simple as possible along with the Flexibility and Customizability of the Base Mapping API itself. Then I thought, Since I’m building an abstraction layer to simplify Bing Maps development, why not implement it in a flexible manor as to be able to support other Mapping API’s as well?

The result of such an effort in a nice Unified API that allows virtually the same code to be written when implementing either Bing Maps or Google Maps. In fact, all you need to do to change your application over to using one mapping provider instead of the other is to just change a single line of code.

Sound too good to be true?

I introduce you to the all new “MvcMaps” project, and I’m releasing it as Open Source under the Microsoft Public License.

Source Code: MvcMaps Preview 1 (698Kb)

Preview Release?

This initial release is just a “Preview” release and isn’t really meant for production use, although there’s absolutely nothing stopping you from using it in such an environment.

Introduction

The above source code download link contains the full source code for the component, plus a very basic “Interactive SDK” style website demonstrating some basic examples of using it.

The following snippet is the most basic example of how to add both a Bing Map and a Google Map within a View:

<%@ Import Namespace="MvcMaps" %>

<style type="text/javascript">
.BingMap
{
    width: 600px;
    height: 400px;
    border: solid 1px black;
}
.GoogleMap
{
    width: 600px;
    height: 400px;
    border: solid 1px black;
}
</style>

<h3>Bing Map</h3>
<% Ajax.BingMap()     // Create a Bing Map
    .CssClass("BingMap") // Define the CSS Style to use. These specify the Maps Size
    .Render();           // Render all the HTML / JavaScript necessary to create the Map to Server.Response
    %>

<h3>Google Map</h3>
<% Ajax.GoogleMap()      // Create a Google Map
    .CssClass("GoogleMap")  // Define the CSS Style to use. These specify the Maps Size
    .Render();              // Render all the HTML / JavaScript necessary to create the Map to Server.Response
    %>

 

Notice that the code is idential, except for the “BingMap” and “GoogleMap” parts. That is how you define the specific mapping provider to use, but the rest of the code is the same.

Plotting Pushpins, Polylines and Polygons

Adding some Pushpins, Polylines and Polygons is extremely simple, and its the same code for both Bing Maps and Google Maps!

Here’s a basic example of adding one of each:

<% Ajax.BingMap().CssClass("BingMap")
    // Add Pushpin Shape
    .AddPushpin(
        new Pushpin(39.9097362345372, -97.470703125,
             "Some Pushpin Title", "Some Pushpin Description")
        )
    
    // Add Polyline Shape
    .AddPolyline(
        new Polyline(new List<LatLng>() {
            new LatLng(48.166085, -121.11328),
            new LatLng(34.270835, -118.34472),
            new LatLng(43.041543, -87.901954),
            new LatLng(38.889546, -77.035338)
        }) {
            LineColor = "#0000FF",
            LineWeight = 6
        }
    )
    
    // Add Polygon Shape
    .AddPolygon(
        new Polygon(new List<LatLng>() {
            new LatLng(34.270835, -118.34472),
            new LatLng(43.041543, -87.901954),
            new LatLng(38.889546, -77.035338)
        }) {
            FillColor = "#00ff00",
            FillOpacity = 0.5,
            LineWeight = 2,
            LineColor = "#FF0000"
        }
    )
    
    // Render Map (HTML and JavaScript) to the Page
    .Render()
%>

 

Dynamic / Interactive Style Map

One of the coolest features I’ve built into the component so far is the ability to extremely easily add Dynamic / Interactive Style Map.

You probably wouldn’t believe me if I explained in words how simple it is to add a dynamic style map, so instead I’ll just show you the most basic code to get it working.

Here’s the code to add the Map to the Page. In this example you just tell the Map what Controller and Action to call to get the Map data to be displayed.

<% Ajax.BingMap()
    .CssClass("BingMap")
    .DynamicMap( new { controller = "DynamicMap", action = "SchoolDistricts" })
    .Render();
%>

 

Then you define your Controller Action, make it accept the Map View (or Map Bounds) which are essentially the Min and Max Lat/Lng values of the visible area on the client, and then you just return a “MapDataResult” object that contains the Pushpins, Polylines and Polygons to be plotted.

The following example demonstrates searching an XML file for School Districts within the Maps Viewable Area, and then plots them on the Map using Pushpins. As of MvcMaps Preview 1, only Pushpins are supported with the MapDataResult object.

public class DynamicMapController : Controller
{
    public ActionResult SchoolDistricts(double minLat, double maxLat, double minLng, double maxLng)
    {
        // Query and Get all School Districts within the passed in "Map View".
        var doc = XDocument.Load(Server.MapPath("~/App_Data/WISchoolDistricts.xml"));
        var schooldistricts = (from d in doc.Element("schooldistricts").Elements("schooldistrict")
                        where double.Parse(d.Attribute("latitude").Value) >= minLat
                        && double.Parse(d.Attribute("latitude").Value) <= maxLat
                        && double.Parse(d.Attribute("longitude").Value) >= minLng
                        && double.Parse(d.Attribute("longitude").Value) <= maxLng
                        select d
                    );

        // Generate "Pushpin" objects for each School District to be Plotted on the Map.
        var pushpins = (from d in schooldistricts
                        select(new Pushpin(
                            double.Parse(d.Attribute("latitude").Value),
                            double.Parse(d.Attribute("longitude").Value)
                        ){
                            Title = d.Attribute("name").Value,
                            Description = d.Attribute("address").Value
                        }));

        // Return a "MapDataResult" object that contains all the data that is to be Plotted on the Map.
        return new MapDataResult()
        {
            Pushpins = pushpins
        };
    }
}

 

Yes it really is that simple!!

How to Customize the Dynamic Map

The Dynamic Map example above is nice and simple, but if you need to customize the client-side map behavior somehow, don’t worry, there are “override” hooks built in to the component that allow you to override the complete behavior of the map in how it displays the data, or just execute some custom code on the returned data after it’s already been plotted.

You can use the DynamicMap  DataLoad option to specify a JavaScript function to get called every time map data is automatically loaded. The following example displayed the total number of Pushpins currently plotted in a SPAN tag above the Map. The “data” parameter on the function is the data object that is returned from the MapDataResult passed down from the controller action method.

Pushpin Count: <span id='lblPushpinCount'></span><br />
<% Ajax.BingMap()
    .CssClass("BingMap")
    .DynamicMap(
        new { controller = "DynamicMap", action = "SchoolDistricts" },
        new DynamicMapOptions() {
            DataLoaded = "function(data) { $('#lblPushpinCount').html(data.pushpins.length); }"
        })
    .Render();
%>

 

Also, you can use the DynamicMap DisplayData option to completely override the maps default behavior of plotting the data. Just in case you need to completely customize it, and the default behavior just doesn’t cut it. The following example plots the Pushpins returned and displays the total number of pushpins in a SPAN tag above the Map:

Pushpin Count: <span id='lblPushpinCount'></span>
<% Ajax.BingMap()
    .CssClass("BingMap")
    .DynamicMap(
        new { controller = "DynamicMap", action = "SchoolDistricts" },
        new DynamicMapOptions() {
            DisplayData = "DynamicMap_DisplayData_Handler"
        })
    .Render();
%>

<script type="text/javascritp">
function DynamicMap_DisplayData_Handler(data) {
    // Method gets called with "this" equaling the Mvc.Maps JavaScript Map Object
    
    // Clear All Currently Plotted Data
    this.clearDynamicMapData();
    
    // Plot New Pushpins that were Loaded
    this.plotPushpins(data.pushpins);

    // Display Pushpin Count
    $('#lblPushpinCount').html(data.pushpins.length);
}
</script>

 

Map Load Event

In case you need to execute some code on the Page as soon as the Map has finished loading, you can specify any JavaScript code you need to be executed.

<%-- Pass in JavaScript as String --%>
<% Ajax.GoogleMap()
        .Load("alert('Map Loaded!');")
        .Render();
        %>
        
<%-- Use Lambda Expression to define JavaScript code --%>
<% Ajax.GoogleMap()
        .Load( () => {%>
            alert('Map Loaded!');
            // var map = this.mapObject  //<-- get ref to underlying map providers object
        <%})
        .Render();
        %>

When specifying code to execute on the Load event, the context of the “this” keyword will be a reference to the client-side MvcMaps Map Object. To get a reference to the underlying Map Providers object (VEMap or GMap2), just access it’s “mapObject” property like so:

var map = this.mapObject;

 

Conclusion

The MvcMaps project really makes it dead simple to implement mapping in an ASP.NET MVC Web Application. There’s no need to worry about all the tedious work that you used to have to do on every page.

I plan on building out a few more features into the component, and getting it to the point of a “Stable” release soon. I just wanted to share what I’ve done so far, so others can provide feedback.

If you have any comments and/or suggestions on the Preview 1 release, please either leave a comment here or post to the projects Discussion Forums.

Also, don’t forget to download the code and the “Interactive SDK” from the link at the top of this post.

Bing Maps, ASP.NET MVC, JavaScript , , ,

A Simple ScriptManager for ASP.NET MVC

13. August 2009

The ASP.NET AJAX ScriptManager makes it really easy to include JavaScript references and register JavaScript blocks into the rendered Page output of an ASP.NET WebForms application. However nice the ScriptManager control is, it’s still just a WebForms control for use with ASP.NET AJAX; thus it’s use isn’t really supported with ASP.NET MVC. Also, to make things just a little more difficult, ASP.NET MVC doesn’t have it’s own “ScriptManager” implementation. This brings me to the point of posting this…

I have worked out a really simple “ScriptManager” component for use with ASP.NET MVC, and I think it works really nice to help simplify the effort of including JavaScript blocks and references in a page.

Setting up the “SimpleScriptManager” for use

To use the “SimpleScriptManager” with ASP.NET MVC you must first Import the “SimpleScriptManager” namespace into your Master Page. Then you must place a single line of code in the Master Page file at the location you want to Render the Script Includes and Blocks to the Page. In order for it to work properly, the Render code needs to be place at the very end of the Master Page; preferably just before the closing Body tag.

Here’s a really short example Master Page file with the “SimpleScriptManager” namespace imported and the call to “SimpleScriptManager().Render()” located at the very end of the page just before the closing Body tag.

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<%-- The SimpleScriptManager Namespace must be Imported to be able to use the Html.SimpleScriptManager Extension --%>
<%@ Import Namespace="SimpleScriptManager" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <asp:ContentPlaceHolder ID="MainContent" runat="server" />
    
    <%-- Render all the Scripts to the Page --%>
    <%-- Must be located at the very end of the Master Page to work properly --%>
    <% Html.SimpleScriptManager().Render(); %>
</body>
</html>

 

This may look a little strange to you since you may be used to placing all your JavaScript Blocks and Script Includes at the top of the page within the <HEAD> tags. However, in order for the “SimpleScriptManager” to work property the call to Render to the page MUST be located at the end of the Master Page file. This allows any other server controls, user controls or pages to add Script Blocks and Includes at any time during the process or building/rendering the page, and then at the end of the Master Page (when the page is just about finished being rendered) the “SimpleScriptManager().Render()” method is called and the scripts are all rendered out to the page at that time. If the “"SimpleScriptManager().Render()” method is called prior to all other components on the Page, then any Script Blocks or Includes added to the “SimpleScriptManager” after Render is called will not be included within the final rendering of the Page that gets sent to the client.

Using the “SimpleScriptManager”

The “SimpleScriptManager” has only two fairly simple methods: ScriptInclude and Script.

“SimpleScriptManager.ScriptInclude” Method

To add a simple Script Include within the page, you just call the “ScriptManager.ScriptInclude” method and pass in the Location / Url of the JavaScript file to include within the page. The Script Location / Url can be either an Absolute or Virtual (“App Relative”) Url.

<% Html.SimpleScriptManager().ScriptInclude("~/Scripts/jquery-1.3.2.js"); %>
<% Html.SimpleScriptManager().ScriptInclude("http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"); %>

 

You can also pass in a “Key” for the specific Script Include you’re registering. This key is a unique identifier used within your application for the specified Script Include, and it allows you to ensure that only a single include/reference to that specific script will get rendered within the Page.

<% Html.SimpleScriptManager().ScriptInclude("jquery", "~/Scripts/jquery-1.3.2.js"); %>

 

For instance the second example of “ScriptInclude” above specifies the Key of “jquery”. You would be able to include this “ScriptInclude” call within any User Controls and/or Pages within your application that require that the “jquery-1.3.2.js” script be included within the page to work, and no matter how many of those controls are rendered to the page, the script would only have a single include/reference rendered to the Page.

I know this isn’t a very good example of adding a script reference that may only be needed within a couple pages of an application, since you’ll most likely want jQuery included within every Page of your Application. To do this you’ll just add the “ScriptInclude” call to top of the Master Page file itself. However, I’m sure you get the idea I’m trying to reference on how to “optionally” include a script reference only when it’s needed, instead of including it within every single page of your application by adding it within the Master Page file.

“SimpleScriptManager.ScriptInclude” Method to Add Web Resource References

One of the things necessary when building Custom Server Controls (instead of just User Controls) is the fact that they reside within an Assembly and contain scripts as Embedded Web Resources. This can cause issues when adding Script Include references for these controls since you need to load the script from the Embedded Web Resource into the Page.

However, this is really simple to do with an additional “ScriptInclude” method overload that uses generics to specify the Assembly to find the Embedded Web Resource within, plus the full resource name to include. There is also a method overload that accept a unique “Key” for the script just like the above “ScriptInclude” example.

To use these overloads of the “ScriptInclude” method you must add a reference to the “SimpleScriptManager” namespace within your custom control. Also, your Custom Control/Component must inherit from the ViewUserControl class so that it gets access to the HtmlHelper object through the Html property.

Here’s a really simple example of this:

// The SimpleScriptManager Namespace must be Imported to be able to use the Html.SimpleScriptManager Extension
using SimpleScriptManager;

// Specify that the "Embedded Resource" is to be a "Web Resource"
[assembly: System.Web.UI.WebResource("EmbeddedScriptResourceTest.TestScriptOne.js", "text/javascript")]

namespace EmbeddedScriptResourceTest
{
    public class TestScriptOneControl : System.Web.Mvc.ViewUserControl
    {
        public string Message { get; set; }

        public override void RenderControl(System.Web.UI.HtmlTextWriter writer)
        {
            base.RenderControl(writer);

            // By specifying a Key when adding the ScriptInclude below, we are ensuring that the script only gets included
            // within the Page once, no matter how many instances of this control are renderd to the Page.
            this.Html.SimpleScriptManager().ScriptInclude<TestScriptOneControl>(
                 "TestScriptOneKey", 
                 "EmbeddedScriptResourceTest.TestScriptOne.js");
        }
    }
}

 

“SimpleScriptManager.Script” Method

To add a Script Block in to the Page you just call the “SimpleScriptManager.Script” method and pass it a String that contains the JavaScript code to include within the Page.

<% Html.SimpleScriptManager().Script("alert('Hello!');"); %>

 

You can also pass in a “Key” that uniquely identifies this specific Script Block. Just as with the “ScriptInclude” method, this allows you to specify that you only want this particular Script Block to be included within the Page only once no matter how many times any components within the Page specify it to be added.

<% Html.SimpleScriptManager().Script("ScriptKey", "alert('Hello!');"); %>

 

“SimpleScriptManager.Script” Method using a Lambda Expression

I also included the ability to pass the “SimpleScriptManager.Script” method a Lambda Expression that will output the desired JavaScript code to the Page. This is something that makes it a little easier to add some Script to the Page and still be able to keep any code formatting in place (for readability) without requiring you to build it within a big, long String within the Page or User Control.

<% Html.SimpleScriptManager().Script( () => {%>
    $(function(){
        alert('Hello!');
    });"
<%}); %>

 

This method also supports the ability to pass in a “Key” to specify you only want this script to be included within the Page a single time.

How “SimpleScriptManager” Works

Besides the “SimpleScriptManager” being included as an Extension Method to the HtmlHelper object; it also “attaches” itself to the HttpContext.Items Dictionary the first time “Html.SimpleScriptManager()” is called and then any subsequent calls just add any Script Includes or Blocks to that same SimpleScriptManager instance. Then when you call the “Render” method it writes out the entire Html code necessary to Render all the Script Includes and Blocks to the Page.

This is actually a fairly simple design, and the code that “attaches” the SimpleScriptManager to the HttpContext is includes within the HtmlHelper Extension Method itself; the rest of the code is contained within the actual SimpleScriptManager object.

Full “SimpleScriptManager” Code

SimpleScriptManagerExtension.cs

// Copyright (c) 2009 Chris Pietschmann (http://pietschsoft.com)
// All rights reserved.
// Licensed under the Microsoft Public License (Ms-PL)
// http://opensource.org/licenses/ms-pl.html

using System.Web.Mvc;

namespace SimpleScriptManager
{
    public static class SimpleScriptManagerExtensions
    {
        private static readonly string simpleScriptManagerKey = "SimpleScriptManager";

        public static SimpleScriptManager SimpleScriptManager(this HtmlHelper helper)
        {
            // Get SimpleScriptManager from HttpContext.Items
            // This allows for a single SimpleScriptManager to be created and used per HTTP request.
            var scriptmanager = helper.ViewContext.HttpContext.Items[simpleScriptManagerKey] as SimpleScriptManager;
            if (scriptmanager == null)
            {
                // If SimpleScriptManager hasn't been initialized yet, then initialize it.
                scriptmanager = new SimpleScriptManager(helper);
                // Store it in HttpContext.Items for subsequent requests during this HTTP request.
                helper.ViewContext.HttpContext.Items[simpleScriptManagerKey] = scriptmanager;
            }
            // Return the SimpleScriptManager
            return scriptmanager;
        }
    }
}

 

SimpleScriptManager.cs

// Copyright (c) 2009 Chris Pietschmann (http://pietschsoft.com)
// All rights reserved.
// Licensed under the Microsoft Public License (Ms-PL)
// http://opensource.org/licenses/ms-pl.html

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web;
using System.Web.Mvc;

namespace SimpleScriptManager
{
    public class SimpleScriptManager
    {
        private HtmlHelper htmlHelper;

        private Dictionary<string, string> scriptIncludes = new Dictionary<string, string>();

        private Dictionary<string, string> scripts = new Dictionary<string, string>();
        private Dictionary<string, Action> scriptsActions = new Dictionary<string, Action>();

        /// <summary>
        /// SimpleScriptManager Constructor
        /// </summary>
        /// <param name="helper">The HtmlHelper that this SimpleScriptManager will use to render to.</param>
        public SimpleScriptManager(HtmlHelper helper)
        {
            // Store reference to the HtmlHelper object this SimpleScriptManager is tied to.
            this.htmlHelper = helper;
        }

        /// <summary>
        /// Adds a script file reference to the page.
        /// </summary>
        /// <param name="scriptPath">The URL of the script file.</param>
        /// <returns>Returns the SimpleScriptManager</returns>
        public SimpleScriptManager ScriptInclude(string scriptPath)
        {
            return this.ScriptInclude(Guid.NewGuid().ToString(), scriptPath);
        }

        /// <summary>
        /// Adds a script file reference to the page.
        /// </summary>
        /// <param name="key">A unique identifier for the script file.</param>
        /// <param name="scriptPath">The URL of the script file.</param>
        /// <returns>Returns the SimpleScriptManager</returns>
        public SimpleScriptManager ScriptInclude(string key, string scriptPath)
        {
            if (!this.scriptIncludes.ContainsKey(key))
            {
                // Check if the scriptPath is a Virtual Path
                if (scriptPath.StartsWith("~/"))
                {
                    // Convert the Virtual Path to an Application Absolute Path
                    scriptPath = VirtualPathUtility.ToAbsolute(scriptPath);
                }
                this.scriptIncludes.Add(key, scriptPath);
            }
            return this;
        }

        /// <summary>
        /// Adds a script file reference to the page for an Embedded Web Resource.
        /// </summary>
        /// <typeparam name="T">The Type whos Assembly contains the Web Resource.</typeparam>
        /// <param name="key">A unique identifier for the script file.</param>
        /// <param name="resourceName">The name of the Web Resource.</param>
        /// <returns>Returns the SimpleScriptManager</returns>
        public SimpleScriptManager ScriptInclude<T>(string key, string resourceName)
        {
            return this.ScriptInclude(key, getWebResourceUrl<T>(resourceName));
        }

        /// <summary>
        /// Adds a script file reference to the page for an Embedded Web Resource.
        /// </summary>
        /// <typeparam name="T">The Type whos Assembly contains the Web Resource.</typeparam>
        /// <param name="resourceName">The name of the Web Resource.</param>
        /// <returns>Returns the SimpleScriptManager</returns>
        public SimpleScriptManager ScriptInclude<T>(string resourceName)
        {
            return this.ScriptInclude(getWebResourceUrl<T>(resourceName));
        }

        /// <summary>
        /// Adds a script block to the page.
        /// </summary>
        /// <param name="javascript">The JavaScript code to include in the Page.</param>
        /// <returns>Returns the SimpleScriptManager</returns>
        public SimpleScriptManager Script(string javascript)
        {
            return this.Script(Guid.NewGuid().ToString(), javascript);
        }

        /// <summary>
        /// Adds a script block to the page.
        /// </summary>
        /// <param name="key">A unique identifier for the script.</param>
        /// <param name="javascript">The JavaScript code to include in the Page.</param>
        /// <returns>Returns the SimpleScriptManager</returns>
        public SimpleScriptManager Script(string key, string javascript)
        {
            if (!this.scripts.ContainsKey(key) && !this.scriptsActions.ContainsKey(key))
            {
                this.scripts.Add(key, javascript);
            }
            return this;
        }

        /// <summary>
        /// Adds a script block to the page.
        /// </summary>
        /// <param name="javascript">The JavaScript code to include in the Page.</param>
        /// <returns>Returns the SimpleScriptManager</returns>
        public SimpleScriptManager Script(Action javascript)
        {
            return this.Script(Guid.NewGuid().ToString(), javascript);
        }
        
        /// <summary>
        /// Adds a script block to the page.
        /// </summary>
        /// <param name="key">A unique identifier for the script.</param>
        /// <param name="javascript">The JavaScript code to include in the Page.</param>
        /// <returns>Returns the SimpleScriptManager</returns>
        public SimpleScriptManager Script(string key, Action javascript)
        {
            if (!this.scripts.ContainsKey(key) && !this.scriptsActions.ContainsKey(key))
            {
                this.scriptsActions.Add(key, javascript);
            }
            return this;
        }

        /// <summary>
        /// Renders the SimpleScriptManager to the Page
        /// </summary>
        public void Render()
        {
            var writer = this.htmlHelper.ViewContext.HttpContext.Response.Output;

            // Render All Script Includes to the Page
            foreach (var scriptInclude in this.scriptIncludes)
            {
                writer.WriteLine(String.Format("<script type='text/javascript' src='{0}'></script>", scriptInclude.Value));
            }
            
            // Render All other scripts to the Page
            if (this.scripts.Count > 0 || this.scriptsActions.Count > 0)
            {
                writer.WriteLine("<script type='text/javascript'>");

                if (this.scripts.Count > 0)
                {
                    foreach (var script in this.scripts)
                    {
                        writer.WriteLine(script.Value);
                    }
                }

                if (this.scriptsActions.Count > 0)
                {
                    foreach (var script in this.scriptsActions)
                    {
                        script.Value();
                    }
                }

                writer.WriteLine("</script>");
            }
        }


        private static MethodInfo _getWebResourceUrlMethod;
        private static object _getWebResourceUrlLock = new object();

        private static string getWebResourceUrl<T>(string resourceName)
        {
            if (string.IsNullOrEmpty(resourceName))
            {
                throw new ArgumentNullException("resourceName");
            }

            if (_getWebResourceUrlMethod == null)
            {
                lock (_getWebResourceUrlLock)
                {
                    if (_getWebResourceUrlMethod == null)
                    {
                        _getWebResourceUrlMethod = typeof(System.Web.Handlers.AssemblyResourceLoader).GetMethod(
                            "GetWebResourceUrlInternal",
                            BindingFlags.NonPublic | BindingFlags.Static);
                    }
                }
            }

            return "/" + (string)_getWebResourceUrlMethod.Invoke(null,
                new object[] { Assembly.GetAssembly(typeof(T)), resourceName, false });
        }

    }
}

 

Conclusion

At first it seemed that the easiest way to get similar functionality to this was to use the ASP.NET AJAX ScriptManager control; however that control requires that it be embedded within a <form runat=”server”></form> tag, and that just doesn’t really work with ASP.NET MVC. Actually the methods to get the ASP.NET AJAX ScriptManager to work with ASP.NET MVC are just plain “Hacks” and they made me feel like I wasn’t being True to the new ASP.NET MVC Platform.

In the end, I’m very happy that I was able to work out an extremely simple solution to this problem that will definitely help when building out ASP.NET MVC Web Applications.

If you have any feedback on this, please leave a comment.

JavaScript, ASP.NET MVC ,

Prototype of VEToolkit + ASP.NET MVC 1.0 Component Checked In

28. March 2009

I just checked in an initial prototype of a reusable Virtual Earth component for ASP.NET MVC. The “VEToolkit.Web.MVC.Map” component is written as an Extension to the ASP.NET MVC AjaxHelper class, and allows for a simpler experience when implementing Virtual Earth mapping within ASP.NET MVC applications. This is an early prototype of what the component will be; it’s not a final release; but you are free to use it.

Download the latest Change Set of VEToolkit

The code is subject to change at any time, since this is currently in a prototype stage, but below is a basic overview of what’s there so far.

Include a Basic Map on the Page

Include the “VEToolkit.Web.MVC” namespace within the page by adding the following Include directive to the top of the Page:

<%@ Import Namespace="VEToolkit.Web.MVC" %>

Add the following two JavaScript Includes (the Virtual Earth JavaScript API and jQuery) to the Page Header:

<script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>
<script type="text/javascript" src="/Scripts/jquery-1.3.2.js"></script>

Add the following to the Page to add a basic Virtual Earth map:

<%=Ajax.Map() %>

And that’s it; you’ll have “default” sized Virtual Earth map on the page.

Specify you own name for the global VEMap Variable that’s used

By default the component autogenerates an ID to use when naming the global VEMap object within the JavaScript that’s generated.

Here’s an example of how to specify your own ID (or variable name) to be used for the global VEMap object that’s created:

<%=Ajax.Map("myMap") %>

Now to get a hold of the global reference to the VEMap object, you can just access it by name, like so:

<input type="button" value="Zoom In" onclick="myMap.ZoomIn();" />
<input type="button" value="Zoom Out" onclick="myMap.ZoomOut();" />

 

Set a couple Map Properties

Here’s a modified example of the above that sets the Center Location, Zoom Level and Map Style:

<%=Ajax.Map("myMap")
    .SetCenter(new Location(44, -78))
    .SetZoom(6)
    .SetMapStyle(MapStyle.Road)%>

 

Call a JavaScript Function Once the Map is Loaded

Also you can specify a JavaScript Function to be called once the Map has finished loading within the Page.

Here’s an example:

<%=Ajax.Map("myMap")
    .SetOnMapLoaded("MapLoaded")%>

And, here’s a simple JavaScript Function named “MapLoaded” to match that adds a Pushpin to the Map at it’s Center Point:

<script type="text/javascript">
    function MapLoaded(sender) {
        // "sender" = The Map that was Loaded
        var map = sender;

        // Add a Shape to the Center of the Map now it's finished loading
        var s = new VEShape(VEShapeType.Pushpin, sender.GetCenter());
        s.SetTitle("Center Point");
        s.SetDescription("This was the original center point when the Map loaded.");
        map.AddShape(s);
    }
</script>

 

Conclusion

As stated above, this is just a basic overview of what’s been implemented so far in the VEToolkit ASP.NET MVC Prototype. There are a couple more things in there than I mentioned above, but I thought I’d keep the intro very basic until development moved further along.

Bing Maps, JavaScript, ASP.NET MVC , ,

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 ,

Custom Themes in ASP.NET MVC Updated for Preview 5

29. 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 

About two weeks ago I posted on how to Implement Custom Theme support in ASP.NET MVC. There were some breaking changes made when the Preview 5 release was released yesterday.

Here's a short list to a couple of the changes I had to make to my code from the previous post to get it working in ASP.NET MVC Preview 5:

  • Delete WebFormThemeViewLocator - The contents of this object is now contained within the ViewEngine itself
  • Delete WebFormThemeControllerFactory- This isn't needed anymore.
  • Modify WebFormThemeViewEngine - Write a bunch of code that finds the appropriate View to use.
  • Modify Global.asax - Remove code that adds the old ControllerFactory, and replace it with code that adds our newly improved WebFormThemeViewEngine
  • Modify ControllerBase - Firstly, rename this to ThemeControllerBase since there is not a ControllerBase in System.Web.Mvc. Then, modify the code for the Execute method since it now takes in a RequestContext object as a parameter instead of a ControllerContext object.

Just for reference here's the code for the WebFormThemeViewEngine.

Download Code: ASPNETMVC_Preview5_CustomThemeImplementation.zip (226.05 kb)

 

Below is the entire code for the WebFormThemeViewEngine, just for reference. If you are interested in looking at how I implemented this, just download and check out the entire code sample at the link above.

using System;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;

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

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

        base.PartialViewLocationFormats = new string[] {
                "~/Views/{2}/{1}/{0}.aspx",
                "~/Views/{2}/{1}/{0}.ascx",
                "~/Views/{2}/Shared/{0}.aspx",
                "~/Views/{2}/Shared/{0}.ascx"
            };
    }

    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(viewName))
        {
            throw new ArgumentException("Value is required.", "viewName");
        }

        string themeName = this.GetThemeToUse(controllerContext);

        string[] searchedViewLocations;
        string[] searchedMasterLocations;

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

        string viewPath = this.GetPath(this.ViewLocationFormats, viewName, controllerName, themeName, out searchedViewLocations);
        string masterPath = this.GetPath(this.MasterLocationFormats, viewName, controllerName, themeName, out searchedMasterLocations);

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

    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(partialViewName))
        {
            throw new ArgumentException("Value is required.", partialViewName);
        }

        string themeName = this.GetThemeToUse(controllerContext);

        string[] searchedLocations;

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

        string partialPath = this.GetPath(this.PartialViewLocationFormats, partialViewName, controllerName, themeName, out searchedLocations);

        if (string.IsNullOrEmpty(partialPath))
        {
            return new ViewEngineResult(searchedLocations);
        }
        return new ViewEngineResult(this.CreatePartialView(controllerContext, partialPath));
    }

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

    private string GetPath(string[] locations, string viewName, string controllerName, string themeName, out string[] searchedLocations)
    {
        string path = null;

        searchedLocations = new string[locations.Length];

        for (int i = 0; i < locations.Length; i++)
        {
            path = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { viewName, controllerName, themeName });
            if (this.VirtualPathProvider.FileExists(path))
            {
                searchedLocations = new string[0];
                return path;
            }
            searchedLocations[i] = path;
        }
        return null;
    }
}

ASP.NET MVC ,

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 ,