ninja star Goodbye Http Handler, Hello FileResult

by Michael Ceranski, posted on March 07 2010

If you have been developing applications in ASP.NET MVC then you are probably familiar with the ActionResult class. The ActionResult is the most common type of object returned from an action. When building MVC apps, most of time you will use the ActionResult class.

    Last week while I was working on my open source project WeBlog, I built an HTTP Handler to serve up images. I started using an HTTP Handler for images because I needed a mechanism to prevent bandwidth leeching. The only bad thing about using an HTTP handler for images is that you end up with some pretty ugly URLS. In my case the URL ended up looking like this:

/Image.axd?image=sample.png

Luckily, my friend Ron noticed my new HTTP Handler and mentioned that I could have accomplished the same thing with a controller action that returned a FileResult instead. After a bit of investigation, I realized that Ron was absolutely right. I deleted my HTTP Handler and replaced it with this code, which was added to the Home Controller:

private string GetContentType(string filename) {
    FileInfo file = new FileInfo(filename);            
    switch (file.Extension.ToUpper()) {
        //images
        case ".PNG" : return "image/png";                                
        case ".JPG" : return "image/jpeg";                    
        case ".JPEG": return "image/jpeg";                    
        case ".GIF" : return "image/gif";                    
        case ".BMP" : return "image/bmp";                    
        case ".TIFF": return "image/tiff";
        default:
            throw new NotSupportedException("The Specified File Type Is Not Supported");
    }            
}

public FileResult GetImage(string id) {
    string path = Path.Combine(Engine.GetImageDirectory().FullName, id);            
    return base.File(path, GetContentType( path ) );
}
 

Since this code resides in my Home controller I would need to use the URL like “/Home/GetImage/sample.png” to display an image. Admittedly this URL is still a big ugly, so I decided to use a custom route to clean it up. The new custom route is named “Images” and is mapped it to the Home controller’s GetImage method. Here is the entry used in the global.asax file:

routes.MapRoute("Images",
    "Images/{id}",
    new { controller = "Home", action = "GetImage", id = "" });

Now I can display images by using the following URL:

”/Images/sample.png”

To the end user, this looks like a traditional file path. However, in reality there is no “Images” folder in the root directory. “Images” is just the name of the route being used. In reality, the image files are actually stored in the App_Data/Images folder.

By using a FileResult object with MVC you not only get a pretty URL but you also get a lot of flexibility on where you want your images to reside. You can store images anywhere you want and the URL will never need to change!

ninja star Building a Star Rating System with ASP.NET MVC and jQuery

by Michael Ceranski, posted on March 01 2010

While working on the WeBlog project I realized that I needed a star rating system for blog posts. A star rating allows your readings to rate content based on a 0-5 scale.

image 

A fully lit star represents a full point on the rating scale. Therefore in order to give half point increments each star uses two images. 

Left off: star-left-off Left on: star-left-on Right off: star-right-off Right on: star-right-on

When you put a left and a right image together it forms a complete star. So If you have a rating a 3.5 you would have the following stars displayed: Star #1 left on, right on #2 left on, right on #3 left on, right on #4 left on, right off #5 left off, right off.

Displaying the Current Rating

In MVC, the logic to determine which stars (images) should be initially displayed is best accomplished with a HTML helper:

public static string Ratings(this HtmlHelper helper, PostModel post) {
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("<span class='rating' rating='{0}' post='{1}' title='Click to cast vote'>", post.Rating, post.ID);
    string formatStr = "<img src='/Content/images/{0}' alt='star' width='5' height='12' class='star' value='{1}' />";

    for (Double i = .5; i <= 5.0; i = i + .5) {
        if (i <= post.Rating) {
            sb.AppendFormat(formatStr, (i * 2) % 2 == 1 ? "star-left-on.gif" : "star-right-on.gif", i);
        }
        else {
            sb.AppendFormat(formatStr, (i * 2) % 2 == 1 ? "star-left-off.gif" : "star-right-off.gif", i);
        }
    }
    sb.AppendFormat("&nbsp;<span>Currently rated {0} by {1} people</span>", post.Rating, post.Raters);
    sb.Append("</span>");
    return sb.ToString();
}

