ASP.Net MVC 2 Auto Complete Textbox With Custom View Model Attribute & EditorTemplate

The full source code for this post can be download from this link. It was developed using Visual Studio 2010.

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:

AutoComplete 

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
  1. <head runat="server">
  2.     <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
  3.     <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
  4.     <link href="../../css/ui-lightness/jquery-ui-1.8.2.custom.css" rel="stylesheet" type="text/css" />
  5.     <script src="../../Scripts/jquery-1.4.2.min.js" type="text/javascript"></script>
  6.     <script src="../../Scripts/jquery-ui-1.8.2.custom.min.js" type="text/javascript"></script>
  7. </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
  1. using System.Collections.Generic;
  2. using System.Web.Mvc;
  3. using System.Web.Routing;
  4. namespace Mvc2Templates.Attributes
  5. {
  6.     public class AutoCompleteAttribute : MetadataAttribute
  7.     {
  8.         public RouteValueDictionary RouteValueDictionary;
  9.         public AutoCompleteAttribute(string controller, string action, string parameterName)
  10.         {
  11.             this.RouteValueDictionary = new RouteValueDictionary();
  12.             this.RouteValueDictionary.Add("Controller", controller);
  13.             this.RouteValueDictionary.Add("Action", action);
  14.             this.RouteValueDictionary.Add(parameterName, string.Empty);
  15.         }
  16.         public override void Process(ModelMetadata modelMetaData)
  17.         {
  18.             modelMetaData.AdditionalValues.Add("AutoCompleteUrlData", this.RouteValueDictionary);
  19.             modelMetaData.TemplateHint = "AutoComplete";
  20.         }
  21.     }
  22. }

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
  1. using System.ComponentModel;
  2. using Mvc2Templates.Attributes;
  3. namespace Mvc2Templates.Models
  4. {
  5.     public class TemplateDemoViewModel
  6.     {
  7.         [AutoComplete("Home", "AutoCompleteResult", "searchText")]
  8.         [DisplayName("European Country Search")]
  9.         public string SearchText { get; set; }
  10.     }
  11. }

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
  1. <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
  2. <%
  3.     var propertyName = ViewData.ModelMetadata.PropertyName;
  4.     var propertyValue = ViewData.ModelMetadata.Model;
  5.     var id = Guid.NewGuid().ToString();
  6.     RouteValueDictionary urlData =
  7.         (RouteValueDictionary)ViewData.ModelMetadata.AdditionalValues.Where(x => x.Key == "AutoCompleteUrlData").Single().Value;
  8.     var url = Mvc2Templates.Views.Shared.Helpers.RouteHelper.GetUrl(this.ViewContext.RequestContext, urlData);
  9. %>
  10. <input type="text" name="<%= propertyName %>" value="<%= propertyValue %>" id="<%= id %>" class="autoComplete" />
  11. <script type="text/javascript">
  12.     $(function () {
  13.         $("#<%= id %>").autocomplete({
  14.             source: function (request, response) {
  15.                 $.ajax({
  16.                     url: "<%= url %>" + request.term,
  17.                     dataType: "json",
  18.                     success: function (data) {
  19.                         response(data);
  20.                     }
  21.                 });
  22.             },
  23.             minLength: 2
  24.         });
  25.     });
  26. </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
  1. using System.Web.Mvc;
  2. using System.Web.Routing;
  3. namespace Mvc2Templates.Views.Shared.Helpers
  4. {
  5.     public static class RouteHelper
  6.     {
  7.         const string Controller = "Controller";
  8.         const string Action = "Action";
  9.         const string ReplaceFormatString = "REPLACE{0}";
  10.         public static string GetUrl(RequestContext requestContext, RouteValueDictionary routeValueDictionary)
  11.         {
  12.             RouteValueDictionary urlData = new RouteValueDictionary();
  13.             UrlHelper urlHelper = new UrlHelper(requestContext);
  14.             
  15.             int i = 0;
  16.             foreach(var item in routeValueDictionary)
  17.             {
  18.                 if (item.Value == string.Empty)
  19.                 {
  20.                     i++;
  21.                     urlData.Add(item.Key, string.Format(ReplaceFormatString, i.ToString()));
  22.                 }
  23.                 else
  24.                 {
  25.                     urlData.Add(item.Key, item.Value);
  26.                 }
  27.             }
  28.             var url = urlHelper.RouteUrl(urlData);
  29.             for (int index = 1; index <= i; index++)
  30.             {
  31.                 url = url.Replace(string.Format(ReplaceFormatString, index.ToString()), string.Empty);
  32.             }
  33.             return url;
  34.         }
  35.     }
  36. }

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:

  1. <%= 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.

