April 2009 - Posts

Once you are done writing your MVC application you will probably start looking at deploying it. In many instances, creating a simple web deployment project should suffice, but if you decide to distribute your application using an MSI then you will most likely need to determine whether MVC is installed on the target system. The simplest way to do this when using WIX is to specify a launch condition. For MVC 1.0 there are two options at your disposal to create a property for a launch condition.

Option 1: Use the registry

When MVC is installed it creates a key in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET\ASP.NET MVC 1.0 called InstallPath. This value can be read using the <RegistrySearch> element and stored in a property. The key won’t be present unless the installation of MVC completed successfully.

All that remains to be done is to define a launch condition that uses this property. Remember that a launch condition specifies the condition under which an installation should continue, not the condition under which it should fail.

   1: <?xml version='1.0' encoding='windows-1252'?>
   2: <Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
   3:   <Product Name='Foo 1.0' Id='8A94F114-6F67-4728-8B5E-B4EC115AF3AF'
   4:     UpgradeCode='B60B3A2A-C7BB-47F7-97C4-7D04332519D3'
   5:     Language='1033' Codepage='1252' Version='1.0.0' Manufacturer='Bar'>
   6:  
   7:     <Package Keywords='Installer' Description='Foo' Manufacturer='Bar'
   8:       InstallerVersion='100' Languages='1033' Compressed='yes' SummaryCodepage='1252' />
   9:       
  10:     <Condition Message='ASP.NET MVC 1.0 is required to proceed with the installation.'>
  11:       Installed OR ASP_NET_MVC_1_0
  12:     </Condition>
  13:     
  14:     <Property Id='ASP_NET_MVC_1_0'>
  15:       <RegistrySearch Id='MVC_InstallDir' Type='directory' Root='HKLM'
  16:                       Key='SOFTWARE\Microsoft\ASP.NET\ASP.NET MVC 1.0'
  17:                       Name='InstallPath'/>
  18:     </Property>    
  19:  
  20:   </Product>
  21: </Wix>

Option 2: Use the assembly file version

Apart from installing into the GAC and creating a native image for System.Web.Mvc, we also drop the DLL along with the XML comments under Program Files\Microsoft ASP.NET\ASP.NET MVC 1.0\Assemblies. If you don’t want to use the registry option you can define your property using a <FileSearch> element. You can use the code from the first example and just replace the <Condition> and <Property> elements with the code below.

   1: <Condition Message='ASP.NET MVC 1.0 is required to proceed with the installation.'>
   2:   Installed OR ASP_NET_MVC_1_0_DLL
   3: </Condition>
   4:  
   5: <Property Id='ASP_NET_MVC_1_0_DLL'>
   6:   <DirectorySearch Id='MVC_DLL_DIR' Path='[ProgramFilesFolder]\Microsoft ASP.NET\ASP.NET MVC 1.0\Assemblies'>
   7:     <FileSearch Id='MVC_DLL_FILE' Name='System.Web.Mvc.dll' MinVersion='1.0.40309'/>
   8:   </DirectorySearch>
   9: </Property>

VERY IMPORTANT: Notice that the code above is expecting a DLL with a minimum version of 1.0.40309. If you examine the properties of System.Web.Mvc.dll in Explorer you’ll notice that the version is in fact 1.0.40310. It’s just some weirdness in how the MinVersion attribute works. The explanation from MSDN states the following:

 

MinVersion
The minimum version of the file, with a language comparison. If this field is specified, then the file must have a version that is at least equal to MinVersion. If the file has an equal version to the MinVersion field value but the language specified in the Languages column differs, the file does not satisfy the signature filter criteria.
 
Note  The language specified in the Languages column is used in the comparison and there is no way to ignore language. If you want a file to meet the MinVersion field requirement regardless of language, you must enter a value in the MinVersion field that is one less than the actual value. For example, if the minimum version for the filter is 2.0.2600.1183, use 2.0.2600.1182 to find the file without matching the language information.

 

Compile and Run

The source code I’ve provided can be compiled and linked using candle and light. I’ve tested this under WiX 2.0, but haven’t tried it using WiX 3.0. You may also see the following ICE warnings when linking the wixobj file:

  • warning LGHT1076 : ICE40: Error Table is missing. Only numerical error messages will be generated.
  • warning LGHT1076 : ICE71: The Media table has no entries.

