In this post I’m going to show how to create a generic, ajax driven Auto Complete text box using the new MVC 2 Templates and the jQuery UI library.
The template will be automatically displayed when a property is decorated with a custom attribute within the view model.
The AutoComplete text box in action will look like the following:
The first thing to do is to do is visit my previous blog post to put the custom model metadata provider in place, this is necessary when using custom attributes on the view model.
http://weblogs.asp.net/seanmcalinden/archive/2010/06/11/custom-asp-net-mvc-2-modelmetadataprovider-for-using-custom-view-model-attributes.aspx
Once this is in place, make sure you visit the jQuery UI and download the latest stable release – in this example I’m using version 1.8.2. You can download it here.
Add the jQuery scripts and css theme to your project and add references to them in your master page.
Should look something like the following:
Site.Master
<head runat="server">
<title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
<link href="../../css/ui-lightness/jquery-ui-1.8.2.custom.css" rel="stylesheet" type="text/css" />
<script src="../../Scripts/jquery-1.4.2.min.js" type="text/javascript"></script>
<script src="../../Scripts/jquery-ui-1.8.2.custom.min.js" type="text/javascript"></script>
</head>
Once this is place we can get started.
Creating the AutoComplete Custom Attribute
The auto complete attribute will derive from the abstract MetadataAttribute created in my previous post.
It will look like the following:
AutoCompleteAttribute
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
namespace Mvc2Templates.Attributes
{
public class AutoCompleteAttribute : MetadataAttribute
{
public RouteValueDictionary RouteValueDictionary;
public AutoCompleteAttribute(string controller, string action, string parameterName)
{
this.RouteValueDictionary = new RouteValueDictionary();
this.RouteValueDictionary.Add("Controller", controller);
this.RouteValueDictionary.Add("Action", action);
this.RouteValueDictionary.Add(parameterName, string.Empty);
}
public override void Process(ModelMetadata modelMetaData)
{
modelMetaData.AdditionalValues.Add("AutoCompleteUrlData", this.RouteValueDictionary);
modelMetaData.TemplateHint = "AutoComplete";
}
}
}
As you can see, the constructor takes in strings for the controller, action and parameter name.
The parameter name will be used for passing the search text within the auto complete text box.
The constructor then creates a new RouteValueDictionary which we will use later to construct the url for getting the auto complete results via ajax.
The main interesting method is the method override called Process.
With the process method, the route value dictionary is added to the modelMetaData AdditionalValues collection.
The TemplateHint is also set to AutoComplete, this means that when the view model is parsed for display, the MVC 2 framework will look for a view user control template called AutoComplete, if it finds one, it uses that template to display the property.
The View Model
To show you how the attribute will look, this is the view model I have used in my example which can be downloaded at the end of this post.
View Model
using System.ComponentModel;
using Mvc2Templates.Attributes;
namespace Mvc2Templates.Models
{
public class TemplateDemoViewModel
{
[AutoComplete("Home", "AutoCompleteResult", "searchText")]
[DisplayName("European Country Search")]
public string SearchText { get; set; }
}
}
As you can see, the auto complete attribute is called with the controller name, action name and the name of the action parameter that the search text will be passed into.
The AutoComplete Template
Now all of this is in place, it’s time to create the AutoComplete template.
Create a ViewUserControl called AutoComplete.ascx at the following location within your application – Views/Shared/EditorTemplates/AutoComplete.ascx
Add the following code:
AutoComplete.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
var propertyName = ViewData.ModelMetadata.PropertyName;
var propertyValue = ViewData.ModelMetadata.Model;
var id = Guid.NewGuid().ToString();
RouteValueDictionary urlData =
(RouteValueDictionary)ViewData.ModelMetadata.AdditionalValues.Where(x => x.Key == "AutoCompleteUrlData").Single().Value;
var url = Mvc2Templates.Views.Shared.Helpers.RouteHelper.GetUrl(this.ViewContext.RequestContext, urlData);
%>
<input type="text" name="<%= propertyName %>" value="<%= propertyValue %>" id="<%= id %>" class="autoComplete" />
<script type="text/javascript">
$(function () {
$("#<%= id %>").autocomplete({
source: function (request, response) {
$.ajax({
url: "<%= url %>" + request.term,
dataType: "json",
success: function (data) {
response(data);
}
});
},
minLength: 2
});
});
</script>
There is a lot going on in here but when you break it down it’s quite simple.
Firstly, the property name and property value are retrieved through the model meta data. These are required to ensure that the text box input has the correct name and data to allow for model binding. If you look at line 14 you can see them being used in the text box input creation.
The interesting bit is on line 8 and 9, this is the code to retrieve the route value dictionary we added into the model metada via the custom attribute.
Line 11 is used to create the url, in order to do this I created a quick helper class which looks like the code below titled RouteHelper.
The last bit of script is the code to initialise the jQuery UI AutoComplete control with the correct url for calling back to our controller action.
RouteHelper
using System.Web.Mvc;
using System.Web.Routing;
namespace Mvc2Templates.Views.Shared.Helpers
{
public static class RouteHelper
{
const string Controller = "Controller";
const string Action = "Action";
const string ReplaceFormatString = "REPLACE{0}";
public static string GetUrl(RequestContext requestContext, RouteValueDictionary routeValueDictionary)
{
RouteValueDictionary urlData = new RouteValueDictionary();
UrlHelper urlHelper = new UrlHelper(requestContext);
int i = 0;
foreach(var item in routeValueDictionary)
{
if (item.Value == string.Empty)
{
i++;
urlData.Add(item.Key, string.Format(ReplaceFormatString, i.ToString()));
}
else
{
urlData.Add(item.Key, item.Value);
}
}
var url = urlHelper.RouteUrl(urlData);
for (int index = 1; index <= i; index++)
{
url = url.Replace(string.Format(ReplaceFormatString, index.ToString()), string.Empty);
}
return url;
}
}
}
See it in action
All you need to do to see it in action is pass a view model from your controller with the new AutoComplete attribute attached and call the following within your view:
<%= this.Html.EditorForModel() %>
NOTE: The jQuery UI auto complete control expects a JSON string returned from your controller action method… as you can’t use the JsonResult to perform GET requests, use a normal action result, convert your data into json and return it as a string via a ContentResult.
If you download the solution it will be very clear how to handle the controller and action for this demo.
The full source code for this post can be downloaded here.
It has been developed using MVC 2 and Visual Studio 2010.
As always, I hope this has been interesting/useful.
Kind Regards,
Sean McAlinden.