July 2010 - Posts - Jon Galloway

July 2010 - Posts

Server installation options for ASP.NET MVC 2

I’ve answered several questions about installing ASP.NET MVC 2 on a server lately, and since I didn’t find a full summary I figured it was time to write one up. Here’s a look at some of the top options:

  • WebPI
  • Bin deploy
  • Run the full AspNetMVC2_VS2008.exe installer
  • Command-line install with aspnetmvc2.msi

WebPI

WebPI has quickly become my favorite way to install Microsoft web platform software (including development tools) on my development machine, and it’s a great option for installing on the server as well. I like WebPI for a lot of reasons – here are the top three:

  • It’s a tiny download (less than 2 MB)
  • It figures out which dependencies you need and which you already have installed, so you get the smallest download and fastest install possible
  • It’s one place to go to get all the new releases

So if you have desktop access to the server, probably the best option is to install ASP.NET MVC 2 via WebPI.

Bin Deployment

ASP.NET MVC was designed so you can use it without needing install permissions, e.g. working with a hosting provider who didn’t have ASP.NET MVC installed. Phil Haack wrote up instructions for Bin Deploying an ASP.NET MVC 1.0 application, and it’s only gotten easier since then. If your server has ASP.NET 4 installed, you’ll just need to set the reference to System.Web.Mvc to “Copy Local”:

Bin Deploy for System.Web.MVC

If you’re running on .NET Framework 3.5 SP1, you’ll need to include System.ComponentModel.DataAnnotations.dll (by setting it to Copy Local as well):

  • System.ComponentModel.DataAnnotations.dll
  • System.Web.Mvc.dll

And if you don’t have .NET Framework 3.5 SP1, you’ll also need to include System.Web.Abstractions.dll and System.Web.Routing.dll, so the full list to Bin deploy ASP.NET MVC 2 on .NET Framework 3.5 without SP1 is:

  • System.ComponentModel.DataAnnotations.dll
  • System.Web.Abstractions.dll
  • System.Web.Mvc.dll
  • System.Web.Routing.dll

For a full walkthrough on this, see Phil’s blog post.

Run the full AspNetMVC2_VS2008.exe installer

The AspNetMVC2_VS2008.exe installer installer is available from Microsoft Downloads at the following (quite memorable) URL: http://www.microsoft.com/downloads/details.aspx?FamilyID=c9ba1fe1-3ba8-439a-9e21-def90a8615a9

I’ll confess that I sometimes forget GUIDs, though, so I always just browse to http://asp.net/mvc and click on the Installer link near the top:

ASP.NET MVC Installer link

The installer is named AspNetMVC2_VS2008.exe, but it doesn’t require you to have Visual Studio 2008 (or any version of Visual Studio) installed, so it’s just fine to run it on a server. According to Jacques, who works on the installer: On a machine that doesn’t have VSTS/VWD installed, the EXE will autodetect this and only install the runtime component.

There are a few reasons you may not want to use this, though:

  • The installer shows a wizard that makes you click through a few steps. You might want to set up a silent / scripted install so you can include it as part of your server build process.
  • If the server happens to have VWD or VSTS installed (not a good idea usually, but it’s your server), the installer will also install the MVC tooling. You might not want that.

In those cases, you can do a scripted install with aspnetmvc2.msi.

Scripted install with aspnetmvc2.msi

There’s no direct download link for aspnetmvc2.msi; it’s included in AspNetMVC2_VS2008.exe. The good news is that AspNetMVC2_VS2008.exe is a self-extracting EXE, so you can either open it up with some archive utilities. Apparently it opens with WinRAR, but doesn’t open in either Windows Explorer, and 7-Zip just shows the resource information rather than the actual contents.

None of that’s a problem, though, because it’s a self-extracting exe. That means you can pass a –extract (or –x for short) argument at the command-line to extract it:

AspNetMVC2_VS2008.exe –x

