Sunday, February 24, 2008

ASP.NET - Client Side List Box

We needed to create a dropdown list that would allows us to reorder items on the list. Piece o' cake, right? We created a composite web control with a drop down list and 3 client side buttons. A bunch of javascript managed adding the items and sorting the list. No big deal.

The interesting part didn't happen until post back. Asp:ListBox doesn't recognize things that are added from the client, only from the server. I thought it would work because you can do that type of things with, for example, a textbox. The different is that a textbox is an input control and a listbox is just a select control.

I created a new composite control called ClientListBox. I have know idea if I did it correctly, but it works. (I have minimal web control experience. I'm more about all of the other tiers. The composite control contains a listbox and a hidden field. As you add items to the list, the text and values are saved to the hidden field. On post back, it wipes out the list times, then creates new ones based on the contents of the hidden field.

The hidden fields stores the values as:
value\ttext\n

The IDs of the listbox and hidden fields are set dynamically based on the id of the composite control. So, if the control id is TEST, then the listbox is TEST_LB, and the hidden is TEST_HIDDEN.

Its intended use is client side script, so it gives you a known method name to retrieve the object.

If the control name is TEST, then in javascript, you can call getTEST() to obtain a reference to the object. You can then all methods like Add(text, value), etc. Conceivably, there would be MoveUp and MoveDown methods too, but we didn't bring it that far.

ie: alert(getTEST().getCount());

getTest() is created on the server side.

function getTEST() { return new ClientSideListbox(hiddenId, listboxId); }


Is that the proper way to write a webcontrol? I have no idea. But its clean and it works.

DvdFriend - Added the RECENT RELEASES section

For a site that would like to sell some DVDs, there certainly has been a lack of price listings.

In all 87 versions of the site that I've built to date, I've always struggled with determining what information to show where. Movies are coming out on DVD... should I list the date? The average rating? The Image? How many should I show? Should it be a number or by date range?

I refused to engage my self in those debates this time. Its showing 50 releases ranging from TODAY + 21 days, to whenever it runs out of movies. They're listed on the right side of the main page. this will eventually evolve into a more detailed page. I'd like to add a mouse-over dialog to it (like netflix does). I can easily do that, but as always, making it pretty will be the difficult part.

A key thing I did tackle is determining which titles to show. There may be a ton of movies in there, but not all of them are interesting enough to put on the front page. So, its only showing DVDs of movies that someone on the site has reviewed. Additionaly, there's a mechanism in there to flag movies that I want to show up. I haven't added the page functionality yet, but the back end is ready to go.

Later, the date headings will become hyperlinks that will show all of the releases for that day, without any filters. At the rate I'm going though, I'll spend 4 minutes doing it in 3 months.

Saturday, February 9, 2008

DvdFriend - User Review Page

In the other blog, Chris requested a page that shows all of the reviews he has written. I estimated it would take 14 seconds. It took 50 minutes to build and deploy.

Example: http://www.dvdfriend.us/UserReviews.aspx?un=DVDFriend

1 - Create a page Called UserReviews.aspx. It uses the normal site master page. Loaded this page into the browser passing it ?un=DvdFriend. The page loaded without any content.

2 - Created a stored procedure called DFGetUserBlogs(@userName). There's an existing view, DFvwBlogEntries, that already has all of the information I need. The stored procedure queries some fields for that where UserName=@userName

3 - Expose the query as a method in one of the business objects. There's an existing class called BlogFactory that has lots of similar methods. I created the new method:


public static DataTable GetUserBlogs(string userName)
{
const string STORED_PROCEDURE = "DFGetUserBlogs";
return AWDatabase.GetReadonly().ExecuteDataSet(STORED_PROCEDURE, userName).Tables[0];
}

4 - Added an object data source to the new page. Pointed it to the new method. Added a query string parameter for un.

5 - Added a datagrid to the page. Wired it up to the object data source. Loaded the page.. it's good!

6 - Tweaked the grid. Shut off AUTO GENERATE COLUMNS, then added the fields in the order I wanted. Formatted the date field.

7 - Added the SortExpression for each of the columns.

8 - Added a server-side H1 to the page. Assigned its value from the UserName property, which returns the value of Request.Querystring["un"];

9 - Changed Default.aspx and P.aspx: converted the USERNAME display fields to hyperlinks for the new page.

10 - Tested

11 - Deployed

Using out-of-the-box asp.net controls, and a little custom code, I was able to create this page pretty quick, but there's a lot more that can be done.

The page is a dump of everything the user has. Currently, DVFriend is the worse-case-scenario, wich isn't so bad. Once the list reaches a certain unknown size, it will be time to add paging.

Adding paging is easy enough; just have to enable it in the grid. However, the problem is that even if you're only showing one page of data, it would still query for all of the rows. I wouldn't be able to sleep if I did that (other than as, maybe, a transitional step).

I had a problem with the sort description. I set the Rating column to Rating,ProductName. I thought that would work, but it didn't. It always sorted by rating ASC. DESC wouldn't work, so I took off product name for now. The sorts will have to be revisited.

Other TO DOs
- I'd also like to add a summary grid and some filters on the top. IE: Pick a particular rating, and or date range; and/or reviews only. The summary would show how many ratings and reviews the user has, how long they have been active, etc.

- Show a list of other users on the right side so you can browser all reviews from all users. As long as the active user list is short, that will work... i can drop it in quick. Once the site conquers the world and the list is bigger, it won't hold.

- Put the grid in an asp.net update panel. I would've done this to begin with if I thought of it at the time.

So, the asp.net controls are pretty powerful; quick and easy. I'm not a huge fan of the final result, though. Its super quick and easy, but each SORT operation is a post back. I'd rather use the querystring so that you can bookmark the page and get back to it in the same state you left it. As is, you can bookmark it, but it will go to its original state.

Prior to asp.net, I created a ton of reports in JTS that needed this functionality. It was all done by adding attributes to the html tags, and javascript would create the new url and request it. In the end, I ended up with one generic ASP page and a bunch of XLSTs and data sources. To sort, you'd add a SORT="" attribute to the corresponding TH. There'd be some notation for asc, desc, etc (I forget the details). That worked out pretty well.

Pet Peeve 277: Ordinals vs key name

Pet Peeve #277

select FirstName, LastName, ShoeSize from MyFavoriteTable

... get a reader ...

while (reader.Read()) {
Console.WriteLine("First Name: " + (string)reader["firstname"]);
}

Per my previous post, I prefer (string)reader["firstname"] over reader["firstname"].ToString(). But it doesn't stop there. Oh no. There's more. I don't like that approach at all. I'd much rather see reader.GetString(FIRST_NAME) but more on that later.


Notice that the select statement queries for FirstName, but we're looking for "firstname" in our reader. The reader is going to do a case-sensitive search for "firstname". When that fails, it will do a case-insensitive search. Why search twice? In fact, why search at all? Just tell it the index number.

string firstName = reader.GetString(0);
string lastName = reader.GetString(1);
Int32 shoeSize = reader.GetInt32(2);

That's more efficient. Its going right to where it needs to go. But, 0, 1 and 2 aren't very readable, are they? Gosh no.

I use constants.

const int FIRST_NAME = 0;
const int LAST_NAME = 1;
const int SHOE_SIZE = 2;

get reader... create while loop, etc

string firstName = reader.GetString(FIRST_NAME);
string lastName = reader.GetString(LAST_NAME);
Int32 shoeSize = reader.GetInt32(SHOE_SIZE);

Of course, for this to work the fields need to be in a known predictable order, which in my experience is usually the case. Maybe it wouldn't fit if you have multiple existing procedures that return similar fields in different orders all going through the same method, but what are the odds of that? I've always been able to define the order.

Reader has another method called GetOrdinal() that you can use to look up the ordinals up front. So, if you really don't know the ordinal, you can do this:

int firstNameOrdinal = reader.GetOrdinal("FirstName");
int lastNameOrdinal = reader.GetOrdinal("LastName");
while (reader.Read()) {
string firstName = reader.GetString(firstNameOrdinal);
string firstName = reader.GetString(lastNameOrdinal);
...
}

That way, you only do the lookups once rather than at every iteration of the loop.

If you know they'll never change, then maybe you assign them to statics. I used to have a pattern to cover that scenario, but I quickly learned to dislike it because of the comingling of static/non-statics. Basically, when it got to the reader, it would check the value of one of the static ordinals. If it was -1, it would assign all of the statics. From there on, it wouldn't have to do it again. I don't like it.

The GetOrdinal follows the same steps as reader["fieldName"]. First it does a case-insensitive search, then a case sensitive search if necessary. So, if you don't have the ordinal, then hopefully you at least have the proper column name.

My position is clear: know the ordinals upfront and use them.

In order to do that effectively, you have to rely on the column order. If you do "select *", that's not reliable or portable. Always explicitly specify your select wether you're hitting a table or view directly, or when a proc does it for you.

Reader Source Code

I took a look at the reader source code to compare reader["string"] to reader.GetString(0). The by-ordinal approach doesn't immediately return the value... it does a little legwork first. By-string does the same legwork, plus the additional work of looking up the ordinal to get it started.

Pet Peeve 318: (string) vs ToString()

I'm trying to make it a point to blog entries on a regular basis. I've been told its a good idea, and I like good ideas, but at this time of the day, I don't have much to talk about.

So, I'm going to fall back to Jay Pet Peeve #318 (I just made that up, but I'll start keeping track.)

