Jon's Blog

.NET Development & More

ASP.NET MVC and jQuery UI Autocomplete

I wanted to document an easy way to add auto complete functionality to a search box in ASP.NET MVC using the jQuery UI Autocomplete widget. I've tried to make the code simple while still being left with a workable example.  This solution is a simplified version of this functionality I saw while watching the free ASP.NET MVC 3 video series from K. Scott Allen of Pluralsight.


Layout

First, below is the layout file we are using for this example.  Nothing special here.  Only thing to really note is the two optional sections: styles and scripts.  These are used on the View to add specific CSS and JavaScript needed for the example.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>ASP.NET MVC Example</title>
    @Styles.Render("~/Content/css")
    @RenderSection("styles", required: false)
</head>
<body>
    @RenderBody()

    @Scripts.Render("~/bundles/jquery")
    @RenderSection("scripts", required: false)
</body>
</html>

 

View

Now let's talk about the View.  The jQuery UI AutoComplete widget is being setup in the scripts @section by calling the jQuery UI autocomplete method and passing the properties I want to use. I am setting the source to the SearchAutoCompleter action method of my SearchExample Controller (see the next section).  I am also setting minLength to 2.  This means the call to the action method will not happen unless the user enters at least 2 characters.

<h2>Autocomplete Enabled Search</h2>

@using (Html.BeginForm())
{
    <input type="text" name="searchQuery" id="autocomplete" />
    <input type="submit" value="Search" />
}
    
@section scripts
{
    <script type="text/javascript" src="~/Scripts/jquery-ui-1.8.24.min.js"></script>
    <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
    
    <script type="text/javascript">
        $(document).ready(function () {
            $("#autocomplete").autocomplete({
                source: "@Url.Action("SearchAutoCompleter","SearchExample")",
                minLength: 2
            });
        });
    </script>
}

 

Controller

Below is an example of what you would have in your controller's action method.  I am showing a LINQ query that could be accessing a LINQ to SQL or Entity Framework repository, etc.  Here I am just getting 10 distinct items, but you can do whatever logic you need to do.  Notice that I am creating an anonymous type with one property named "label".  This is what the jQuery UI AutoComplete widget is expecting.  Note that label is case sensitive here.  Finally, the collection is returned as Json for the AutoComplete widget to process.

public class SearchExampleController : Controller
{
  public JsonResult SearchAutoCompleter(string term)
  {

      var myObjects = (from o in myRepository.MyClass
                      where o.ColumnToSearchOn.Contains(term)
                      select new{
                          label = o.ColumnToSearchOn
                      }).Distinct().Take(10);

      return Json(myObjects, JsonRequestBehavior.AllowGet);
  }
}

Resources

 

Reporting Services: Unspecified Error

First off, I want to start by saying I don't know a whole lot about Reporting Services. Smile

We recently had an issue where a staff member told us that they hadn't been receiving a certain report. When I logged onto Reporting Services and tested the connection to the data source I was getting "Unspecified Error". Not too helpful. I then tried to find the last successful run. I could not find this in the Reporting Services interface. However, there is a view named ExecutionLog2 that had what I wanted.

SELECT TOP 100 elog.status, elog.TimeStart, elog.*
FROM [dbo].[ExecutionLog2] elog
WHERE elog.reportPath = 'MyReportPath'
AND elog.UserName = 'MyReportUser'
ORDER BY elog.timestart DESC

We actually ended up restarting the Reporting Services service and the "Unspecified Error" went away. I just thought I'd post this in case it helps anyone else.

Using SQL Session State with Multiple Environments

I really like storing Session State out-of-process in a SQL Server database. However, I use custom code that switches the connection string based on the current environment my code is running in (test, stage, production, etc.). It was a little tricky to get this setup properly. But I am pretty happy with the following solution.

  • First, you have to setup the Session State database on the SQL machines/environments you are interested in storing your out-of-process session state in. You need to execute a command similar to the following on the command line to build out the database structure:
    aspnet_regsql -S localhost -E -ssadd -sstype p
    See this link for further information: http://msdn.microsoft.com/en-us/library/ms229862%28v=vs.100%29.aspx
  • Add the following line in your web.config under the <system.web> node. Make note of the partitionResolverType. We will discuss that further in the next step.
    <sessionState mode="SQLServer" partitionResolverType="MyApplication.Utility.SessionConnStringResolver" timeout="60" cookieless="false"/>
  • The partitionResolverType above can reference a cusom class that can be used to choose which connection string you want to use at run-time. For example you can create the following class for dynamically switching your connection string:
namespace MyApplication.Utility
{
    public class SessionConnStringResolver : System.Web.IPartitionResolver
    {
        public void Initialize() { }

        // Return the correct ASP.NET Session DB for the current running environment
        public String ResolvePartition(Object key)
        {
            // Custom code to get connection string based on current environment
            return MyConfigClass.GetConfigSettings.SessionDBConnString;
        }
    }
}

 

If you are using LINQ to SQL you should also set Serialization Mode to Unidirectional on the Object Relational Designer as discussed in this link. This allows you to serialize/deserialize your LINQ classes when saving to the database.

For a better explanation of the Partition Resolver feature of SQL Server Session State see the following links. Above is just the steps I followed to set everything up.

ASP.NET AJAX UpdatePanel + jQuery UI Dialog

The jQuery UI Dialog does not seem to play nicely with the ASP.NET AJAX UpdatePanel.  After doing a partial page postback using an UpdatePanel the jQuery UI Dialog div would no longer function.  Thanks to Stack Overflow and some blog posts I discovered that you need to add some code to hook into the ASP.NET AJAX JavaScript pageLoaded event and this will reinitialize the jQuery UI Dialog div every time.  We also need to append the div to the form element so that any ASP.NET buttons on the div will post back to our page.  In the following JavaScript snippet my jQuery modal dialog div has an ID of "myDiv". Also, in this example we are setting up the dialog to be modal and autoOpen equal to false.

<script type="text/javascript">
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function (evt, args) {

    var myDiv = $("#myDiv").dialog({ autoOpen: false, modal: true, open: function (type, data) {
         $(this).parent().appendTo("form");
        }
    });
});
</script>

LINQ: Complicated Advanced Search with Dynamic Where Clause

Recently I was working a project that needed a very complicated Advanced Search.  I had to dynamically build the Where clause.  The tricky part for this project was that the Where statement would be using both SQL ORs and ANDs.  Using LINQ, the prior way I was building dynamic Where statements was just using ANDs.  Basically I was just adding AND statements if they needed to be included in the query.  However, this time I needed to include a clause where the user could pick one or more of an item from a list.  Thankfully, I discovered the PredicateBuilder class (part of the LINQKit library), which allows you to dynamically build a Where statement with both ANDs and ORs.

I have put together a very simple and crude example to summarize what I did.  Please excuse the simplicity of the code below.  Usually I would write the code much cleaner, but I wanted to keep it simple for readability. 

So let's say you have an advanced restaurant search form.  The user must select one or more cuisine types and then can optionally choose the city, state, country where the restaurant is located.  The form might look like this:

The query I was building in my project was more complicated than this, but you get an idea of how complicated these kinds of queries can get. Here's some example SQL of what LINQ might need to generate.

SELECT *
FROM Restaurant
WHERE City='Dallas'
AND State='TX'
AND(Mexican=1 OR German=1 OR Italian=1)

 

With the PredicateBuilder class this is totally doable.  Following is the button click event for the Search button where you can get an idea of how to use PredicateBuilder.  First, you build the inner OR statement and then you can add that to your full Where clause at the end.  This is just a quick example, so go read the documentation for a better understanding of how PredicateBuilder works.

using LinqKit;

