You are only limited by your imagination

Stop SOPA/PIPA

January 18, 2012 by m0gb0y74 | 0 comments

The SOPA/PIPA bills threaten the open nature of the Web and thus threaten site like this. If you have the ability to vote in the United States, help us out and contact your representatives.

If you are not able to vote in the United States, you can help by petitioning the U.S. state department. Bills like these have a habit of spreading to other countries if we don’t stop them at their source.

Visit AmericanCensorship.org to learn how to do both.

ASP.NET MVC3 AJAX Search Tutorial

December 20, 2011 by m0gb0y74 | 0 comments

Overview

Having moved across to ASP.NET MVC3 within the last 6 months, one of the things that I found missing in the wealth of quality tutorials was how to use AJAX to pass a user-defined class. This tutorial aims to illustrate one way of doing this through the creation of an MVC3 AJAX-enabled search page to allow searching a catalogue of books.

You can download the completed C# project here.

Creating the Web Application

To start the tutorial, open Visual Studio 2010 and create a new project using the ASP.NET MVC 3 Web Application template. I named the application “Id.AjaxSearch.Example” but you can call it whatever you wish.

In the New ASP.NET MVC 3 Project dialog, select Intranet Application, select the Razor view engine, make sure ‘Use HTML5 semantic markup’ is checked and then click OK.

Adding the Model

The application uses the following simple model:

This is an Entity Framework 4.1 ‘Code-First’ model that uses SQL Server Compact 4.0 as its datastore. A detailed example of how to build a ‘Code-First’ model is detailed in the MVC Music Store tutorial – Part 4: Models and Data Access.

The ‘seed’ data to be added to the new model is found in the class ‘LibraryInitializer’ which is located in the Models folder. The code for this class is shown below.

