Long Cloud Technologies
"... A Yankee in the Land of the Long White Cloud, Aotearoa ..."

Working with Orchard from a Hard Programmers Prospective – Part II

Creating a Hello World Module the Orchard way

So here we are ready to do the next part in our series of Orchard Discovery… Let’s make their Hello World module in the default installation, and then let’s see about implementing the Hello World in Visual Studio 2010, using no helpers.  “Buckle Up Boys, it’s going to be a bumpy ride”

So to start with, Here is the link to the tutorial provided by Orchard website on how to do the “Hello World”.  The first thing they tell you is before you get started to download and install the Code Generation feature Command-line Code Generation . To be totally fair I’m going to use the OrchardWpi we got running in WebMatrix to do this part…  I start up WebMatrix, select Choose a Site option and Select my Orchard Site.

image

image

Now we click on the URL for the orchard website and open it up, get into Manage settings and from here on out we just follow the steps in the original article on installing the tools.

Tips for their instructions:

  • first up, there are way too many modules in the gallery if you use the Feed: Any optionimage Be sure to drop that down and select the image and click the Apply button.
  • Gee wouldn’t it be nice if there was a sort order? don’t look for it there isn’t one as of 1.0
  • Gee wouldn’t it be nice if there was a search function?  Again don’t look for it, there isn’t one as of 1.0
  • Quickest way to find your module is do a Ctrl+F (find function for your browser) and search for Code Generation
    image
  • Hey, if I install a module, how about we assume that I WANT to use it, other wise I probably wouldn’t have isntalled it. Can we make the default for installed modules as setting it to already being enabled? that would be helpful

With that done, we continue on our way with the Hello World Tutorial. And right off the bat we have our first question from their instructions: “Open the Orchard command-line”; so how do you open the command-line.  Intuitive it is not, here is a link to the instructions on using the command-line interface.  Basically you need to open a Command line in the bin directory of the Orchard website you want to work on… Hey, give them a break, this is version 1.0…  Don’t forget to make sure you start up the command line as an administrator, to save yourself some grief, and making it a Visual Studio 2010 command prompt would probably be best, although supposedly not required.

You can find the bin directory by going back to WebMatrix and clicking on the path link for the website, and navigate down from there to the bin directory.

image

Ok, now that we got the command prompt open we continue on with our exercise from the Hello World Tutorial. First you need to get the Orchard Commandline tool started.  I’ll save you the digging around in the documentation to find out how: from the command line in the bin directory run the Orchard.exe.  That will initialize the Orchard Command line tools and then we basically just run the command codegen module HelloWorld.

image

I decided to follow the editing instructions using the built in editor in WebMatrix

image

Editorial: A txt file?  you might as well have an INI file.  AND you have a warning that I should be careful to use spaces and not tabs to indent?  Come on Orchard developers, I know XML is verbose and over used, but in this case it seems a lot more intelligent, what we have now looks so VB 3 world type… (and for the record I started programming in VB3 so please don’t flame me too much).

Ok, I save my changes and move onto the next step, where they recommend that I put in a Routes.cs file in the HelloWorld Folder, let me show you how I did that in WebMatrix.  (make sure to click on the “Files” header in the Un-outlook bar), I click on the HelloWorld folder and then click on the Create a new file link on the page.

image