This helper method builds the images based on the post's current rating. The loop, which creates the images, steps in half point increments. If the counter variable is less than or equal to the post rating then a "turned on" image is displayed. There is also logic in the loop to determine if a right or left image is displayed based on the current counter value. Once, the helper method is in place you can display the rating control with the following line of code :

<%=Html.Ratings( Model ) %>

 

User Rating Mode

When a user hovers over the stars, the control goes into "user rating mode". Instead of the stars showing the average rating it will now show the rating that the user is trying to apply to the post. So if I hover over the first half of the third star, all the images up to that point will show as turned on, and the remaining images will be turned off. In order to toggle the images on and off I used jQuery.

The following code is fired whenever a user puts their mouse over an element which uses the class name "star". All the images in the star rating control use are utilizing the “star” class. Each image element also has an attribute "value" which contains the rating for that particular element. For example the first half star has a value of .5, the second star has a value of 1 and so on and so forth. The images are then updated using the setRating function which we will discuss momentarily.

$(".star").mouseover(function () {
    var span = $(this).parent("span");
    var newRating = $(this).attr("value");
    setRating(span, newRating);
});

A similar function is used to re-draw the average rating when the user’s mouse is moved out of the control. Whenever a user leaves the control the average rating needs to be displayed again. The average rating value is stored in a rating attribute on the span that encapsulates all the images:

$(".star").mouseout(function () {
    var span = $(this).parent("span");
    var rating = span.attr("rating");
    setRating(span, rating);
});

Here is the setRating function. This function updates the images based on the rating value:

function setRating(span, rating) {
    span.find('img').each(function () {
        var value = parseFloat($(this).attr("value"));
        var imgSrc = $(this).attr("src");
        if (value <= rating)
            $(this).attr("src", imgSrc.replace("-off.gif", "-on.gif"));
        else
            $(this).attr("src", imgSrc.replace("-on.gif", "-off.gif"));
    });
}

The final piece of jQuery code is used to handle the click event. The click event is responsible for casting the vote to the server:

$(".star").click(function () {
    var span = $(this).parent("span");
    var newRating = $(this).attr("value");
    var postID = span.attr("post");
    var text = span.children("span");
    $.post("/Post/SetRating", { id: postID, rating: newRating },
        function (obj) {
            if (obj.Success) {
                text.html("Currently rated " + obj.Result.Rating + " by " + obj.Result.Raters + " people"); //modify the text
                span.attr("rating", obj.Result.Rating); //set the rating attribute
                setRating(span, obj.Result.Rating); //update the display
                alert("Thank you, your vote was casted successfully.");
            }
            else {
                alert(obj.Message); //failure, show message
            }
        }
    );
});

This code is fired whenever you click on one of the stars. The Post controller has a method called SetRating  ("/Post/SetRating") which is called using jQuery's post method. This makes an asynchronous call to the server and saves the rating to the data store. When the results are returned from the server an object is returned which contains a Boolean flag (obj.Success) indicating the data was updated successfully, an optional message (obj.Message) which can display and errors caught in the controller and a Result (obj.Result) object which contains the updated value for the number of raters (obj.Result.Raters) and the average rating (obj.Result.Rating). Here is the code for the SetRating method:

public JsonResult SetRating(Guid id, double rating) {
    try {
        if (CanUserVote(id, rating) == false) {

            return Json(new JsonResponse
            {
                Success = false,
                Message = "Sorry, you already voted for this post"            
            });
        }
        PostModel post = Engine.Posts.SetRating(id, rating);
        return Json(new JsonResponse
        {
            Success = true,
            Message = "Your Vote was cast successfully",
            Result = new { Rating = post.Rating, Raters = post.Raters }
        });
    }
    catch (Exception ex) {
        return Json(new JsonResponse
        {
            Success = false,
            Message = ex.Message                    
        });
    }
}