protected override void Seed(LibraryEntities context)
{
    Subject Antiquarian = new Subject() { Name = "Antiquarian, Rare & Collectable" };
    Subject Biography = new Subject() { Name = "Biography" };
    Subject Crime = new Subject() { Name = "Crime, Thrillers & Mystery" };
    Subject History = new Subject() { Name = "History" };
    Subject Horror = new Subject() { Name = "Horror" };
    Subject Romance = new Subject() { Name = "Romance" };
    Subject SciFiAndFantasy = new Subject() { Name = "Science Fiction & Fantasy" };
    Subject Travel = new Subject() { Name = "Travel & Holiday" };

    context.Subjects.Add(Antiquarian);
    context.Subjects.Add(Biography);
    context.Subjects.Add(Crime);
    context.Subjects.Add(History);
    context.Subjects.Add(Horror);
    context.Subjects.Add(Romance);
    context.Subjects.Add(SciFiAndFantasy);
    context.Subjects.Add(Travel);

    Format Hardcover = new Models.Format() { Name = "Hardcover" };
    Format Paperback = new Models.Format() { Name = "Paperback" };
    Format Kindle = new Models.Format() { Name = "Kindle Books" };

    context.Formats.Add(Hardcover);
    context.Formats.Add(Paperback);
    context.Formats.Add(Kindle);

    Author LKHamilton = new Author() { Name = "Laurell K. Hamilton" };
    Author KCast = new Author() { Name = "Kristin Cast" };
    Author PCCast = new Author() { Name = "P. C. Cast" };
    Author RPike = new Author() { Name = "Richard Pike" };
    Author CGibson = new Author() { Name = "Chris Gibson" };
    Author RVincent = new Author() { Name = "Rachel Vincent" };
    Author CHarris = new Author() { Name = "Charlaine Harris" };
    Author JLake = new Author() { Name = "John Lake" };
    Author MStyling = new Author() { Name = "Mark Styling" };
    Author CWSmith = new Author() { Name = "Charles William Smith" };
    Author CTomalin = new Author() { Name = "Claire Tomalin" };
    Author ASugar = new Author() { Name = "Alan Sugar" };
    Author TPratchett = new Author() { Name = "Sir Terry Pratchett" };
    Author REFeist = new Author() { Name = "Raymond E. Feist" };
    Author PIrvine = new Author() { Name = "Peter Irvine" };
    Author DFriebe = new Author() { Name = "Daniel Friebe" };
    Author KDoner = new Author() { Name = "Kim Doner" };

    context.Authors.Add(LKHamilton);
    context.Authors.Add(KCast);
    context.Authors.Add(PCCast);
    context.Authors.Add(RPike);
    context.Authors.Add(CGibson);
    context.Authors.Add(RVincent);
    context.Authors.Add(CHarris);
    context.Authors.Add(JLake);
    context.Authors.Add(MStyling);
    context.Authors.Add(CWSmith);
    context.Authors.Add(CTomalin);
    context.Authors.Add(ASugar);
    context.Authors.Add(TPratchett);
    context.Authors.Add(REFeist);
    context.Authors.Add(PIrvine);
    context.Authors.Add(DFriebe);
    context.Authors.Add(KDoner);

    context.Books.Add(new Book()
    {
        Title = "The Lightning Boys: True Tales from Pilots of the English Electric Lightning",
        Price = 12.25M,
        Isbn = "190811715X",
        PublicationDate = new DateTime(2011, 7, 14),
        Format = Hardcover,
        Subject = History,
        Authors = new List<Author>() { RPike }
    });

    context.Books.Add(new Book()
    {
        Title = "Awakened (House of Night)",
        Price = 4.12M,
        Isbn = "1905654855",
        PublicationDate = new DateTime(2011, 10, 25),
        Format = Paperback,
        Subject = Horror,
        Authors = new List<Author>() { KCast, PCCast }
    });

    context.Books.Add(new Book()
    {
        Title = "Destined: A House of Night Novel",
        Price = 6.49M,
        Isbn = "1905654871",
        PublicationDate = new DateTime(2011, 10, 25),
        Format = Hardcover,
        Subject = Horror,
        Authors = new List<Author>() { KCast, PCCast }
    });

    context.Books.Add(new Book()
    {
        Title = "Dragon's Oath: A House of Night Novella",
        Price = 3.82M,
        Isbn = "1907411186",
        PublicationDate = new DateTime(2011, 7, 12),
        Format = Paperback,
        Subject = Horror,
        Authors = new List<Author>() { KCast, PCCast }
    });

    context.Books.Add(new Book()
    {
        Title = "Dragon's Oath: A House of Night Novella",
        Price = 3.56M,
        Isbn = "1907410708",
        PublicationDate = new DateTime(2010, 10, 26),
        Format = Hardcover,
        Subject = Horror,
        Authors = new List<Author>() { PCCast, KDoner }
    });

    context.Books.Add(new Book()
    {
        Title = "Bullet (Anita Blake, Vampire Hunter)",
        Price = 4.55M,
        Isbn = "0755352580",
        PublicationDate = new DateTime(2010, 11, 11),
        Format = Paperback,
        Subject = Horror,
        Authors = new List<Author>() { LKHamilton }
    });

    context.Books.Add(new Book()
    {
        Title = "Incubus Dreams (Anita Blake Vampire Hunter 12)",
        Price = 4.87M,
        Isbn = "0755355407",
        PublicationDate = new DateTime(2010, 3, 4),
        Format = Paperback,
        Subject = Horror,
        Authors = new List<Author>() { LKHamilton }
    });

    context.Books.Add(new Book()
    {
        Title = "Vulcan's Hammer: V-Force Aircraft and Weapons Projects Since 1945",
        Price = 20.80M,
        Isbn = "1902109171",
        PublicationDate = new DateTime(2011, 4, 30),
        Format = Hardcover,
        Subject = History,
        Authors = new List<Author>() { CGibson }
    });

    context.Books.Add(new Book()
    {
        Title = "Deadlocked: A True Blood Novel",
        Price = 14.24M,
        Isbn = "0575096578",
        PublicationDate = new DateTime(2012, 5, 17),
        Format = Hardcover,
        Subject = Horror,
        Authors = new List<Author>() { CHarris }
    });

    context.Books.Add(new Book()
    {
        Title = "B-52 Stratofortress Units 1955-73",
        Price = 12.99M,
        Isbn = "1841766070",
        PublicationDate = new DateTime(2004, 1, 16),
        Format = Paperback,
        Subject = History,
        Authors = new List<Author>() { JLake, MStyling }
    });

    context.Books.Add(new Book()
    {
        Title = "Abstractions",
        Price = 14517.21M,
        Isbn = "B00085JQEI",
        PublicationDate = new DateTime(1939, 1, 1),
        Format = Hardcover,
        Subject = Antiquarian,
        Authors = new List<Author>() { CWSmith }
    });

    context.Books.Add(new Book()
    {
        Title = "The Way I See It: Rants, Revelations And Rules For Life",
        Price = 8.00M,
        Isbn = "0230760899",
        PublicationDate = new DateTime(2011, 9, 29),
        Format = Hardcover,
        Subject = Biography,
        Authors = new List<Author>() { ASugar }
    });

    context.Books.Add(new Book()
    {
        Title = "The Way I See It: Rants, Revelations And Rules For Life",
        Price = 4.79M,
        Isbn = "0230760899",
        PublicationDate = new DateTime(2011, 9, 29),
        Format = Kindle,
        Subject = Biography,
        Authors = new List<Author>() { ASugar }
    });

    context.Books.Add(new Book()
    {
        Title = "Charles Dickens: A Life",
        Price = 13.50M,
        Isbn = "0670917672",
        PublicationDate = new DateTime(2011, 10, 6),
        Format = Hardcover,
        Subject = Biography,
        Authors = new List<Author>() { CTomalin }
    });

    context.Books.Add(new Book()
    {
        Title = "What You See Is What You Get",
        Price = 4.27M,
        Isbn = "0330520474",
        PublicationDate = new DateTime(2010, 9, 30),
        Format = Kindle,
        Subject = Biography,
        Authors = new List<Author>() { ASugar }
    });

    context.Books.Add(new Book()
    {
        Title = "Snuff: Discworld Novel 39",
        Price = 8.99M,
        Isbn = "038561926X",
        PublicationDate = new DateTime(2011, 10, 13),
        Format = Hardcover,
        Subject = SciFiAndFantasy,
        Authors = new List<Author>() { TPratchett }
    });

    context.Books.Add(new Book()
    {
        Title = "I Shall Wear Midnight: Discworld Novel 38",
        Price = 4.59M,
        Isbn = "0552555592",
        PublicationDate = new DateTime(2011, 6, 9),
        Format = Paperback,
        Subject = SciFiAndFantasy,
        Authors = new List<Author>() { TPratchett }
    });

    context.Books.Add(new Book()
    {
        Title = "I Shall Wear Midnight: Discworld Novel 38",
        Price = 3.99M,
        Isbn = "0552555592",
        PublicationDate = new DateTime(2011, 6, 9),
        Format = Kindle,
        Subject = SciFiAndFantasy,
        Authors = new List<Author>() { TPratchett }
    });

    context.Books.Add(new Book()
    {
        Title = "Magician",
        Price = 5.29M,
        Isbn = "0586217835",
        PublicationDate = new DateTime(2008, 9, 1),
        Format = Paperback,
        Subject = SciFiAndFantasy,
        Authors = new List<Author>() { REFeist }
    });

    context.Books.Add(new Book()
    {
        Title = "Silverthorn",
        Price = 6.74M,
        Isbn = "0007229429",
        PublicationDate = new DateTime(2008, 9, 1),
        Format = Paperback,
        Subject = SciFiAndFantasy,
        Authors = new List<Author>() { REFeist }
    });

    context.Books.Add(new Book()
    {
        Title = "A Darkness at Sethanon",
        Price = 5.29M,
        Isbn = "0007229437",
        PublicationDate = new DateTime(2008, 9, 1),
        Format = Paperback,
        Subject = SciFiAndFantasy,
        Authors = new List<Author>() { REFeist }
    });

    context.Books.Add(new Book()
    {
        Title = "Scotland The Best",
        Price = 9.49M,
        Isbn = "0007442440",
        PublicationDate = new DateTime(2011, 12, 8),
        Format = Paperback,
        Subject = Travel,
        Authors = new List<Author>() { PIrvine }
    });

    context.Books.Add(new Book()
    {
        Title = "Mountain High: Europe's 50 Greatest Cycle Climbs",
        Price = 10.00M,
        Isbn = "0857386247",
        PublicationDate = new DateTime(2011, 10, 27),
        Format = Hardcover,
        Subject = Travel,
        Authors = new List<Author>() { DFriebe }
    });

    context.SaveChanges();

}