The model dialog that opens has Common Selected for “File Types” none of which will give you the ability to create a class, click on the Suggested Category scroll down and find the Class (C#) item, click on it and in the Name box below set it to Routes.cs and click OK

image

Copy and paste the text from the tutorial and repeat the process as instructed for the HomeController.cs, then do the same for the View file.

Then the instructions tell you to add some lines manually to the HelloWorld.csproj file ( and this tutorial is obviously not for the newbie or someone who wants to ask questions.  It tells you want to do, not so much on the how ).  Just follow thru the instructions. So I did and this is what I got…

image

OK, so I can make Orchard say hello, I have no idea how that all comes together so let’s see if I can take it apart.

First things first, I think we need to know what the CodeGen did before we know how to proceed manually.  Consequently I’m going to run CodeGen and create a new module called UMadeThis.  I’ll follow the same steps as above from the command line.

Note: if you create a module in Orchard using command line and it doesn’t show up in WebMatrix, be sure to Right Mouse on the Modules Folder and Click the Refresh, your module should now show up.

Going again in the Files group, find the UMadeThis folder and let’s “expand” every folder; Now let’s go see what got made…

image

Not a whole bunch.  The web.config for both the Scripts and the Styles is the following

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appSettings>
    <add key="webpages:Enabled" value="false" />
  </appSettings>
  <system.web>
    <httpHandlers>
      <!-- iis6 - for any request in this location, return via managed static file handler -->
      <add path="*" verb="*" type="System.Web.StaticFileHandler" />
    </httpHandlers>
  </system.web>
  <system.webServer>
    <handlers accessPolicy="Script,Read">
      <!--
      iis7 - for any request to a file exists on disk, return it via native http module.
      accessPolicy 'Script' is to allow for a managed 404 page.
      -->
      <add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
    </handlers>
  </system.webServer>
</configuration>

The web.config is in the View Directory is a little more interesting

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="webpages:Enabled" value="false" />
  </appSettings>
  <system.web>
    <httpHandlers>
    </httpHandlers>

    <!--
        Enabling request validation in view pages would cause validation to occur
        after the input has already been processed by the controller. By default
        MVC performs request validation before a controller processes the input.
        To change this behavior apply the ValidateInputAttribute to a
        controller or action.
    -->
    <pages
        validateRequest="false"
        pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
        pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
        userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
      <controls>
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" namespace="System.Web.Mvc" tagPrefix="mvc" />
      </controls>
    </pages>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <handlers>
    </handlers>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="2.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

At first glance seems pretty standard for an MVC View Web.config, but taking a closer look I noticed something else: just below the first block of code is from the code generated by the CodeGen in Orchard, the second block is from a normally generated MVC project.

<pages
        validateRequest="false"
        pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
        pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
        userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">

<pages 
        validateRequest="false" 
        pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
        userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

They look almost the same but I see a minor difference in the Orchard Stuff,  The type engines for the Orchard have all had the processorArchitcture specified processorArchitecture=MSIL. basically if not specified your module would be built to what ever processorArchitecture was specified in the build settings, but this extra flag overrides that, and makes sure that your module is built to the MSIL, Microsoft Intermediate Language.  MSIL can be viewed as the assembly code of the CLR.  It means of course that your module is not optimised for the target processor architecture, but on the plus side, this means it will run correctly in CLR at run time.

Finally I noticed one other thing, the View web.config DID NOT include the section for razor web pages…

Now that was interesting, so where is that information for Razor pages located? it is located in base Orchard website web.config file.  In a normal MVC generated project, the reverse is true.  Let me do a quick comparison between the razor entry in the Orchard Web site’s base web.config

<system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <pages pageBaseType="Orchard.Mvc.ViewEngines.Razor.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Linq" />
        <add namespace="System.Collections.Generic" />
        <add namespace="Orchard.Mvc.Html" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

And the razor entry in a standard MVC projects View Web.Config file

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="RecoveryAssist.Dal" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

And if you look at the pageBaseType you see the real difference.  In Orchard they want to make sure that the Page base type is based on the Orchard.Mvc.ViewEngines.Razor.WebViewPage, whereas a standard mvc project makes System.Web.Mvc.WebViewPage the page base type.  Again, I’m not sure what impact that has, but I’d be willing to bet it isn’t insignificant.

Next time, we will do this all over again, but inside of Visual Studio 2010…

Posted on 20 Mar 11 14:38 by matthew.hintzen |

Bookmark this post with:

E-mail | Comments(0) | Comment RSS


Working with Orchard from a Hard Programmers Prospective – Prologue

So after much research and study, our intrepid group of programmers at Red Jungle decided we would attempt to settle on the Orchard CMS as our Platform of choice for clients looking for a CMS solution.  We did this for many reasons, but here are the highlights as far as I’m concerned

  • Completely open source http://orchardproject.net/
  • ASP.NET MVC 3 base (with Razor)
  • Originally built by Microsoft, but no longer owned by Microsoft
  • Funded by Microsoft (with no strings attached) for the next two years
  • Principle Developers of Oxite, Erik Porter and Nathan Heskew joined team (outside MS perspective)
  • Plug-in Architecture and extensibility built on a simplified model.

Before I go much further, I think a quick retrospective on last Microsoft Opensource CMS system is in order.  It was built in ASP.NET 1.0 using what was then considered best practices.  During internal development it was known as IBuySpy, and was meant to showcase those best recommended practices when creating a n-tiered web platform.  The Main developer they had brought in for the example site took the core of the IBuySpy example and extended it to a Full Featured CMS system that we all know better as DotNetNuke.

I’ve used DotNetNuke in the past, I’ve even wrote a tutorial about making modules for DNN and explaining what is really going on under the hood as you develop the module: Creating a DNN Module and Understanding DNN Architectural Approach.  While DNN continues to be used and does a great job, it, like so many old systems, has a bit of a legacy built in that it can’t just drop, that at times can make it feel creaky and old.

The world has moved on quite a bit since IBuySpy, and sometimes it is for the best to sell the old house and start from a fresh new foundation, we at Red Jungle feel that Orchard is just such a candidate.  If it becomes even half as successful as DNN was before it, I think we will have made a VERY good choice.

Now that doesn’t mean that I don’t have some issues with Orchard, life is beautiful, la la la.  First up, the install, extension and maintenance of Orchard was made to be easy for people with minimal technical background. By default if you use the install provided by the The Microsoft Web Platform it uses the new Microsoft WebMatrix which in itself is a free-ware and geared towards open source development (you didn’t really expect Microsoft to let AMP be the only free option out there did you)?  While I think the WebMatrix is a great tool, and the price is right (free for everything to run a website) for a hard code monkey like myself it just doesn’t have the horse power and the shade tree mechanic capabilities I want.

Also the out of the box tutorials and instructions of how to extend Orchard are VERY easy, but they “hide” too much under the covers for my taste.  I may yet end up using the shortcuts and tools, but I want to understand exactly how all those helpers fit together to make the magic work.

So I have three main goals with this set of blog posts and two sub-goals. 

  1. I want to move Orchard (and document how) to run in a more robust development environment then WebMatrix, get it to run in IIS with full customization from within Visual Studio 2010 Universal, as a Web Application.
  2. Create the Hello World module using the CodeGen capablities as explained on the Orchard website here, then recreate the Module doing everything manually.
  3. Finally Recreate my “ToDo Task List” from my original DotNetNuke tutorial on codeproject to be applicable to Orchard and deconstruct the Architecture of the system as it applies to Module Creation.
  4. Sub-goals
    1. Package the ToDo Task list as a redistributable, and document how to do so
    2. Publish the ToDo module on NuGet feed for Orchard.
Posted on 13 Mar 11 13:16 by matthew.hintzen |

Bookmark this post with:

E-mail | Comments(0) | Comment RSS


Enumerated Data Types, and ASP.NET MVC (Editor Template for ease of use)

This one took me a few hours to figure out.  I could see that the seeds necessary to pull off what I wanted to accomplish were “planted” the new MVC 2.0, but documentation at this point is just a bit on the light side for some of the new features.

So here is the skinny; Imagine you have a Car Object that has four properties that are fixed, meaning they are perfect candidates for enumerations.

clip_image002

So you create four types for the Car Object

public class Enums
{
    public enum Color
    {
        None=0,
        Black,
        Red,
        NeonOrange
    }

    public enum DoorCount
    {
        None=0,
        Two,
        Four,
        Five
    }

    public enum CarModel
    {
        None=0,
        Family,
        BoyRacer,
        Bachelor,
        Retired
    }

    public enum VehicleType
    {
        None=0,
        Ute,
        Sedan,
        Coup,
        Van,
        SUV
    }
}

Great, now you want to make a screen where the person can create a new car.  Pretty straight forward in MVC… but I have four properties that are all basically enumerations, unfortunately the are all different lists.  I could code up four drop downs, but that’s a lot of typing for the same thing over and over.  What would be really cool is if I could somehow use the EditorFor (which uses the Data driven EditorTemplates framework) function where I could pass the field to be edited and the select list to use, and the control would do the rest for me.  Good news is I can, not so good news, figuring out how.

Let’s start by doing some data annotations on the model for the car as per His Gu’ness’s instructions

namespace BloggingMVCSkeleton.Models
{
	[MetadataType( typeof( Car_Metadata ) )]
	public partial class Car
	{
	}
	internal class Car_Metadata
	{
		[HiddenInput]
		public Guid Uid { get; set; }

		[DisplayName( "Choose Car Color" )]
		[Required( ErrorMessage = "Color must be selected" )]
		public Enums.Color Color { get; set; }

		[DisplayName( "Choose Number of Doors (odd # denotes Hatchback)" )]
		[Required( ErrorMessage = "Number of doors must be selected" )]
		public Enums.DoorCount DoorCount { get; set; }

		[DisplayName( "Choose Model" )]
		[Required( ErrorMessage = "Model type required" )]
		public Enums.CarModel CarModel { get; set; }

		[DisplayName( "Vehicle Type" )]
		public Enums.VehicleType VehicleType { get; set; }

	}
}

Now we need some select lists for those enums, so Let’s add a static method to the Enums class that returns select list items (and uses a NEW enumeration of our enumeration types).

public enum SelectListItemType
{
    None = 0 ,
    Color ,
    DoorCount ,
    CarModel ,
    VehicleType
}


public static IEnumerable<SelectListItem> SelectListFor (
    SelectListItemType selectListItemType ,
    int selectedItem = -1 ,
    string firstItemPromptText = "" ,
    bool includeEmptyFirstItem = false )
{
    var selectList = new List<SelectListItem>( );

    if ( includeEmptyFirstItem )
    {
        selectList.Add( new SelectListItem( ) { Text = "" , Value = "" } );
    }
    else if ( !string.IsNullOrWhiteSpace( firstItemPromptText ) )
    {
        selectList.Add( new SelectListItem( ) { Text = firstItemPromptText , Value = "" } );
    }

    switch ( selectListItemType )
    {
        case SelectListItemType.Color:
            selectList.Add( new SelectListItem( )
            {
                Text = "Black" ,
                Value = Color.Black.ToString( ) ,
                Selected = ( ( ( int )Color.Black ) == selectedItem )
            } );
            selectList.Add( new SelectListItem( )
            {
                Text = "Red" ,
                Value = Color.Red.ToString( ) ,
                Selected = ( ( ( int )Color.Red ) == selectedItem )
            } );
            selectList.Add( new SelectListItem( )
            {
                Text = "NeonOrange" ,
                Value = Color.NeonOrange.ToString( ) ,
                Selected = ( ( ( int )Color.NeonOrange ) == selectedItem )
            } );
            break;
        case SelectListItemType.DoorCount:
            selectList.Add( new SelectListItem( )
            {
                Text = "2" ,
                Value = DoorCount.Two.ToString( ) ,
                Selected = ( ( ( int )DoorCount.Two ) == selectedItem )
            } );
            selectList.Add( new SelectListItem( )
            {
                Text = "4" ,
                Value = DoorCount.Four.ToString( ) ,
                Selected = ( ( ( int )DoorCount.Four ) == selectedItem )
            } );
            
	... [SNIP] ...

        case SelectListItemType.None:
        default:
            break;
    }

    return selectList;

}

I admit that last part looks a little ugly, and I can see some seeds that should assist in building up those select list items, but I didn’t want to hold back this entry while I figure that out, I’ll just amend this entry (probably put in a new entry) when I figure it out.

Pressing on for now, and accepting that the last bit didn’t look very elegant, but the pay off is worth it!

So first let’s create the car controller and put in the Index and the Create functionality

public class CarController : Controller
{
    //
    // GET: /Car/

    public ActionResult Index ( )
    {
        using ( var dc = new Models.CarDataContext( ) )
        {
            var cars = dc.Cars.ToList( );
            return View( cars );
        }
    }

    //
    // GET: /Car/Create
    public ActionResult Create ( )
    {
        var car = new Car( );
        return View( car );
    }
}

Create our views by right mouse clicking, and making them a strongly typed view

clip_image003

Here is what we get…

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

    <% using (Html.BeginForm()) {%>
        <%: Html.ValidationSummary(true) %>

        <fieldset>
            <legend>Fields</legend>
                       
            <div class="editor-label">
                <%: Html.LabelFor( model => model.Color )%>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor( model => model.Color )%>
                <%: Html.ValidationMessageFor( model => model.Color )%>
            </div>
            
            <div class="editor-label">
                <%: Html.LabelFor(model => model.DoorCount) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.DoorCount) %>
                <%: Html.ValidationMessageFor(model => model.DoorCount) %>
            </div>
            
            <div class="editor-label">
                <%: Html.LabelFor(model => model.CarModel) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.CarModel) %>
                <%: Html.ValidationMessageFor(model => model.CarModel) %>
            </div>
            
            <div class="editor-label">
                <%: Html.LabelFor(model => model.VehicleType) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.VehicleType) %>
                <%: Html.ValidationMessageFor(model => model.VehicleType) %>
            </div>
            
            <p>
                <input type="submit" value="Create" />
            </p>
        </fieldset>

    <% } %>

    <div>
        <%: Html.ActionLink("Back to List", "Index") %>
    </div>