You can safely ignore these warnings. The installer code I provided is really stripped down to the bare minimum to produce an MSI that does absolutely nothing except trying to detect whether or not MVC 1.0 is installed. When you launch the MSI on a system without MVC you should see the message blow.

wixerror

 

Posted by jeloff | 7 comment(s)
Filed under: ,

About six months ago Phil Haack wrote a post on how to use the DefaultModelBinder to bind a form to a list. He concluded by asking how this functionality would be used. In this post I'm going to show how a dynamic form that uses the model binder’s ability to work with lists can be created using MVC and jQuery. The example I’m going to use was inspired by an application I'm working on that provides a web interface to the bug tracking system we used during the development of MVC. The application is not intended to become an internal tool, but rather to explore the capabilities of MVC and hopefully identify areas that can be improved in future releases. It also afforded me a chance to explore jQuery.

Background

The system we use for tracking bugs in MVC contains over 3000 databases spanning multiple products and product families. Apart from using the system to create and resolve bugs it allows users to create and save queries that are executed against a specific databases. For example, I could create a query to find all the work items that were assigned to me for RC1 of MVC. The interface provided by the application to accomplish this is fairly simple as shown below. jQuery and MVC provided all the necessary tools to design a web application that could mimic this behavior.

query

Sample Application

I’ve made the source code of the sample application available for download instead of posting everything here. Instead I’m just going to highlight some aspects of the application. The design can definitely be improved.

  • The JavaScript is embedded within the Create view. This avoids script code from being cached by the development server in Visual Studio (I got tired of remembering to hit Ctrl+F5 every time I launched the application when I modified the JavaScript). The downside of this is that it complicates script debugging in Visual Studio.
  • The only validation being performed by the controller is to check whether the form is empty (no rows were inserted by the user) and that any rows that are present have a value inside the corresponding textbox.

QueryController

Once the page for the Create view has completed loading it immediately makes a request to the controller to retrieve a list of all the field definitions. The definitions are stored on the client side to avoid going to the server every time a new row is added to the form.

   1: var fieldDefinitions = null;
   2:  
   3: $(document).ready(function() {
   4:     // Retrieve all the field definitions once the page is loaded.
   5:     $.getJSON("/Query/Fields", null, function(data) {
   6:         fieldDefinitions = data;
   7:     });
   8: });

The Fields action simply returns a list of definitions that have been created when the application starts. In a real application one might need to retrieve this data from a file, a web service or a database. The definitions are then serialized and consumed on the client when a new row is inserted into the form. If you go through the source code you’ll notice that the field definitions are stored inside a dictionary, hence the reference to the Values property in the code below. Using a dictionary makes accessing a field definition on the server easier during validation.

   1: public ActionResult Fields() {
   2:     return Json(FieldDefinitions.Fields.Values);
   3: }

The Create action takes two parameters. The first is a string that contains a comma separated list of field names that appear in the query and will be used during validation when the form needs to be generated again in case there were errors. The second is a list of fields with each entry representing a single row on the form that was submitted. The action doesn’t do much right now. It performs some basic validation and if successful, creates a string representation of the query the user created that is echoed back in the Results view.

   1: public ActionResult Create() {
   2:     return View();
   3: }
   4:  
   5: [AcceptVerbs(HttpVerbs.Post)]
   6: public ActionResult Create(string queryFields, IList<Field> query) {
   7:     if (!ValidateQuery(query)) {
   8:         ViewData["queryFields"] = queryFields;
   9:         return View();
  10:     }
  11:  
  12:     StringBuilder queryString = new StringBuilder();
  13:  
  14:     foreach (Field field in query) {
  15:         queryString.AppendFormat("{0} {1} {2} {3}", field.AttachWith, field.Name, field.Operator, field.Value);
  16:         queryString.AppendLine();
  17:     }
  18:  
  19:     ViewData["queryString"] = queryString.ToString();
  20:  
  21:     return View("Results");
  22: }