In the code above, there is a call to the method named CanUserVote. This code prevents users from voting multiple times on the same post by storing a cookie. Basically, all this method does is store an entry for each post in a cookie named "Votes". If the post is not in the cookie then it returns true, meaning you can vote. Otherwise it returns false.

private Boolean CanUserVote(Guid id, double rating) {
    HttpCookie voteCookie = Request.Cookies["Votes"];

    if (voteCookie != null) {
        if (voteCookie[id.ToString()] != null) {
            return false;
        }
    }

    //create the cookie and set the value
    voteCookie = new HttpCookie("Votes");
    voteCookie[id.ToString()] = rating.ToString();
    Response.Cookies.Add(voteCookie);
    return true;
}
 

Conclusion

Building a star rating system is a lot of fun. The algorithms are straightforward and jQuery makes the client side code easy to write. If you want to see the working demo then download the source from the WeBlog project. WeBlog is the open source blogging engine that I started a couple of months ago. WeBlog is written in MVC2 and NetFx 4 and relies on jQuery for the client side magic. You will need Visual Studio 2010 RC in order to compile it.

ninja star Client Side Validation with jQuery

by Michael Ceranski, posted on February 24 2010

Validating user input on the client definitely has its advantages. It not only avoids unnecessary round trips to the server but it can also drastically improve the user experience. When I think about client side validation I think about jQuery. If you are familiar with jQuery, then you know that there are a ton a plug-ins available. Just like an Apple IPod has “an app for that”,  jQuery has a “plugin for that”. Anyway, after a quick search I discovered the jQuery Validation plugin.

There are two different ways to use the Validation plugin. The first way, is to the pure JavaScript route. This means you establish all the validation rules, messages and callback events from code. The second way is to decorate your input fields with special CSS classes. The plugin will then inspect the class attribute of each field at runtime and apply validation rules accordingly. In the upcoming example, I used the CSS approach. However, if you decided that you want to go in the other direction there are some excellent demos on the validation plugin homepage.

There are a bunch of different class names that the plugin will honor. The most basic class name is required. However in addition to checking if a field is required you can also perform pattern matching. For example my form has an email field. When someone fills out this field I not only want it to be required, but I also want to make sure that a valid email address is entered. In order to achieve this I use the class name required email. Similar validators are available for website addresses, dates, credit card numbers and more. See the documentation page for the complete list.

Here is a  screenshot of my comment form:

 image

Here is the HTML markup for my form. Notice the class names used in each of the input fields. From the html you can deduce that the full name is required. The Email is required and must be a valid address. The website field is optional but if text is entered is must be a valid URL. Finally, the comment field is required: 

<form id="comment_form" action="#">
<fieldset>
    <ul>
        <li>
            <label>Full Name *</label>                
            <input type="hidden" id="postID" name="postID" value='<%=Model.ID %>' />
            <input type="text" id="author" name="author" class="required" value="" size="40"/>
        </li>
        <li>
            <label>Email *</label>
            <input type="text" id="email" name="email" class="required email" value="" size="40"/>
        </li>        
        <li>
            <label>Website</label>
            <input type="text" id="website" name="website" class="url" size="40" />
        </li>        
        <li>
            <label>Comment *</label>
            <textarea id="comments" name="comments" rows="8" cols="60" class="required"></textarea>
        </li>
        <li>
            <label></label>
            <input type="submit" id="btnSaveComment" value="Save Comment" class="submit" />        
            <img id="ajax_loading" src="/Content/images/ajax_loading.gif" alt="loading" />          
        </li>
    </ul>
</fieldset>
</form>     


To wire up the plug-in you only need one line of code in the document.ready event:

var validator = $("#comment_form").validate();

Summary

Client side validation is a great way to enhance user experience. However, keep in mind that client side and server side validation can be used together. As always, pick the right tool for the job. For example, if you are working with mobile devices that have limited capabilities it may make more sense to use server-side validation.