Let me be upfront about Pet Peeve #318: It is superceded by Pet Peeve #277. If you have to do a lookup by string, then its covered by #277. However, I feel you should do the lookups by ordinal, which is covered by the next post.


ToString()

select FirstName, ShoeSize from SomeStupidTableThatDoesntActuallyExist

The name is a varchar, and the shoesize is an integer.

You write some code to execute the query, and you end up with a data reader. Then you start looping through the reader.

while (reader.Read()) {
Console.WriteLine("First Name: " + reader["firstname"].ToString());
Console.WriteLine("Shoe Size: " + reader["shoesize"].ToString());
}

An alternative for first name is:
Console.WriteLine("First Name: " + (string)reader["firstname"]);

The first approach is technically sound, though I prefer the second simply for semantics. ToString() is used to provide the string representation of an object. When your object is already a string, you're essentially saying "convert my string to a string". Whereas, the 2nd approach says "here's my string". You wouldn't do "hello".ToString(), would you?

Does it matter? No. Its a pet peeve. I tried to be upfront about that. Pardon me if I wasn't clear.

As for the "shoesize", that's a different story. That's an integer, but you want to display it as a string, so ToString() doesn't offend my delicate sensibilities in that case.

NOTE: I don't like "firstname" and "lastname" in the reader calls either... that's covered in the next post.