The Field model used to represent a single query row is very simple.

   1: public class Field {
   2:     public string AttachWith {
   3:         get;
   4:         set;
   5:     }
   6:  
   7:     public string Name {
   8:         get;
   9:         set;
  10:     }
  11:  
  12:     public string Operator {
  13:         get;
  14:         set;
  15:     }
  16:  
  17:     public string Value {
  18:         get;
  19:         set;
  20:     }
  21: }

 

Query View

Initially the user is confronted with a simple form that only contains two buttons; one to add a new row and another to submit the form.

EmptyQuery

I’ve used a table for the form layout since each row contains exactly the same elements and this makes it a bit easier to keep the form organized.

   1: <form action="/Query/Create" method="post"><input id="queryFields" name="queryFields" type="hidden" value="" />
   2:   <table id="queryTable">
   3:     <thead>
   4:       <tr>
   5:         <th>Attach With</th>
   6:         <th>Field</th>
   7:         <th>Operator</th>
   8:         <th>Value</th>
   9:       </tr>
  10:     </thead>
  11:     <tbody>
  12:     </tbody>
  13:   </table>
  14:   <p>
  15:     <input type="button" value="Add Field" onclick="addQueryField()" />    
  16:     <input type="submit" value="Submit Query" onclick="updateQueryFields()" />
  17:   </p>
  18: </form>

When a user clicks on the Add Field button the addQueryField function will insert a new row into the table. The row contains three dropdown lists, a text field and a button to remove the row from the form.

CreateQuery

Since the form will be bound to an IList<Field> we need to ensure that the indices generated for the name attribute in the various HTML elements remain sequential. If we end up with non-sequential indices then the form fields will not be bound properly to our model. The HTML for the newly added row in the example above will look like this:

row0html

Determining the value of the index used by the various name attributes is quite easy using jQuery’s selectors as shown below on line 3.

   1: function addQueryField() {
   2:     // Determine the index of the next row to insert
   3:     var index = $("tr[id^=queryRow]").size();
   4:     // Create DOM element for table row
   5:     var oTr = $(document.createElement("tr")).attr("id", "queryRow" + index);
   6:     // Create DOM element for value textbox
   7:     var oValueTextBox = $(document.createElement("input")).attr("name", "query[" + index + "].Value").attr("id", "Value"+index).attr("type", "text");
   8:     // Create DOM element for Name select list
   9:     var oSelectListName = createSelectListForName(index);
  10:     // Create DOM element for Remove button to delete the row from the table
  11:     var oButtonRemove = $(document.createElement("input")).attr("type", "button").attr("value", "Remove").attr("id", "Remove"+index).click(function() {
  12:         removeRow(index);
  13:     });
  14:     // Create <td> elements
  15:     oTr.append($(document.createElement("td")).append(createSelectListForAttachWith(index)));
  16:     oTr.append($(document.createElement("td")).append(oSelectListName));
  17:     oTr.append($(document.createElement("td")).append(createSelectListForOperator(oSelectListName.val(), index)));
  18:     oTr.append($(document.createElement("td")).append(oValueTextBox).append(oButtonRemove));
  19:     // Insert the row into the table
  20:     $("#queryTable").append(oTr);
  21: }

On line 12 we bind the removeRow function to the onclick event of the the Remove button. This function is responsible for two tasks:

  1. It needs to remove the row from the table.
  2. It needs to update the remaining rows to keep the indices sequential.

Updating the rows is not a difficult task, but the syntax required by the DefaultModelBinder to specify a property and index for the model conflicts with the syntax used by the selectors in jQuery. The ‘[‘ and ‘]’ characters are used to specify attribute values inside a selector. To work around this the id attributes of the elements that are inserted into the DOM only contains alphanumerical characters. This allows the removeRow function to select and update each row using jQuery selectors. On lines 8, 9, 13, and 14 we need to unbind our functions before rebinding them since their index parameters have changed. Without doing the .unbind() jQuery will just chain the event handlers and you’ll end up with some really funny behavior.

   1: function removeRow(index) {
   2:     // Delete the row
   3:     $("#queryRow" + index).remove();
   4:     // Search through the table and update all the remaining rows so that indices remain sequential
   5:     $("tr[id^=queryRow]").each(function(i) {
   6:         $(this).attr("id", "queryRow" + i);
   7:         $("td select[id^=AttachWith]", $(this)).attr("name", "query[" + i + "].AttachWith").attr("id", "AttachWith" + i);
   8:         $("td select[id^=Name]", $(this)).attr("name", "query[" + i + "].Name").attr("id", "Name" + i).unbind("change").change(function() {
   9:             updateOperator(i);
  10:         });
  11:         $("td select[id^=Operator]", $(this)).attr("name", "query[" + i + "].Operator").attr("id", "Operator" + i);
  12:         $("td input[id^=Value]", $(this)).attr("name", "query[" + i + "].Value").attr("id", "Value" + i);
  13:         $("td input[id^=Remove]", $(this)).attr("id", "Remove" + i).unbind("click").click(function() {
  14:             removeRow(i);
  15:         });
  16:     });
  17: }

