Tuesday, December 28, 2010

T-Mobile Customer Experience

One of my personal conviction is generosity. So that conviction, combined with good service (especially paid ones) usually produce a desire to reward the service giver beyond the typical means. I personally don't think this is unique to me - for example in tipping your servers in a restaurant, when you are getting excellent service, some people will give more tip, etc.

In this particular experience, I was getting superb service from T-Mobile and this blog post is a way of rewarding them of their excellent efforts to me as a customer.

-- read more and comment ...

Tuesday, November 23, 2010

Windows Phone 7 Experience

About 2 weeks ago, I bought a new cell phone. I am a T-Mobile customer (T-Mo FTW!) so I renewed my contract and got the phone at a discounted price. My previous phone was a T-Mobile G1 (the 1st Google/Android phone) and I bought it used from a friend of mine. That device served me extremely well and it is still running well and my wife is using it.

So for my new phone, I bought an HTC HD7, running the new Windows Phone 7. This blog post is about my experience with it so far - what I like about it, dislike, a little bit comparison to Android, etc. Nothing really scientific, but just sharing my experience, including an app development experience (since I am a software engineer). Obviously, my experience is a combined experience between the hardware (HTC HD7), the OS (WP7) - but when necessary I will mention the specifics.

What I REALLY Like
  • This is something that is hard to describe, but there is such a pleasant feeling in using the device with the WP7. Every scroll is buttery smooth, no lag, no stutter, snappy, and my user-experience for the 80% of my usage of the phone is superb (this includes making phone calls, receiving calls, speaker phone, contacts, searches, calendar, email syncs, twitter, browsing). 
  • Panoramic view is totally awesome. It helps to go through things quickly, less click/swipe/back, etc. The panoramic background in some hubs are also contributing to increase the niceness and pleasant of using the device. All this being carried in a 4.3" screen - superb. iPhone 4 screen is better with the Retine Display, but HTC HD7's is bigger.
  • The lock screen is also surprisingly well designed. I did not think about this before - but then I realized that showing all my notifications & next event on the lock screen is awesome! One of my cell-phone related habit is to look at my phone to see the time AND to see what is the next item in my calendar - which totally fits into the WP7 paradigm (and I have a feeling that I am not the only one with that habit).
  • The email interface is awesome, every email is delineated well, clear text, eye-pleasing font, easy to use (virtual) keyboard, the sync is also fast, and does not lock the screen, excellent progress bar with the dots, etc. As far as email related experience, this is probably the best I have seen (compared to iPhone/iPod Touch, WinMo, BB, Android).
  • The Calendar interface is also very robust and well done. It syncs seamless with my Exchange calendar, displays the next "event" on "Home" and "lock" screens, and for the extra kick, the calendar date "flips" when you scroll the events in the event list. Haha - I thought that was small but slick nonetheless. 
  • The "Tile" or the "Metro" design. I though it is awesomely done and intuitive. I am a bit biased to big icons - so the tile suits me well. Some also animate (changes in photos, people, news images, etc) - which added some "wow" factor to it. Some provide crucial information: the weather app gives the current weather summary, Outlook shows number of new messages, etc. There is no extra "widget" or differentiation between "widget" and "app shortcut" (like in Android) - which makes things simpler.
  • The Media hub is really nice, it combines radio (like iHeartRadio), YouTube streams, your videos, music, podcasts, etc, and organized well via panoramic views. They just works seamlessly. No need to open YouTube app separately or media player app or radio app separately. One hub and everything works.
  • Zune software. I HATE itunes - and Zune is awesome. Much better design, fast, unobtrusive updates, AND YOU CAN SYNC WITH MULTIPLE COMPUTERS! Yeay! Also, sync media over the air!
  • Developing an app for WP7 is very very easy. Well I am a bit biased since I use Microsoft tools for my work. But here is the thing - I experienced quite some pain to get Android/iPhone dev environment to be up and running and I did created a some kind of "hello world" app, but nothing beyond that. With WP7, in 2+ hours I cranked out a panoramic RSS reader with style and all. Certainly (subjectively) a huge added value for me.
What I Like But Consider Not As Important
  • XBOX Live is really cool. You can create/modify your Avatar from the phone, play some games, sync accomplishments, etc. But, since I am not a big XBOX Live player, I don't use this feature as much. 
  • Instead of paying per music/album, Zune allows you to pay subscription based: A $14.99 / mo gives you unlimited access to millions of songs you can stream on your PC, Xbox 360, Windows Phone 7, or Zune HD - AND you get to keep 10 per month FOR FREE FOREVER!
  • The WP7 OS is always stock and all the bloatware from carriers can always be uninstalled just like any other app without rooting or hack. This was not my experience with Android or WinMo.  
  • WP7 Bing's voice search/command seems to be able to pick up my voice better compared to my old Android. I don't think this is a big deal - could be just because my G1 can't go beyond Android 1.6. I think Froyo most likely has better support for this.
  • The robust kick-stand in HTC HD7. It's awesome when watching videos or reading on a table.
  • The camera is nice. Being able to take 720p video and take a 5mpix photos are good features to have. Plus, the camera software is really fast - you can go from "lock" to "save picture" in about 5 seconds or less. Although I consider this as "not as important" for now, but I do see that I take pictures more because of the quickness or the camera & its ease of use. 
What I Don't Like & Needs Improvement
  • Marketplace. It needs to differentiate the search between music, apps, games, etc. Right now, when I am searching for "News", it gives me ALL related with "news" instead of contextual search result. I am fine with the "all" search and there are times when it is useful, but there needs to be specific search filter too.
  • Map & navigation. Bing maps in WP7 is awesome and slick, but Android's turn by turn saved my ass multiple times. I heard some rumors that this is coming out in SP1 in Q1 2011 - at least I hope so.
  • Need more apps, FREE apps. I want my free "Alchemy" or "Angry Bird" like in Android ... Or QR code reader, Google Sky Map. More apps ... WP7 apps availability is nothing compared to iPhone or Android.
  • Twitter integration to People, like Facebook or MySpace or LinkedIn
  • Phone wide search. Search people, docs, emails, etc from a single place.
So there you have it - my conclusion? I like it - a lot. Probably the best device I have used to date. If I have to rate my OS of choice it would be like this:
  1. Windows Phone 7 - for those of you who are skeptical, I suggest you to try it. I was too a skeptics, but I am sold out now. Yes, it is not perfect, has room for improvement, but it is my device of choice for now.
  2. Android, probably the G2 - fast with HSPA+, excellent keyboard, stock Froyo, loaded with free apps, nice slick phone. (Or if you are on Verizon, go with Droid X)
  3. iPhone - only if iPhone is available outside AT&T.
I have to say, in all this experience, T-Mobile has been awesome. I have been their customer for almost 8 years and there are reasons for that. Their phone customer service is impeccable, their store people are great, their rate are the most economical of all, ultra-fast network, and great selection of phones, PLUS they are the first in many things (1st Android device, 1st Google provider, 1st big-screen-phone in HTC HD2, 1st in getting WP7 in US) - and they always use hot women in their ads. What more can you ask for?

-- read more and comment ...

Monday, November 22, 2010

Acadia National Park

In August, I went to a 10 day vacation, spanning over 11 states! We went from Ohio to New York to Vermont, New Hampshire, Maine, Massachusetts, Rhode Island, Connecticut, Pennsylvania and passing over West Virgina and New Jersey on our drive.

But the highlight of our trip was when we were at Acadia National Park in Maine. Acadia National Park is about 5 hours north of Boston, MA. When I planned our trip, I had some expectations about how beautiful this park going to be, but I was still blown away - it exceeded all of my expectations! I think all of our family and friends agreed that it was the best national park we've seen and the highlight of our trip. I eager to go back next year and spend more time over there.

Acadia National Park has everything - like from beaches (ocean sandy beach or lake beach), serene lakes, hiking trails, bike trails, rocky coastline, sandy coastline, mountains, and spectacular mountain top view.

Anyway - if you have the time, Acadia is a must visit. I think our family is going back there in summer 2011!







-- read more and comment ...

Tuesday, October 26, 2010

Smart Power Control

I setup an SSH server at home running on my Media Center box. ALL of the shows that I am recording runs at night, so during the day, other than the time it has to download updates or running some batch process, it usually goes to suspend/sleep mode - which of course makes my SSH to become unavailable.