Wednesday, February 6, 2008

More string silliness

Lets convert that previous example into a C# 3.0 extension method.

It would be cool if we could add static extension methods instead.
IE: string.IsNullOrTrimmedEmpty();
rather than
x == null || x.IsTrimmedEmpty()

Test Class




using MbUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace YaddaYadda
{
class Program
{
public static bool IsNullOrTrimmedEmpty(string s)
{
return s == null || s.IsTrimmedEmpty();
}
static void Main(string[] args)
{
Assert.IsTrue(IsNullOrTrimmedEmpty(null));
Assert.IsTrue(IsNullOrTrimmedEmpty(""));
Assert.IsTrue(IsNullOrTrimmedEmpty(" "));
Assert.IsFalse(IsNullOrTrimmedEmpty(" a "));
Assert.IsFalse(IsNullOrTrimmedEmpty("a "));
Assert.IsFalse(IsNullOrTrimmedEmpty(" a"));
Assert.IsFalse(IsNullOrTrimmedEmpty("a"));
Console.ReadLine();
}
}
}


The class with the extension method



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace YaddaYadda
{
public static class StringExtensions
{
public static bool IsTrimmedEmpty(this string s)
{
return s.Trim().Length == 0;
}

}
}

String silliness

Yesterday at work, Carlos and I had a silly converstation about the best way to check if a trimmed string is NULL or EMPTY.