</asp:Content>

Good enough… but is it?  couple of things come up, first up, this is for create, now when I want to do the edit page, I’m going to end up with a duplicate of all of this, that means any changes to the model means I have to make changes in two places. That is NOT acceptable!  My mantra (that I have really come to fully embrace only in the last two years) is

“… if you have the same thing happening in two places, then Refactor into one place and have two pointers”

Before we go any further let’s run the app and get to the create page to see what we got.

clip_image004

So let’s use the new EditorFor template capabilities as described on MSDN: Walkthrough: Using Templated Helpers to Display Data.

In the View | Car folder let’s create the EditorTemplates folder

clip_image005

Now let’s create a partial view in that folder set up for the Car.

clip_image006

To start with it looks almost identical to the Create page,

clip_image007

But we edit it a bit and we end up with this.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<BloggingMVCSkeleton.Models.Car>" %>

	<div class="editor-label">
		<%: Html.LabelFor( model => model.Color )%>
	</div>
	<div class="editor-field">
		<%: Html.TextBoxFor( model => model.Color )%>
		<%: Html.ValidationMessageFor( model => model.Color )%>
	</div>

	<div class="editor-label">
		<%: Html.LabelFor(model => model.DoorCount) %>
	</div>
	<div class="editor-field">
		<%: Html.TextBoxFor(model => model.DoorCount) %>
		<%: Html.ValidationMessageFor(model => model.DoorCount) %>
	</div>

	<div class="editor-label">
		<%: Html.LabelFor(model => model.CarModel) %>
	</div>
	<div class="editor-field">
		<%: Html.TextBoxFor(model => model.CarModel) %>
		<%: Html.ValidationMessageFor(model => model.CarModel) %>
	</div>

	<div class="editor-label">
		<%: Html.LabelFor(model => model.VehicleType) %>
	</div>
	<div class="editor-field">
		<%: Html.TextBoxFor(model => model.VehicleType) %>
		<%: Html.ValidationMessageFor(model => model.VehicleType) %>
	</div>