Why not just turn on "Wake up on LAN"? Well, I tried that and it worked, but it also somehow wake up the PC when I don't want it - like in the middle of the night or on a Saturday morning, or several seconds after it goes to sleep. In the end, I basically turn-off "Wake up on LAN" and trying to find a different solution.

Basically, what I want is this:

  1. The PC needs to be on between 8am to 5:30pm - this the window where I usually need my SSH
  2. It also needs to be recording my scheduled recording in Media Center
  3. Other than those, it should be in suspend/sleep mode
A friend of mine suggest moving the SSH into a router running DD-WRT. This is probably the best solution, so I can operate my PC on "Power Saving" mode and just leave the router on. Since I am using a Linksys router that is compatible with DD-WRT, I tried it. The problem is that my router does not have much memory, so not only I have to put the micro_plus_ssh version, but also when SSH is running the router would lock up and I have to reboot it. So after playing with this option for about 2 days, I went back to running my SSH on my Media Center box. Maybe one day when I am buying a new router - I will get one that is much more capable to run SSH on. But now back to original plan.

So for a while I just put my PC to "Never" sleep and just turn it off at night and turn it back on in the morning. I moved all my scheduled maintenance stuff (virus scan, windows update, etc) to morning/afternoon. Obviously this gets to be a pain after a while - especially when I keep forgetting to turn it on or off.

So for now, I use a software called "SmartPower" - where I schedule my PC to be "ON" between certain times and/or when certain parameters are set. Here is an article in LifeHacker about it. The way to use it is basically setting your computer to "Never" sleeps, and let SmartPower take over the sleep management (instead of letting Windows doing it). So, to meet my needs, I set:
  1. In "Schedules" tab to awake / stay on during the weekdays between 8:15am to 11:55pm
  2. Under "CPU" tab, set to 27% - this is for watching recordings past midnight or on weekends
  3. I set my PC to awake when Media Center remote is used
That's it - works flawlessly. Oh - and I replaced the power supply unit into the one that is 80 Plus certified to conserve more power during idle or low processing time while it's on.

Additional readings:
-- read more and comment ...

Thursday, October 7, 2010

Conditional Reference in VS 2010

In one of our projects, we are using some third party components that have separate dlls for 32 bit and 64 bit. Since our old development box and production server were 32 bit, so we used the 32 bit version. No problem whatsoever.

In a different project for different client, our development box is a 64 bit machine. But, since we are running VS 2010 and using Cassini for our debugging, we use the 32 bit reference. But since our production box is running 64 bit, last minute before build for release in our build server, we swap the reference to 64 bit, re-check-in and build. So we just keep doing it that way - until one day, we forgot to do that and a production build got pushed to production environment and (of course) it breaks. So we fix it and then we vowed to find a solution to the last minute reference swap thing.

So here is how we fix it:
In VS, right click on the project that has the reference to the 32/64bit ref, do an unload project and open using XML editor and in it, you will see something like this:
...
    <PropertyGroup>
        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    </PropertyGroup>
...

    <ItemGroup>
        <Reference Include="Project.Reference.MyProject">
            <HintPath>..\References"Project.Reference.MyProject.40.x86.dll</HintPath>
        </Reference>
    </ItemGroup>
...
What you need to do is add a condition in your project file, so on certain environment it will use the x86 and x64 for other. The easiest way to do this (which is what we did) is adding a conditional statement itself on the reference element - when compiled as "debug", use the x86 dll and to use the x64 when compiled as "release". So we changed it to be like this:
 
...
    <ItemGroup>
        <Reference Include="Project.Reference.MyProject" Condition="'$(Configuration)' == 'Debug'">
            <HintPath>..\References\"Project.Reference.MyProject.40.x86.dll</HintPath>
        </Reference>
    </ItemGroup>
    <ItemGroup>
        <Reference Include="Project.Reference.MyProject" Condition="'$(Configuration)' == 'Release'">
            <HintPath>..\References\"Project.Reference.MyProject.40.x64.dll</HintPath>
        </Reference>
    </ItemGroup>
...
-- read more and comment ...

Tuesday, September 14, 2010

Session Timeout Settings in IIS7

We did a release to a production environment running IIS7.x, .NET 4, MVC2, etc. Everything went smoothly and after several hours, we started to get calls about time-out issue. Our users want the time-out to be 1 hour (the current setting) which is obviously longer than 20 minutes (which is the default setting).

I remember changing and rechecking this parameter value in the web.config and also in the IIS box, both in our test and production environments. So we double checked and everything was setup correctly - so we did our own test. After 10 minutes, no time out - as expected. But on the 20-25 minutes test, we did get a time out. We triple-check our web.config and application pool setting - they are all setup correctly.

So, apparently, I missed one other time out setup in IIS - the cookie time out. Basically there are 3 (or 4 depending on your authentication setup) settings that need to be managed. IIS will use the lowest value. The main 3-4 settings that I have to change are:
  1. Application Pool - - Advanced Settings Menu - Process Model - Idle Time-out (minutes)
  2. Sites - - Session State - Cookie Settings - Time-out (in minutes)
  3. If you are using State Server or SQL Server to manage your session (instead of InProcess), you will need to set these values up too: Sites - - Session State - Session State Mode Settings
  4. Under web.config - system.web - authentication mode - forms - timeout (for form authentication)
-- read more and comment ...

Thursday, August 26, 2010

VS 2008 locking up after Office 2010 install

I installed MS Office 2010 about 3 weeks ago and everything went smooth. For the most part, I have been developing in VS 2010, but lately, when I was doing code review, I have to a .NET 3.5 project in VS 2008 - and it locks up on me almost every time.

I thought it was some extension I installed or something screwed up on my project. So I uninstall all extensions, but it was still locking up. I open the project on a different computer, it was running fine, same source and references and all that. I went to search on the internet and found out that there are some issues between VS 2008 and Office 2010 installation. One my particular issue, all I need to do is to rerun "Microsoft Visual Studio Web Authoring Component".

The setup.exe file is located at : C:\Program Files (x86)\Common Files\microsoft shared\OFFICE12\Office Setup Controller\.

So just shut down all VS 2008 instance and any Office programs and then run the Setup.exe and it should be good to go - at least that did it for me.

Credit to Martin Hinshelwood!
-- read more and comment ...

Wednesday, August 25, 2010

StringLength validator into MaxLength attribute via Html Helpers

Let's say you use Data Annotation validations and attributes in your object classes, such as this:

[StringLength(50, ErrorMessage = "Title may not be longer than 50 characters")]
public string Title { get; set; }

[DisplayName("First Name")]
[Required(ErrorMessage = "First Name is required")]
[StringLength(100, ErrorMessage = "First Name may not be longer than 100 characters")]
public string First { get; set; }

[DisplayName("Last Name")]
[Required(ErrorMessage = "Last Name is required")]
[StringLength(100, ErrorMessage = "Last Name may not be longer than 100 characters")]
public string Last { get; set; }

Then you setup your view:
<table>
<tr>
    <td class="fieldLabel"><%:Html.SmartLabelFor(m => m.Title)%></td>
    <td class="fieldValue"><%:Html.EditorFor(m => m.Title)%>
    <%:Html.ValidationMessageFor(m => m.Title)%></td>
</tr>
<tr>
    <td class="fieldLabel"><%:Html.SmartLabelFor(m => m.Last)%></td>
    <td class="fieldValue"><%:Html.EditorFor(m => m.Last)%>
    <%:Html.ValidationMessageFor(m => m.Last)%></td>
</tr>
<tr>
    <td class="fieldLabel"><%:Html.SmartLabelFor(m => m.First)%></td>
    <td class="fieldValue"><%:Html.EditorFor(m => m.First)%>
    <%:Html.ValidationMessageFor(m => m.First)%></td>
</tr>
</table>
Everything works great, the validators are validating, the display name shows up correctly, etc. Then you realize that it would have been much more awesome if the textbox is also limiting the entry to the StringLength specs using the maxlength attribute of the input element. It should not be that bad, right? Pretty easy? Just make or modify an Html helper and display another attribute based on a validation value - right? Nope ... turns out it is quite a challenge.