28 Comments

  • Great work. Thanks for writing this.

  • Thanks for the article.

    Arun

  • Great article. However can't you set the JsonRequestBehavior.AllowGet parameter on the default JsonResult to allow responding to Get Requests?

  • Hi David, Your absolutely correct - probably would be better.

  • Always nice to read a MVC article have one question, why have the parameterName on the attribute and not follow the standard of jqueryui? Then your can just set your source to your url and not use your GetUrl method.

  • Hi Magnus,

    It is so the control can be truly re-usable/generic, irrespective of the template implementation.

    Cheers,
    Sean.

  • But Sean your parameterName and GetUrl() is completely unnecessary, in your code it dosen't do anything. And if you do it my way you can remove your route that can collide with the basic route.

  • Hi Magnus,

    I guess I'm not totally sure of your solution. The reasons behind my choices were firstly to create a restful route (regardless of the caller), secondly, the getUrl method is there so changes to the routing would be automatically represented in the generated url, thirdly the attribute and the view would not be reliant on any jquery UI default parameter keys as this would result in unnecessary coupling (i.e what if you moved away from JQuery UI to another library, or rolled your own) - I think all these things are necessary to create a generic control that is run via metadata, having said that, if you have a better way that can fulfil these requirements post it up as if it can be simplified - all the better.

    Cheers,

    Sean.

  • Well Sean I can't talk about the restfulness of the route, but with the string concat soulution with the url in the javascript your route can't change that much. Because you allways need to have the search string at the end and thanks to your route you allways have to have the same name of your action so why have the that as a inparameter to your constructure in the attribute? I would think its better to use the defualt route and control the name of the querstring with the paramterName of the attribute.

    And then the low coupling to your javascript framework, well I don't think the http protocol will change that much so the only way to send data is with url, querystrings and forms posts right? And the modelbinder will take care of all ;)

  • Hi Magnus,
    I imagine that this is possibly going to come down to opinion, the action and controller names in the attribute is pretty important though as you may want to use multiple attributes with different routes.
    The coupling to the javascript library wasn't to do with the http protocol (obviously), it was in reference to your comment - "why have the parameterName on the attribute and not follow the standard of jqueryui?", with this I could only assume that you were either going to get the parameter value straight from the form collection or set your action parameter name to the default parameter generated by the UI library... this is what I meant by coupling.

    Anyway, the main point of the post was really to show people a nice way to use a custom modelMetaDataProvider, the implementation of the auto complete was just a simple example to show how it could be hooked up, in no way is it production ready code, it was something I knocked up late in the evening.

    Cheers,

    Sean.

    Cheers,

    Sean.

  • But Sean thats the thing if it's a modelMetaDataProvider example why make it so "low coupled" and generic it's not something for a framework it's an example you can copy and modifie so why have code in it that dosen't do anything? If you hade an advanced situation in your example that your code solved it would be nice, but now I just think it's overkill.

    Sincerely, Magnus

  • Hi Magnus, we should probably leave it there as I'm not totally sure we're on the same page - there is no code that does nothing so I'm not totally following you. You are correct in that I could have chosen to make a non-generic control, I also could have strongly typed a view instead but it would have lost the point of the blog.
    But anyway, good debating with you.

    Kind Regards,

    Sean.

  • Thanks Sean, although this works if I mark up a field in the ViewModel, if I attempt to add an AutoComplete attribute to something in my EF model (via a buddy class) then it is ignored. (my ViewModel has a property of an EF class) (other MetadData on the buddy class works as expected. Any thoughts ?

  • Hi Andiih,
    It's possible that it's being ignored due to the default object template.
    The default object template pins down the view to only look at the top level object (this is set through the ViewData.TemplateInfo.TemplateDepth), this means that any properties which are complex objects like the EF class will be ignored as they have a higher template depth. - the solution to this is to override the object template.
    Here is a good link which will show how to override: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html
    Hope this helps,
    Sean.

  • Thanks. Scratching my head looking at that. I think I will go back to the 1st of his articles!

  • it seems classic ASP, I don't like MVC

  • Brilliant article! Your explanations are great. =)

    I've seen a few of these tutorials (autocomplete using jquery in MVC) but yours has been the cleanest way of implementing it now I'm looking at the source. Thank you VERY much!

  • Thanks Stewart.

  • Just wanted to say thanks for posting this article. This is EXACTLY what I wanted to do in my application!

  • Hi to every , since I am really keen of reading this website's post to be updated regularly. It includes pleasant stuff.

  • I know this site provides quality dependent posts and other data,
    is there any other web page which offers these information in quality?

  • Thanks to my father who stated to me about this blog, this weblog is really amazing.

  • Wow, fantastic weblog format! How long have you ever been blogging for?
    you make running a blog glance easy. The full glance of your website is fantastic, let alone the content!

  • I visited many web sites however the audio feature
    for audio songs present at this web site is
    truly marvelous.

  • An impressive share! I have just forwarded this onto
    a coworker who had been conducting a little research on this.
    And he in fact bought me breakfast because I discovered it
    for him... lol. So allow me to reword this...

    . Thanks for the meal!! But yeah, thanx for spending some time to talk about
    this issue here on your website.

  • I was recommended this blog by my cousin. I'm not sure whether this post is written by him as no one else know such detailed about my problem. You are incredible! Thanks!

  • I'm not sure where you are getting your info, but good topic. I needs to spend some time learning more or understanding more. Thanks for great info I was looking for this info for my mission.

  • Can I simply just say what a comfort to discover an individual
    who genuinely understands what they're discussing on the internet. You definitely know how to bring a problem to light and make it important. More and more people ought to look at this and understand this side of your story. I can't believe you're not more popular since you certainly possess the gift.

Comments have been disabled for this content.