You can also specify an extraction directory, like this:

Extracting AspNetMVC2_2008.exe 

Note: I don’t know of a way to specify the extraction directory in a way that prevents the confirmation prompt from being displayed. Do you?

There are three nested directories inside of the result: mvcruntime, mvctoolsvs2008, and mvctoolsvwd2008. The runtime, not surprisingly, is in the mvcruntime directory.

aspnetmvc2.msi hiding inside AspNetMVC2.exe

aspnetmvc2.msi can be run via command-line, and you can pass additional arguments supported by msiexec. Passing /q will specify a quiet install, so no dialogs are displayed. When you’re doing that, you’ll probably want to log the output in case something goes wrong, and you can do that using the /l argument:

C:\Users\Jon\Downloads\AspNetMVC2\mvcruntime>msiexec /i aspnetmvc2.msi /q /l*v .\mvc.log

You can find out more about Windows Installer (msiexec) command-line switches on MSDN, or you can just run msiexec /? to show the help.

You can drop the MVC_SERVER_INSTALL

The ASP.NET MVC 1.0 installer had an additional switch for server installation: MVC_SERVER_INSTALL. This switch is no longer required, because the installer detects if developer tools are installed and automatically falls back to the server install. So you’ll see people recommending that you use

msiexec /i AspNetMVC2.msi /l*v .\mvc.log MVC_SERVER_INSTALL="YES"

to install MVC 2, and while that extra flag won’t hurt anything, it’s completely unnecessary.

Installing on IIS 6

Note: I haven’t been near IIS 6 for a while now, so please let me know if I’m missing something.

Most of the difficulty in setting up ASP.NET MVC 1.0 to work on IIS 6 was in handling extension-less URL’s. Phil wrote up a walkthrough on setting up MVC 1.0 with ASP.NET 3.5 on IIS 6, and there’s a full tutorial on setting up ASP.NET MVC 1.0 with different versions of IIS over at http://asp.net/mvc.

Fortunately ASP.NET 4 takes care of that because it automatically handles extension-less URL’s. Thomas Marquardt has a great blog post explaining how it works under the hood.

Posted by Jon Galloway | 12 comment(s)
Filed under: ,

CodePlex now supports ClickOnce

I’m really excited to see that CodePlex just added support for ClickOnce!

I’ve worked a few open source client applications for Windows in both WPF and Winforms, and found that deployment – especially updates – was always the most painful part. It’s been a big drag on Witty deployment lately, so much so that I’d pretty much decided that any open source client applications I was going to work on in the future needed to be Silverlight Out-Of-Browser applications for the ease of deployment alone. Silverlight Out-Of-Browser is still a very interesting option for other reasons, like cross-platform support, but it’s nice to evaluate based the best end-user experience without having to make compromises based on deployment limitations.

ClickOnce is a great way to deploy small, open source applications:

  • It’s web-based
  • It handles auto-updates really smoothly
  • ClickOnce apps install per-user and don’t require administrator permission

The problem has always been that there was nowhere that offered free, simple ClickOnce hosting - free and simple both being two essential ingredients for open source development. If you wanted to support ClickOnce, you needed to set up and maintain two presences for your application – one to handle the open source project, and another to host the ClickOnce installation. That adds friction throughout the development process – for instance, when you add a new developer, they need FTP permissions on specific folders on the ClickOnce server as well as on the project hosting server. That kind of friction can really slow things down when you have very limited hours of everyone’s time.

CodePlex seemed like a natural fit for this, as a Microsoft hosted open source hosting solution. It’s no surprise that ClickOnce support was the number one requested feature on CodePlex.

All that to explain my excitement at seeing that CodePlex had officially added ClickOnce support today! Matt Hawley wrote up a blog post describing how to publish a ClickOnce release on CodePlex. I’ll take a look at setting that up for Witty and Data Dictionary Creator, and other open source apps I work on in the future.