Now back on the Create Page we can modify it to read a-lot more simply, Pay attention to Line 15 below.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<BloggingMVCSkeleton.Models.Car>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Create
</asp:Content>

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

    <% using (Html.BeginForm()) {%>
        <%: Html.ValidationSummary(true) %>

        <fieldset>
            <legend>Fields</legend>
                       
				<%:Html.EditorFor(model => model) %>            
            <p>
                <input type="submit" value="Create" />
            </p>
        </fieldset>

    <% } %>

    <div>
        <%: Html.ActionLink("Back to List", "Index") %>
    </div>

</asp:Content>

The real advantage will come with the Edit Page, let’s do that real quickly so you can see why this is worth while right from the start.  We modify the controller’s edit to be

public ActionResult Edit ( Guid uid )
{
    using ( var dc = new Models.CarDataContext( ) )
    {
        var car = dc.Cars.FirstOrDefault( c => c.Uid == uid );
        return View( car );
    }
}

And generate our Edit view, and after a little clean up we get (looking again at line 17):

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<BloggingMVCSkeleton.Models.Car>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Edit
</asp:Content>

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

    <h2>Edit</h2>

    <% using (Html.BeginForm()) {%>
        <%: Html.ValidationSummary(true) %>
        
        <fieldset>
            <legend>Fields</legend>
            
				<%:Html.EditorFor(model => model) %>            
            
            <p>
                <input type="submit" value="Save" />
            </p>
        </fieldset>

    <% } %>

    <div>
        <%: Html.ActionLink("Back to List", "Index") %>
    </div>