I usually do this:

if (string.IsNullOrEmpty(x) x.Trim().Length == 0)


Carlos liked this better because its prettier:

if (x == null || x.Trim().Length == 0)

Of course, I took offense to that because I find my version to be quite attractive as well. So, instead of settling on which one is prettier, we broke it down into basic operations to see which one would be (theoretically) quicker.

In most cases, x will be a valid string.

string x = "123";
if (string.IsNullOrEmpty(x) || x.Trim().Length == 0)

1 - Check for null
2 - Check for empty (.Length == 0)
3 - Trim the string
4 - Check the length
Under normal circumstances, there are 4 things to do


string x = "123";
if (x == null || x.Trim().Length == 0)

1 - Check for null
2 - Trim
3 - Check the length
Under normal circumstances, there are 3 things to do.

So, the second choice is the better one, and we will use that going forward.

Out of curiosity, I took a look at the string source code to see how it was doing some things.


if (x.Trim().Length == 0)
or
if (x.Trim() == string.Empty)


.Length has been my means of choice. Its just a property value as opposed to a comparison. The string source code does it the same way.


System.String.IsNullOrEmpty()
if (value != null) { return value.Length == 0;} return true;

I think we've made a lot of progress towards solving some insignificant problems. Super.

Sunday, February 3, 2008

Code Camp 2008 recap

Code Camp 2008 is an excellent programming event sponsored by a variety of companies (including the one one I work for) in South Floida. Its a day of 72 classes at 70 minues each. This year's agenda is here:

http://codecamp08.fladotnet.com/agenda.aspx

Its a great event. Its completely free, and you can get exposure to a lot of stuff.

7:30 -8:00 - Registration
This was chaotic. There several copies of sign in sheets spewed about. You had to find a page with your name on it, and sign. Presumbably someone later consolidated the lists. After that, we were given the goody package. The goody packages was assembled on demand rather than prepackaged, so the line was pretty backed up.

8:00 - 8:30 - Keynote
There were 600 people crammed into a cafeteria that felt as if it were only intended for 1/2 that number. I fell asleep for 10 minutes and didn't fall over.

At 8:15, there was a very brief welcome, and code camp officially began.

8:30 - 9:40
I went to "Essential of the Architect and Architecture". I didn't have any expectations for this, because that's how I roll, but if I came up with some, I would've been way off. Ron Jacobs from Microsoft was the presenter. He's a pretty significant character in the great northwest, so it was good to get his view of things.

He described the roll of an architect by breaking it down in to three major components and comparing them to the roles of real people such as Christopher Columbus (explorer) and OJ Simpson's layer (advocate). It was interesting. I have several take aways from that session.

There were 2 other sessions at the time that would've been intresting: Dynamic Data Fields and Tools

9:15 - 11:00
I've already mastered the "Science of Great UI" as clearly demonstrated by the multitude of 3 color sites that I have assembled. Its real simple: Use a table for layout, then make everything look ad boring as possible within the cells of the table. Clearly I don't need a class.

I went to the Web Service Software Factory Modeling Edition. The software factories are basically code generators. It gets you started, you fill in the blanks. The modeling edition is more than that because you can work with the designer and regenerate from the model as you need to. I'm going to have to become more familiar with this.

Stan Schultes was the presenter
http://www.vbnetexpert.com/

11:10 - 12:20
From C# to F# in 60 minutes
This was a good introduction to F#. The gist of it was this: "We can't make light got any faster". Computer power has stopped doubleing every 18 months, so we have to make better us of multicores, etc. F# is a functional, scripting, and OOP language all rolled into one. Its great for list based stuff, and it makes it very easy to fire off stuff asychronously. It was nostalgic to watch it in action because most of the commands were issued from a command line. Good stuff.