Adding the View Model

In Solution Explorer, right-click the Project and add a new folder called ViewModels. Right-click this new folder and add a new class called SearchCriteria.cs.

This class represents the search criteria provided by the user when searching the books catalogue. A new folder was created to separate the domain (database) model from the view model.

Build the application so that the user model will be available to the scaffolding wizard in the next step.

Creating the Default View

The next step is to add an action method and view to allow the user to search.

Delete the existing Views\Home\Index.cshtml file. You will create a new Index file to display the search fields.

In the HomeController class, replace the contents of the class with the following code:

Models.LibraryEntities libraryDB = new Models.LibraryEntities();

public ActionResult Index()
{
    var formats = libraryDB.Formats.OrderBy(f => f.Name).ToList();
    ViewBag.Format = new SelectList(formats, "FormatId", "Name");

    var subjects = libraryDB.Subjects.OrderBy(s => s.Name).ToList();
    ViewBag.Subject = new SelectList(subjects, "SubjectId", "Name");

    return View();
}

Right-click inside the Index method and then click Add View.

Select the Create a strongly-typed view option. For Model class, select SearchCriteria. (If you don’t see SearchCriteria in the Model class box, you need to build the project.) Make sure that the View engine is set to Razor. Set Scaffold template to Details and then click Add.