</asp:Content>

So now if I need to modify the car, all I have to do is go edit the Car.ascx in the EditorTemplates folder, and Viola both Edit and Create are automatically updated. So far nothing really spectacular and I still haven’t justified the mess I made inside the Enums class with that static SelectListItem function.  So let’s go on to the Enumeration Selects, the interesting take away so far however is I only need to implement the update in the Cars.ascx and both create and edit will get it.

Let’s start with Color:

Being good little MVC programming Children, we DON’T EVER do any code processing in the View, that must all take place in the Controller, so obviously in the Create function we need to get the select list for colors and pass it into the View.

public ActionResult Create ( )
{
    var car = new Car( );
    var colors = Enums.SelectListFor( Enums.SelectListItemType.Color , firstItemPromptText: " [ Select Color ] " );
    ViewData["Colors"] = colors;
    return View( car );
}

We don’t need to make any changes to the View, all we need to do is make a quick modification to the Cars.ascx in the EditorTemplates folder

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<BloggingMVCSkeleton.Models.Car>" %>

	<div class="editor-label">
		<%: Html.LabelFor( model => model.Color )%>
	</div>
	<div class="editor-field">
		<%
			var selectList= this.ViewData["ColorsSelectList"] as IEnumerable<SelectListItem>;		
		%>
		<%: Html.DropDownListFor( model => model.Color , selectList )%>
		<%: Html.ValidationMessageFor( model => model.Color )%>
	</div>

	<div class="editor-label">
		<%: Html.LabelFor(model => model.DoorCount) %>
	</div>
	<div class="editor-field">
		<%: Html.TextBoxFor(model => model.DoorCount) %>
		<%: Html.ValidationMessageFor(model => model.DoorCount) %>
	</div>

	<div class="editor-label">
		<%: Html.LabelFor(model => model.CarModel) %>
	</div>
	<div class="editor-field">
		<%: Html.TextBoxFor(model => model.CarModel) %>
		<%: Html.ValidationMessageFor(model => model.CarModel) %>
	</div>

	<div class="editor-label">
		<%: Html.LabelFor(model => model.VehicleType) %>
	</div>
	<div class="editor-field">
		<%: Html.TextBoxFor(model => model.VehicleType) %>
		<%: Html.ValidationMessageFor(model => model.VehicleType) %>
	</div>