Validation

Performing client side validation on a dynamic form makes perfect sense and jQuery provides the necessary tools to do this. Depending on how your application works it’s reasonable to expect that some elements can only be validated on the server. The only problem that needs to be solved is displaying all the original fields that the user added when redirecting back to the form. To solve this, I included a hidden input named queryFields. When the user hits the submit button it executes a function to update the hidden input with a comma separated list of fields. When validation fails the string is placed into ViewData and the form the user submitted can be generated using the HTML helpers.

   1: <%
   1:  
   2:    string queryFields = ViewData["queryFields"] as string;
   3:    if (!String.IsNullOrEmpty(queryFields)) {
   4:        int i = 0;
   5:        foreach (string field in queryFields.Split(new[] { ',' })) {
   6:            string queryPrefix = "query["+Convert.ToString(i)+"].";
   7:            string attachWithName = queryPrefix + "AttachWith";
   8:            string fieldName = queryPrefix + "Name";
   9:            string operatorName = queryPrefix + "Operator";
  10:            string valueName = queryPrefix+"Value";
  11:            string trId = "queryRow"+Convert.ToString(i); 
%>
   2:           
   3:            <tr id="<% =trId %>">
   4:              <td><%
   1:  =Html.DropDownList(attachWithName, FieldDefinitions.AttachWith, new { id = "AttachWith" + Convert.ToString(i) }) 
%></td>
   5:              <td><%
   1:  =Html.DropDownList(fieldName, FieldDefinitions.FieldNames, new { id = "Name" + Convert.ToString(i), onchange="updateOperator("+Convert.ToString(i)+")"}) 
%></td>
   6:              <td><%
   1:  =Html.DropDownList(operatorName, FieldDefinitions.Fields[field].Operators, new { id = "Operator" + Convert.ToString(i) }) 
%></td>
   7:              <td>
   8:                <%
   1:  =Html.TextBox(valueName, null, new { id = "Value" + Convert.ToString(i) })
%>
   9:                <input type="button" value="Remove" onclick="removeRow(<% =Convert.ToString(i) %>)" />
  10:                <%
   1:  =Html.ValidationMessage(valueName) 
%>
  11:              </td>
  12:            </tr>
  13:            i++;
  14:        }
  15:    } %>
QueryValidation 

Once the system passes validation you should see a screen that echoes the query back to you. Consider the following query:

querysuccess

Hitting the Submit Query button will produce the result below.

queryResult

IE8 Quirks

While working on the application I mentioned at the beginning of this post I discovered a small bug in the developer tools of IE8 (Open IE8 and hit F12 to open the tools). When creating a new element in the DOM and setting its name attribute the toolbar will display the attribute as propdescname instead of name. The DOM is still correct though and the problem does not occur if you specified the name attribute explicitly in the HTML.

domie8

Conclusion

 

 

 

 

 

 

 

 

When I wrote the first version of my application I avoided using jQuery. The result of that was that my code only worked in IE. Getting it to work in Safari and FireFox took another day. Now I understand why, when talking to JavaScript developers, you sometimes see little drops of blood welling up in their eyes. jQuery on the other hand takes care of all the browser compatibility issues. Apart from this using jQuery made the code much easier to maintain and modify. I’m going to attribute that to two things: selectors and chaining. If you have any suggestions on how to improve the JavaScript in the example I’ve given I’d love to hear from you.

Posted by jeloff | 10 comment(s)
Filed under:
More Posts