http://cs.jaxdug.com/blogs/eugene_chuvyrov/
I just went to the presenters Blog, and the top entry is "VB.NET Must Die". I love it.


12:20 to 1:20 - Lunch
Once again, 600+ people crammed into the cafeteria. There were rumors of pizza going througout the crowd. I wasn't keen on the idea of waiting in another line and trying to find somewhere to sit, so Carlos, Steve and I went to Chilis instead.

1:20 - 2:30
High Speed Development in Visual Studio with Code Rush and refactor.
This was great. I installed CodeRush a few weeks ago, but haven't really used it. I didn't take the time to learn it. It turns out that there's a TRAINING window for inflight training. I'm going to turn that on, then most likely buy it when the trial expires.
http://www.devexpress.com/Products/NET/IDETools/CodeRush/

2:40 - 3:50
For Love or More Money, Developing Your IT Career
I initially thought this was a poor choice, but it worked out. I figured that by "Developing your IT Career", it would cover how to grow as an IT professional. There was some of that, but mostly it was "how to interview for another job". It was presented by http://www.sherstaff.com/, a company I haven't worked with before. (I've only changed jobs twice, so I don't get around a lot.) Despite my initial trepidation, I did get some pointers out of it. (IE: Keep a technical blog).

I've always had trouble with the interview process. There's a lot of etiquette. There are things you should and shouldn't say, should and shouldn't do. I have a very hard time accepting that. I like putting my forks AND spoons on the same side of the plate. I don't care where the wine glass is or the accessibility of my cloth napkin. Just give me food. I'm the same way with the interviews which, amittedly, isn't good. I want to get a job based on my experience with technology and people, and I would prefer to do that without rehearsing lines in front of the mirror. Thankfully, I don't interview often.

4:00 - 5:10
"Orcas for Architects" - I think its time to update the presentation name.
This was a 70 minute crash course in all things VS 2008 and beyond. I got a kick out of it. Most was stuff I'm already familiar with, but there were some gold nuggets in there such as ParallelFX. This reminded me of the "can't make light go any faster" comment earlier in the day. ParallelFX offers some new contructs that will automatically perform certain operations in parallel. It will use the other cores on your CPU to finish things faster.

The first set of example was mostly linear: on a dual core, it was twice as fast. On a quad core, it was 4 times as fast. The second one was graphic based. A quad core was 53% faster.

I told a couple guys about this later that night, and messed it up. I thought it was called PLINQ, but it looks like PLINQ is just part of it.

I'm looking forward to that one.

5:10
Wrapup. We once again assembled in the fire-hazard cafeteria. There were some nice raffle items, and closing comments from several of the sponsors. Some of the sponsors put together an after party, but I was unable to attend due to other plans.

It was a great day. I can't say I learned a lot of detail about anything in particular, but I got a lot of exposure to a lot of cool things, which is important.

Most of my takeaways came from the first Architecture session and the "For Love or Money" session.

Hamlet Code

"Hamlet Code" is a piece of foolishness that I came up while trying to find a new XBOX Live ID. My original ID was "DvdFriend", and ode to my website, but after a while I realized it was a lame handle.

I tried a variety of different code related user names, but they were all taken. A thought process that I don't recall came up with "to code or not to code", which is too big, so I rounded it down to "Hamlet Code". I only hope that my recollection of 9th grade literature is accurate.

So anyway. I have another blog at http://jayallard.blogspot.com/. I occasionaly add some foolishness there, and every once in a while I toss in some technical stuff. Quite honestly, its a disaster. However, I call the blog "Jay's Rants", so I'm being upfront about the kind of attention I apply to it. I just go in, type whatever I want to type, then hit save. More than one person has commented on typos and grammatical whoopsies.

This new blog is strictly for technical foolishness. If you want to read about how our community flooded, or the affect of the 1986 Transformers animated movie on my relationship with my nephew, or how I feed my dogs, then go to the other one. If you want to read about code related stuff, then read this one. In the end, there's something for everyone (or, at least for me).