Wednesday, December 19, 2012

Implementing a Tree View - Small Case Study

Implementing the control that allows navigating my blog history could be roughly divided into 4 steps.

1. Select and group posts from the database

Here the LINQ grouping came handy. Starting with grouping posts by year published to create my top level in hierarchy, the query would look like this:

var results = from allPosts in db.Posts.OrderBy(p => p.DateCreated)
     group allPosts by allPosts.DateCreated.Year into postsByYear;

Here results is the enumeration of groups - in my case, groups of posts which were published in the certain year. Posts are grouped by the key, which is defined in the IGrouping interface.

Moving further, I want to create child groups, in my case - posts by the month. I have to add a nested query along these lines

var results = from allPosts in db.Posts.OrderBy(p => p.DateCreated)
     group allPosts by allPosts.DateCreated.Year into postsByYear

     select new
     {
      postsByYear.Key,
      SubGroups = from yearLevelPosts in postsByYear
         group yearLevelPosts by yearLevelPosts.DateCreated.Month into postsByMonth;
     };

This is still not too bad. The first level are posts by year. Each year has SubGroups property which stores the group of posts published in a certian month. Now I finally need to get all the posts published in a month. I end up with the following query:

var results = from allPosts in db.Posts.OrderBy(p => p.DateCreated)
     group allPosts by allPosts.DateCreated.Year into postsByYear

     select new
     {
      postsByYear.Key,
      SubGroups = from yearLevelPosts in postsByYear
         group yearLevelPosts by yearLevelPosts.DateCreated.Month into postsByMonth
         select new
         {
          postsByMonth.Key,
          SubGroups = from monthLevelPosts in postsByMonth
             group monthLevelPosts by monthLevelPosts.Title into post
             select post
         }
     };

It is fully functional and suits my purposes. It is on the edge of being unreadable, however, and if I had to add one more level of depth it would probably be beyond. Following the example from Mitsu Furuta's blog, I make the query generic. The GroupResult class holds the grouping key and the group items. The GroupByMany extension allows for an undefined number of group selectors. This is the code I need to make it work:

public static class MyEnumerableExtensions
{
 public static IEnumerable<GroupResult> GroupByMany<TElement>(this IEnumerable<TElement> elements, params Func<TElement, object>[] groupSelectors)
 {
  if (groupSelectors.Length > 0)
  {
   var selector = groupSelectors.First();

   //reduce the list recursively until zero
   var nextSelectors = groupSelectors.Skip(1).ToArray();
   return
    elements.GroupBy(selector).Select(
     g => new GroupResult
     {
      Key = g.Key,
      Items = g,
      SubGroups = g.GroupByMany(nextSelectors)
     });
  }
  else
   return null;
 }
}

public class GroupResult
{
 public object Key { get; set; }
 public IEnumerable Items { get; set; }
 public IEnumerable<GroupResult> SubGroups { get; set; }
}

And now I can rewrite my query in one line:

var results = db.Posts.OrderBy(p => p.DateCreated).GroupByMany(p => p.DateCreated.Year, p => p.DateCreated.Month);

2. Populate a tree structure that will be used to generate HTML

I used a complete solution suggested by Mark Tinderhold almost without changes.

The BlogEntry class has a Name, which will be rendered, and references to Children and Parent nodes.

public class BlogEntry : ITreeNode<BlogEntry>
{
 public BlogEntry()
 {
  Children = new List<BlogEntry>();
 }

 public string Name { get; set; }
 public BlogEntry Parent { get; set; }
 public List<BlogEntry> Children { get; set; }
}

A list of BlogEntry is populated from my query results

var entries = new List<BlogEntry>();

//years
foreach (var yearPosts in results)
{
 //create "year-level" item
 var year = new BlogEntry { Name = yearPosts.Key.ToString().ToLink(string.Empty) };
 entries.Add(year);

 //months
 foreach (var monthPosts in yearPosts.SubGroups)
 {
  var month = new BlogEntry { Name = new DateTime(2000, (int)monthPosts.Key, 1).ToString("MMMM").ToLink(string.Empty), Parent = year };
  year.Children.Add(month);

  foreach (var postEntry in monthPosts.Items)
  {
   //create "blog entry level" item
   var post = postEntry as Post;
   var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl()), Parent = month };
   month.Children.Add(blogEntry);
  }
 }
}

3. Use the tree structure to generate HTML

The TreeRenderer writes out HTML.

public interface ITreeNode<T>
{
 T Parent { get; }
 List<T> Children { get; }
}

public static class TreeRenderHtmlHelper
{
 public static string RenderTree<T>(this HtmlHelper htmlHelper, IEnumerable<T> rootLocations, Func<T, string> locationRenderer) where T : ITreeNode<T>
 {
  return new TreeRenderer<T>(rootLocations, locationRenderer).Render();
 }
}
public class TreeRenderer<T> where T : ITreeNode<T>
{
 private readonly Func<T, string> locationRenderer;
 private readonly IEnumerable<T> rootLocations;
 private HtmlTextWriter writer;
 public TreeRenderer(IEnumerable<T> rootLocations, Func<T, string> locationRenderer)
 {
  this.rootLocations = rootLocations;
  this.locationRenderer = locationRenderer;
 }
 public string Render()
 {
  writer = new HtmlTextWriter(new StringWriter());
  RenderLocations(rootLocations);
  return writer.InnerWriter.ToString();
 }
 /// <summary>
 /// Recursively walks the location tree outputting it as hierarchical UL/LI elements
 /// </summary>
 /// <param name="locations"></param>
 private void RenderLocations(IEnumerable<T> locations)
 {
  if (locations == null) return;
  if (locations.Count() == 0) return;
  InUl(() => locations.ForEach(location => InLi(() =>
  {
   writer.Write(locationRenderer(location));
   RenderLocations(location.Children);
  })));
 }
 private void InUl(Action action)
 {
  writer.WriteLine();
  writer.RenderBeginTag(HtmlTextWriterTag.Ul);
  action();
  writer.RenderEndTag();
  writer.WriteLine();
 }
 private void InLi(Action action)
 {
  writer.RenderBeginTag(HtmlTextWriterTag.Li);
  action();
  writer.RenderEndTag();
  writer.WriteLine();
 }
}

The renderer will be called the following way from the view:

<div id="treeview" class="treeview">
    @MvcHtmlString.Create(Html.RenderTree(Model.BlogEntries, x => x.Name))
</div>

4. Render the HTML on the webpage

After reviewing a couple of other options, I decided on a jsTree. It has rich capabilities, but to this point I only used the "default" options. I added the tree to the _Layout.cshtml by adding a line of code

@Html.Action("BlogResult", "BlogEntry")

This line calls the function in the BlogEntryController

public PartialViewResult BlogResult()
{
 var results = db.Posts.OrderBy(p => p.DateCreated).GroupByMany(p => p.DateCreated.Year, p => p.DateCreated.Month);

 entries = new List<BlogEntry>();
 
 //code that populates entries - see above

 BlogEntryViewModel model = new BlogEntryViewModel(entries);

 return PartialView(model);
}

The BlogEntryViewModel is extremely simple.

public class BlogEntryViewModel
{
 public List<BlogEntry> BlogEntries { get; set; }

 public BlogEntryViewModel(List<BlogEntry> blogEntries)
 {
  BlogEntries = blogEntries;
 }

 public BlogEntryViewModel()
 {
 }
}

Finally, the partial view that is rendered

@model Recipes.ViewModels.BlogEntryViewModel

@{ Layout = null; }

<link href="@Url.Content("~/Content/blogentry.css")" rel="stylesheet" type="text/css" />

<!-- Tree View jstree -->
<script src="@Url.Content("~/Scripts/jquery.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.hotkeys.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.cookie.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.jstree.js")" type="text/javascript"></script>