In addition to the benefits to CodePlex users, I think this will be a boost to ClickOnce as a technology, for two reasons:

  • Increased use will (hopefully) more public information and more development, just like Visual Studio dogfooding did for WPF
  • Users will see ClickOnce and become familiar with it
  • That percentage of new ClickOnce users who also happen to be pointy haired bosses will ask for it

What do you think?

Posted by Jon Galloway | with no comments
Filed under: , ,

Using ViewModel information in an ASP.NET MVC 2 Editor or Display template

Editor and Display templates are a great new feature in ASP.NET MVC 2. They allow you to define a template which will be used for any datatype you’d like, and they can be set per-controller or site-wide.

The common scenario you may have seen is to set up a jQueryUI datapicker control for all DateTime editors site-wide. It’s really easy to set up – we create a /Views/Shared/DateTime.ascx file and add the following:

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

<%= Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { @class = "text-box single-line" }) %>

<script type="text/javascript">
    $(function () {
        $("#<%: ViewData.TemplateInfo.GetFullHtmlFieldId(String.Empty) %>").datepicker();
    });
</script>

Then we add the references to our jQueryUI js & css in our Site.master (or on pages as appropriate) and magically all our DateTime fields get datepickers.

Templates for custom objects

That’s great, but one of the more powerful uses of MVC2 templating is to create display or editor templates for your own business objects. For example, if you have a site that sells disco balls, you could create a discoball.ascx display template and use it whenever you display a discoball object (and of course, the same for editing as well).

I demonstrated an example of that in the MVC Music Store – there’s an Editor template for an Album, and it’s used in both the Edit and Create views.

The problem: DropDowns require ViewModels

Many editor templates will require a ViewModel, since your model object won’t contain all the possible values for dropdowns. For example here’s the Edit template for an Album:

The Album table holds an ArtistId and a GenreId, which link to the Artist and Genre tables. That means that our Album model knows about an ArtistId, but doesn’t have a list of all the Artists necessary to display in the dropdown. To do that, we need to introduce a ViewModel, which contains all the information our View will need:

using System.Collections.Generic;
using MvcMusicStore.Models;

namespace MvcMusicStore.ViewModels
{
    public class StoreManagerViewModel
    {
        public Album Album { get; set; }
        public List<Artist> Artists { get; set; }
        public List<Genre> Genres { get; set; }
    }
}

Note: This is a simple usage of a ViewModel. There are several usage patterns for ViewModels, and more advanced ViewModel patterns dictate that you never pass your domain entities to your view. That’s a good topic for another post, but worth mentioning in passing here.

Our StoreManagerController.Create() action can then return a ViewModel which holds an empty Album object, along with lists of Artists and Genres to be populated:

// 
// GET: /StoreManager/Create

public ActionResult Create()
{
    var viewModel = new StoreManagerViewModel
    {
        Album = new Album(),
        Genres = storeDB.Genres.ToList(),
        Artists = storeDB.Artists.ToList()
    };

    return View(viewModel);
}

The Problem: How do we pass additional information to a Template?

Until the RTM release of MVC 2, the above scenario wouldn’t work. You just couldn’t get at the additional ViewModel information – in this case, the Album and Genre lists – from your template files. There was no way to pass the information from your view to your template, which meant that you had to resort to stuffing the additional information into ViewData:

// 
// GET: /StoreManager/Create

public ActionResult Create()
{
    var album = new Album();
    // NOTE: Example! Don't copy / paste / deploy! 
    // Not the best way to do this!
    ViewData["Genres"] = storeDB.Genres.ToList();
    ViewData["Artists"] = storeDB.Artists.ToList();

    return View(album);
}

But I avoid ViewData as much as possible. It’s a weakly typed collection, and I really dislike returning a strongly typed object to the View and then sneakily passing additional information via ViewData.

The Solution: Templated Helper overrides that pass additional View Data

MVC 2 RTM included a nice new feature to solve this exact problem. From the MVC 2 RTM release notes:

Templated Helpers Allow You to Specify Extra View Data

ASP.NET MVC 2 now includes new overloads of the EditorFor and DisplayFor methods. These overloads contain a parameter that accepts an anonymous object that can be used to provide extra view data. The view data provided in this parameter is merged with any existing view data that is passed to the template.

Let’s look at the implementation in MVC 2 Source Code in System.Web.Mvc.Html.TemplateHelpers.TemplateHelper():

if (additionalViewData != null) {
    foreach (KeyValuePair<string, object> kvp in new RouteValueDictionary(additionalViewData)) {
        viewData[kvp.Key] = kvp.Value;
    }
}

That’s pretty straightforward – the common method used by both EditorTemplates and DisplayTemplates checks for additional view data and copies it to ViewData keyed with the name we provided. So, yes, it’s still using ViewData, but at least our Controller and View don’t need to know that.

Putting it all together

The View (/Views/StoreManager/Create.aspx)

We’ll use one of the EditorFor() overrides which allows passing additional view data. Note that these overloads use anonymous objects as dictionaries. This pattern is pretty common throughout the ASP.NET MVC framework, since it allows for very terse key-value pair definitions.

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcMusicStore.ViewModels.StoreManagerViewModel>" %>

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

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

    <h2>Create</h2>

    <% Html.EnableClientValidation(); %>

    <% using (Html.BeginForm()) {%>

    <fieldset>
        <legend>Create Album</legend>
        <%: Html.EditorFor(model => model.Album, new { Artists = Model.Artists, Genres = Model.Genres })%>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>

    <% } %>

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


</asp:Content>

The Editor Template (/Views/Shared/EditorTemplates/Album.ascx)

This editor template is strongly typed to the Album, but is making use of the additional view data for the Html.DropDownList() calls.

<%@ Import Namespace="MvcMusicStore"%>

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

<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>

<p>
    <%: Html.LabelFor(model => model.Title)%>
    <%: Html.TextBoxFor(model => model.Title)%>
    <%: Html.ValidationMessageFor(model => model.Title)%>
</p>
<p>
    <%: Html.LabelFor(model => model.Price)%>
    <%: Html.TextBoxFor(model => model.Price)%>
    <%: Html.ValidationMessageFor(model => model.Price)%>
</p>
<p>
    <%: Html.LabelFor(model => model.AlbumArtUrl)%>
    <%: Html.TextBoxFor(model => model.AlbumArtUrl)%>
    <%: Html.ValidationMessageFor(model => model.AlbumArtUrl)%>
</p>
<p>
    <%: Html.LabelFor(model => model.Artist)%>
    <%: Html.DropDownList("ArtistId", new SelectList(ViewData["Artists"] as IEnumerable, "ArtistId", "Name", Model.ArtistId))%>
</p>
<p>
    <%: Html.LabelFor(model => model.Genre)%>
    <%: Html.DropDownList("GenreId", new SelectList(ViewData["Genres"] as IEnumerable, "GenreId", "Name", Model.GenreId))%>
</p>

We can continue to use the ViewModel and Controller Action code I showed earlier (repeated here for clarity):

StoreManagerViewModel.cs:

using System.Collections.Generic;
using MvcMusicStore.Models;

namespace MvcMusicStore.ViewModels
{
    public class StoreManagerViewModel
    {
        public Album Album { get; set; }
        public List<Artist> Artists { get; set; }
        public List<Genre> Genres { get; set; }
    }
}

StoreManagerController.cs - Create():

// 
// GET: /StoreManager/Create

public ActionResult Create()
{
    var viewModel = new StoreManagerViewModel
    {
        Album = new Album(),
        Genres = storeDB.Genres.ToList(),
        Artists = storeDB.Artists.ToList()
    };

    return View(viewModel);
}
Posted by Jon Galloway | 41 comment(s)
Filed under: ,
More Posts