Tags: ,

ninja star Getting Started with LINQ to XML

by Michael Ceranski, posted on February 21 2010

man-pulling-hair-out I recently announced the WeBlog project. WeBlog is a blogging platform which will support multiple data providers. Out of the box I plan on offering SQL Server and XML support. Most people like the XML option because it drastically reduces web hosting costs. The only problem with XML is that it can be painful to work with. In general, XML makes me want to pull my hair out!

When building a blog you have a few basic entities that you need to deal with. Most typical blogs have posts, categories, tags, users and roles. Therefore I made an XML file to represent each of these items. However, for this tutorial I will focus on parsing the XML for categories. For a point of reference here is the XML structure that I am using to store category information:

 

<?xml version="1.0" encoding="utf-8"?>
<categories>
  <category id="19770e74-9ec9-4cde-b2ab-e5051aaaf348" description="Posts about my adventures with WeBlog" 
     parent="" name="WeBlog">
    <posts>
      <post id="0e05a782-7440-46e9-8fc4-e33fd51685e9" />
    </posts>
  </category>
  <category id="c223353c-1aef-4a46-afd1-cb61ab1a792d" description="" parent="" name="Tech">
    <posts>
      <post id="1aeaa3a2-6dfb-4a57-a633-0c1597e162ff" />
    </posts>
  </category>
<categories>

From the XML above you can deduce that each category has a ID, Name, Description and Parent. In addition, the category has related posts.

Since my application supports multiple data providers I first created an interface which all providers must adhere to. The methods defined in the interface return common objects. This allows me to abstract from the underlying data store. For example, here is the CategoryModel class which is really just an object representation of the XML:

public class CategoryModel {
    public Guid ID { get; set; }
    public Guid? Parent { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public int PostCount { get; set; }
}

Selecting Data

The job of the XML data provider is to create a list of CategoryModel objects by reading the XML. Luckily, this process is relatively simple with LINQ to XML. First, we create an XDocument object and then loop through each category node. For each category we can read the attributes and elements to populate a CategoryModel object which can be added to a generic list:

 public List<CategoryModel> FindCategories()
 {      
     List<CategoryModel> categories = new List<CategoryModel>();
     XDocument xmlDoc = GetCategoryXML();
     var query = from category in xmlDoc.Descendants("category")
                 select new CategoryModel
                 {
                     ID = Guid.Parse(category.Attribute("id").Value),
                     Name = category.Attribute("name").Value,
                     Parent = (Guid?)(category.Element("parent") == null ? (Guid?)null :
                              Guid.Parse(category.Element("parent").Value)),            
                     Description = category.Attribute("description").Value,
                             PostCount = category.Descendants("post").Count()   
                 };
     categories = query.ToList();            
     return categories;
 }

Deleting Data

To delete a category we can use a similar approach. Again we need to create an XDocument and find the correct node by matching the id attribute of the category node. Once, we have the proper node selected we can call Remove() and then save the document:

public void DeleteCategory( Guid id )
{            
    XDocument xmlDoc = GetCategoryXML();
    var category = from x in xmlDoc.Descendants("category")
                where
                    x.Attribute("id").Value == id.ToString()
                select x;
    category.Remove();
    xmlDoc.Save(GetCategoryXMLFilename());   
}

Creating Data

Creating data is also a simple task. You create the category element and then assign the attributes and child elements. The syntax is a little bit different than the select and delete operations but overall it is fairly straightforward:

public void InsertCategory(CategoryModel source)
{
    XDocument xmlDoc = GetCategoryXML();
    XElement category = new XElement( "category",
                            new XAttribute( "id", source.ID.ToString() ),                                    
                            new XAttribute("description", source.Description),
                            new XAttribute( "parent", source.Parent == null ? "" : source.Parent.ToString() ),
                            new XAttribute("name", source.Name),
                        new XElement( "posts" ));
    xmlDoc.Element("categories").Add( category );            
    xmlDoc.Save(GetCategoryXMLFilename());
}

Updating Data

In order to update data you need to first find the correct element in the XML document. Once you have the element selected you can update the attributes and child values by using assignment operators. Once you make the updates you can save the document :

public void UpdateCategory(CategoryModel source)
{
    XDocument xmlDoc = GetCategoryXML();
    var category = ( from x in xmlDoc.Descendants("category")
                   where
                      x.Attribute("id").Value.Equals(source.ID.ToString(), StringComparison.CurrentCultureIgnoreCase)
                   select
                      x ).Single();
    category.Attribute("description").Value = source.Description;
    category.Attribute("name").Value = source.Name;
    xmlDoc.Save(GetCategoryXMLFilename());
}

As I mentioned before, XML is not my favorite format but LINQ to XML at least makes the process bearable. Hopefully this brief introduction will make you feel better about XML parsing. Maybe it will even save you from pulling your hair out!

Tags: ,

ninja star Announcing WeBlog – A Next Generation Blogging Platform

by Michael Ceranski, posted on February 19 2010

One of the best ways to keep your skills sharp as a developer is to start your own open source project. For the last 6 months or so I have been trying to come up with ideas on what kind of application I want to build. Since I enjoy blogging so much I decided that I will build my own blogging platform. There are a lot of good blogging platforms already available such as Wordpress, dasBlog and BlogEngine.NET. In order to make something comparative to these products will take a fair amount of time and effort. However, having a pet project like this is fun and I am really looking forward to working on it.

The WeBlog (pronounced We Blog) project is hosted on codeplex. I have already checked in some code and things are starting to take shape even though it is still early in the process. If you want to compile the code you will need need Visual Studio 2010. I figured there is no point starting a project on old technology so I went for the latest and greatest technology with MVC 2 and the .NET Framework 4.

Initial Feature List

Here are the list of features that I want to include in the first release. Once, I start gaining some momentum I will be looking for feedback from the user community.

  • Multiple Data Provider Support - Data can be stored in XML or in SQL Server. The data layer will be extensible so people can write their own providers. By having XML as an option, hosting is cheaper because you will not need to pay for a database.
  • Migration from BlogEngine.NET will be included in the first release. This is for selfish reasons :-)
  • Theme support – The goal is to make theme development a relatively simple task so I can get people in the community to develop new ones.
  • Full syndication support – RSS and Atom
  • Social networking support. Will start with twitter first. If time permits I will work on a mechanism to find tweets related to a post and add them to the comments section (a.k.a. Tweetbacks).
  • Anti-Spam features - Comments will use the ReCaptcha and Askimet to block spam comments. Comment moderation will be included.  
  • Gravatar support.
  • Trackbacks and Pingbacks.
  • Windows Live Writer Support – A must have, can't write posts without it!

Why Build Another Blogging Engine

First of all, there are a lot of different technologies and skills required to build a successful blog engine. Even if the project fails to gain popularity it will still be a good learning experience. The goal, is to build a new blogging platform that can be used as a showcase for modern .NET programming technologies.

Secondly, most of the developers that I admire have built or worked on a blog platform. The list includes Scott Hanselman (dasBlog) , Phil Haack (Subtext) and Rob Conery (see the video series on TekPub Build Your Own Blog—Rails ).

Thirdly, I really enjoy blogging. Its a way to give back to the community and share ideas and solutions with other developers. Unfortunately, It's not always easy to come up with ideas to write articles about. This project should provide me with plenty of content for the months (and possibly years) to come. As a side effect I will be building my very own blogging platform!

Tags: , ,

About the author

MikeMichael Ceranski is a developer specializing in the .NET stack. I have spent time as a DBA, Web Developer and even a network engineer. Up til now most of my career has revolved around the .NET stack but I have recently taken an interest in microcontrollers which has forced me to get acquainted with lower level languages such as C, and C++.

View my resume

Sponsors