Auto Complete in Razor MVC with JQuery and LINQ to Entities


I had to create an auto-complete text box this week in Razor and didn’t see anything specific to doing it in Razor so I thought I’d throw this out there. I would like to say this post for getting it to work in MVC was very helpful and it didn’t take much effort to move from that to Razor and the Entity Framework.

In my example below I’ll be auto populating the product name based on the existing product names in the database using LINQ to Entities.

Step 1 : Download the JQuery Auto-Complete Library from google code

http://code.google.com/p/jquery-autocomplete/downloads/list

Step 2: Extract the following files into your scripts directory

jquery.autocomplete.js
jquery.autocomplete.css
indicator.gif

You can move them around later but for now lets keep them all in one folder and modify the css file to point to our new location for the loading gif.

background : url(‘/scripts/indicator.gif’) right center no-repeat;

Step 3 : Build your controller class

I’m selecting the top 10, distinct where the product name contains the typed in text.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MyNameSpace.Models;

namespace MyNameSpace.Controllers
{
    public class AutoCompleteController : Controller
    {
        MyNameSpaceEntities mhgDb = new MyNameSpaceEntities();

        public ActionResult ProductName(string q)
        {
            var productNames = (from p in mhgDb.Products where p.ProductName.Contains(q) select p.ProductName).Distinct().Take(10);

            string content = string.Join<string>("\n", productNames);
            return Content(content);
        }

    }
}

Step 4:  Add the JS and CSS files to your view


<script src="@Url.Content("~/Scripts/jquery.autocomplete.js")" type="text/javascript"></script>
<link href="@Url.Content("~/Scripts/jquery.autocomplete.css")" rel="stylesheet" type="text/css" />

Step 5 : Bind the Textbox to the Controller


<div class="editor-label">
@Html.LabelFor(model => model.ProductName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ProductName)
@Html.ValidationMessageFor(model => model.ProductName)
</div>

<script type="text/javascript">

$(document).ready(function () {
$("#ProductName").autocomplete('@Url.Action("ProductName", "AutoComplete")', { minChars: 3 });
});

</script>

Step 6 : Build and Test

That’s all, there’s some tips around styling and parameters for autocomplete in the link to the other blog post I linked to above. I’d be interested in seeing a better LINQ statement if anyone has one.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MyHobbyGear.Models; 

namespace MyHobbyGear.Controllers
{
public class AutoCompleteController : Controller
{
MyHobbyGearEntities mhgDb = new MyHobbyGearEntities();
//
// GET: /AutoComplete/

public ActionResult Manufacturer(string q)
{
var manufacturers = (from p in mhgDb.Products where p.ManufacturerName.Contains(q) select p.ManufacturerName).Distinct().Take(10);

string content = string.Join<string>(“\n”,manufacturers);
return Content(content);
}

public ActionResult ProductName(string q)
{
var productNames = (from p in mhgDb.Products where p.ProductName.Contains(q) select p.ProductName).Distinct().Take(10);

string content = string.Join<string>(“\n”, productNames);
return Content(content);
}

}
}

Advertisements

Rich Text Editing with CKEditor, MVC 3 and the Razor Engine


I was creating a model /view for editing large chunks of text on my website with MVC and the Razor engine. I created everything following Sott Gu’s tutorial on how to build a music store. When all was said and done I had a 50 character wide single line textbox for editing a large html block and thought ok this isn’t I deal.  Here are the steps I took to get a rich text editor in there.

Most of this came from this post on code project but it’s a bit outdated and doesn’t have everything.

The FCK Editor became the CK Editor, I guess they opted to drop the F over adding a U.

Step 1  : Change to Text Area

You should  have something like this in your Create and Edit Views

@Html.EditorFor(model => model.Text)

Lets change that to :

@Html.TextAreaFor(model => model.Text, new { @class = “adminRichText” })

Step 2 : Widen the text area

In your Site.Css file add the folowing to widen the text area :

.adminRichText
{
width:450px;
}

Step 3 : Add the CK Editor (formerly FCK Editor) to your project

a. Download here

b. Create a ‘Javascript’ folder under ‘Content’ in your website.

c. Extract to /Javascript/Content.

Step 4 : Include the scripts in your View

Below the validation scripts add

<script type=”text/javascript” src=”@Url.Content(“~/content/javascript/ckeditor/ckeditor.js”)”></script>

<script type=”text/javascript” src=”@Url.Content(“~/content/javascript/ckeditor/adapters/jquery.js”)”></script>

Step 5 : Turn the text area into a Rich Text Box

At bottom of view after controls are initialized add:

<script language=”javascript” type=”text/javascript”>
jQuery(“.adminRichText”).ckeditor();
</script>

Step 6 : Enable Post Back of Rich Text for your Edit and Create Methods in your controller class

[HttpPost, ValidateInput(false)]
public ActionResult Create(AreaText areaText)

[HttpPost, ValidateInput(false)]
public ActionResult Edit(string id, FormCollection collection)

Step 7: Use Html.Raw to display the data

On your index view update the output area to decode the html.

item.Text

becomes

@Html.Raw(item.Text)

ckEditor in mvc

ckEditor in mvc

Note : There are some good suggestions in the comments on ways to continue using validation :

Option 1 from Micheal.
“In your CKEditor config file add the following line:
config.htmlEncodeOutput = true;”

Option 2 from Blair.
“I was able to get this to work without having to turn off validation by putting in
[AllowHtml] in the property definition”

Internal linking with a C# HttpModule


I’m still not sure all this relative vs absolute path internal linking stuff I’m reading about in SEO ins’t due to the fact there’s confusion between what an absolute link is vs a link with a fully qualified domain but here’s a http module that will include the fully qualified domain with protocol for all of your internal links.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Text;

namespace MvcApplication1
{
     public class AbsolutePathRewriter : IHttpModule
    {
        HttpApplication application;
        #region IHttpModule Members
        public void Dispose()
        {
            //Empty
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(OnBeginRequest);
            application = context;
        }

        #endregion
        void OnBeginRequest(object sender, EventArgs e)
        {
            if (application.Request.AppRelativeCurrentExecutionFilePath.Contains(".aspx"))
            {
                application.Response.Filter = new AbsolutePathRewriterStream(application.Response.Filter);
            }
        }
    }

    public class AbsolutePathRewriterStream : MemoryStream
    {
        private Stream outputStream = null;

        public AbsolutePathRewriterStream(Stream output)
        {
            outputStream = output;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            string bufferContent = UTF8Encoding.UTF8.GetString(buffer);
            string absolutePath = HttpContext.Current.Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Authority;
            bufferContent = bufferContent.Replace("href=\"/", "href=\"" + absolutePath + "/");
            bufferContent = bufferContent.Replace("href='/", "href='/" + absolutePath + "/");
            outputStream.Write(UTF8Encoding.UTF8.GetBytes(bufferContent), offset, UTF8Encoding.UTF8.GetByteCount(bufferContent));
            base.Write(buffer, offset, count);
        }
    }
}


MVC Routes and SEO


I’ve been looking at Micrsofts MVC engine lately and I can’t help but think why isn’t the default route more SEO friendly.

When I create a new project this code is placed inside of my global.asax.cs file.

public static void RegisterRoutes(RouteCollection routes)
 {
 routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

 routes.MapRoute(
 "Default", // Route name
 "{controller}/{action}/{id}", // URL with parameters
 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
 );

 }

That will give me a product url like this :

www.somewebsite.com/products/view/1.aspx

But I’d rather have a friendly name in there somewhere like this :

www.somewebsite.com/products/view/my-friendly-product-name/1.aspx

So I think if you go with defualt routing you’re making a mistake.