The issue is that the ModelMetadata does not carry all the attributes. So in our case, the validator attribute for StringLength is nowhere to be found. Well, not really, it's in there, but we have to do a little bit more digging.

The ModelMetadata has all the validators, so we can get them. Once we get all the validators, it's only a matter of getting the one we want, which is the one associated with StringLength, then it's all a matter of appending the right Html attribute the the element. Here is the completed code (named string.ascx, located in /Views/Shared/EditorTemplates/):
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<%
    System.Collections.Generic.Dictionary<string, object> attributes = new Dictionary<string, object>() { { "class", "text-box single-line" } };
    IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(ViewData.ModelMetadata, ViewContext);
    ModelClientValidationRule stringLengthRule = validators.SelectMany(v => v.GetClientValidationRules()).FirstOrDefault(m => m.ValidationType == "stringLength");
    if (stringLengthRule != null && stringLengthRule.ValidationParameters.ContainsKey("maximumLength")) {
        attributes.Add("maxlength", stringLengthRule.ValidationParameters["maximumLength"]);
    }
    Response.Write(Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, attributes));
%>
Additional readings:
-- read more and comment ...

Sunday, August 8, 2010

Restaurant Review: Basi Italia

Helen and I went to an Italian restaurant for her birthday called "Basi Italia". In short: it was probably the best Italian food experience we've had. Highly recommended. We are looking for opportunities to return there.

It is located in Short North area, on Hihgland St. Click here for map & other reviews.

The place is small. I don't think there is enough room for 25 people inside. But, they have a pretty nice outdoor setup - so there is more room outside. It is a more intimate setting and small.

Must get: the zucchini appetizer. We picked it because it was unique on the menu and how we did not regret. It was awesome. It is julienned zucchini with pecans tossed with light vinegar based oil with sliced cheese. Extra yummy.

For entrĂ©e,  I got the special for that day, which is ravioli two ways with 2 different sauces. Best ravioli I have ever had. I even forgot the name ... but it was a special thing, so not on the menu.

Helen got the seafood pasta and it was excellent. Good (not huge) portion with plenty of scallops, shrimps, and clams.

Overall, we thoroughly enjoyed our experience there and certainly will return!
-- read more and comment ...

Thursday, July 15, 2010

Html.SmartLabelFor

I was really happy with the "EditorFor", "LabelFor" etc helpers in MVC2 and I have been using them all over the place in my project. Most of my models have some kind of required fields in them and although they have been validating correctly and displaying the error messages correctly, I want to notify the user that the field is required on the initial form. Usually this is done by giving some kind of visual indicator for the required fields, such as "*" or making the label as bold. In my initial attempts, I just add "*":


        <div class="editor-label">
            <%: Html.LabelFor(m => m.FullName) %> *
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(m => m.FullName) %>
            <%: Html.ValidationMessageFor(m => m.FullName) %>
        </div>

After looking at it for a while, it looks ugly. It worked, but I want a more elegant solution and I made an Html Helper.

What I want it to do is basically appending an "*" next to the label, ONLY IF the field is marked as REQUIRED, so I can do this:

        <div class="editor-label">
            <%: Html.SmartLabelFor(m => m.FullName) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(m => m.FullName) %>
            <%: Html.ValidationMessageFor(m => m.FullName) %>
        </div>

If "FullName" is attributed with "Required", it will render "Full Name *" but will just render "Full Name" if it's not marked "Required".

Without further ado, here is the code - pretty simple:
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Linq.Expressions;

    public static class LabelExtensions {
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
        public static MvcHtmlString SmartLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) {
            return LabelHelper(html,
                               ModelMetadata.FromLambdaExpression(expression, html.ViewData),
                               ExpressionHelper.GetExpressionText(expression));
        }

        internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName) {
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText)) {
                return MvcHtmlString.Empty;
            }

            if (metadata.IsRequired) labelText = labelText + " *";

            TagBuilder tag = new TagBuilder("label");
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);
            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
        }
    }
}
Additional readings:
-- read more and comment ...

Wednesday, July 7, 2010

Html Helper for Model Binding in MVC2

In ASP.NET MVC2, you can create a model and bind its properties to the UI by using "DisplayFor" or "LabelFor" or "EditorFor". To illustrate, let's take a look an example. For instance, if I have a model:

 
    public class Person {
        [Required]
        [DisplayName("Full Name")]
        public string FullName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [DisplayName("Email Address")]
        public string Email { get; set; }

        [DataType(DataType.MultilineText)]
        [DisplayName("Address")]
        public string Address{ get; set; }
    }  
To create an editor for the model will look like this in the old way of MVC:
   

        <div class="editor-label">
            Full Name 
        </div>
        <div class="editor-field">
            <%=Html.TextBox("FullName") %>
            <%=Html.ValidationMessage("FullName") %>
        </div>

With the new "DisplayFor", "EditorFor" and "LabelFor" etc, we can transform that code into something like this - and it will render the same way:
  

        <div class="editor-label">
            <%: Html.LabelFor(m => m.FullName) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(m => m.FullName) %>
            <%: Html.ValidationMessageFor(m => m.FullName) %>
        </div>

This code is much cleaner, flexible, easier to maintain, and validation is centralized in the business layer. So for example, if for whatever reason the label should be changed to "Name" instead of "Full Name", all we need to do is change the "DisplayName" attribute in the model declaration.

Now for the address field, we attributed "DataType(DataType.MultilineText)" to it. This will automatically render out a text area instead of a regular text box.

If we do not like the way the default template render, we can always override it. Brad Wilson has an excellent blog post series about this.
-- read more and comment ...

Sunday, June 13, 2010

Using T4MVC in ASP.NET MVC2

For the last 2 months, I have been using T4MVC for my MVC2 projects - and it is awesome! Previously, to provide strongly typed view name/path, I have to create a helper class that list all of my view names and location, so when I refer to them I can just type:

<% Html.RenderPartial(ViewLocations.MyCustomview);%>
This is handy, but every time I create new view or rename it, I have to be diligent in renaming it in my helper class. With T4MVC, all this are done automatically!

You can get T4MVC template via MVC Contrib project in Codeplex. How to use it - after the jump.

To use it, you will need to follow these easy steps:

  1. Download T4MVC (consisting of 2 files: T4MVC.tt and T4MVC.settings.t4)
  2. Put the files at the root of your ASP.NET MVC project
  3. That's it basically. If you need to regenerate code, right click on the T4MVC.tt file and click "Run Custom Code"
Here are some of the benefits of using T4MVC template that I heavily depending upon now. For more examples, click here for T4MVC docs on Codeplex.

View Name Constants
Instead of writing this:
 <% Html.RenderPartial("~/Views/MyController/MyCustomView.ascx");%>
I can now write:
 
<% Html.RenderPartial(MVC.MyController.Views.MyCustomView);%>
  
It even works if you have deeper folder structure, so instead of:
  