protected void btnSearch_Click(object sender, EventArgs e)
{
    // Create Inner Where Statements
    var innerWhere = PredicateBuilder.False<Restaurant>();

    if (cbMexican.Checked)
        innerWhere = innerWhere.Or(q => q.Mexican);

    if (cbItalian.Checked)
        innerWhere = innerWhere.Or(q => q.Italian);
    
    if (cbChinese.Checked)
        innerWhere = innerWhere.Or(q => q.Chinese);

    if (cbGerman.Checked)
        innerWhere = innerWhere.Or(q => q.German);

    if (cbAmerican.Checked)
        innerWhere = innerWhere.Or(q => q.American);

    // Build Full Where Statement
    var outerWhere = PredicateBuilder.True<Restaurant>();

    if (!string.IsNullOrEmpty(ddlCountry.SelectedValue))
        outerWhere = outerWhere.And(q => q.Country == ddlCountry.SelectedValue);

    if (!string.IsNullOrEmpty(ddlState.SelectedValue))
        outerWhere = outerWhere.And(q => q.State == ddlState.SelectedValue);

    if (!string.IsNullOrEmpty(txtCity.Text.Trim()))
        outerWhere = outerWhere.And(q => q.City == txtCity.Text.Trim());

    // Add the inner OR query to the Where statement
    outerWhere = outerWhere.And(innerWhere);

    var query = from r in myDC.Restaurants.Where(outerWhere)
                select r;

    // Bind query to ListView, GridView, etc.
}

 

ASP.NET: Improving Your Error Logging & Handling

One of the first things I like to setup when I am creating a new site is a customized error page and error logging.  To log all unhandled errors on your site you can add some code to the Application_Error event of your Global.asax file.  What I do is to grab the exception and then I use a static method of a helper class I have in my class library.  I pass the name of the web application where the error occurred and the exception itself.  In the helper method I can then extract the details I need from the exception and log it to a database table.

Global.asax.cs:

public const string APP_NAME = "Name of My App";

void Application_Error(object sender, EventArgs e)
{
    Exception ex = Server.GetLastError().GetBaseException();
    SiteErrorLog.LogError(APP_NAME, ex);
}

 

Next, in the web.config you can setup your site so the user sees a nice, customized error page instead of the default ASP.NET error page.  Under the <system.web> node, add the following:

<customErrors mode="RemoteOnly" defaultRedirect="ErrorPage.aspx"></customErrors>

 

Now when your site is hit from a remote client (RemoteOnly) and an unhandled error occurs they will be redirected to the page you created (ErrorPage.aspx).  This is much nicer for the user of your site.

ASP.NET AJAX: Return to Top of Page After Partial Postback

After an AJAX partial postback you may need to return to the top of your ASPX page to display an error message, etc.  Here is one way that I have done it.  You can add the JavaScript function below to your ASPX page and then call the method when needed in your code-behind by using the ScriptManager.RegisterClientScriptBlock method.

ASP.NET C# Code-behind:

ScriptManager.RegisterClientScriptBlock(this, Page.GetType(), 
     "ToTheTop", "ToTopOfPage();", true);

 

JavaScript:

<script type="text/javascript">
    function ToTopOfPage(sender, args) {
        setTimeout("window.scrollTo(0, 0)", 0);
    }
</script>

 

You can also just JavaScript to scroll to the top of the page by using the OnClientClick property of your button.  But this will cause this behavior to occur every time the button is clicked and not just when you want it to happen.  For example:

<asp:Button id="bntTest" runat="server"
Text="Test" OnClick="btn_Test"
OnClientClick="javascript:window.scrollTo(0,0);" />

How to Use FreeTextTable

The FreeTextTable function in SQL Server is very useful when you need to search a column for words, phrases, etc. FreeTextTable will return a table that you can join on.  It also returns the Rank of the matches so you can order the most relevant matches at the top.

In the example below I am searching the Keywords column of the MyTable table using the @Keywords passed into the stored procedure.  I am then ordering them by Rank so the highest matches show up first.

CREATE PROCEDURE [dbo].[FreeTextSample]
    @Keywords [varchar](1000) = ''
WITH EXECUTE AS CALLER
AS
BEGIN
    SET NOCOUNT ON;

    SELECT myTable.MyPrimaryKeyID,
        myTable.Title,
        myTable.Description
  FROM MyTable myTable(nolock) INNER JOIN FreeTextTable(dbo.MyTable, Keywords, @Keywords) AS searchTable ON myTable.MyPrimaryKeyID = searchTable.[KEY] ORDER By searchTable.[RANK] DESC END GO

 