Run it and here is what we get

clip_image008

So yes I could do the other four properties exactly the same, but… that would mean that with the exception of which select list I get, I would be repeating almost identical boiler plate and we all know the Manta…

“… if you have the same thing happening in two places, then Refactor into one place and have two pointers”

This is where things begin to get a little tricky.  Just as we can make an editor for a whole object we can make an editor for just an particular field on an object.  So that just like we have <%: Html.DropDownListFor( model => model.Color , selectList )%>  we should also be able to have a <%: Html.EditorFor( model => model.Color , selectList )%> and so we can.  And thinking of Inheritance and other object oriented ideas, all of the properties are of some type of Enumeration, so let’s see if we can refactor out a single partial view that can handle any of our enumerations.

Let’s start by creating a partial view in the EditorTemplates folder and set up to be strongly typed for Enum (note: you have to Manually type in “Enum”, it isn’t in the drop down).  Let’s give this partial view the logical Name of “EnumeratedType”.

clip_image009

For now, inside of the partial view let’s just copy in what we had for the dropdown for Color

clip_image010

As you can see we have some read squigglies, the error for all three of these is:

‘System.Enum' does not contain a definition for 'Color' and no extension method 'Color' accepting a first argument of type 'System.Enum' could be found

At this point the control is expecting you to just pass in a single value of type Enum, in short the “model” referenced in the code at this point should be paired down to just the single property being handled.  So let’s delete the “.Color” for all the lines.