<script type="text/javascript">
    jQuery(function ($) {
        $("#treeview").jstree({ "plugins": ["themes", "html_data"] });
    });
</script>

<div class="blogheader">
<h2>Blog Archives</h2>
</div>
<div id="treeview" class="treeview">
    @MvcHtmlString.Create(Html.RenderTree(Model.BlogEntries, x => x.Name))
</div>

What I had to make sure of to make it work:

And for information, this is the contents of blogentry.css

div.treeview, div.blogheader {
    width: 14em;
    background: #eee;
   overflow: hidden;
 text-overflow: ellipsis;
}

div.blogheader h2 
{
    font: bold 11px/16px arial, helvetica, sans-serif;
    display: block;
    border-width: 1px;
    border-style: solid;
    border-color: #ccc #888 #555 #bbb;
    margin: 0;
    padding: 2px 3px;
    
    color: #fff;
    background: #000;
    text-transform: uppercase;
}

The end result looks like that:

Resulting TreeView

Resulting Treeview

References:

How can I hierarchically group data using LINQ?
Playing with Linq grouping: GroupByMany ?
Rendering a tree view using the MVC Framework
jQuery Treeview Plugin Demo
jsTree – Part 1: Introduction
jsTree on GitHub
Best place to learn JStree
by . Also posted on my website

Sunday, December 9, 2012

SEO Basics: Friendly URLs

Implementing SEO-friendly URLs turned out to be much easier than I expected - MVC routing already takes care of the "heavy lifting". The developer only needs to provide a function that returnes the "friendly urls" from strings (product names, blog titles etc.) and to update action links.

1. Routing. A new route needs to be added. It has to be added above the default route so that MVC framework attempted to match it first. The seofriendly parameter can be pretty much anything that will satisfy valid url requirements.