One thing to note is that LINQ does not contain an equivalent keyword for FreeTextTable.  So what I did was to create a new method on my DataContext that returns the results of the stored procedure.  See my post here for further details on how to do this.

ASP.NET: Fun with Paths and Directories

In the following table the page executing the code is at the following physical location on my machine:

C:\Users\me\Documents\Visual Studio 2010\Projects\TestStuff\TestStuff\Nested\Paths.aspx

Property/Method and Description Result
Request.ApplicationPath

Gets the root virtual path.

/TestStuff
Request.CurrentExecutionFilePath

Gets the virtual path to the current file.
Same as Request.Path and Request.FilePath.

/TestStuff/Nested/Paths.aspx
Request.CurrentExecutionFilePathExtension

Gets the file extension of the current file.

.aspx
Request.FilePath

Gets the virtual path to the current file.
Same as Request.CurrentExecutionFilePath and Request.Path.

/TestStuff/Nested/Paths.aspx
Request.MapPath("")

Gets the full physical path to the current directory. Seems to be the same as Server.MapPath("").

C:\Users\me\Documents\Visual Studio 2010\Projects\TestStuff\TestStuff\Nested
Request.Path

Gets the virtual path to the current file.
Same as Request.CurrentExecutionFilePath and Request.FilePath.

/TestStuff/Nested/Paths.aspx
Request.PhysicalApplicationPath

Gets the physical path to the root directory.
Seems to be the same as AppDomain.CurrentDomain.BaseDirectory.

C:\Users\me\Documents\Visual Studio 2010\Projects\TestStuff\TestStuff\
Request.PhysicalPath

Gets the full physical path to the current file.

C:\Users\me\Documents\Visual Studio 2010\Projects\TestStuff\TestStuff\Nested\Paths.aspx
AppDomain.CurrentDomain.BaseDirectory

Gets the physical path to the root directory.
Seems to be the same as Request.PhysicalApplicationPath.

C:\Users\me\Documents\Visual Studio 2010\Projects\TestStuff\TestStuff\
Server.MapPath("")

Gets the full physical path to the current directory.
Seems to be the same as Request.MapPath("").

C:\Users\me\Documents\Visual Studio 2010\Projects\TestStuff\TestStuff\Nested

 

Also, if you are joining a directory to a file/path I recommend using the Path.Combine method found in the System.IO namespace.  Here is an example:

using System.IO;

string fileName = "MyFile.pdf";
string fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
fileName);

Hiding ASP.NET Ajax Modal Popup Dialog Using JavaScript

Following is an easy way to hide the modal popup dialog extender from the AJAX control toolkit using JavaScript.  The key is to set a BehaviorID on the ModalPopupExtender.  Then you can use this ID to call the hide() method via Javascript like this:

function HideModal() {
  $find('modalPopupBehavior').hide();
}

In the full example below I am closing the modal popup dialog box when the user clicks on a link.

Full ASPX example:

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxtk" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>My Page</title>
    <link href="css/Test.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="aspNetForm" runat="server">
    
<asp:ScriptManager ID="scriptMan" runat="server" />

<script type="text/javascript" language="javascript">
    function HideModal() {
        $find('modalPopupBehavior').hide();
    }
</script>

<asp:UpdatePanel ID="upnlMain" runat="server">
    <ContentTemplate>
    
        <div>
            <ajaxtk:ModalPopupExtender ID="modalPopup" runat="server" 
                BehaviorID="modalPopupBehavior"
                TargetControlID="btnPopup" PopupControlID="pnlPopup">
            </ajaxtk:ModalPopupExtender>
            
            <asp:Button ID="btnPopup" runat="server" Text="Show Popup" />
            
            <asp:Panel ID="pnlPopup" runat="server" CssClass="modalPopup" 
                style="width: 715px; display: none;">
                <div>
                    <p>
                    <a href="http://www.jonathanjungman.com/blog/" 
                        target="_blank" onclick="HideModal()">Hide Modal</a>
                    </p>
                </div>
            </asp:Panel>
        </div>
    
    </ContentTemplate>
</asp:UpdatePanel>
   
</form>
</body>
</html>