Now we have

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Enum>" %>
	<div class="editor-label">
		<%: Html.LabelFor( model => model )%>
	</div>
	<div class="editor-field">
		<%
			var selectList= this.ViewData["ColorsSelectList"] as IEnumerable<SelectListItem>;		
		%>
		<%: Html.DropDownListFor( model => model , selectList )%>
		<%: Html.ValidationMessageFor( model => model )%>
	</div>

Now that we have done that, we can go off to the original Cars.ascx partial view and modify it to call this editor.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<BloggingMVCSkeleton.Models.Car>" %>

	<%: Html.EditorFor( model => model.Color )%>

	<div class="editor-label">
		<%: Html.LabelFor(model => model.DoorCount) %>
	</div>

... [ SNIP ] ...

and when we run it we now get …

clip_image011

No change, so far so good.  Now it’s Refactor payoff time (finally)!

Now I need to modify the EnumeratedTypes.ascx partial view so it will accept all of the enumerated properties for the car.  We want the Cars.ascx to look like this:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<BloggingMVCSkeleton.Models.Car>" %>

	<%: Html.EditorFor( model => model.Color )%>
	<%: Html.EditorFor( model => model.DoorCount )%>
	<%: Html.EditorFor( model => model.CarModel )%>
	<%: Html.EditorFor( model => model.VehicleType )%>

First things first, we need to add the select lists to the view data, so back to the controller.  Before I show you the changes I made, I would like you to Note that I VERY carefully named the Enumerations to be the same as the property name of the value on the Car. 

public ActionResult Create ( )
{
    var car = new Car( );
    ViewData["Color"] = Enums.SelectListFor( Enums.SelectListItemType.Color , firstItemPromptText: " [ Select Color ] " );
    ViewData["DoorCount"] = Enums.SelectListFor( Enums.SelectListItemType.DoorCount , firstItemPromptText: " [ Select # Doors ] " );
    ViewData["CarModel"] = Enums.SelectListFor( Enums.SelectListItemType.CarModel , firstItemPromptText: " [ Select Model ] " );
    ViewData["VehicleType"] = Enums.SelectListFor( Enums.SelectListItemType.VehicleType , firstItemPromptText: " [ Select Vehicle Type ] " );
    return View( car );
}

Please also note that I modified ViewData[“ColorSelectList”] to be ViewData[“Color”].  This will allow me to look up the select list to use with the property in the EnumeratedTypes.ascx partial view by looking at the name of the property passed in.  I could have left “SelectList” in the key, but then I would have to stay with that pattern for the other items.  Anyways, here is the new code in the EnumeratedTypes Partial View.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Enum>" %>
	<div class="editor-label">
		<%: Html.LabelFor( model => model )%>
	</div>
	<div class="editor-field">
		<%
			//var selectList= this.ViewData["ColorsSelectList"] as IEnumerable<SelectListItem>;
			var propName = this.ViewData.ModelMetadata.PropertyName;
			var selectList= this.ViewData.FirstOrDefault( i => i.Key == propName ).Value as IEnumerable<SelectListItem>;			
		%>
		<%: Html.DropDownListFor( model => model , selectList )%>
		<%: Html.ValidationMessageFor( model => model )%>
	</div>

You’ll notice that now I look up the prop name passed into the partial view from the model metadata, then I use that to look up the select list in the view data.  If I had wanted to leave the select lists with “SelectList” tacked onto the end then I would have had to do the same for the other selectlists passed into the view data and I would have to modify the linq look up to be

var selectList= this.ViewData.FirstOrDefault( i => i.Key == propName + "SelectList" ).Value as IEnumerable<SelectListItem>;

Ok so that’s the magic, what do we have now on our Create (and Edit page as well)?