Replace the contents of the newly created Views\Home\Index.cshtml file with the following:

@model Id.AjaxSearch.Example.ViewModels.SearchCriteria
@{
    ViewBag.Title = "Ajax Search Example :: Search Books";
}
<h2>
    Search Books</h2>
@using (Html.BeginForm())
{
    <fieldset id="search">
        <legend>Search Criteria</legend>
        <div>
            <div class="searchOption">
                @Html.LabelFor(model => model.Title)
                @Html.EditorFor(model => model.Title)
            </div>
            <div class="searchOption">
                @Html.LabelFor(model => model.FormatId)
                @Html.DropDownList("Format", "All Formats")
            </div>
        </div>
        <div>
            <div class="searchOption">
                @Html.LabelFor(model => model.Author)
                @Html.EditorFor(model => model.Author)
            </div>
            <div class="searchOption">
                @Html.LabelFor(model => model.SubjectId)
                @Html.DropDownList("Subject", "All Subjects")
            </div>
            <div class="searchOption">
                <input type="hidden" id="StartIndex" value="1" />
                <input type="button" id="Search" title="Search" value="Search" /> <img src="@Url.Content("~/Content/themes/base/images/busy.gif")" alt="Please wait..." title="Please wait..." id="waitImage" height="22" width="22" style="display: none;" />
            </div>
        </div>
    </fieldset>
}
<div id="searchResults">
    <!-- placeHolder for search results -->
</div>