<% Html.RenderPartial("~/Views/MyController/Controls/MyCustomView.ascx);%>
  
Now I can do this:
 
<% Html.RenderPartial(MVC.MyController.Views.Controls.MyCustomView);%>
  
Also wonderfully works with Area:
 
<% Html.RenderPartial(MVC.MyArea.MyController.Views.Controls.MyCustomView);%>
  
This obviously also works from inside the controller,so instead of:
return View("~/Area/MyArea/Views/Controls/MyCustomView.ascx");
Now:
return View(MVC.MyArea.MyController.Views.Controls.MyCustomView);


Controller Actions
Previously in MVC you have write something like these:
<% =Html.ActionLink("Click here", "MyAction", "MyController")%>

<% =Html.ActionLink("Next Page", "GoToPage", "MyController", new { id = 1, page = 2, active = true })%>

Now with T4MVC magic, they become:
<% =Html.ActionLink("Click here", MVC.MyController.MyAction())%>

<% =Html.ActionLink("Next Page", MVC.MyController.GoToPage(1, 2, active))%>

Or if you have to add additional parameters that are not in the signature of the action:
<% =Html.ActionLink("Click here", MVC.MyController.MyAction().AddRoutevalue("param1", "John"))%>

<% =Html.ActionLink("Next Page", MVC.MyController.GoToPage(1, 2, active).AddRouteValues(new { sortField = "Name", sortDirection = "asc" }))%>


Using it in BeginForm
<%
using (Html.BeginForm(MVC.MyController.Edit(Model.Id), FormMethod.Post)) {
...
}

%>
There are much more usage of T4MVC that will make your MVC coding much more easier. What I have shown you are just the stuff I used the most. I recommend you to check out the docs in Codeplex for more examples.
-- read more and comment ...

Wednesday, June 9, 2010

jQuery 1.4.x Ajax traditional option

I have a very simple jQuery code that basically sends an array of integer back to my ASP.NET MVC Controller class.

 
$.post ("/MyController/MyAction", { myArray: ids });
My controller action:
 
public void MyAction (int[] myArray) {
   // do stuff here ...
}
But upon upgrading to jQuery 1.4.x, it does not work anymore. The param "myArray" in MyAction is always null.

During debugging, I found out that somehow it sent the post data as "myArray[]" instead of "myArray". Of course, you cannot name a parameter name with brackets in C# so simply renaming the parameter name in MyAction won't work.

After some readings, it turns out that this is a change in the jQuery 1.4 release - it changes the way it does param serialization. But, jQuery also provides a new flag in $.ajax to override the default behavior (and go back to use the old way of param serialization): the "traditional" option in $.ajax. So here is my revised code:
 
$.ajax({
   type: 'POST',
   url: "/MyController/MyAction",
   data: { myArray: ids },
   traditional: true
});
You can read more about the new param serialization here and here (under "Nested param serialization" section).
-- read more and comment ...

ASP.NET MVC2 Editor Template for DateTime with jQuery datepicker

Equipping a textbox with datepicker using jQuery is super easy. Once all the jQuery javascript files are referenced, all you need to do is this:

 
$(document).ready(function () {
    $("#myTextbox").datepicker({
        showOn: 'focus',
        changeMonth: true,
        changeYear: true
    });
});
So now, with EditorTemplate in ASP.NET MVC2, this gets easier. So basically I can override the default EditorTemplate for System.DateTime class (which just returns a regular text-box). Once the EditorTemplate is overridden, every time there is a DateTime in edit mode, it will be displayed using my template - which display a smaller text-box and pops up a datepicker once is focus.

Here is my EditorTemplate, located at /Views/Shared/EditorTemplates/DateTime.ascx:
 
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime?>" %>
<%=Html.TextBox("", (Model.HasValue ? Model.Value.ToString("MM/dd/yyyy") : DateTime.Today.ToShortDateString()), new { @class = "UseDatePicker date-text-box" })%>
Now, to declare the datepicker binding once and for all, I moved it to my master page - and instead of using the "id" of the element, use the class as the selector:
 
$(document).ready(function () {
    $(".UseDatePicker").live('click', function () {
        $(this).datepicker('destroy').datepicker({
            showOn: 'focus',
            changeMonth: true,
            changeYear: true
        }).focus();
    });
});
Now adjust the CSS for width of the text-box and other styles:
 
.date-text-box { width: 6em; }
UPDATE: Get the fully working sample project here.

Additional readings:
-- read more and comment ...

Monday, June 7, 2010

VS 2010 Coded UI Test

Last year, ASP.NET Test team release ASP.NET Lightweight Automation Framework and I blogged about it here. With VS 2010 Ultimate, you now can use "Coded UI Test" - which I think replaces it as a whole. The Coded UI Testing minimizes coding and recorded your actions - while also allowing coding and customization.

So how it works is basically like this:

  1. It records your behaviors (on a browser/WPF/Silverlight etc application) 
  2. Then it tries to guess what you are doing (moving a mouse, hover, waiting, clicking a button, typing text, etc).
  3. Generate code to replicate your behaviors/actions based on its guess. 
  4. You can also create assertions, to check whether certain elements (like texbox) has certain value, some labels display certain text, etc.

I have to say that for my web application that I am working on, it does an excellent job. It does all the low end work of recording my actions - and all I have to do is string them all together into a coherent sequence of actions in my tests.

MSDN has an excellent walk through on how to start one and customizing it to fit your needs and I am not going to repeat it in my post. You can read the MSDN how-to here.

In my experience in using it, it has its quirks:
  1. In some AJAX scenarios it will retain the old value or view of certain actions. So let's say you are looking at a product administration "Detail" screen and you click "Edit" to bring up the editing capability. Upon save, your application then brings you back to the "Detail" screen. In this scenario, sometimes upon "Save", the Coded UI test still holds the old "view" of the "Detail" screen instead of reloading/refreshing with the new "Detail" screen with the new/updated values. As a result, when you have an assertion to check whether certain values are updated, it will fail.
  2. Updating and deleting an assertion test or a recording is quite difficult. You have to manually delete it in code all the way. Updating certain test is also quite difficult if you want to do it manually. So for example if I have a test that checks whether a particular textbox value is "Something" and I want to change it to check for "some things" instead, intuitively I went to look for the line of generated code and change the checking value. But upon running, the code is regenerated and my changes are reverted back to "Something".
  3. The generated code also combine most of the generated code into 1 UI map. Which gets huge once you start building multiple tests. MSDN has a walkthrough on how to build tests using multiple UI mappings, but it is quite a challenge.
Beyond those issues that I have experienced so far, the tool itself is pretty impressive and I like it.

Some more readings and how-tos from MDSN

-- read more and comment ...

Saturday, June 5, 2010

How to Suppress .NET Warnings

In my current project, there are several warnings (C#) that are caused by several lines of code resulting from CSLA inheritance. Those warnings are basically about a local variable that is assigned but never used. Completely harmless and localized.Or on a different scenarios, you may run a Code Analysis and it returns a bunch or warnings.

For the longest time, I just ignore those warnings. But when I moved from VS 2008 to VS 2010 and to .NET 4, I thought that now is the right time to suppress those warnings.

There are several ways to remove/suppress warnings:
  1. Obviously, the warnings are there for a reason, and maybe that reason is valid. Maybe you declare a variable that is never used and should be removed, or make some methods "static", or unreachable code, etc etc. If these warnings are valid - then you should fix your code.
  2. Please do not go do this step if you have not done #1 thoroughly. OK, now you decided that the code is correct but the warning is still there and you want to suppress it. In most cases, you can right click on the warning in the list and select "Suppress Message" and pick whether you want to suppress it inline in the code or put them in a separate GlobalSuppression.cs file. Only the warnings that have a "CA" number or "CheckId" can be suppress this way. Warnings that do not have CheckId must be handled in a different manner - see #3.
  3. Using #pragma. Read this for MSDN explanation of pragma. Basically, with pragma, you do this: #pragma warning [disable|restore] [CheckId]. The CheckId must not contain the "CA" prefix. 
There you go ... now we can sleep a bit better knowing we have less warnings in our code. 
-- read more and comment ...

Sunday, May 16, 2010

Bundling Javascripts & CSS Files

If you are using jQuery for your site, it is possible that eventually your page header section may look like this:

 
<link href="stylesheets/ui.all.css" rel="stylesheet" type="text/css" />
<link href="stylesheets/admin.css" rel="stylesheet" type="text/css" />
<link href="stylesheets/maincontent.css" rel="stylesheet" type="text/css" />

<script src="scripts/jquery-latest.min.js" type="text/javascript"></script>
<script src="scripts/ui.core.js" type="text/javascript"></script>
<script src="scripts/mycustom.js" type="text/javascript"></script>
Now in those lines of code, there are 6 requests being made separately to the server and this can become a bottle neck for your site performance. Both YSlow and PageSpeed (read here and here) recommend reducing the number of requests being made as a way to create a high performance site.

Easy, right? Just combine the files with a simple text editor - both javascript files and css files are plain text anyway! Yes - you can do that. But this approach will yield a nightmare-ish maintainability and just plain ugly.

Justin Etheredge made a small framework to precisely do this - he called it Bundler.Framework. You can read his latest blog post about it here and download the code here. It is very simple to use and I have had excellent results with it. The framework itself is pretty self-explanatory and Justin's posts highlight on how to use it pretty well.

Through this post, I just want to highlight some of the small things I have to do to eventually get it working (and it has been working awesomely).
  • Add a refence to Bundler.Framework.dll
  • For the code to actually create a single js file (or css), you will need to make sure that your site's compilation is configure to NOT debug. You can do this in web.config, or else your js files will just be listed on one by one instead of being combined:
    <compilation debug="false" targetFramework="4.0">
    
  • Bundler also provide options to compress your css and also to minify your js with different minifiers.
     
    <%
        Response.Write(
            Bundler.Framework.Bundle.Css()
                .Add("~/stylesheets/ui.all.css")
                .Add("~/stylesheets/admin.css")
                .Add("~/stylesheets/maincontent.css")
                .WithCompressor(Bundler.Framework.Css.Compressors.CssCompressors.YuiCompressor)
                .RenderOnlyIfOutputFileMissing()
                .Render("/JJCMS/Content/Stylesheets/combined.css")
        ); 
    %>
    <compilation debug="false" targetFramework="4.0">
    
  • Bundler also provide options to only render combined js or css file if the rendered file is non-existent. You use the "RenderOnlyIfOutputFileMissing" option. See code above for example.

So now, with Bundler, your js and css will be rendered like this:
 
<script type="text/javascript" src="/scripts/combined.js?r=67EFAB2205D48FBBB390A6F11C8A4002E"></script>
<link rel="stylesheet" type="text/css" href="/stylesheets/combined.css?r=A84FC8E0836CD939A781066B0BBDE028" />
-- read more and comment ...

Wednesday, May 12, 2010

Installing Visual Studio GDR R2

Visual Studio 2008, by default, will work with SQL 2005 database project, BUT NOT SQL 2008 based. Microsoft provided a GDR (General Distribution Release) to make VS 2008 work with SQL Server 2008. You can work the latest version of the GDR here.

Reading the description in the download link, the requirements are VS2008 SP1, nothing CTP or Beta or Power Tools install. So, I think my box met all those requirements, and I ran the install. To my surprise, it failed and it gives me this message: "Visual Studio Team System 2008 Database Edition GDR Does Not Apply or is blocked by another condition on your system. Please click the link below for more details.".


To which I was not sure of what to do. So by reading the forums and blog postings, I found out that the installer actually write a log file, located in your local data temp file dir. Mine (running on Win 7) is located in: "C:\Users\\AppData\Local\Temp". The files should be labeled like "Visual Studio Team System 2008 Database Edition GDR ... .html". If open one of those, you should see the log generated by the installed and you can detect where it failed.

By examining the log file, my installation failed because it was looking for the SP1 flag in the registry. So even though I have SP1 installed, it did not matter, the installer is just looking in to the flag in the registry and if it does not find it, it abort the installation.

Eventually I have to run the SP1 Preparation Tool and the SP1 itself.

After the SP1 re-installation, the registry setting for SP1 is properly marked as 1 (or true). So the next step is to run again the GDR installer - and it went successfully this time. All is happy now.

Update: Gert Drapers wrote a much more extensive blog post about this here. -- read more and comment ...

Monday, May 10, 2010

Need wallpapers?

Want some themes or wallpapers for your multi-monitors running Windows 7? Here are some sites that I think have some cool wallpapers:


Do you have any particular sites that I can add into this list? Let me know in the comments. And here is the rest of it. -- read more and comment ...

Friday, May 7, 2010

Enabling Expiration/Cache in IIS7

Adding "expiration header" into your static files help the browser to cache those files - therefore reducing the payload in the network in which then increase the performance perceived by user. Both YSlow and PageSpeed highly recommend this approach in their high performance website articles.

You can do this programatically, this post is about enabling this in IIS7 - which is pretty easy.

  1. Open IIS Manager
  2. Navigate using the tree-view on the left pane to the folder where your static files are located. In most cases these are your static html, image, css, and javascript files. For example, I put all my images in 1 folder called "images" - so I highlighted that folder and on the right pane, in the "Feature View", double click on "HTTP Response Header".
  3. Under "Actions" - located top right, click "Set Common Headers"
  4. Check "Expire Web Content" checkbox and specify how long you want the files to be cache. I usually set this to 30 days. Or you set a particular date & time too if you want. Hit "OK". Do the same thing for all the folders whose files you want to cache.
  5. That's it! Now if you browse your site and look using YSlow or Fiddler or PageSpeed, you will see that the files under the folders are cache by the browser and not retrieved from the server every time.
-- read more and comment ...

Sunday, May 2, 2010

How to Turn on IIS7 Compression

One thing you can do to speed up your site is by turning on compression on your web server. This approach is highly recommended by Yahoo in their website performance analysis tool YSLOW as well as Google via PageSpeed.

In essence, compression helps in reducing data payload transmitted between the web server and the client browser. With machines (both the server and client machine) that are relatively powerful nowadays to handle compression easily - this than becomes a very viable and efficient solution to reduce network bottlenecks, thus increasing the perceived response to the browsing experience.

Here is how to do it in IIS7:
  1. Open IIS Manager and In "Features" view, double-click Compression. 
  2. Choose one or both of the following: 

    • Enable dynamic content compression to configure IIS to compress dynamic content. 
    • Enable static content compression to configure IIS to compress static content. 

  3. Open the configuration file at "C:\Windows\System32\inetsrv\config\applicationhost.config" and make sure your compression settings are correct or if you want to add/remove any compression settings.
Mine looks like this:
 
<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
    <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" />
    <dynamicTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="*/*" enabled="false" />
    </dynamicTypes>
    <staticTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="*/*" enabled="false" />
    </staticTypes>
</httpCompression>
That's it! Easy!

Now one caveat is that it seems that IIS7 does not compress javascript file immediately, so it may need a few hits before you can detect it using fiddler or firebug etc. I waited for about 2-3 days before eventually YSLow and PageSpeed both detect that all my javascript files are compressed.
-- read more and comment ...

Monday, March 15, 2010

Browsing securely using an SSH tunnel

Browsing on an internet cafe or a open/free wireless hot spot is scary sometimes. Some places are secured enough (like airport), but some are pretty questionable. You're not sure who owns the router, whether they put security on it, packet tracer, or a phising software etc ... Some times you also want to avoid the proxy that tracks your cookie, traffic, website blocker, etc etc. Regardless of the reasons, you may want to be able to browse securely - and a way to do it is by tunneling it via SSH.

So how does this work? Basically you want to setup an SSH server and redirect all of your browser traffic via this SSH server using an SSH client on your computer (instead of directly via http into the internet). This SSH tunnel is secured, so no one can peek into your traffic, unlike the regular http traffic. So here is how I did mine using Bitvise WinSSHD & Tunnelier. You can download both (free) from Bitvise website.

Installing WinSSHD (the SSH server)

  1. You will need a computer that is always on or with wake-up on LAN enabled. I use my media center machine for this. 
  2. Download WinSSHD (from here) and install it. Just follow the default instruction on the screen and you should be ok. If you are not an administrator on your machine then you will need to install this as an administrator. There is a more detailed User's Guide by Bitvise here.

Configuring WinSSHD

  1. Once installed, the WinSSHD control panel should open with the "Easy WinSSHD Settings" window open.
  2. Under "Server Settings", you can customize the listening ports etc if you want - or just leave then with the default settings and click "Next" and go to "Windows Account".
  3. Under "Windows Account", you can allow or disallow Windows Account to login/connect to your SSH server. The default setting is to allow. I disallow this for security reason and created a locked down virtual users instead. You can read more about virtual vs Windows account here.
  4. Under "Virtual Users", you can add virtual users. Click "Add" and assign account name and customize the permissions for that user, then click "OK". Once the user shows up in the list, select the user row, and click the pencil icon under "Virtual account password" column and the click the "Manage" button, then set the password for that user.
  5. Once the "Easy WinSSHD Settings" window is gone, now you are looking at the WinSSHD control panel.
  6. Now click "Edit advanced settings" and go to "Windows Firewall" area. Make sure that both drop downs are set to "Open ports to any computer" - so you can connect from outside your network/internet. You can read about opening up WinSSHD to the internet here.
  7. Make sure the service is running. If not, start it. There should be a link in the control panel to start or stop the WinSSHD service.

Installing & Configuring Tunnelier

  1. Download Tunnerlier (from here) and install it. Just follow the default instruction on the screen and you should be ok. If you are not an administrator on your machine then you will need to install this as an administrator. There is a more detailed User's Guide by Bitvise here.
  2. Once installed, try connecting to your WinSSHD from within your network by entering the IP/host name, port, username, and password in the login area of Tunnelier and click "Login".
  3. You should see the status of connection and data transmission in the right text area.
  4. Now once it is setup within the network, before you can try it from the outside of your network, you will need to configure port forwarding in your router.

Configuring Router Port Forwarding

  1. A router usually has a port-forwarding tool built-in. I am not sure how each router does their setting specifically, but usually there are several simple steps to register the port-forwarding. Go to portforward.com/ for the specific instructions on how to do it for your router.
  2. Make sure your box that runs the SSH server is running on a static IP address within the local network. You can do this via the router's DHCP or using the TCP/IP setting in your computer.

Registering (dynamic) DNS Name

  1. If you are running your SSH server from home, most likely you have a dynamic IP address. So, what you want to do here is register a dynamic DNS name, so that when your IP change, it is be also updated and tied to your DNS name dynamically. In the end, all you need to remember is your DNS name. The way this works is that dynamic DNS registration company has a small software in your box that will send an updated IP address to your dynamic DNS registry so it continuously updating your DNS name registration with your current IP address.
  2. There are a lot of dynamic DNS provider out there in the internet - make your own pick here. I use No-IP. Usually you just need to register/create an account on their site, pick your DNS name and download the updater client software and install it on your box and you are set!

Trying It From Outside Your Network

  1. To do this obviously you will need to be connecting to the internet from outside your LAN
  2. Open up Tunnelier
  3. Instead of entering the internal IP address, enter the dynamic DNS name you have registered and leave everything else the same and then click "Login"
  4. If the port-forwarding and Windows Firewall and the account settings are set up correctly, you should be connected to your SSH server.

Setting Up Your Web Browser & Tunnelier for Secure Browsing

  1. Open up Tunnelier and go to "Services" tab. Make sure "SOCKS/HTTP Proxy Forwarding" is enabled. Put "127.0.0.1" for "Listen Interface", pick an unused port number (like 8081) enter it in "Listen Port" field, leave the "Server Bind Interface" to be all zeros. Basically, follow these settings in Tunnelier.
  2. Now in your browser, follow these settings: Firefox, Internet Explorer
  3. Now you are set!
-- read more and comment ...

Wednesday, March 10, 2010

AJAX Animation using jQuery

During an AJAX call from a website, usually the screen won't blink - because page is not refreshing. This is neat because it gives the impression to the user that their interaction with the site to be seamless and uninterrupted.

But sometimes, you do want to give a visual clue to the user that an action is happening and you want them to know about it. Maybe by giving them an hourglass or a "loading ..." clue etc. So how do you do this easily? This post is about using jQuery to provide visual clue to the user on AJAX requests.

First, obviously you will need to reference jQuery library. I usually use the one hosted in Google:
 <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
Add the HTML to display our visual clue. In this example I am displaying a black bar on the top of the browser, about 25px in height that has a "Loading ..." text in it. So when an AJAX call is happening, this black bar will be displayed until the call is done.
 <div style="z-index:101; width:100%; height:25px; position:fixed; left:0px; top: 0px;">
<div id="loadingbar" style="height:25px;display:none; margin-left:auto; margin-right:auto; background-color:#000000; color:#fff">
  Loading ...</div>
<div class="stretch"> </div>
</div>
Now we add the javascript to show and hide the bar, plus I also like to gray out the background a little bit.
 $(document).ready(function() {
    jQuery().ajaxStart(function() {
        $("body").fadeTo("fast", 0.70);
        $("#loadingbar").show("fast");
    });
    jQuery().ajaxStop(function() {
        $("body").fadeTo("fast", 1.0);
        $("#loadingbar").hide("fast");
    });
});

Done. Now anytime there is an AJAX call in your page, the background will be grayed out a little bit and a black bar will show up on the top. To increase/decrease the opacity of the background during the graying out, you can adjust the value on the "fadeTo" call. Currently it is set to "0.70" or 70%. If you want less transparency (or more grayed out), then set it lower (between 0.00 to 1.0), and set it higher for less transparency.
-- read more and comment ...

Sunday, February 21, 2010

Food Deals, Tips, and Tricks

In this post, I thought I share some of the food deals, tips, and tricks that I found to be beneficial for me - and of course these food joints are the ones I frequent. I am pretty sure there are more out there - if you know those deals, tips, and tricks, let me know in the comment section and I will try them and add them into this post with credit to you.

Deals:
Restaurants.com: Several times a year, Restaurants.com has excellent deals with their discount code. I bought several coupon for $25 off for certain restaurants for only $3 per coupon, some for $4 (depending on the deals). Be on the look out at fatwallet.com or deals.woot.com for coupon code.

Roosters: on Olentangy River Rd chain, they have appetizers deal on Tuesdays. So each Tuesday they will give you a discount on one selected appetizers. For example, last time I went there, it was $1.07 for an order cheese sticks (has 5 sticks in it).

Qdoba: On Tuesday nights, kids eat free at Qdoba. If you are a Qdoba card holder, get double points on Friday. If you go to the one in 5th ave, they give free chips if you come after 8pm - for any day. Students can have a $5 meal - any day.

City Barbeque: Buck-a-bone Tuesdays. So instead of paying $17.99 for a full slab - pay only ~$12. Some stores have $4 off a sampler Mondays.

Applebee's: 1/2 Price Appetizers with any beverage purchase from 10 p.m. to close. At the bar from 11 a.m. to 4 p.m. (dine-in only. excludes sampler).

Tips & Tricks:
Chipotle/Qdoba:

  • For "more" portion in your fajita/burrito without bursting your tortilla, as for bol/naked with tortilla on the side - no extra charge!
  • Chipotle will make quesadilla for you - even though it is not on the menu
  • Order a Quesorito in Chipotle - a hybrid of quesadilla and burrito.
  • You can get half/half (i.e. chicken and steak) for your meat selection - and pay only for the more expensive price
  • You can get "double meat" and pay extra $2 (nore really sure about the price - but should be close)
  • At Chipotle, if you are ordering salad, you can add fajita onions/peppers with no extra charge
BD's Mongolian Barbeque:
  • Instead of paying the full buffet price ($13.99 I think), you can elect to go once ($8.99 - LUNCH only). For most women, this is usually enough, if not - or if you are a man - read the next point.
  • Now, there is a trick on how to arrange your ingredients in your bowl so that you can have plenty (I am mean PLENTY) of food by just going once. The key is to put your vegetables on the bottom and the meat on top - so that the vegetables will get pushed to the bottom by the meat therefore allowing more room. In detail, follow these steps:
    1. If you do not want vegetables, skip to step 2. Get your empty bowl and go to the vegetable section right away, skip the meat for now. Get your vegetables, try to arrange them neatly and allowing an empty opening in the middle.
    2. If you do not want seafood, skip to step 3. Go to the seafood section. If you have vegetables, put your seafood in the opening in the middle of you bowl. If you do not have veggies, arrange them however you want, but try to be neat.
    3. If you do not want carbs, skip to step 4. Go to the carbs section. Get your noodle/pasta and try to arrange them neatly. These "arrange neatly" thing is important but don't be OCD about it. Just make sure they are packed and not overflowing.
    4. Now go the spices section, get the "cooking" ingredients. What I call the "cooking" ingredients are the items that you want to be cooked along from the beginning with your veggies, meat, seafood. For the ingredients that will go later, I call them "sauce" ingredients. So in this step, go get your "cooking" ingredients; usually something like garlic, ginger, some spices, etc - depending on your style and recipe. Don't want to go crazy here, since you can always add them later in the "sauce" - I usually just get minced garlic in this step.
    5. Go back to the meat section. Whatever you do, put beef/steak last - since it is usually sliced thinly - so you use is as a "roof" to hold everything together. Get your meat - in the order or smallest to largest - then put beef strips last.
    6. Now, assemble your sauce. Depending on how much you got to pile on your bowl, you may require more than 1 mini-sauce-bowl to hold your sauce - which is alright. Put your food bowl and on the sauce counter and assemble your sauce, spices, and condiments. 
    7. CRUCIAL STEP - if the cooking area is crowded, make sure you look and pay attention to where do you think you will be lining up at - and go there ONLY WITH YOUR FOOD BOWL, leaving the sauce bowl(s) behind on the sauce counter. If you are not on the cooking counter directly (someone is in front of you with their food being cooked), put your food bowl on the counter and then go back to the sauce counter to get your sauce. If necessary, go get egg(s) as well and go back to your spot in the cooking line and put your sauce bowl(s) and egg(s) on the cooking counter.
    8. Now wait for your turn ...
    9. Pay attention to your food being cooked and tell the cook about your preferences (sauce now/sauce later, egg now/later, etc). When it's done cooking, make sure the cook put your food in the yellow bowl, NOT the blue bowl. Blue bowl is for repeats, so you will be charged full buffet instead of the single. If for some reason, you got a blue bowl, go back to your seat and tell your server about it - that you only went once with 1 ingredients bowl and don't want to be charged full buffet (this only ever happened to me once).
Roosters:
  • You can combined up to 3 wings sauces without any extra charge. So if you want your wings to be in "chipotle" an "garlic" - just say "chipotle garlic" on your sauce selection when you order.


For some more food items that are not in the menu:

-- read more and comment ...

Wednesday, February 17, 2010

Using ASP.NET Chart Controls in ASP.NET MVC

In the past, I blogged about using Google Visualization to render charts with ASP.NET MVC here. It's pretty easy, it works, and fast. What if you do not want to use Google Visualization / javascripts to make your charts? Well, Microsoft released ASP.NET Charting Controls late 2008 and this blog post will discuss several options you can have in using it with ASP.NET MVC.
There are several ways you can do this (that I have tried/used):

  1. Generating and rendering the chart in the controller and returning it as a FileResult using MemoryStream
  2. Generating the chart in the controller and put it in the view data and let the aspx/ascx renders it using the webform control
There are advantages and disadvantages for both approaches. Using the MemoryStream approach works everytime, but it does not render the mapping, so it does not attach url, tooltip, etc on the legend and the chart by default. You have to do those things manually.

Now using the webform control gives you all the bells and whistles (url, tooltip, label, etc) - no extra work needed. BUT - in my experience, it does not work if you run your MVC project under a virtual path. For example, if you run your project under root, such as http://localhost:2000/Home/Index - it will work. But if you then go to the Project Property window in the solution explorer in VS 2008, go the the "Web" tab, and set a virtual path, and build and run (i.e. http://localhost:2000/Chart/Home/Index) - it will break. When it runs, it will give you "Failed to map the path '/ChartImg.axd'". I am still unable to solve this problem yet - you can see my StackOverflow question here.

Before I show you my code, I have to give credit to Mike Ceranski - he blogged about how to use the webform control in rendering ASP.NET chart - in which his approach become the one I used in my projects and become a partial subject of this blog post.

You will need to download a couple of things to get started:

First, you will need to prepare your web.config by adding these lines:
 

    ....
    <appsettings>
        <add key="RenderMode" value="MEMORYSTREAM" />
        <add key="ChartImageHandler" value="privateImages=false;storage=file;timeout=20;deleteAfterServicing=false;url=/App_Data/;WebDevServerUseConfigSettings=true;"/>
    </appSettings>
    ....
    <system.web>
        <httphandlers>
            <add verb="*" path="ChartImg.axd" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
        </httpHandlers>
    </system.web>
    ....
In your development environment, when running using VS 2008 by hitting F5, the images will always be rendered in memory regardless what you put in the appSettings, unless you are including the "WebDevServerUseConfigSettings=true;" in there.

You can also read more about the possible enumerations and values for the appSettings here.

Now, the HTML:
 
<%         
    if (ConfigurationManager.AppSettings["RenderMode"] == "MEMORYSTREAM") {
        Response.Write("");
    } else {
        myChart.Controls.Add(ViewData["Chart"] as Chart);
    }
%>


The controller code:
 
public ActionResult Index() {
    ViewData["Message"] = "Welcome to ASP.NET MVC!";

    if (ConfigurationManager.AppSettings["RenderMode"] != "MEMORYSTREAM") {
        Chart myChart = CreateChart();
        ViewData["Chart"] = myChart;
    }
    return View();
}

public ActionResult Chart() {
    using (var ms = new MemoryStream()) {
        Chart myChart = CreateChart();
        myChart.SaveImage(ms, ChartImageFormat.Png);
        ms.Seek(0, SeekOrigin.Begin);
        return File(ms.ToArray(), "image/png", "mychart.png");
    }
}

private Chart CreateChart() {
    Chart chart = new Chart();
    chart.Width = 350;
    chart.Height = 300;
    chart.Attributes.Add("align", "left");

    chart.Titles.Add("MY CHART"); // Display a Title  
    chart.ChartAreas.Add(new ChartArea());

    chart.Series.Add(new Series());

    chart.Legends.Add(new Legend("MY CHART"));
    chart.Legends[0].TableStyle = LegendTableStyle.Auto;
    chart.Legends[0].Docking = Docking.Bottom;
    chart.Series[0].ChartType = SeriesChartType.Pie;

    for (int i = 0; i < 10; i++) {
        string x = "MEMBER " + (i + 1).ToString();
        decimal y = ChartTest.Models.Utility.RandomNumber(1, 100);
        int memberId = i;
        int point = chart.Series[0].Points.AddXY(x, y);
        DataPoint dPoint = chart.Series[0].Points[point];
        dPoint.Url = "/Member/Detail/" + memberId.ToString();
        dPoint.ToolTip = x + ": #VALY";
        dPoint.LegendText = "#VALX: #VALY";
        dPoint.LegendUrl = "/Member/Detail/" + memberId.ToString();
        dPoint.LegendToolTip = "Click to view " + x + "'s information...";
    }

    chart.Series[0].Legend = "MY CHART";
    return chart;
}
Now, depending on how your "RenderMode" setting is in the web.config, it will either render the chart using approach #1 (memory stream) or #2 (using webform control).

Additional readings/resources:
-- read more and comment ...

Tuesday, February 9, 2010

GeoDocs Reborn, version 8.0 Released!

After a long overdue, GeoDocs 8 was released by AWH on December 2009 (this blog post is late). Last month, GeoDocs revamped its website using the new version, new look and feel, and faster!

In a collaboration with Nationwide Children's Hospital, they became the first client to upgrade to the new version.

GeoDocs was also recognized as a semi-finalist for the 2009 TechColumbus Innovation Awards! GeoDocs was nominated in the category of 'Outstanding Product, Fewer than 50 Employees'.

So what makes GeoDocs 8 to be better than its predecessor?


There are a lot of reasons why GeoDocs 8 is better, as far as the technical aspects - here are some of them:
  • Running on .NET framework 3.5 (GD 7 was running on .NET 1.1)
  • The UI has been rebuilt from scratch using ASP.NET MVC
  • SEO friendly URL
  • AJAX integration with jQuery and jQuery UI
  • Better and cleaner integration with Google Mini/Search Appliance
  • Cleaner code base that enhance development experience
Now if you are a user, here are the benefits of GD 8:
  • Much, much, much faster - and can be faster still depending on you configuration
  • AJAX means better user experience:

    • Less screen refresh to load/reload data
    • Faster feedback in returning on-demand data
    • Nice and unobtrusive animations/cues for user actions
    • Faster and friendlier editing panel (Web View Editor)
    • More robust WYSIWYG editor
  • SEO friendly URL, means nice looking URL and easily crawled by search engine
  • If you are developing your own custom templates/look & feel - it will be much easier
This does not mean that we are removing all the good features and functionality that GD 7 has, GD 8 still has them, but better! GeoDocs since version 7 has boasted robust feature such as:
  • Manage multiple website authors, provide workflow and security
  • Help you manage your graphical brand and optimize navigation and layout
  • Provide secure, granular access and information to defined users
  • Customizable content types that will suit your needs
  • Excellent and popular modules such as Form Builder & Calendar will continue to be supported and enhanced
GeoDocs is a product created and maintained by the Allen, Williams & Hughes Company, or AWH.

You can follow GeoDocs on twitter and on facebook.
-- read more and comment ...

Monday, February 8, 2010

Best Bang for The Buck!

One of the restaurant that I probably most frequently visited in the last 2 years is Tai's Asian Bistro. It's a small place on Lane Ave, close to OSU West Campus (click here for map). How frequent did I go? At least once a week, more likely twice a week and sometimes more. I think remember that there were times when I actually went everyday of the week, excluding weekends.

So, why? Is is that good? Cheap? Economical? Fit my taste? What are the reasons that so compelling for me to keep coming back?

Well, the blog title pretty much sums them up, but here are the details about why Tai's Asian Bistro is the best bang for the buck for me (some may not apply for you):

  • The food themselves are excellent. If you are a fan of spicy food, you need to try their "Hot Pepper Chicken". This used to be Mark Pi's best, but somehow Tai's makes it better. Read for some of my recommendations.
  • It's relatively cheap (between $6 to $8)
  • The portion is HUGE, I meant heaping amount of food. For most people, this can mean lunch and dinner (for under $10).
  • The food is also of good quality. The breaded chicken is breaded and fried fresh daily - not from frozen, the ingredients are fresh, egg rolls are fried on demand, etc.
  • The place is clean, including the kitchen. You can see the kitchen right there and watch your food being cooked.
  • The service is excellent. Food being cooked super-fast and if you are dining in, it will delivered to your seat. All the servers are helpful and friendly.
  • For carry-out, they give you this plastic tub that is awesome and reusable. So instead of styrofoam that barely holds your food and melted easily, they use good quality container.
  • If you are a student, free pop! They also serve beer if you want.
  • Did I mention they are generous with their portion?
Tai's has a small selection in their menu, but for each one, they do it extremely well. I have pretty much tried every single item in their menu and in the order of how much I like them here are the top ten items to order at Tai's Asian Bistro:
  1. Hot Pepper Chicken (very spicy)
  2. Beef with Wild Mushroom
  3. Singaporean Rice Noodle (a bit spicy)
  4. Cantonese Chow Fun
  5. Dan Dan Noodle
  6. Pad Thai
  7. Tai's Asian Chicken
  8. Double Pan Friend Noodle
  9. General Tso's Chicken
  10. Rice Noodle Salad

-- read more and comment ...

Friday, February 5, 2010

Implementing jQuery FullCalendar PlugIn with ASP.NET MVC

I have been using jQuery for most of my javascript work, so when I need to display a calendar related data in a "month view", I did a search for jQuery plug-ins that will work with my existing project (and extensible to be used for future projects as well). My current projects are built using ASP.NET MVC, so working with jQuery and its plug-ins just makes sense.

After a careful research, trial & error, I finally picked "FullCalendar" jQuery plug-in. You can download it here. It says in its website as:

"FullCalendar is a jQuery plugin that provides a full-sized, drag & drop calendar like the one below. It uses AJAX to fetch events on-the-fly for each month and is easily configured to use your own feed format (an extension is provided for Google Calendar). It is visually customizable and exposes hooks for user-triggered events (like clicking or dragging an event)"

So first thing first, let's just try to get the calendar to display. In one of my view, let's call it "MonthView.aspx", I have this javascript (jQuery):
 $(document).ready(function() {
   $('#calendar').fullCalendar();
}
Somewhere in the HTML:
 <div id='calendar'></div>
Set the controller code to display the view:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult MonthView() {
   return View();
}
At this point, we should be able to display the empty calendar by going to http://<site>/<controller_name>/MonthView.

So now, we are going to modify this empty calendar to display the data from somewhere (db/xml/etc) via AJAX. For this, I created a class called "CalendarEvent" which structure is mimicking the JSON object model described in here; such as this:
 public class CalendarEvent {
   public string id { get; set; }
   public string title { get; set; }
   public string date { get; set; }
   public string start { get; set; }
   public string end { get; set; }
   public string url { get; set; }
}
Then in the controller code:
 
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult CalendarData() {
   DateTime start = new DateTime(1970, 1, 1);
   DateTime end = new DateTime(1970, 1, 1);

   start = start.AddSeconds(double.Parse(Request["start"]));
   end = end.AddSeconds(double.Parse(Request["end"]));

   // call middle tier/orm/whatever to get data from source
   List list = SearchForEvents(start, end);
   
   return Json(list);
}

private List SearchForEvents(DateTime start, DateTime end) {
   // code to get data here

}
Now, we need to modify the jQuery to call the action in our controller to return data:
 
$(document).ready(function() {
   $('#calendar').fullCalendar({
      events: '<%= Url.Action("CalendarData", "MyController") %>'
   });
}
Voila!

-- read more and comment ...

Wednesday, January 20, 2010

My Blog Shows Up in Google But Not in Bing - Why?

When I started blogging, all my traffic pretty much were from direct, meaning someone knows the URL to my blog and type it in the location bar. Most likely those people are myself. As time goes by, I added more content, post links in twitter, stackoverflow.com, facebook, etc, my blog starts to pick up some traffic, both from referral (someone clicks a link to get to my blog) or from search engine (mostly Google). Now, my site traffic source is about 80% from search engine results. I use Google Analytics to track my traffic.

So, when Microsoft released Bing, I tried it and I like it. I was considering switching totally to Bing as my main search engine, but one thing that saddened me was that Bing did not produce results for my blog, even when I entered partial URL of my blog address, or the title of my blog, or any keywords in it. Basically I came to the conclusion that since Blogger or Blogspot is owned by Google, they must be crawling it automatically, maybe somewhat prioritize it in their ranking. But, Bing does not crawl Blogspot - which includes my blog.

So how can we fix this?




Turns out, there is a "Bing Webmaster Center" page (here), where you can submit your site to be crawl by Bing (assuming it has not been crawled). Voila! So I went in there, click "Sign in to use the tools" button in the middle of the page. Then it asked me to sign in with my Windows Live ID (this can be your hotmail account, LIVE account, MSN account). Once logged in, I clicked on the "Add a site" button, enter my blog address in the "Web address:" textbox and click "Submit".

Then the webmaster tool will present an option to authenticate your site. You can either place an XML file in your site or add a meta tag in the homepage of your site. I selected to use the second option (add metatag) since my blog is hosted in Blogger/Blogspot and I cannot add a file - but adding a metatag just means I have to modify the template of my blog. So I went to my blog layout administration screen, copied and pasted the metatag from Bing Webmaster page and saved the template.

Now back in the Bing Webmaster page, I clicked "Return to site list". In there I can see all the sites that I have registered with Bing to be crawled and I can check the status as well by clicking on the link for each site. Wait for several days for crawling and everything is all set! Pretty easy!

Bing
Bing Webmaster Center
Google Analytics
-- read more and comment ...

Tuesday, January 19, 2010

AWH Has Job Openings

Need a job? Well, AWH - where I am currently employed - has several openings for .NET developers. Why work for AWH? Well, I have been working there for 10+ years - that ought to tell you something, huh? It's an excellent place to work, with good and vibrant team members, excellent leadership, relatively flat organization, cutting edge technology, and more importantly, we're growing!

So, what are you waiting for? You can see our openings and submit your resume here.

-- read more and comment ...

Cheap Mini Photo Portable Studio

I had several hours of free time during the Christmas holiday and I grabbed my camera and experimented on how to make a small item shot that looks decent, with a small/no budget. This exercise is pretty useful in real life if you are someone who put a lot of stuff on Ebay or online listing (ie. craigslist, etc).

The goal is to be portable, cheap, and quick - but also produces nicely lit object. I want a fairly soft light to create a nice even light. So, I am thinking either bounced light or diffused light. I went with diffused light, since it is easier to control the power that way.

So here are items that I used, which most if not all you can find in your house or buy cheaply:

  • Parchment paper or wax paper. This is for the diffuser.
  • Open cardboard box for creating a boxed controlled environment, cut out to attach the parchment paper (see setup shot for an example).
  • A black or dark surface. Or glass/reflective surface will also work.
  • A piece of blank white paper/carton to be a reflector.

I happen to have a black dining table that is a fairly smooth surface - so I used that as my surface. I put my box above the object, with another cut-out hole on the top of the box to let the light go through.

I setup my flash about 2-3 ft above the box facing down into the box. Attach the parchment paper on the cut-out hole to diffuse the light. Place the paper reflector inside the box on the other side of where the flash is.

Setup up the max sync speed on your camera and adjust your aperture to get a complete darkness exposure - this is for making sure that your only source light is your flash, not the dining room lamp, the sun, etc etc. Now turn on your flash and adjust the power of your flash to get a good exposure. Tweak the distance between your flash and the box as necessary, or add another layer of parchment paper if needed.

Here is my setup shot:




Learn How To Light via Strobist
Overpowering The Ambient

-- read more and comment ...