clip_image012

So to Recap

Create Code Tree

Code from Controller

public ActionResult Create ( )
{
    var car = new Car( );
    ViewData["Color"] = Enums.SelectListFor( Enums.SelectListItemType.Color , firstItemPromptText: " [ Select Color ] " );
    ViewData["DoorCount"] = Enums.SelectListFor( Enums.SelectListItemType.DoorCount , firstItemPromptText: " [ Select # Doors ] " );
    ViewData["CarModel"] = Enums.SelectListFor( Enums.SelectListItemType.CarModel , firstItemPromptText: " [ Select Model ] " );
    ViewData["VehicleType"] = Enums.SelectListFor( Enums.SelectListItemType.VehicleType , firstItemPromptText: " [ Select Vehicle Type ] " );
    return View( car );
}

Code From Create View

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<BloggingMVCSkeleton.Models.Car>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Create
</asp:Content>

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

    <% using (Html.BeginForm()) {%>
        <%: Html.ValidationSummary(true) %>

        <fieldset>
            <legend>Fields</legend>
                       
				<%:Html.EditorFor(model => model) %>            
            <p>
                <input type="submit" value="Create" />
            </p>
        </fieldset>

    <% } %>

    <div>
        <%: Html.ActionLink("Back to List", "Index") %>
    </div>

</asp:Content>

Code From Car.ascx (the EditorFor template for car)

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<BloggingMVCSkeleton.Models.Car>" %>

	<h2>Specify your new Car Properties</h2>

	<%: Html.EditorFor( model => model.Color )%>
	<%: Html.EditorFor( model => model.DoorCount )%>
	<%: Html.EditorFor( model => model.CarModel )%>
	<%: Html.EditorFor( model => model.VehicleType )%>

Code from EnumeratedType.ascx (the EditorFor template for enumeration values)

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Enum>" %>
	<div class="editor-label">
		<%: Html.LabelFor( model => model )%>
	</div>
	<div class="editor-field">
		<%
			//var selectList= this.ViewData["ColorsSelectList"] as IEnumerable<SelectListItem>;
			var propName = this.ViewData.ModelMetadata.PropertyName;
			var selectList= this.ViewData.FirstOrDefault( i => i.Key == propName ).Value as IEnumerable<SelectListItem>;			
		%>
		<%: Html.DropDownListFor( model => model , selectList )%>
		<%: Html.ValidationMessageFor( model => model )%>
	</div>

All the hard work is done for me in the datatyping.  I’m loving this new MVC 2.0!!!

Posted on 19 Apr 10 22:45 by matthew.hintzen |

Bookmark this post with:

E-mail | Comments(0) | Comment RSS


Getting the additionalViewData Object out of the EditorFor in Asp.net MVC

EditorExtensions.EditorFor<TModel, TValue> Method (HtmlHelper<TModel>, Expression<Func<TModel, TValue>>, Object)

Isn't this great, they tell you how to pass some additional data.  Now if only someone would tell you how to get at that data in the editor.  Not too worry, my pain is your gain.
Additional Data should be passed into the editor using an anonymous type say something like

Html.EditorFor( model => model.Claim , new { employers = Model.Employers, claimStatuses = Model.ClaimStatuses } )

You then access that data in the Editor using

this.ViewData.Values

But wait there is more, this Values is actually the ValueCollection that was created by the Dictionary that was created by the anonymous type.  That unfortunately means that you can't get to your items using the keys you specified in the anon type, i.e. you CAN'T do ViewData.Values["employers"].
And to make matters worse, it is an ICollection, so you can't do the indexer (it doesn't know how many items are in it) so you CAN'T do ViewData.Values[0].
So how do you get to one exact item in the collection?  Well you have to force the ICollection into a list, then you can index it. so using the above example let's assume that the Model.Employers is a collection of SelectListItems, you would get it out doing something like this.

var employers = this.ViewData.Values.ToList( )[0] as IEnumerable<SelectListItem>;

Posted on 17 Apr 10 08:49 by matthew.hintzen |

Bookmark this post with:

E-mail | Comments(0) | Comment RSS