Add the following to the bottom of Content\Site.css:

#search
{
    padding-top: 8px;
    width: auto;
    font-size: 0.85em;
}
#search label
{
    width: 75px;
    display: inline-block;
}
.searchOption
{
    margin: 4px 4px 2px 4px;
    display: inline;
}
#searchResults, #searchResults table
{
    width: 100%;
}

Creating the Search Results View

The next step is to add a Search action method and a partial view to display the search results.

Add the following Search method to the home controller:

[HttpPost]
public ActionResult Search(ViewModels.SearchCriteria criteria)
{

    string authorCriteria = string.Empty;
    string titleCriteria = string.Empty;

    if (!string.IsNullOrWhiteSpace(criteria.Author)) authorCriteria = criteria.Author.ToLower().Trim();
    if (!string.IsNullOrWhiteSpace(criteria.Title)) titleCriteria = criteria.Title.ToLower().Trim();

    IQueryable<Models.Book> results = libraryDB.Books;

    if (criteria.FormatId != 0)
        results = results.Where(b => b.Format.FormatId == criteria.FormatId);
    if (criteria.SubjectId != 0)
        results = results.Where(b => b.Subject.SubjectId == criteria.SubjectId);
    if(titleCriteria != string.Empty)
        results = results.Where(b => b.Title.ToLower().Contains(titleCriteria));
    if (authorCriteria != string.Empty)
    {
        results = (from book in results
                  from author in book.Authors
                  where author.Name.ToLower().Contains(authorCriteria)
                  select book).Distinct();
    }

    results = results.Include(b => b.Authors).OrderBy(b => b.Title);

    return PartialView("SearchResults", results.ToList());

}


Right-click inside the Search method and then select Add View.

Select the Create a strongly-typed view option. Name the view SearchResults, verify that the Model class box contains Book. Make sure that the View engine is set to Razor. Set Scaffold template to List. Select the Create as a partial view option and then click Add.

Replace the contents of the newly created Views\Home\SearchResults.cshtml file with the following:

@model IEnumerable<Id.AjaxSearch.Example.Models.Book>
<div id="searchResults">
    <table>
        <tr>
            <th>
                Title
            </th>
            <th>
                Authors
            </th>
            <th>
                Price
            </th>
            <th>
                Isbn
            </th>
            <th>
                Publication Date
            </th>
            <th>
                Format
            </th>
            <th>
                Subject
            </th>
        </tr>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Title)
                </td>
                <td>
                    <ul>
                        @foreach (var author in item.Authors)
                        {
                            <li>
                                @Html.DisplayFor(modelItem => author.Name)
                            </li>
                        }
                    </ul>
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Price)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Isbn)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.PublicationDate)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Format.Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Subject.Name)
                </td>
            </tr>
        }
    </table>
</div>


Notice that the partial view contains the same Div element that is present in the Index view that you created previously. Why this is there is discussed in the next section.

Creating the AJAX search method

Add the following to the bottom of Views\Home\Index.cshtml

<script type="text/javascript">

    $(function () {

        $("#waitImage").ajaxStart(function () {
            $(this).show();
        }).ajaxStop(function () {
            $(this).hide();
        });
        $("#Search").click(function () {
            var searchParameters = GetSearchParameters();

            var jsonData = JSON.stringify(searchParameters, null, 2);

            $.ajax({
                url: '@Url.Content("~/Home/Search/")',
                type: 'POST',
                data: jsonData,
                datatype: 'json',
                contentType: 'application/json; charset=utf-8',
                success: function (data) {
                    $('#searchResults').replaceWith(data);
                },
                error: function (request, status, err) {
                    alert(status);
                    alert(err);
                }
            });

            return false;
        });

        function GetSearchParameters() {
            var title = $("#Title").val();
            var formatId = $("#Format").val();
            var author = $("#Author").val();
            var subjectId = $("#Subject").val();

            if (formatId == undefined) formatId = 0;
            if (subjectId == undefined) subjectId = 0;

            return { Title: title,
                Author: author,
                FormatId: formatId,
                SubjectId: subjectId
            };
        }
    });