routes.MapRoute(
 name: \"SEOFriendly\",
 url: \"{controller}/{action}/{id}/{seofriendly}\",
 defaults: new { controller = \"Home\", action = \"Index\", id = UrlParameter.Optional, seofriendly = \"\" }
);

2. Creating friendly urls. Here is an example I found on the web and added it "as is".

public static string ToSeoUrl(this string url)
{
 // make the url lowercase
 string encodedUrl = (url ?? \"\").ToLower();

 // replace & with and
 encodedUrl = Regex.Replace(encodedUrl, @\"\&+\", \"and\");

 // remove characters
 encodedUrl = encodedUrl.Replace(\"'\", \"\");

 // remove invalid characters
 encodedUrl = Regex.Replace(encodedUrl, @\"[^a-z0-9]\", \"-\");

 // remove duplicates
 encodedUrl = Regex.Replace(encodedUrl, @\"-+\", \"-\");

 // trim leading & trailing characters
 encodedUrl = encodedUrl.Trim('-');

 return encodedUrl;
}  

3. Making use of the friendly url. Just adding an extra parameter to the object.

Before:

<div class=\"display-button\">@Html.ActionLink(\"Edit\", \"Edit\", new { id=item.PostID }) </div>
<div class=\"display-button\">@Html.ActionLink(\"Details\", \"Details\", new { id = item.PostID }) </div>
<div class=\"display-button\">@Html.ActionLink(\"Delete\", \"Delete\", new { id = item.PostID }) </div>

After:

<div class=\"display-button\">@Html.ActionLink(\"Edit\", \"Edit\", new { id=item.PostID, seofriendly = item.Title.ToSeoUrl() }) </div>
<div class=\"display-button\">@Html.ActionLink(\"Details\", \"Details\", new { id = item.PostID, seofriendly = item.Title.ToSeoUrl() }) </div>
<div class=\"display-button\">@Html.ActionLink(\"Delete\", \"Delete\", new { id = item.PostID, seofriendly = item.Title.ToSeoUrl() }) </div>

References:

SEO-Friendly URLs in ASP.Net MVC 3
How can I create a friendly URL in ASP.NET MVC?
by . Also posted on my website

Sunday, December 2, 2012

SEO Basics: Linking My Content to Google+ Using rel='author'

I've learned the first step of using rich snippets to make links to my content look better in search results. The process is not extremely complicated, but it also is not intuitive to me, so I'd better write it down. I linked the content on my Blogger blog and also on my website which I'm using as training grounds. There are several steps involved - I need to modify my Google+ account, and I need to modify the content where I publish it.

1. Google+ account.

Assuming I already have a Google+ profile with photo on it, I go to my Profile, About and select Edit Profile.

Edit Profile

I scroll down to where Contributor to section is. In there I add the places I'm going to post my content. I edit this section to specify where my content is posted. Now Google+ knows where I'm posting, but that's not enough - I have to provide a way to verify that it's actually me.

Edit Contributor

2. My Website.

Here I have full control! I can experiment without fear to break things beyond repair. I did a few simple things so far: In the _Layout.cshtml, the partial view that is rendered on every page, I added the link to my Google+ account

<head>
    <link rel="author" href="https://plus.google.com/112677661119561622427/posts"/>
 ...
</head>

Additionally (optional) I modified the view that will display my posts to update the MetaKeywords and MetaDescription (see my previous post) dynamically.

@{
    ViewBag.MetaDescription = "Description of this post";
    ViewBag.MetaKeywords = "Keywords of this post";
    ViewBag.Title = Model.Title;
}

I'll add appropriate properties to the Model later, but that's beyond the scope of this post. I think that's all.

3. Blogger.

For reason I'll try to explain below, I had to add the following to the template of my blog in Blogger:

<a class='updated' expr:href='data:post.url' rel='bookmark' title='permanent link'><abbr class='updated' expr:title='data:post.timestampISO8601'><data:post.timestamp/></abbr></a>

Edit Blogger Template

I also added the same link as I did for my website - I'm not sure it's absolutely necessary though.

I'll also be adding the following to the end of my posts:

by <a title="Evgeny" rel="author" href="https://plus.google.com/112677661119561622427?rel=author" alt="Google+" title="Google+">Evgeny</a>.

With all that done I can publish this very post on my website and Blogger and then test the results.

4. Testing

Now I can test the results by entering the link to my post in the Structured Data Testing Tool. I enter the url and the tool tests the link for me.

This is the Blogger post.

Blogger - Positive Test Result

And this is my website.

Website - Positive Test Result

Finally, what would have happened if I hadn't added that bit to the Blogger template? I did not save the exact screenshot, but the error returned was "Missing required field 'Updated'" and looked similar to the image below.

Missing Required Field "Updated"

References

Warning: Missing required field "updated" in Blogger Rich Snippet Webmaster Tool [Solved]
Embrace Authorship - The importance of rel=me and rel=author on your content's SEO and Google
Rich snippets for idiots. And, er, you.
by . Also posted on my website

Wednesday, November 28, 2012

MVC and SEO basics: inject title, keywords and description into views

Almost by accident, I came across Google's starter guide for SEO optimisation and decided that it is a good idea to make some improvements I've been neglecting. Here's what I did so far and how I applied it to the MVC framework.

1. Create unique, accurate page titles

One way to do it with the MVC framework is to create a placeholder on the master page and then override it on the view page.

Master:


    
    
        <%=this.Page.Title%>
    

View:


       Home Page

For now, I chose the easier approach to set the title in the _Layout.cshtml

@ViewBag.Title

And assign it in each view separately

@{
    ViewBag.Title = "Main Page - The Stepping Stone Markov Chain Algorithm - MVC Stepping Stone Example";
}

2. Make use of the "description" and "keywords" meta tags

This takes a little more work. Here's my chosen approach: First, make sure each controller inherits from the BaseController. Then create two new classes, MetaDescriptionAttribute and MetaKeywordsAttribute, and inherit them from System.Attribute

public class MetaDescriptionAttribute : Attribute
{
 private readonly string _parameter;

 public MetaDescriptionAttribute(string parameter)
 {
  _parameter = parameter;
 }

 public string Parameter { get { return _parameter; } }
}

public class MetaKeywordsAttribute : Attribute
{
 private readonly string _parameter;

 public MetaKeywordsAttribute(string parameter)
 {
  _parameter = parameter;
 }

 public string Parameter { get { return _parameter; } }
}

In BaseController, override OnActionExecuting

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
 var keywords = filterContext.ActionDescriptor.GetCustomAttributes(typeof(MetaKeywordsAttribute), false);
 if (keywords.Length == 1)
  ViewData["MetaKeywords"] = ((MetaKeywordsAttribute)(keywords[0])).Parameter;

 var description = filterContext.ActionDescriptor.GetCustomAttributes(typeof(MetaDescriptionAttribute), false);
 if (description.Length == 1)
  ViewData["MetaDescription"] = ((MetaDescriptionAttribute)(description[0])).Parameter;

 base.OnActionExecuting(filterContext);
}

Decorate the appropriate controller method with newly created attributes

[MetaKeywords("C#, MVC, Markov Chain, Stepping Stone, Programming")]
[MetaDescription("Stepping Stone Markov Chain model is an example that has been used in the study of genetics. In this model we have an n-by-n array of squares, and each square is initially any one of k different colors. For each step, a square is chosen at random. This square then chooses one of its eight neighbors at random and assumes the color of that neighbor")]
public ActionResult Index()
{
 SteppingStoneHelpers.CreateNewTable();
 HtmlString table = new HtmlString(SteppingStoneHelpers.table.ToString());
 return View(table);
}

Finally, in the _Layout.cshtml, add the following


All done! There we go, the resulting html:




    
    
    Main Page - The Stepping Stone Markov Chain Algorithm - MVC Stepping Stone Example
    
    

References:

Google Starter Guide
ASP.NET MVC - View with master page, how to set title?
asp.net mvc - strategy for including SEO information such as meta keywords and descriptions
by . Also posted on my website

Monday, November 26, 2012

WebGrid: AJAX Updates, Server Sorting

This is a brief summary of the changes I did to implement the AJAX updates to the WebGrid and sorting behaviour. I plan to put more detailed notes and the source code on my website

To use AJAX and update grid content, firstly the grid needs to be placed in the div which has an id. The ajaxUpdateContainerId has to be specified in the WebGrid declaration. To put it simple, in my main view I have a div

<div id="wbgrid" style="float:left; min-width:500px;">
 @Html.Partial("_WebGrid", Model)
</div>

And in the partial view the div name wbgrid is specified as ajaxUpdateContainerId.

@{ var grid = new WebGrid<Recipes.Models.Yahoo.YahooData>(null, rowsPerPage: 5, defaultSort: "YahooSymbolName", ajaxUpdateContainerId: "wbgrid");
grid.Bind(Model.Datas, rowCount: Model.TotalRows, autoSortAndPage: false);
}

The link on the WebGrid column has the following format http://localhost/Yahoo/Index?sort=DateTime&sortdir=ASC

Therefore, the controller function can automatically receive those parameters with the following signature:

public ActionResult Index(int page = 1, string sort = "YahooSymbolName", string sortDir = "Ascending")

The parameters will be then passed over to the function that retrieves data

public List<YahooData> GetData(out int totalRecords, int pageSize, int pageIndex, string sort = "YahooSymbolName", SortDirection sortOrder = SortDirection.Ascending )
{
 IQueryable<YahooData> data = db.YahooData;
 totalRecords = data.Count();

 Func<IQueryable<YahooData>, bool, IOrderedQueryable<YahooData>> applyOrdering = _dataOrderings[sort];
 data = applyOrdering(data, sortOrder == SortDirection.Ascending);

 List<YahooData> result = data.ToList();
 if(pageSize > 0 && pageIndex >=0)
 {
  result = result.Skip(pageIndex*pageSize).Take(pageSize).ToList();
 }
 return result;
}

A couple of helper functions are utilized by GetData: CreateOrderingFunc and _dataOrderings

// helpers that take an IQueryable<Product> and a bool to indicate ascending/descending
// and apply that ordering to the IQueryable and return the result
private readonly IDictionary<string, Func<IQueryable<YahooData>, bool, IOrderedQueryable<YahooData>>>
 _dataOrderings = new Dictionary<string, Func<IQueryable<YahooData>, bool, IOrderedQueryable<YahooData>>>
       {
        {"YahooSymbolName", CreateOrderingFunc<YahooData, string>(p=>p.DataName)},
        {"Ask", CreateOrderingFunc<YahooData, decimal?>(p=>p.Ask)},
        {"Time", CreateOrderingFunc<YahooData, DateTime>(p=>p.DateTime)}
        // Add for more columns ...
       };

/// returns a Func that takes an IQueryable and a bool, and sorts the IQueryable (ascending or descending based on the bool).
/// The sort is performed on the property identified by the key selector.
private static Func<IQueryable<T>, bool, IOrderedQueryable<T>> CreateOrderingFunc<T, TKey>(Expression<Func<T, TKey>> keySelector)
{
 return
  (source, ascending) => ascending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector);
}

Finally, to complete the functional example, I added a jQuery dialog that displays the data that was retrieved from Yahoo. In the view, the RetrieveData function triggers the controller action AddDataToDB (which calls the Yahoo website and adds results to the database).

function RetrieveData() {
 $.post('@Url.Action("AddDataToDB","yahoo")',
 function (d) {
  ShowDialog(d.o);
 });
}

function ShowDialog(msg) {
 $('<div/>').dialog({ title: 'Retrieved the following data', width: 450, height: 250, close: function(){ location.reload(); }}).html(msg);
}

AddDataToDB returns a Json result, containing the html table.

public ActionResult AddDataToDB()
{
 List<YahooData> datas = GetSingleSet();
 datas.ForEach(d => db.YahooData.Add(d));
 db.SaveChanges();

 string s = "<table><thead><tr class=\"webgrid-header\"><th>Company</th><th>Time</th><th>LTP</th><th>Volume</th><th>Ask</th><th>Bid</th><th>High</th><th>Low</th></tr></thead><tbody>";

 foreach (var yahooData in datas)
 {
  s = s + "<tr class=\"webgrid-row-style\">" + 
   "<td class=\"company\">" + yahooData.DataName + "</td>" +
   "<td class=\"time\">" + yahooData.DateTime.ToString("dd/MM/yyyy hh:mm") + "</td>" +
   "<td class=\"ask\">" + yahooData.LTP + "</td>" +
   "<td class=\"volume\">" + yahooData.Volume + "</td>" +
   "<td class=\"ask\">" + yahooData.Ask + "</td>" +
   "<td class=\"ask\">" + yahooData.Bid + "</td>" +
   "<td class=\"ask\">" + yahooData.High + "</td>" +
   "<td class=\"ask\">" + yahooData.Low + "</td>" +
   "</tr>";
 }

 s = s + "</tbody></table>";

 return Json(new { o = s });
}

The result is then utilised by the ShowDialog function, that displays a jQuery dialog. When the user closes the dialog, the page is refreshed so that the WebGrid contents are updated with the latest data retrieved.

Complete example

References

Get the Most out of WebGrid in ASP.NET MVC
by . Also posted on my website

Sunday, November 25, 2012

WebGrid: Stronly Typed with Server Paging

Continuing with the WebGrid, I first made it strongly typed. To achieve that, I created a derived type WebGrid. The source came from the reference at the end of the post, and the code I used is displayed below too. While it does not seem to change much in terms of functionality, the main advantage is that the IntelliSense and compiler checking will work with the grid now.

Next I added server paging. Why wouldn't I use the built-in paging? Well, the database table behind WebGrid may have hundreds of records. I wouldn't want to pass it all to my ViewModel and then to the view just to display 5 or 10 of the records I actually need. It is handy that the WebGrid paging is in the form of http://website/Items/ShowAll?page=3. This way my controller knows which page is to be displayed and can preselect the data just for this page only.

To implement paging, I added the TotalRows to the model - that will specify the total number of records in the database table.

The controller method now looks as follows:

public ActionResult Index(int page=1)
{
 int totalRecords;
 List<YahooData> datas = GetData(out totalRecords, pageSize: 5, pageIndex: page - 1);
 List<YahooSymbol> symbols = db.YahooSymbols.ToList();
 YahooSymbol symbol = symbols.First();
 int id = symbol.YahooSymbolID;
 return View(new YahooViewModel(id, symbol, symbols, datas, totalRecords));
}

public List<YahooData> GetData(out int totalRecords, int pageSize, int pageIndex)
{
 List<YahooData> data = GetData();
 totalRecords = data.Count;
 if(pageSize > 0 && pageIndex >=0)
 {
  data = data.Skip(pageIndex*pageSize).Take(pageSize).ToList();
 }
 return data.ToList();
}

The concept is quite simple - get data from the database table, identify the records that will be displayed on the WebGrid page that is requested, and only pass those records to the view. The WebGrid part of the view now looks as follows

<div id="webgrid" style="float:left; min-width:500px;">
 @{ var grid = new WebGrid<ViewModels.YahooData>(null, rowsPerPage: 5, defaultSort: "YahooSymbolName");
    grid.Bind(Model.Datas, rowCount: Model.TotalRows, autoSortAndPage: false);
    @grid.GetHtml(columns: grid.Columns( 
    grid.Column("DataName", header:"Company", format:@<text>@Html.ActionLink((string)item.DataName, "Details", "Company", new {id=item.SymbolId}, null)</text>),
    grid.Column("DateTime", header:"Time", style:"time", format:@<text>@item.DateTime.ToString("dd/MM/yyyy hh:mm")</text>), 
    grid.Column("LTP"), grid.Column("Volume"), grid.Column("Ask"), grid.Column("Bid"), grid.Column("High"), grid.Column("Low")),
    tableStyle: "webGrid", headerStyle: "header", alternatingRowStyle: "alt");
  }
</div>

My plan from here is to implement AJAX updates to the WebGrid content.

The strongly typed WebGrid samples:

//Strongly Typed WebGrid
public class WebGrid<T> : WebGrid
{
 // Wrapper for System.Web.Helpers.WebGrid that preserves the item type from the data source
 public WebGrid(IEnumerable<T> source = null, IEnumerable<string> columnNames = null, string defaultSort = null, int rowsPerPage = 10, bool canPage = true, bool canSort = true, string ajaxUpdateContainerId = null, string ajaxUpdateCallback = null, string fieldNamePrefix = null, string pageFieldName = null, string selectionFieldName = null, string sortFieldName = null, string sortDirectionFieldName = null)
  : base(source.SafeCast<object>(), columnNames, defaultSort, rowsPerPage, canPage, canSort, ajaxUpdateContainerId, ajaxUpdateCallback, fieldNamePrefix, pageFieldName, selectionFieldName, sortFieldName, sortDirectionFieldName)
 {
 }
 public WebGridColumn Column(string columnName = null, string header = null, Func<T, object> format = null, string style = null, bool canSort = true)
 {
  Func<dynamic, object> wrappedFormat = null;
  if (format != null)
  {
   wrappedFormat = o => format((T)o.Value);
  }
  WebGridColumn column = base.Column(columnName, header, wrappedFormat, style, canSort);
  return column;
 }
 public WebGrid<T> Bind(IEnumerable<T> source, IEnumerable<string> columnNames = null, bool autoSortAndPage = true, int rowCount = -1)
 {
  base.Bind(source.SafeCast<object>(), columnNames, autoSortAndPage, rowCount);
  return this;
 }
}

public static class EnumerableExtensions
{
 public static IEnumerable<TTarget> SafeCast<TTarget>(this IEnumerable source)
 {
  return source == null ? null : source.Cast<TTarget>();
 }
}
//WebGrid extensions
public static class WebGridExtensions
{
 // Light-weight wrapper around the constructor for WebGrid so that we get take advantage of compiler type inference
 public static WebGrid<T> Grid<T>(this HtmlHelper htmlHelper, IEnumerable<T> source, IEnumerable<string> columnNames = null,
   string defaultSort = null, int rowsPerPage = 10, bool canPage = true, bool canSort = true,
   string ajaxUpdateContainerId = null, string ajaxUpdateCallback = null, string fieldNamePrefix = null,
   string pageFieldName = null, string selectionFieldName = null, string sortFieldName = null, string sortDirectionFieldName = null)
 {
  return new WebGrid<T>(source, columnNames, defaultSort, rowsPerPage,
    canPage, canSort, ajaxUpdateContainerId, ajaxUpdateCallback, fieldNamePrefix, 
    pageFieldName, selectionFieldName, sortFieldName, sortDirectionFieldName);
 }

 // Light-weight wrapper around the constructor for WebGrid so that we get take advantage of compiler type inference and to automatically call Bind to disable the automatic paging and sorting (use this for server-side paging)
 public static WebGrid<T> ServerPagedGrid<T>(this HtmlHelper htmlHelper, IEnumerable<T> source, int totalRows, IEnumerable<string> columnNames = null,
   string defaultSort = null, int rowsPerPage = 10, bool canPage = true, bool canSort = true, string ajaxUpdateContainerId = null, 
   string ajaxUpdateCallback = null, string fieldNamePrefix = null,
   string pageFieldName = null, string selectionFieldName = null, string sortFieldName = null, string sortDirectionFieldName = null)
 {
  var webGrid = new WebGrid<T>(null, columnNames, defaultSort, rowsPerPage, canPage,
    canSort, ajaxUpdateContainerId, ajaxUpdateCallback, fieldNamePrefix,
    pageFieldName, selectionFieldName, sortFieldName, sortDirectionFieldName);
  return webGrid.Bind(source, rowCount: totalRows, autoSortAndPage: false); ;
 }
}

References

Get the Most out of WebGrid in ASP.NET MVC
by . Also posted on my website

Tuesday, November 13, 2012

Starting with WebGrid

WebGrid is an HTML helper provided as part of the MVC framework to simplify rendering tabular data. It is actually very simple to start with WebGrid. The following is enough to create a complete working example:

@model YahooViewModel

...

@{ var grid = new WebGrid(Model.Datas);
   @grid.GetHtml();
 }

Here the "Datas" is my list of YahooData entities. This, however, looks a little ugly, so I'll spend a few minutes on styling straight away. The following is a basic style for a WebGrid

<style type="text/css">
    .webGrid {margin: 4px; border-collapse: collapse; width: 300px;}
    .header {background-color: #E8E8E8; font-weight: bold; color: #FFF;}
    .webGrid th, .webGrid td { border: 1px solid #C0C0C0; padding: 5px;}
    .alt {background-color: #E8E8E8; color: #000;}
</style>

The style is applied as follows

@{ var grid = new WebGrid(Model.Datas);
   @grid.GetHtml(tableStyle: "webGrid", headerStyle: "header", alternatingRowStyle: "alt");
 }

First WebGrid

I don't want to show each and every column to the user. I can rewrite the WebGrid specifying the actual columns to show. Only specified columns will be displayed. Also, now the order of the columns is the same as the order I define them.

@{ var grid = new WebGrid(Model.Datas, 
       columnNames: new[] {"DataName", "Date", "LTP", "Time", "Volume", "Ask", "Bid", "High", "Low"});
   @grid.GetHtml(tableStyle: "webGrid", headerStyle: "header", alternatingRowStyle: "alt");
 }

Specific columns

Another way to do it is to actually define columns explicitly. First advantage is that I can now specify a name for the header.

@{ var grid = new WebGrid(Model.Datas, columnNames: new[] {"DataName", "Date", "LTP", "Time", "Volume", "Ask", "Bid", "High", "Low"});
   @grid.GetHtml(columns: grid.Columns( grid.Column("DataName", header: "Company"), grid.Column("Date"), grid.Column("LTP"), grid.Column("Time"), grid.Column("Volume"), 
   grid.Column("Ask"), grid.Column("Bid"), grid.Column("High"), grid.Column("Low")),
   tableStyle: "webGrid", headerStyle: "header", alternatingRowStyle: "alt");
 }

Finally, let's assume I want to let the user click the Company name and navigate to the page that provides some more information about the company. I can use format parameter of the Column to display an ActionLink.

@{ var grid = new WebGrid(Model.Datas, columnNames: new[] {"DataName", "Date", "LTP", "Time", "Volume", "Ask", "Bid", "High", "Low"});
   @grid.GetHtml(columns: grid.Columns( 
   grid.Column("DataName", header:"Company", format:@<text>@Html.ActionLink((string)item.DataName, "Details", "Company", new {id=item.SymbolId}, null)</text>),
   grid.Column("Date"), grid.Column("LTP"), grid.Column("Time"), grid.Column("Volume"), 
   grid.Column("Ask"), grid.Column("Bid"), grid.Column("High"), grid.Column("Low")),
   tableStyle: "webGrid", headerStyle: "header", alternatingRowStyle: "alt");
 }

The ActionLink will be created in the following format: "http://localhost/Company/Details/1"

Finally (for today) I would like to combine Date and Time in a single column and format it. The last bit of code shows how to format the date in the column and how to apply the style to a specific column.

<style type="text/css">

...

    .time {width: 200px; font-weight:bold;}
</style>

@{ var grid = new WebGrid(Model.Datas, columnNames: new[] {"DataName", "Date", "LTP", "Time", "Volume", "Ask", "Bid", "High", "Low"});
   @grid.GetHtml(columns: grid.Columns( 
   grid.Column("DataName", header:"Company", format:@<text>@Html.ActionLink((string)item.DataName, "Details", "Company", new {id=item.SymbolId}, null)</text>),
   grid.Column("DateTime", header:"Time", style:"time", format:@<text>@item.DateTime.ToString("dd/MM/yyyy hh:mm")</text>), 
   grid.Column("LTP"), grid.Column("Volume"), grid.Column("Ask"), grid.Column("Bid"), grid.Column("High"), grid.Column("Low")),
   tableStyle: "webGrid", headerStyle: "header", alternatingRowStyle: "alt");
 }

Better formatting

The plan from here is to add server-side paging to reduce the stress on the view when the number of records is high.

References

Get the Most out of WebGrid in ASP.NET MVC
WebGrid WebHelper in ASP.NET MVC 3 RC
by . Also posted on my website

Wednesday, November 7, 2012

Yahoo Data Download

Stock data can be downloaded from http://finance.yahoo.com/d/quotes.csv?s=[stock symbol string]&f=[special tags]. Some tags are listed in the table at the end of the post, but that's not the point. I'll be using a static url for a code example, such as http://download.finance.yahoo.com/d/quotes.csv?s=GOOG+AAPL+MSFT+YHOO&f=snd1l1t1vb3b2hg which will return values for Symbol, Name, Last trade date, Last trade (price only), Last trade time, Volume, Bid (real-time), Ask (real-time), Day's High and Day's Low.

The plan is to have a list of symbols (configurable), to get the data from yahoo and dynamically load the data into the WebGrid control. Therefore, I started with the basic ViewModel that has two sets of entities - one for the symbols and one for the data itself. Eventually the list of symbols will be made configurable.

//ViewModel
public class YahooViewModel
{
 public List<YahooData> Datas { get; set; }
 public List<YahooSymbol> Symbols { get; set; }
 public YahooSymbol Symbol { get; set; }
 public int YahooSymbolID { get; set; }

 public YahooViewModel(int symbolid, YahooSymbol symbol, List<YahooSymbol> symbols, List<YahooData> datas)
 {
  Symbol = symbol;
  YahooSymbolID = symbolid;
  Symbols = symbols;
  Datas = datas;
 }
}

The controller requests and populates the data, and later the automatic authentication may be added as described in the previous post.

//Controller
public ActionResult Index()
{
 List<YahooData> datas = GetData();
 List<YahooSymbol> symbols = db.YahooSymbols.ToList();
 YahooSymbol symbol = symbols.First();
 int id = symbol.YahooSymbolID;
 return View(new YahooViewModel(id, symbol, symbols, datas));
}

public List<YahooData> GetData()
{
 List<YahooData> datas = new List<YahooData>();

 HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://download.finance.yahoo.com/d/quotes.csv?s=GOOG+AAPL+MSFT+YHOO&f=snd1l1t1vb3b2hg");
 HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

 using (StreamReader streamReader = new StreamReader(resp.GetResponseStream()))
 {
  string t = streamReader.ReadToEnd();
  string[] strings = t.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
  datas = InsertData(strings);
 }
 return datas;
}

private List<YahooData> InsertData(string[] lines)
{
 List<YahooData> datas = new List<YahooData>();

 foreach (string line in lines)
 {
  if (!String.IsNullOrEmpty(line))
  {
   YahooData datum = GetDatum(line);
   datas.Add(datum);
  }
 }
 return datas;
}

private YahooData GetDatum(string line)
{
 var datum = new YahooData();
 string[] splitLine = line.Split(',');
 datum = new YahooData()
 {
  DataName = splitLine[1].Replace("\"", ""),
  Date = DateTime.ParseExact(splitLine[2].Replace("\"", ""), "MM/d/yyyy", CultureInfo.InvariantCulture),
  LTP = decimal.Parse(splitLine[3]),
  Time = DateTime.Parse(splitLine[4].Replace("\"", "")),
  Volume = decimal.Parse(splitLine[5]),
  Ask = decimal.Parse(splitLine[6]),
  Bid = decimal.Parse(splitLine[7]),
  High = decimal.Parse(splitLine[8]),
  Low = decimal.Parse(splitLine[9])
 };
 return datum;
}

The symbols are seeded initially, and may be later made configurable.

//Seeding database with initial values
public class SampleData : DropCreateDatabaseIfModelChanges<RecipesEntities>
{
 protected override void Seed(RecipesEntities context)
 {
  AddSymbols(context);
 }
}

public static void AddSymbols(RecipesEntities context)
{
 List<YahooSymbol> symbols = new List<YahooSymbol>
 {
  new YahooSymbol {YahooSymbolID = 1, YahooSymbolName = "GOOG"},
  new YahooSymbol {YahooSymbolID = 2, YahooSymbolName = "AAPL"},
  new YahooSymbol {YahooSymbolID = 3, YahooSymbolName = "MSFT"},
  new YahooSymbol {YahooSymbolID = 4, YahooSymbolName = "YHOO"}
 };

 symbols.ForEach(p => context.YahooSymbols.Add(p));
 context.SaveChanges();
}

Finally, the table of tags and their meanings - just for the interest.

a Ask a2 Average Daily Volume a5 Ask Size
b Bid b2 Ask (Real-time) b3 Bid (Real-time)
b4 Book Value b6 Bid Size c Change & Percent Change
c1 Change c3 Commission c6 Change (Real-time)
c8 After Hours Change (Real-time) d Dividend/Share d1 Last Trade Date
d2 Trade Date e Earnings/Share e1 Error Indication (returned for symbol changed / invalid)
e7 EPS Estimate Current Year e8 EPS Estimate Next Year e9 EPS Estimate Next Quarter
f6 Float Shares g Day's Low h Day's High
j 52-week Low k 52-week High g1 Holdings Gain Percent
g3 Annualized Gain g4 Holdings Gain g5 Holdings Gain Percent (Real-time)
g6 Holdings Gain (Real-time) i More Info i5 Order Book (Real-time)
j1 Market Capitalization j3 Market Cap (Real-time) j4 EBITDA
j5 Change From 52-week Low j6 Percent Change From 52-week Low k1 Last Trade (Real-time) With Time
k2 Change Percent (Real-time) k3 Last Trade Size k4 Change From 52-week High
k5 Percebt Change From 52-week High l Last Trade (With Time) l1 Last Trade (Price Only)
l2 High Limit l3 Low Limit m Day's Range
m2 Day's Range (Real-time) m3 50-day Moving Average m4 200-day Moving Average
m5 Change From 200-day Moving Average m6 Percent Change From 200-day Moving Average m7 Change From 50-day Moving Average
m8 Percent Change From 50-day Moving Average n Name n4 Notes
o Open p Previous Close p1 Price Paid
p2 Change in Percent p5 Price/Sales p6 Price/Book
q Ex-Dividend Date r P/E Ratio r1 Dividend Pay Date
r2 P/E Ratio (Real-time) r5 PEG Ratio r6 Price/EPS Estimate Current Year
r7 Price/EPS Estimate Next Year s Symbol s1 Shares Owned
s7 Short Ratio t1 Last Trade Time t6 Trade Links
t7 Ticker Trend t8 1 yr Target Price v Volume
v1 Holdings Value v7 Holdings Value (Real-time) w 52-week Range
w1 Day's Value Change w4 Day's Value Change (Real-time) x Stock Exchange
y Dividend Yield
by . Also posted on my website

Thursday, October 25, 2012

Automating Website Authentication

Recently I had to implement automated logging on the website. In my particular case, that was Yahoo.com website, so the code snippets will be specific to this site. It should not be hard to modify them for other purposes. I developed two separate ways to achieve that, the first one has more code and is more complex (have to subscribe to two events and make more logical checks), but I figured it out first. It makes use of the WebBrowser class.

Create an instance of the WebBrowser and subscribe to Navigated and DocumentCompleted events

_browser = new WebBrowser();
_browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
_browser.Navigated += new WebBrowserNavigatedEventHandler(browser_Navigated);

On a timeline, first meaningful event that will be caught is browser_DocumentCompleted on the login.yahoo.com. The code then will analyse the controls on the page. For successful operation, I need to know actual names of the login and password input elements. I find them by name, and set the values to actual login and password. Then I simulate the click on the login button.

Next meaningful event is browser_Navigated on my.yahoo.com page - see below.

After that, I'll point the browser to the url of the document I want to read or download. I'll catch browser_DocumentCompleted again, on that page, and read the contents using the WebBrowser.Document.Body.InnerText (end of the code snippet).

void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
 //loaded the Yahoo login page
 if (_browser.Url.AbsoluteUri.Contains(LoginUrl))
 {
  if (_browser.Document != null)
  {
   //Find and fill the "username" textbox
   HtmlElementCollection collection = _browser.Document.GetElementsByTagName("input");
   foreach (HtmlElement element in collection)
   {
    string name = element.GetAttribute("id");
    if (name == "username")
    {
     element.SetAttribute("value", _login);
     break;
    }
   }

   //Find and fill the "password" field
   foreach (HtmlElement element in collection)
   {
    string name = element.GetAttribute("id");
    if (name == "passwd")
    {
     element.SetAttribute("value", _password);
     break;
    }
   }

   //Submit the form
   collection = _browser.Document.GetElementsByTagName("button");
   foreach (HtmlElement element in collection)
   {
    string name = element.GetAttribute("id");
    if (name == ".save")
    {
     element.InvokeMember("click");
     break;
    }
   }
  }
 }
 
 //downloaded "quote.csv"
 if(_browser.Url.AbsoluteUri.Contains(".csv"))
 {
  if (_browser.Document != null && _browser.Document.Body != null)
  {
   string s = _browser.Document.Body.InnerText;
  }
 }
}

Here I actually copy the cookies, but that is not necessary. The WebBrowser will keep them internally and use them. The purpose of this code is to check if the browser is redirected to "my.yahoo.com", which is the indication of successful login. Further logic may be applied from here.
void browser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
 //Successful login takes to "my.yahoo.com"
 if (_browser.Url.AbsoluteUri.Contains(MyYahoo))
 {
  if (_browser.Document != null && !String.IsNullOrEmpty(_browser.Document.Cookie))
  {
   _cookies = _browser.Document.Cookie;
  }
 }
}

The second approach is shorted, but it took me longer to figure out. Here I have to explicitly use the CookieContainer to save the cookies "harvested" by the HttpWebRequest which does the login, and use them in the HttpWebRequest which asks for the file after authentication. Of course, I still need to know what are the names of login and password elements, because I'm sending the values in the POST data.

Step one - authentication

string strPostData = String.Format("login={0}&passwd={1}", _login, _password);

// Setup the http request.
HttpWebRequest wrWebRequest = WebRequest.Create(LoginUrl) as HttpWebRequest;
wrWebRequest.Method = "POST";
wrWebRequest.ContentLength = strPostData.Length;
wrWebRequest.ContentType = "application/x-www-form-urlencoded";
_yahooContainer = new CookieContainer();
wrWebRequest.CookieContainer = _yahooContainer;

// Post to the login form.
using (StreamWriter swRequestWriter = new StreamWriter(wrWebRequest.GetRequestStream()))
{
 swRequestWriter.Write(strPostData);
 swRequestWriter.Close();           
}

// Get the response.
HttpWebResponse hwrWebResponse = (HttpWebResponse)wrWebRequest.GetResponse();

Step two - accessing data using the cookies.

HttpWebRequest req = (HttpWebRequest)WebRequest.Create(_downloadUrl);
req.CookieContainer = _yahooContainer;
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

using(StreamReader streamReader = new StreamReader(resp.GetResponseStream()))
{
 string t = streamReader.ReadToEnd();
}

References:

WebBrowser control
submit a form data from external address !
C# Login to Website via program
how to login to yahoo website programatically by . Also posted on my website

Sunday, October 14, 2012

Crystal Reports, C#, Object as Data Source

Based on the Ping example from one of the recent posts, I'm continuing it with the Crystal Reports example, because I have never used Crystal Reports until now. So consider that the following class was added to the solution.

public class PingResult
{
 public string sPacketsSent;
 public string sPacketsReceived;
 public string sPacketsLost;
 public string sPacketTime;
}

An instance of the class is created and populated with results when the ping command runs and its results are parsed. So I have one of the simplest possible objects to use as a source for a report. The next step is to add a Crystal Report to the application. Visual Studio 2010 has an item to add called "Crystal Report", but they are not installed.

Crystal Report Online Template

When I select this item, I'm prompted with a download screen.

Download Crystal Reports

Installation is simple - just following the instructions. I chose the standard version, and the download size is 288MB. After a few short hours, I have a Crystal Report called pingReport.mht in my solution. I have an option to configure my report using the wizard, which I'm doing by choosing the following options:

On the first page, Using the Report Wizard, and Standard layout.

Create a New Crystal Report Document

On the next page, I choose to populate my report from .NET Object in project data. My PingResult class is in the list, and I move it to the Selected Tables.

Choose the data you want to report on

Then I choose the fields to display, of which I select all.

Choose the information to display on the report

I skip Grouping, Report Selection and Report Style, leaving default values. Now I have my report editor. I only want to do a little change - make the headers human readable, so I edit them in the following manner

Edit text object

Now some tricks: when I build my solution, I get the following error:

Warning 1 The referenced assembly "CrystalDecisions.CrystalReports.Engine, Version=13.0.2000.0, Culture=neutral, PublicKeyToken=692fbea5521e1304, processorArchitecture=MSIL" could not be resolved because it has a dependency on "System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" which is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client". Please remove references to assemblies not in the targeted framework or consider retargeting your project. PingTest

This is quite obvious, I need to add a reference to System.Web, but to do that I need to first change the Target Framework default setting of .NET Framework 4 Client Profile to just .NET Framework 4. Now the project builds.

In my toolbox I now have the pingReport1 component, which I add to the form.

pingReport1

I also need a report viewer, which I also add

CrystalReportViewer

The final effort: connect the report with the object that contains data. Here's how:

pingReport1 myReport = new pingReport1();
myReport.SetDataSource(new[] { pingResult });
pingReportViewer1.ReportSource = myReport;

Looks simple, just note how the objects are wrapped into array. This is important.

And the final trick, when I run the application, I get the FileNotFound exception

FileNotFoundException

which is resolved by adding the useLegacyV2RuntimeActivationPolicy="true" parameter to the startup node of my app.config which now looks like the following

<?xml version="1.0"?>
<configuration>
<startup  useLegacyV2RuntimeActivationPolicy="true">
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

At this point I consider my small example complete.

Complete

References

Creating Crystal Reports using C# with Datasets
Very Odd situation with CrystalReport and/or Visual studio 2010 I don't know maybe .Net Framework
Can crystal reports get data from an object data source? by . Also posted on my website

Sunday, October 7, 2012

A Simple Show/Hide Technique with jQuery

Today I learned a simple technique to show/hide parts of content on the page. In my sample application, I'm using it to display a part of a long blog post, and then at the end of the part to display a button that will show the rest of the post. The button initially says "Show more". When the button is clicked, it displays the previously hidden part and its text changes to "Show less". So, in essence, it toggles the display state of the div it is connected to, and its own text.

Additionally, I'm displaying posts dynamically, so for each post there is a div with the first part of the post, the div with the rest, and the toggle button. Here's how it works:

For each blog post in the model a div is created for the rest of the post, and a button. The ID for both is assigned dynamically using a simple counter.

@model Recipes.ViewModels.BlogViewModel
@{ int i = 0; }
@foreach (var post in Model.Posts)
{
    string divID = "hide" + i.ToString();
    string btnID = "btn" + i.ToString();
    <div id="middlecolumn">
        <div class="blogcontainer">
            <h3 class="title"><a href="#">@post.Title</a></h3>
            <div class="info"><span class="submitted">Submitted on @post.DateCreated</span></div>
            @MvcHtmlString.Create(post.BriefContent)
            <div class="buttonlink"><a id=@btnID href="javascript:toggleDiv('@divID', '@btnID');">Show more</a></div>
            <div id=@divID  style="display:none">@MvcHtmlString.Create(post.RestOfContent)</div>
        </div>
    </div>
    i = i + 1;
}

The javaScript function toggleDiv() takes two parameters: div ID and button ID. First the function toggles the div display property by using the jQuery function toggle(). Next, based on the div display state, the button text is set to one of the two possible values. And that's it.

<script type="text/javascript">
<!--
    function toggleDiv(divId, btnId) {
        $("#" + divId).toggle();
        if ($("#" + divId).css('display') == 'none')
        {$("#" + btnId).html('Show more'); }
        else
        { $("#" + btnId).html('Show less'); }
    }
-->
</script>

Here's the example of the page with the blog post

Rest of the post hidden

Rest of the post expanded

References

How to hide, show, or toggle your div
How to hide, show, or toggle your div with jQuery by . Also posted on my website

Thursday, October 4, 2012

jQuery Show/Hide Toggle

When I have a long list on a web page, I would like to give the user an option to hide it. Turns out that is simple to do with a little bit of jQuery. All the contents that I would like to hide or show would go into a div element. Next to it, I will add a link to toggle the hide/show behaviour. Here's how it looks in my code:

<div class="buttonlink">
 <a href="#" class="show_hide">Show/Hide List</a>
</div>
<div class="slidingDiv">
 @foreach (var item in Model.Recipes)
 {
  <ol>
   <li class="styled">
    <div class="display-button">@Html.ActionLink("Edit", "Edit", new { id = item.RecipeID })
    </div>
    <div class="display-button">@Html.ActionLink("Details", "Details", new { id = item.RecipeID })</div>
    <div class="display-button">@Html.ActionLink("Delete", "Delete", new { id = item.RecipeID })</div>
    <div class="display-info">@item.RecipeName</div>
   </li>
  </ol>
 }
 <a href="#" class="show_hide">Hide</a>
</div>

Here is the jQuery function that is called when the link is clicked

$(document).ready(function () {

 $(".slidingDiv").hide();
 $(".show_hide").show();

 $('.show_hide').click(function () {
  $(".slidingDiv").slideToggle();
 });

});

And the styles for the classes that I've just introduced above.

.slidingDiv {
    height:300px;
    padding:20px;
    margin-top:35px;
    margin-bottom:10px;
}
 
.show_hide {
    display:none;
}

That's it.

Shown

Hidden

References

jquery show/hide div
Simple jQuery Show/Hide Div
Fiddle
by . Also posted on my website

Thursday, September 27, 2012

Playing With Google Search Results - 2

Another way of getting back web search results from Google is to use Google API. I've spent a couple of hours researching the option to do that, and did not find too many exciting choices. There is an option to use Google API which is deprecated, and limits the amount of searches to about 100 per day, and does not return more than 64 results, and does not allow automatic searches, or to use Google Custom Search, which can be used only to create site-specific custom searches. Anyway, as an exercise I decided to implement a call to Google API (deprecated one). There are a few options available.

The easiest way to use Google API I found was to use Google API for .NET. After I downloaded and referenced the GoogleSearchAPI.NET20 dll, it took me a surprisingly small amount of lines of code to create a quick prototype of querying the Google API

private void btnSearch_Click(object sender, EventArgs e)
{
 string searchTerms = txtTerms.Text;
 List<string> GoogleApiResults = GoogleAPI.StringResultList(searchTerms, 100);
}

public static class GoogleAPI
{
 public static GwebSearchClient client = new GwebSearchClient("");

 public static List<String> StringResultList(string terms, int number)
 {
  IList<IWebResult> list = client.Search(terms, number);
  List<String> results = new List<string>();
  foreach (var result in list)
  {
   results.Add(result.Url);
  }
  return results;
 }
}

Search Results

References

Google API for .NET by . Also posted on my website

Thursday, September 13, 2012

Learning MVC: No parameterless constructor defined for this object

I'm developing a sample application using MVC - a "blog engine". OK, getting rid of all the buzzwords, it is just a few tables: Blogs, Bloggers, Posts. You can add bloggers, create blogs and add posts to a selected blog. Being a good boy, I'm trying not to pass objects like Blog or Post to the view, but rather use ViewModels wherever makes sense. Nothing complicated, for example

public class BlogViewModel
{
 public Blog Blog;
 public List<Post> Posts;
 ...
 
 public BlogViewModel(Blog blog, List<Post> posts, ... )
 {
  Blog = blog;
  Posts = posts;
  ...
 }
}

and then in the BlogController I would have these methods for creating a new blog:

public ActionResult Create()
{
 Blogger selectedBlogger = db.Bloggers.First();
 Blog blog = new Blog();
 return View(new BlogViewModel(blog, new List<Post>(), ...));
}

[HttpPost]
public ActionResult Create(BlogViewModel viewModel)
{
 Blog blog = viewModel.Blog;
 blog.Blogger = db.Bloggers.Where(b => b.BloggerID == viewModel.BloggerID).FirstOrDefault();

 if (ModelState.IsValid)
 {
  try
  {
   db.Blogs.Add(blog);
   db.SaveChanges();
  }
  
  // process errors
 }
 return View(new BlogViewModel(blog, new List<Post>(), ...));
}

Something like that. So I'm testing the create method when I suddenly recieve the "No parameterless constructor defined for this object" error.

No parameterless constructor defined for this object

That left me scratching my head for some time, because I could not figure out what constructor I'm missing. Took a bit of searching to realise: the constructor is missing in the ViewModel. If I modify the constructor shown above as follows

public class BlogViewModel
{
 public Blog Blog;
 public List<Post> Posts;
 ...
 
 public BlogViewModel()
 { 
 }

 public BlogViewModel(Blog blog, List<Post> posts, List<Blog> blogs, int bloggerid, List<Blogger> bloggers)
 {
  Blog = blog;
  Posts = posts;
  ...
 }
}

the error just goes away (notice that parameterless constructor that is just sitting there now, happily doing nothing?). Why is that? Well, I'll be totally honest: I have no idea.

Reference

Fun and Struggles with MVC – No Parameterless Constructor Defined by . Also posted on my website

Tuesday, September 11, 2012

Running an Command Line Program in C# and Getting Output

A simple example. Let's say I want to run ping from command line, but to make this more automated, or maybe user friendly, I would like to run a C# application that pings an IP address, captures the returned result and displays it in a user-friendly format.

Fist thing is to start the command prompt and execute a process. Here's one of the most convenient ways to use it: utilize ProcessStartInfo and Process classes, which are part of System.Diagnostics namespace. ProcessStartInfo takes the program to run, in this case cmd.exe, and parameters, in this case ping, together with its own parameters. Here's how it works:

private void btnPing_Click(object sender, EventArgs e)
{
 string command = "/c ping " + txtIP.Text;

 ProcessStartInfo procStartInfo = new ProcessStartInfo("CMD", command);

 Process proc = new Process();
 proc.StartInfo = procStartInfo;
 proc.Start();
}

Command prompts started from Windows Form

The process starts and the familiar command window appears, then the ping command runs. Now to capture the results of the ping, a few other lines are needed. Firstly, the output of the process needs to be redirected. The following values need to be set:

procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;

Next, to capture the output line by line as it is sent by the process, I'll attach a function that does it asynchronously.

proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();

The function can do anything, but in my case I'm simply redirecting the output to the Windows Form.

void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
 if (e.Data != null)
 {
  txtOutput.Text = txtOutput.Text + e.Data.Trim() + Environment.NewLine;
 }
}

Looks correct, so why am I receiving this exception:

Cross-thread operation not valid: Control 'txtOutput' accessed from a thread other than the thread it was created on.

Well, looks like it's telling me that the process is running from another thread and can not quite access my text box from that thread. Long story short, this is the shortest solution I have found for this issue (there are many options, some as complicated as using a BackgroundWorker).

void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
 if (e.Data != null)
 {
  string newLine = e.Data.Trim() +Environment.NewLine;
  MethodInvoker append = () => txtOutput.Text += newLine;
  txtOutput.BeginInvoke(append);
 }
}

Command prompt output redirected to Windows Form

References:

Having trouble with Process class while redirecting command prompt output to winformCapturing process output via OutputDataReceived event by . Also posted on my website

Friday, September 7, 2012

Playing with Google Search Results

You will need:

Create a Visual Studio project, for example C# Windows Forms application. Drop a TextBox, a Button and a ListView on the form. Creat a class for the methods to be used, let's say Helper.cs. First, I'm using the System.Net.Webclient to call Google and get a page of search results.

public static WebClient webClient = new WebClient();

public static string GetSearchResultHtlm(string keywords)
{
    StringBuilder sb = new StringBuilder("http://www.google.com/search?q=");
    sb.Append(keywords);
    return webClient.DownloadString(sb.ToString());
}

The string that is returned is the html of the first page of the Google search for the string that is passed to the method. Opened in the web browser, it will look something like this

Google search result page

What I want to extract is the actual links, which are marked in red on the screenshot above. Here I'm going to use HtmlAgilityPack to load the string into the HtmlDocument object. After the string is loaded, I will use a simple LINQ query to extract the nodes that match certain conditions: They are html links (a href), and the URL of the link contains either "/url?" or "?url=". By this point, I get quite and unreadable list of values.

Raw URLs

To bring it into readable form, I'll match it to a regular expression and then load the results into the ListView. Here is the code:

public static Regex extractUrl = new Regex(@"[&?](?:q|url)=([^&]+)", RegexOptions.Compiled);

public static List<String> ParseSearchResultHtml(string html)
{
    List<String> searchResults = new List<string>();

    var doc = new HtmlAgilityPack.HtmlDocument();
    doc.LoadHtml(html);

    var nodes = (from node in doc.DocumentNode.SelectNodes("//a")
                 let href = node.Attributes["href"]
                 where null != href
                 where href.Value.Contains("/url?") || href.Value.Contains("?url=")
                 select href.Value).ToList();

    foreach (var node in nodes)
    {
        var match = extractUrl.Match(node);
        string test = HttpUtility.UrlDecode(match.Groups[1].Value);
        searchResults.Add(test);
    }

    return searchResults;
}

Here is the result:

Final Results

I'm not quite sure why this may be useful, but as an exercise it is possible to add an option to parse through a certain number of pages, rather than just the first page. But if you try to run those queries in an automated mode, Google will soon start serving 503 errors to you.

by . Also posted on my website

Sunday, August 12, 2012

Securing Access to Windows 7 Folder from Everyone but a Single User

Today I had to perform a fairly specific task: restrict access to a Windows 7 folder to a single user. I think I found the way to do it properly, and it is not a straightforward task. Before I forget, I might keep a record of all actions required because I did not find a clear sequence anywhere on the net. It will only take 10 easy steps.

Let's assume there is a folder called Bob's Documents where only Bob, and not even the Admin>, should have access.

  1. Right-click on Bob's Documents and select Properties
  2. Select Bob's Documents Properties

  3. Bob's Documents Properties window will open. Switch to Security tab and click Advanced button.
  4. Bob's Documents Properties

  5. Advanced Security Settings for Bob's Documents will open. On the Permissions tab, Click Change Permissions button.
  6. Advanced Security Settings for Bob's Documents

  7. Another window will open. Unfortunately, it's too called Advanced Security Settings for Bob's Documents, adding to confusion. In this new window, untick Include inheritable permissions from this object's parent - that will simplify things a lot, because we only care about permissions to this folder, not its parent folder.
  8. Advanced Security Settings for Bob's Documents - but not the same one!

  9. As soon as the chechbox is unticked, a warning called Windows Security will pop up. Since we're getting rid of parent permissions, click Remove.
  10. Windows Security warning

  11. All permissions should have disappeared from the Permission entries. Now click Add.
  12. Select User or Group window will open. In Enter the object name to select, type Bob and click Check Names to make sure there is no typo. Bob's name should resolve to PCName\Bob.
  13. Select User or Group

  14. Click OK. Now the Permissions Entry for Bob's Documents window will pop up. Let's give Bob full control - click the checkbox across from Full Control under Allow. All other checkboxes under Allow will select automatically. Click OK to close this window.
  15. Permissions Entry for Bob's Documents

  16. About done. Click OK in Advanced Security Settings for Bob's Documents to close it, and in another Advanced Security Settings for Bob's Documents to close it too, and OK in Bob's Documents Properties.
  17. Try to browse to Bob's Documents. Even if you're on Administrator account, you should not be able to, but you should if you are logged in as Bob.
  18. Permissions are set

by . Also posted on my website