</script>


Performing the AJAX request is handle via jQuery. The jQuery library has a full suite of AJAX capabilities that are cross-browser.

The first step is to provide a JSON version of the SearchCriteria object that the Search method expects. This is done by constructing a JavaScript version of that object. This is achieved through the following code:

function GetSearchParameters() {
    var title = $("#Title").val();
    var formatId = $("#Format").val();
    var author = $("#Author").val();
    var subjectId = $("#Subject").val();

    if (formatId == undefined) formatId = 0;
    if (subjectId == undefined) subjectId = 0;

    return { Title: title,
        Author: author,
        FormatId: formatId,
        SubjectId: subjectId
    };
}


Using the JSON.stringify method this is then converted into a JSON string.

Finally the Ajax call is made to the server. Note that the url parameter is set by invoking a Razor method. This allows complete portability of you application. If the method is successful, the call-back function defined as the success parameter is invoked. This function replaces the element (and all its contents) defined with the results from the Search method. This is why it is important to wrap the search results in the same element that was used as place holder in the Index.cshtml view. If you didn’t return the element you replaced then you would not be able to find it to replace it with the results from further searches.

You now have a simple but functional AJAX enabled search page.

References

Semantic HTML – Why should we worry?

November 14, 2011 by m0gb0y74 | 0 comments

Semantic HTML is one of those ideals that I aspire to achieve. I see its value, but I am often faced with the worry that I am adding unnecessary markup in the form of DIVs and SPANs. Smashing Magazine has just published two excellent articles that cover the value of semantic HTML from two differing points of view.

Divya Manian argues in her article ‘Our Pointless Pursuit Of Semantic Value‘ that:

Mark-up structures content, but your choice of tags matters a lot less than we’ve been taught for a while.

On the surface this statement is correct. To many people ‘meaning’ in a document is conveyed by how it looks, and this is not just for those published as web pages. Think of a Word document. How many people use ‘Styles’ to define a heading and not just choose a larger font size and ‘bolding’ the text? Both methods achieve the same goal – a line of text that stands out from the rest.

You have to dig below the surface though. Still using the Word document example, you have to consider beyond just the visual. Making use of the ‘Styles’ to denote a heading truly sections the document and provides hooks to generate a table of contents and other functions. Benefits are provided from using Word and it’s markup correctly.

More importantly, we also have to consider the fact that many users suffer from varying degrees of visual impairment. These users rely on various aides to help them ‘read’ the web page. Many of these ‘aides’ rely on the tags that are used within the document to provide the intent of the text in the same way that spacing, font type, font size etc provide visually. Ask yourself this – are you callous enough to disregard these people, when you may be one of those people in the future?

Jeremy Keith responds to Ms Manian in his article ‘Pursuing Semantic Value‘ and does point out what Divya Manian was really trying to get at:

Not semantics are useless but its not worth worrying over minute detail on.

I agree with this statement to a degree. The example Divya presents at the start of her article of somebody worrying about the amount of DIV and SPAN tags that they have used within their page is exactly the same I was confronted with. My take on all this is as follows.

Make sure the semantic meaning of the document is correct but don’t get hung up on the amount of layout tags that you are using.

PostScript, LaTeX, Word etc all have layout control characters or tags. They are needed and are used as often as required, whether that is a couple or a couple of hundred. Just remember that  text surrounded by STRONG or EM has a meaning beyond the visual that is not conveyed by a styled DIV or SPAN.

If my argument has yet to sway you, let me make a cheap shot at your ego – in the Word example, the user who doesn’t use ‘Styles’ is the novice who is just muddling their way through. The user that uses the ‘Styles’ is the proficient user who knows what they are doing. Which are you?