Latest Posts
Well, I'm in month nine of my pregnancy. I don't know if it will be a boy or girl and that is driving me nuts! LOL But, it will be a fun surprise when that time comes. Right now, I'm just trying to keep up with my two year old.
Dan Davis of Colorado has invented Wacquetball, a slower version of racquetball that uses a foam ball instead of the rubber version in racquetball. It looks like seniors are getting into it, and women as well. Maybe this is a good way to introduce them to the sport, or could this be a sport on it's own? Is the game getting too fast? I wonder what it would look like if some professionals tried it out.
Check out the video from 7 News of Denver.
Updated Mar 22nd: Looks like the leagues have been reinstated. The people have spoken!
Update Feb 25th: We have organized a petition that can be signed. Please sign it and get your friends to sign it as well, we'll really only be voicing our concern as a group of members that currently have memberships and I think it will really help if it's done as a group, instead of a few phones calls here and there.
Yes, you read that correctly. The nasty rumor has been spreading for about a week now and it's getting larger each day. At first I thought it was just a rumor that someone had jokingly started, as in the past the LA Fitness chain has seemed only to want to support the sport of Racquetball through leagues and high money tournaments, but lately I've seen the trend.
In Portola Springs a brand new LA Fitness emerged without Racquetball courts. This is extremely puzzling as two other clubs, one in Irvine and the other in Foothill Ranch are constantly booked at all peak hours. A few more courts would have definitely alleviated the other two clubs and driven membership to it. As such, without courts, I've never sat foot in the club and probably never will.
The most recent news is an email:
Subject: Complain about it - LA Fitness.
LA Fitness is canceling all future racquetball leagues (they describe it as "taking a break from leagues for now") and will end its Shoot Out calendar events as of June 2008. They are doing the same in Florida, New Jersey, Arizona and Texas. They are in the process of telling most of their club directors that if they will not agree to run Basketball leagues that their services are no longer needed.
The management indicates that they may (a weak may) return to leagues in the future but won't say when. I spoke to her for about 30 minutes yesterday. She won't say why they are canceling leagues. She indicates we still have the ability to reserve courts, play on our own among our selves, and that LA Fitness is concentrating on Basketball Leagues (yes, which does not compete against RB, but they don't care). Cannot understand why they are abandoning this money maker for LA Fitness. It does not make financial sense, and the bottom line is that they do not care (though they will not concede this point.)
Edited: To complain, sign the petition above.
Others are drafting a few sample letters to circulate that I will likewise share, that each of you can revise to your own specific complaints. Circulate this to the vocal majority. It would assist if you supply me each with your email addresses/phone numbers and copies of your complaints so that we can track the quantity of complaints. Some are suggesting a joint complaint letter with the members names, email and member number. Working on that but individually we can voice our separate dissatisfaction for now.
While this letter says to complain, I agree that this is one option. We're going to contact the management and see once and for all if there is truth to these rumors to put it to rest. If they are true, we'll most likely quit our memberships and call for a boycott of LA Fitness from all racquetball players that we know. There is no better way to get an organization to pay attention, than by taking from their bottom line.
In addition to the slap in the face to all loyal paying racquetball members, the above letter says that they will focus on basketball. What the letter doesn't say, but the rumor does tell is that it's because Basketball is an Olympic sport. While I see no reason not to support another sport that could bring well being into your life, I see absolutely no reason to get rid of another, Olympic or not.
I just watched Son take on the Gladiators in the remake of the 80's show American Gladiators. The show was pretty entertaining for the most part, and seeing someone you know bashing being thrown down a foam mountain is pretty intense. Son's competitor was in crazy shape though. He was the fastest person through the Eliminator, with Son not far behind. This is where I thought the show kinda stunk. Even if Son beat one of the top 4 times, he still "lost" because the person he went up against beat him. I just think this could be a point to where the contestants could compete against the top 4 and not each other - might have some surprising things happen if that were the case.
I thought it was a bit unfair because they also never had to directly compete. It was always the Gladiators against the two contestants and if you look at the competition where you have to run through the Gladiators Son went through the first 2 way faster than his competitor but got stuck on the last two dudes and didn't even make it to the end. Again on the pyramid, Son had a few shots but got trapped up because of his opponent.
I'm making a few excuses but we wanted him to win :)
The show was pretty entertaining, but just seemed unfair to the contestants in certain ways. Almost to a point to where it seemed almost lucky that a person won a competition, not because they had more skill or endurance. After the show I wondered how some other racquetball players would have done. Mainly was thinking Dan Obremski and Jack Huczek. Would be interesting...
Weblog:
http://blechie.com/wpierce/
Feed:
http://feeds.feedburner.com/wcpierce/
Felt great last night. Second time playing with the 03 Copper racquet and I'm getting used to the weight. It's been a loong time since Ektelon had the balls to not go lightweight. Everyone seems to like the feather light racquets but overtime I believe they can kill your joints.
With the heavier racquet I don't have to swing so fast. In Open level play that means even though my racquet is heavier, my racquet is faster because I can swing slower (F=ma). This may seem like quite the opposite, but think about this, to swing the lighter racquet, I have to swing it faster to equal the same amount of power. With the heavier one, I don't. I can take shorter swings, don't have to swing as hard, and I'll have more control.
It takes a few matches to really get control of it though. As soon as you figure out that you don't have to really smash the ball to get it going, everything start really rolling for me.
If you have a big frame, are tall, and can lift 250 grams...try it out, just remember to relax your swing and don't try to swing as fast (takes practice). If you demo it, you're going to be frustrated the first 10 games you play. Give it a chance though and after game 10...
Racquetball doesn't have too many large scale events. I'm really perplexed as to why, but I believe it mainly comes down to money. The logistics involved with setting up a grand slam event is huge. With Outdoor racquetball, I would think that it would be easier to squeeze into existing venues.
This past weekend there was a Venice beach event. Ektelon and WOR put it on and and Shawn Royster at Royster productions videoed everything in High Definition with 10 camera's rolling. To me this sounded like the first grand slam outdoor exhibition. It seemed like the matches went well and the crowds were there and there was only one accident with Son Nyugen get a racquet to the face. Son ended up with a few stiches but no worse for the wear so all in all a successful event...right?
While I'm glad everyone played well and Son's OK, I'm also really interested in the crowd. Whats the court look like? Where was it positioned? Were there bleachers? Where did people watch from? How close were you to the other event? Did they not care that you guys were doing something that potentially stole their viewers, or was it cohesive? Was the ball more visible through the HDTV content, with the built court? While everyone keeps calling it an event, I'd hope that someone else out there was treating it more of an experiment to see if it would be feasible to pull more of these events off in other high traffic locales.
My main points would be:
- Would having racquetball and another sport together increase both sport's attendance?
- Was the high traffic helpful in attracting people, and how long did those people stick around?
- Was anyone polled about anything? (Would you pay for a ticket to watch this? How interesting is the game to you? Would you like to learn the rules? Have you ever played?)
When Son got hurt 1/3 of the crowd was lost. Nothing against Son's injury, but did they just get bored with nothing going on? How much time elapsed? With 300 people watching, and losing 100 of them...that's huge. These types of things could happen in the future and there needs to be a plan (maybe another match going, or bring another match on, or maybe make the models dance around on trampolines).
We went up to Santa Barbara this weekend for a collegiate tournament that Jackie played in. It was an indoor tournament, and I basically had fun with the kids while she got a few games in.
The facility was pretty sweet, it had two gigantic pools, soccer, football, and even archery. I was thinking, I bet there are outdoor courts here too. Being the busy Dad I was, I didn't go searching, but after getting back and looking at some aerial photos, it looks like I was a few yards away from one and didn't know it.
We were playing a the playground about 40 feet away from what I thought was a building, but never went to the other side of it. It looks like there are court lines, but the other court has none. It also looks like a window or something on the right court. Anyone know for sure?
http://local.live.com/default.aspx?v=2&cp=pr2mzc50c4t0&style=o&lvl=2&tilt=-90&dir=0&alt=-1000&scene=7994759&sp=Point.pr2n4h50c52n_Racquetball%20Courts%3F___&encType=1
Ever since Ian was born in 2003 that particular racquetball event has always been on the chopping block. It's for the love of the sport, and the love of the camaraderie that we continued going. This year however, we decided to hand the torch off to someone else.
A few reasons came into play. Ian and Lexi were a huge reason. It's kind of tough to expect them to contain themselves while we are working on things, and I can't expect them to sit quietly in the tower. Willie and I never got paid monetarily (though it was nice to have a room paid for and food paid for), and always used vacation time to go "work" somewhere else. If that makes sense. Now with the kids our desire to go off and do other things with them has increased, and that was also a reason. Plus with the addition of them into our family, a rental car is completely necessary, I'm not going to chance terrible weather walking blocks to the club with the kids in a stroller freezing, so that was an added expense.
It's going to be tough this year. We've provided coverage for that event for seven years. Willies been at the open since its inaugural year (first as a player than as employee). It's always been something that we look forward too, and it will be greatly missed on our agenda.
I haven't played in awhile due to my shoulder killing me and my back hurting. Not sure why everything is affecting me so, but I think a big portion of it is that I'm working longer hours and more intensely. The repetitive stress to my ligaments and joints aggrevates any type of exercise after the fact.
Today I got on the court to see what the potential differences were between running on an elliptical and playing on the court. Imagine my suprise when playing on the court felt better than running on an elliptical. I thought that the elliptical would be a lower impact exercise and thus hurt less. Maybe I was wrong? I do want to try just swimming a few laps on the next workout session and compare that. I also saw some dudes playing volleyball, so I might give that a whirl. I think basketball is out, and I don't want bad knees and I really suck :)
Either way, it's looking up for playing racquetball again!
Well, I can totally understand making it to the finals when you didn't expect it, and having to catch a flight, but the way the women were bumped all the way to the end of the day, even after the Mens Doubles really sucked. The Carson Huczek Match took place instead of the womens finals, and then instead of putting the womens finals on, they waited until after the Men's Open Doubles...which means...everyone left. I'm hoping that everyone stayed and supported them. I would have stayed except my situation is a little different with the kids and such, and we were done.
Very cool. The Pan-Ams used to be the sports largest event. Not in terms of racquetball people of course, but in terms of those not playing racquetball that attend the event. To be re-inducted back into the Pan Am is a big step towards getting Racquetball out there.
Meet and play forums
Due to some injuries I sat out this year. Jackie played a few matches, lost every match, but had fun, which is what it's all about. The Men's pro was again the focus of the tournament, with the #1 indoor player Jack Huczek joining the fray.
The draws were pretty standard with no suprises. Rocky, like last year, was looking especially sharp. He is the new Brian Hawkes, and it's a shame that Hawkes won't get in there now to play. He advanced through the tournament pretty easily to meet Jack in the finals.
The Jack Huczek and Greg Solis match was a little suprising as I thought that Greg would have beaten Jack. Greg has way more experience outdoors, but he looked really nervous or overconfident or something... Either way he played pretty badly, all the while Jack just kept the same heading of getting a few more points than the error prone Greg.
We were going to take off after that match, but then Jack and Rocky decided to play early. Was a nice deal for me as there were no jumpies on Sunday for the kids (one of the jumpies had died, and the kids were trapped or something on Saturday) so it was hard to justify sitting around for 2 more hours watching them.
Rocky played outstanding against Jack. I wish he'd show the same amount of confidence and resolution in indoor. His serves were masked extremely well, appearing just over the short line and within the wide serve line. These prove especially difficult to retrieve, and Jack was having a really hard time doing that. Outdoor singles gets very frustrating because if your serves aren't on, you're going to lose. Jack early on showed some great gets but Rocky's outdoor experience would have Jack scrambling and then Rocky hitting excellent out the door shots. I actually saw Jack "give up" on 2 shots. He looked beaten later in the game, and Rocky just kept pounding away.
The crowd was also wanting Rocky to win badly. I felt like shouting out some helpful hints for Jack but didn't think he'd listen, plus I'd be the only one cheering for Jack (Hey, I gotta live and play with these dudes the rest of the year :)). The last match point was particularly uneventful as Rock pounded a head high serve, Jack returned and Rocky dinked it to the corner to finish it out.
VB6/VBScript was one of the first languages I learned to use. Today, VBScript is still my fall back language when I just need to 'Getter Done!'. Coupled with Windows Script Host and the Scripting runtime I can use it to automate a large number of otherwise manual tasks. We have some VBScripts laying around that export data from our datastore on scheduled intervals and make it available to our customers to download via FTP-SSL. We are adding some new information to the export that is of the sensitive nature and so encryption came up. Figured since 'Standard' is the S in AES, it made a good choice. The problem was finding some code to do this in VBScript. Some digging turned up an implementation in VB6/ASP but it was god awfully large and complicated. Additionally it was difficult to verify that once encrypted on our side, our customer could easily decrypt it using a previously exchanged Key and Initialization Vector (also using a different programming language all together). Since the .Net Framework makes encryption dirt simple and it was already on the server I whipped up a simple class that is exposed to COM and easily incorporated into our existing VBScript.
Without going too deep, AES requires a Key and an Initialization Vector (IV) to encrypt something. You need the same Key and IV to decrypt the info at a later time. Both of these are byte arrays (byte[]). The resulting encrypted data is also a byte array. Now sharing/hard coding a byte array in code is not all that easy. Rather than share the raw byte array you can base64 encode the data and then you have something that is easily inserted into a text file or transferred via email. The code below is written in a very targeted manner expecting/returning base64 data, hard coding the algorithm used, ascii encoding, etc. Please feel free to tweak for your needs. To expose this to COM, just run "regasm.exe My.Product.dll" and you can run the...cough...unit tests...cough at the end of the sample. Program note: all of the methods here could be marked static, but COM doesn't like static methods.
namespace My.Product
{
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
[ProgId("My.Product.Crypto")]
public class Crypto
{
public string Encrypt(string base64key, string base64iv, string clearText)
{
SymmetricAlgorithm algorithm = GetAlgorithm();
algorithm.Key = Convert.FromBase64String(base64key);
algorithm.IV = Convert.FromBase64String(base64iv);
byte[] clearTextBytes = Encoding.ASCII.GetBytes(clearText);
byte[] cipherTextBytes = Transform(algorithm, clearTextBytes, false);
return Convert.ToBase64String(cipherTextBytes);
}
public string Decrypt(string base64key, string base64iv, string base64cipherText)
{
SymmetricAlgorithm algorithm = GetAlgorithm();
algorithm.Key = Convert.FromBase64String(base64key);
algorithm.IV = Convert.FromBase64String(base64iv);
byte[] cipherTextBytes = Convert.FromBase64String(base64cipherText);
byte[] clearTextBytes = Transform(algorithm, cipherTextBytes, true);
return Encoding.ASCII.GetString(clearTextBytes, 0, clearTextBytes.Length);
}
public int DefaultKeySize
{
get
{
SymmetricAlgorithm algorithm = GetAlgorithm();
return algorithm.KeySize;
}
}
public int DefaultBlockSize
{
get
{
SymmetricAlgorithm algorithm = GetAlgorithm();
return algorithm.BlockSize;
}
}
public string GenerateBase64Key(int keySize)
{
SymmetricAlgorithm algorithm = GetAlgorithm();
algorithm.KeySize = keySize;
return Convert.ToBase64String(algorithm.Key);
}
public string GenerateBase64IV(int blockSize)
{
SymmetricAlgorithm algorithm = GetAlgorithm();
algorithm.BlockSize = blockSize;
return Convert.ToBase64String(algorithm.IV);
}
private static byte[] Transform(SymmetricAlgorithm algorithm, byte[] bytes, bool decrypt)
{
byte[] transformedBytes = null;
using (ICryptoTransform transform = GetTransform(algorithm, decrypt))
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
{
cryptoStream.Write(bytes, 0, bytes.Length);
}
transformedBytes = memoryStream.ToArray();
}
}
return transformedBytes;
}
private static ICryptoTransform GetTransform(SymmetricAlgorithm algorithim, bool decrypt)
{
if (decrypt)
{
return algorithim.CreateDecryptor();
}
return algorithim.CreateEncryptor();
}
private static SymmetricAlgorithm GetAlgorithm()
{
RijndaelManaged algorithm = new RijndaelManaged();
algorithm.KeySize = 256;
algorithm.BlockSize = 128;
return algorithm;
}
}
}
And it's usage in VBScript:
Option Explicit
Call Main()
Sub Main()
Dim crypto
Dim base64key, base64iv
Dim clearText, cryptText, cryptText2, decryptText
Set crypto = CreateObject("My.Product.Crypto")
base64key = crypto.GenerateBase64Key(crypto.DefaultKeySize)
base64iv = crypto.GenerateBase64IV(crypto.DefaultBlockSize)
clearText = "EncryptThis"
cryptText = crypto.Encrypt(base64key, base64iv, clearText)
decryptText = crypto.Decrypt(base64key, base64iv, cryptText)
If clearText <> decryptText Then
MsgBox "Decrypting did not yield correct value"
Exit Sub
End If
cryptText = crypto.Encrypt(base64key, base64iv, clearText)
cryptText2 = crypto.Encrypt(base64key, base64iv, clearText)
If cryptText <> cryptText2 Then
MsgBox "Encrypting twice produces different results"
Exit Sub
End If
MsgBox "All Tests Passed"
End Sub
I'm a big fan of keyboard shortcuts for the Windows, Resharper, Firefox, Web Browsing, GMail, etc. The less I touch the mouse the more productive I am. One thing that can make a great web application suck hard is poor or absent tab indexing on form fields. It takes a little extra time on the developers part but it makes a world of difference to the end user. Rather than hard code tabIndexes, I use the following technique using a simple accumulator:
<?brail
tabIndex = 1
?>
${Form.FormTag({'action':'login'})}
<table>
<tr>
<td>${Form.LabelFor('username', 'Username')}</td>
<td>
${Form.TextField('username', {'tabIndex':tabIndex++})}
</td>
</tr>
<tr>
<td>${Form.LabelFor('password', 'Password')}</td>
<td>${Form.PasswordField('password', {'tabIndex':tabIndex++})}</td>
</tr>
<tr>
<td />
<td>${Form.Submit('Login', {'tabIndex':tabIndex++})}</td>
</tr>
</table>
${Form.EndFormTag()}
One problem I struggled with in ASP.Net was allowing tabIndexes to properly flow from a page to a user control then back again. I'm going to ping Oren to see if it is possible to declare tabIndex 'globally' (in a layout maybe?) and allow all views/sub views/view components to use the same value to try and address this in MonoRail.
I recently blogged about using Dexagago validation API with MonoRail. The API allows you to specify your own advice messages in any HTML element. The framework will take care of showing/hiding the element as needed during validation. I wanted to be able to display a javascript alert box with validation messages in certain cases. Here's what I came up with:
<script type="text/javascript">
function AlertAdvice(isValid, el)
{
if( !isValid )
{
alert(el.title);
if(el.select) el.select();
}
}
</script>
${Form.FormTag({'action':'login','immediate':'false','stopOnFirst':'true','onElementValidate':'AlertAdvice'})}
<table>
<tr>
<td>${Form.LabelFor('username', 'Username')}</td>
<td>
${Form.TextField('username', {'class':'required','title':'Please enter a Username'})}
<div id="advice-username" style="visibility: hidden;" />
</td>
</tr>
<tr>
<td>${Form.LabelFor('password', 'Password')}</td>
<td>${Form.PasswordField('password', {'class':'required'})}</td>
</tr>
<tr>
<td />
<td>${Form.Submit('Login')}</td>
</tr>
</table>
${Form.EndFormTag()}
First we specify a javascript function 'AlertAdvice' that will be called by the validation API for each validator on our form. If the validator fails (empty required field, alpha in a numeric field, etc) we display the contents of the title attribute, then for good measure we select the contents of the field. When specifying your form tag you want to make sure to set immediate to false. I found this out the hard way (took me a few tries :) otherwise you will be locked in a tight loop with a constant alert on invalid fields. I also set stopOnFirst to true so that the user doesn't get overwhelmed with prompts for a large number of invalid fields. Finally, we hook up our AlertAdvice function with the onElementValidate property.
You need to put the message you want displayed in the prompt in the title attribute of your field, ${Form.TextField('username', {'class':'required','title':'Please enter a Username'})} in our example above. Finally, I wasn't able to find a way to disable the API's automatic display of an advice message. To work around this, I add a hidden div element with a name of advice-<fieldName>.
Lightly tested in IE6 and FF2.
Need to set initial focus to a field on your form? This component is adapted from Fábio's code on the MonoRail forums.
namespace My.Product.Mvc
{
using Castle.MonoRail.Framework;
[ViewComponentDetails("InitialFocus")]
public class InitialFocusComponent : ViewComponent
{
private const string FOCUS_TEMPLATE =
"<script type=\"text/javascript\">Event.observe(window,'load',function(){{$('{0}').focus();}});</script>";
private string _id;
private bool _shouldRender;
[ViewComponentParam(Required=true)]
public virtual string Id
{
get { return _id; }
set { _id = value; }
}
public override void Initialize()
{
if( !Context.ContextVars.Contains("__Initial_Focus_Set__") )
{
Context.ContextVars["__Initial_Focus_Set__"] = Id;
_shouldRender = true;
}
}
public override void Render()
{
if( _shouldRender )
{
RenderText(string.Format(FOCUS_TEMPLATE, Id));
}
}
}
}
Use in your view like so:
<% component InitialFocus, {'id':'username'} %>
This component is "first in wins" meaning if you include multiple component references in the same view/subview, the first field specified will get initial focus. So far this has only been lightly tested on text fields.
For some reason I've never really gotten all that sore in my life in althetics. I've played tournaments and had the sore muscles after the fact, but never the cramping, never an injury, never intense pain.
With my back now I was told stuff about using ice and heat, along with some stuff like IB Profen, but never really did it...until now.
I was thinking that my back would take another few weeks to heal up, but I've been icing the crap out of it along with taking medicine to thin the blood. Sure enough it's helping a lot! I can ice it for 10 minutes when it really starts hurting and for awhile it'll actually go away entirely.
I love you Ice.
Localization is a pain. Anybody who says different is a sadist. It's especially difficult if you are trying to retrofit an app that didn't even try i.e. hard coding all strings in code/views. Even if I'm not developing an application with localization in mind, I still strive to put all strings in a resx file and never hard code them anywhere. The recommended method for localizing a standard ASP.Net application was to have a single resx file per page. Similarly for a WinForms app you would have one resx file per Form. In a WinForms app this is slightly more understandable since you may need to resize fields to accommodate different languages. However in web apps the only thing I ever store in a resx file is strings.
ASP.Net 2.0 introduced the concept of an App_GlobalResource that can be accessed from any page in your site. This was a step in the right direction because you could easily reuse common strings as needed. There are cases however when I need to generate a message in my business layer that will ultimately be displayed to the user. So I end up needing a resx file in my Core assembly, and a resx file in my web app. Kind of defeats the purpose of a centralized location for strings. The problem is further compounded by the fact that VS2005 generates a strongly typed wrapper for your resx file that has all strings marked as internal. So I can't easily put all of my strings in my Core assembly and then reference them in my web app.
I little googling turned up a work around that allows you to generate a wrapper with public properties. This method worked just fine but I get anal about such things and wanted my publicly generated code to match as closely as possible to what VS2005 would have created for me. Here is my adapted code to generate a file called Strings.Designer.cs that contains a public wrapper class called Strings in namespace My.Product.Core for my Strings.resx resource file:
<Compile Include="Strings.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Strings.resx</DependentUpon>
</Compile>
<ItemGroup>
<EmbeddedResource Include="Strings.resx">
<SubType>Designer</SubType>
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<!-- Generate Strings.Designer.cs with Public access -->
<Target Name="BeforeBuild" DependsOnTargets="GenStrongTypeResource" />
<Target Name="GenStrongTypeResource" Inputs="Strings.resx" Outputs="Strings.Designer.cs">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="SdkPath" />
</GetFrameworkSdkPath>
<Exec Command=""$(SdkPath)\bin\resGen.exe" /str:c#,My.Product.Core,Strings,Strings.Designer.cs /publicClass Strings.resx" />
</Target>
The first part of the post is pretty generic, lets see how we can now use our strongly typed Strings class in our MonoRail views.
MonoRail supports Localization in a similar manner to ASP.Net. You specify which resource file you want to read from and then assign it a friendly name you can use in your view. Using my technique described above, you essentially bypass all of that. My view engine of choice is Brail. To be frank, I'm not sure if you will be able to use this method with NVelocity or any of the other engines.
First off, we need to configure Brail with our Core assembly. You do so in your web.config file:
<brail debug="true" saveToDisk="false" saveDirectory="BrailGen" batch="false" commonScriptsDirectory="CommonScripts">
<reference assembly="My.Product.Core" />
</brail>
Next, you add an import statement to your view and then you can use the generated Strings class just like you would in your C# code. Here is the localized version of the login view I used as an example in my last post:
<%
import My.Product.Core
%>
${Form.FormTag({'action':'login'})}
<table>
<tr>
<td>${Form.LabelFor('username', Strings.Username)}</td>
<td>${Form.TextField('username', {'class':'required'})}</td>
<td><div id="advice-username" class="advice" style="display: none;">${Strings.UsernameRequired}</div></td>
</tr>
<tr>
<td>${Form.LabelFor('password', Strings.Password)}</td>
<td>${Form.PasswordField('password', {'class':'required'})}</td>
<td />
</tr>
<tr>
<td />
<td>${Form.Submit(Strings.Login)}</td>
<td />
</tr>
</table>
${Form.EndFormTag()}
Nice and easy. Now all of the strings used in your application are centralized in a single file. This makes translation more straight forward and helps prevent duplication of strings in multiple resx files. You could easily extend this and have a Strings assembly that only contains a resx file that could be reused in multiple applications.
Why would you want to do something the hard way? Well of course you do something the hard way when you can't do something the easy way (unless you are Oren in which case you do everything your way :). Hammett has several excellent posts on how to do form validation the easy way using the Castle Validator component (screencast and additional thoughts). The component makes validation dirt simple especially if you are using ActiveRecord. However the default implementation ties validators to attributes on the model which works 80% of the time.
For the 20% of the time when you want to get validators from somewhere else one would need to implement a custom IValidatorRegistry. IValidatorRegistry is responsible for providing all validators for given type and/or property back to the validation framework. Are you feeling the love? All you need to do is customize where the validators come from, and you still benefit from the runtime and client side validation generation. w00t! So, in theory your DatabaseValidatorRegistry could query a...database...to retrieve all validators for a given type and property. Allowing you to craft an interface where by the end user could modify validation rules at will. I'm getting a little dizzy just thinking about the possibilities. I haven't gone there yet but when I do you will be the first to know.
That last paragraph literally popped into my head as I was starting this post. I actually was going in a different direction for this particular discussion but I just had to put it down on paper. What I really want to talk about is doing simple client side validation using the client side Validator scripts. I have a simple login prompt with username and password. I want to do client side validation to ensure they enter a username and password before submitting the form. What do you do? Go.
First things first, you need to install the client side scripts. It is recommended to do this in your layout so that the scripts are included on all of your pages:
<head>
<title>MonoRail Validation</title>
${Ajax.InstallScripts()}
${Form.InstallScripts()}
</head>
What exactly does this do? Among other things it emits script tags for the Prototype libraries and the Dexagogo 'really easy field validation' libraries. The next step is to create a form in your view with the necessary fields:
${Form.FormTag({'action':'login'})}
<table>
<tr>
<td>${Form.LabelFor('username', 'Username')}</td>
<td>${Form.TextField('username', {'class':'required'})}</td>
<td><div id="advice-username" class="advice" style="display: none;">Please enter a Username</div></td>
</tr>
<tr>
<td>${Form.LabelFor('password', 'Password')}</td>
<td>${Form.PasswordField('password', {'class':'required'})}</td>
<td />
</tr>
<tr>
<td />
<td>${Form.Submit('Login')}</td>
<td />
</tr>
</table>
${Form.EndFormTag()}
How hard is it to make a field required? Excruciating! First the Form.FormTag automatically hooks up the required logic to the validation libraries. See the 'class':'required' in the TextField and PasswordField elements? The Dexagogo code uses CSS selectors (similar to Behaviour) to execute validation when the user clicks the Login button. THAT'S IT! Now, when you click Login without entering a username, the form will not be submitted and you will be presented with a lovely error message. The Dexagogo site documents the twelve built in validations you can perform (dates, numbers, email, etc). If you need more you can specify your own validation callback that will get executed when the form is submitted. Dexagogo has a number of standard validation messages, but you can easily substitute your own by adding an element to your page with an id of "advice-<fieldName>". If validation fails, this element will be shown to the user. I applied a class of "advice" to my element so I could easily style error messages. One oddity was that I had to inline specify the display: none. Putting this in my external stylesheet caused the error message to not be shown properly. The use of CSS selectors also makes it very easy for you to style your required fields (with a yellow background maybe?) and style your numeric only fields and provide a standard legend to your users.
You can also specify some additional options in the FormTag (Hammett touched on these briefly in his screen cast). If you want "advice" shown to the user immediately when the leave a field and you want to turn off the default of focusing on invalid fields, you would specify your FormTag like so.
${Form.FormTag({'action':'login','immediate':'true','focusOnError':'false'})}
Finally I'll reiterate the best practice you've heard before, always validate your data server side in addition to any client side validation you may perform. For your reference here is the fully generated code for the view above:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>MonoRail Validation</title>
<script type="text/javascript" src="/MonoRail/Files/AjaxScripts.rails?RC3_0006"></script>
<script type="text/javascript" src="/MonoRail/Files/FormHelperScript.rails?RC3_0006"></script>
</head>
<body>
<form action='/login/login.rails' method='post' id='form1' >
<table>
<tr>
<td><label for="username">Username</label></td>
<td><input type="text" id="username" name="username" value="" class="required" /></td>
<td><div id="advice-username" class="advice" style="display: none;">Please enter a Username</div></td>
</tr>
<tr>
<td><label for="password">Password</label></td>
<td><input type="password" id="password" name="password" value="" class="required" /></td>
<td />
</tr>
<tr>
<td />
<td><input type="submit" value="Login" /></td>
<td />
</tr>
</table>
<script type="text/javascript">
if (!window.prototypeValidators) prototypeValidators = $A([]);
var validator = new Validation('form1', {onSubmit:true, focusOnError:true, stopOnFirst:false, immediate:true, useTitles:true});
prototypeValidators['form1'] = validator;
</script>
</form>
</body>
</html>
In my last post I talked about how each of our customers identify themselves to our app via a CustomerId query string parameter. Rather than give links to our customers with ?CustomerId=12345 sprinkled about, I thought it would be nice to take advantage of the built in URL rewriting capabilities of MonoRail. My target URLs have the following form, https://my.product.com/customerId/controller/action.rails and this will get rewritten to https://my.product.com/controller/action.rails?CustomerId=customerId. Turns out this is a very simple configuration change. URL rewriting is handled by the routing service in MonoRail. You can configure custom 'routes' by adding a subsection to the monorail config section in your web.config file. You also need to add the routing HTTP module to your list of loaded modules. Here is what I added to get my desired result (adapted from the Exesto routing configuration):
<routing>
<rule>
<pattern>/(?'customerId'\d+)/(?'controller'\w+)/(?'action'\w+)\.rails(\?(?'queryString'.*))?</pattern>
<replace><![CDATA[ /${controller}/${action}.rails?CustomerId=${customerId}&${queryString} ]]></replace>
</rule>
</routing><httpModules>
<!-- The order these are listed is important -->
<add name="routing" type="Castle.MonoRail.Framework.RoutingModule, Castle.MonoRail.Framework" />
<add name="monorail" type="Castle.MonoRail.Framework.EngineContextModule, Castle.MonoRail.Framework" />
</httpModules>
A few things to note. We aren't currently using Areas and this routing rule would conflict with the default routing. Make sure you load the routing module before the monorail module or your custom routes may not work. Happy day! We get prettier URLs and we still uniquely identify each customer's site which means they see their custom UI and execute their custom business logic. There is one small issue though. Whenever we LinkTo, Redirect, or BeginFormTag, we will need to take the CustomerId and make sure it is injected in the right place in our URLs. Ugh! big pain, no thank you. MonoRail to the rescue!
The beauty of MonoRail is that it is composed of a number of independent services that can be replaced at will with custom implementations. The difficult task is determining where is the best place to inject the custom functionality you desire. In my previous post we implemented runtime views and layouts using a Filter. Is this the only way we could have achieved it? No, but it was probably the most logical and the way MonoRail was intended to be extended. There are a number of ways I can extend MonoRail to ensure that the CustomerId querystring parameter is persisted from request to request. I chose to make my extension to MonoRail's UrlBuilder. The UrlBuilder is in charge of...building URLs :) It allows you to pass in an area, controller, and action, and it will return a well formed URL to said resource. This seemed like the most logical place to add something to a URL. I derive from DefaultUrlBuilder and override InternalBuildUrl which is the method used to generate final URLs (I've submitted a patch to make this method virtual, should be in the trunk in the next few days). I simply check to see if CustomerId is already on the QueryString. If so, it is inserted into the proper place to generate a URL that will be recognized by the routing rule we implemented above.
namespace My.Product.Mvc
{
using Castle.MonoRail.Framework;
using Castle.MonoRail.Framework.Services;
public class AppendCustomerIdUrlBuilder : DefaultUrlBuilder
{
protected override string InternalBuildUrl(string area, string controller, string action, string protocol, string port,
string domain, string subdomain, string appVirtualDir, string extension,
bool absolutePath, bool applySubdomain, string suffix)
{
if( !absolutePath )
{
string customerId = MonoRailHttpHandler.CurrentContext.Request.Params["CustomerId"];
if( !string.IsNullOrEmpty(customerId) )
{
appVirtualDir = string.Concat(appVirtualDir, '/', customerId);
}
}
return base.InternalBuildUrl(area, controller, action, protocol, port, domain, subdomain, appVirtualDir,
extension, absolutePath, applySubdomain, suffix);
}
}
}
Now, how do I instruct MonoRail to use my UrlBuilder instead of the DefaultUrlBuilder? Once again, it is a simple configuration change. Based on information from the MonoRail Configuration reference, its a piece of cake to slip in a custom service. The configuration below goes in the MonoRail section of your web.config. Using the well known id 'UrlBuilder' and the full type of my class, we are rocking and rolling.
<services>
<service id="UrlBuilder" type="My.Product.Mvc.AppendCustomerIdUrlBuilder, My.Product.Core" />
</services>
I've been on a bit of a MonoRail kick lately and thought I might share some gems. In his recent post Hammett talks about, among other things, using a filter to change layouts at runtime. He didn't elaborate so I thought I would share the way I've implemented his idea. In our hosted environment we generally prefer each customer to have a separate host header. For various reasons this isn't possible for some customers. Instead, we identify them based on an internal id. We key everything about the application off of this id. We use this id to display a customized user interface and run custom logic on a per customer basis. The way I am currently implementing the customized user interface is, as Hammett stated, with Filter.
We grab the CustomerId from the query string and use that to determine if that particular customer has a custom view using a simple folder structure: Views -> CustomerId -> Controller -> View.brail. If no custom view is found, we instead use a 'base' view that is stored in an identical structure Views -> Base -> Controller -> View.brail. Pretty straight forward. We do the same thing for layouts. Decorate your base controller with an AfterAction attribute and you are all set.
namespace My.Product.Mvc
{
using System.IO;
using Castle.MonoRail.Framework;
public class CustomViewFilter : IFilter
{
public const string BASE = "Base";
public const string LAYOUTS = "Layouts";
#region IFilter Members
public bool Perform(ExecuteEnum exec, IRailsEngineContext context, Controller controller)
{
string customerId = context.Params["CustomerId"];
return Perform(exec, context, controller, customerId);
}
#endregion
public bool Perform(ExecuteEnum exec, IRailsEngineContext context, Controller controller, string customerId)
{
// Use the Base or Customer specific view
string viewName = null;
if( null != controller.SelectedViewName )
{
viewName = Path.Combine(BASE, controller.SelectedViewName);
if( null != customerId )
{
string customerViewName = Path.Combine(customerId, controller.SelectedViewName);
if( controller.HasTemplate(customerViewName) )
{
viewName = customerViewName;
}
}
}
controller.SelectedViewName = viewName;
// Use the Base or Customer specific layout
string layoutName = null;
if( null != controller.LayoutName )
{
layoutName = Path.Combine(BASE, controller.LayoutName);
if( null != customerId )
{
string customerLayoutName = Path.Combine(customerId, controller.LayoutName);
string customerLayoutPath = Path.Combine(LAYOUTS, customerLayoutName);
if (controller.HasTemplate(customerLayoutPath))
{
layoutName = customerLayoutName;
}
}
}
controller.LayoutName = layoutName;
return true;
}
}
}
My back has been really killing me for the past few weeks. I have a pinched nerve that I injured a few years ago. I was playing 4 times a week strenuously, while not being in the best of shape. A day came when I could barely stand up. I went and had an MRI and multiple doctors told me that I shouldn't play the game anymore.
I slowly nursed myself back to playing shape and had some of the best games of my life, and was pretty pain free until again this year. This time though I can walk fine, but anything other than lying down hurts. It's not the doubled over in pain type of hurt, just the constant "pinch" all the way down my leg, into my back, down my arms, and in my neck. My right leg gets most of the shooting pain.
It's really annoying and sometimes I cramp up just walking about. Walking has also started to really aggrevate it, so trips to Disneyland are starting to get harder. I don't have to take breaks because I'm tired, but because I have to strech.
It makes me feel like a weakling and it freaking sucks. I hate laying about dormant, so I've taken up some less strenuous exercise like elliptical machines and swimming (although I am just starting to do those). I also think I need to buckle down and get a physical therapist.
I think back to my roomate in college, Chris Odegard, and how he had crippling back and leg injuries that made it painful to walk (one time he couldn't even get on the lift at Vail - $80 go bye bye!). He said that he worked with a physical therapist and now he's pretty pain free. He's even playing some great racquetball, which gives me some hope.
I was talking the other day and I do think I'm done in outdoor racquetball. I just think it's too hard on my body. It's a great game, but I'm still hurting really bad from practicing 3 weeks ago. I made it to the semi's at Nationals after playing for 2 years, so that's good enough :) I think I might try doubles as the movement isn't as bad, but I've got to get my back healed to a point to where that can happen. Not very happy about it.
Over the past few days I've been working out on things other than racquetball, trying to keep my heart rate up around at least 140 (about 72% according to this). During the day I can peak around 146, but I feel pretty tired (over a continuous 20-30 minute workout). Tonight I was able to sustain 150 (78%) fairly easily for 30 minutes working up a good sweat on an elliptical. Jackie pointed out that I was on a different machine, but I'm going more towards keep my heart rate up and going than what particular aerobic exercise I'm doing.
Oren posted quite a while ago on a little DSL for configuring Castle Windsor using Boo. He has creatively titled this DSL Binsor. I had previously used the Batch Registration facility because I did not want to maintain XML configuration for 75+ components. Since I am revisiting the project now and updating to the trunk Rhino Tools I thought I would try out Binsor. Below is the complete script I use to configure MonoRail with Windsor, configure logging, wire up the NHibernate Repository, load all of my MonoRail Controllers/ViewComponents, and all supporting components. This script is derived from the Hibernating Forums binsor script and the source for the Batch Registration facility.
import System
import System.Reflection
import Castle.Facilities.Logging
import Castle.MonoRail.Framework
import Castle.MonoRail.WindsorExtension
Facility( "rails", RailsFacility)
Facility( "logging", LoggingFacility, loggingApi: "log4net", configFile: "My.Product.log4net")
Component("repository", IRepository, NHRepository)
Component("unitOfWorkFactory", IUnitOfWorkFactory, NHibernateUnitOfWorkFactory)
coreAsm = Assembly.Load("My.Product.Core")
for type in coreAsm.GetTypes():
if typeof(Controller).IsAssignableFrom(type):
Component(type.Name, type)
elif typeof(ViewComponent).IsAssignableFrom(type):
Component(type.Name, type)
elif type.IsDefined(typeof(CastleComponentAttribute), false):
compAttr = type.GetCustomAttributes(typeof(CastleComponentAttribute), false)[0] as CastleComponentAttribute
Component(compAttr.Key, compAttr.Service, type, compAttr.Lifestyle)
I would be the Women's Open Champion if I didn't have to leave on Saturday and come home to work on Sunday. I am playing the best I have played all year and after beating Adrienne and Vivian, there was no one in the way from claiming the title.
I didn't play well against Brenda in the Qualifier, for that I was disappointed, I just didn't handle her serve well. There were some nerves but not too many. I had a great time in Houston and I had friends that won their divisions, so I am happy for them.
I want to take this opportunity to thank Randi for the continous support and love to get through the season. to my coach lorraine who took a gold and a silver. Congrats and thanks, to my fans, thanks for everything, I will be playing all the WPRO stops next year and national doubles and singles. I am dissapointed with Houston, but with the wins over viv and adrienne I know I am ready to move up and to keep improving my game.
I am going to work hard over the summer and play alot of outdoor. And do you want to know a secret??? come back on July 1st to find out!!!
*Disclaimer: I am going to describe a symptom of a problem and provide a simple solution to address the symptom, not the problem :)
One of our products is a web based catalog engine that we host for our customers. We provide our customers with an administration tool they can use to setup end-user accounts (username/password) so they control who can view their catalogs. We also have a number of customers that integrate our catalog application into a broader offering of their own. Our customers want to take care of the user management through their own systems and simply redirect end-users from their site to our hosted catalog engine. The issue that arises in this case is when the our customer disables an end user account they want to prevent the end user from accessing our catalog application. If the end user has bookmarked our catalog engine, they can bypass the customers site and continue using our catalog engine.
One solution that may have popped into your head was to check the HTTP_REFERRER property on the request and match it against a known list of valid referrers. The problem with this approach is that HTTP_REFERRER can be very easily spoofed. There are a number of ways to address this issue but we needed something that was simple, repeatable, and required as little technical knowledge on the customer end as possible.
My quick solution was to implement one time pad (we aren't encrypting anything but I think the concept is still applicable) authentication to validate that the user was coming to our catalog engine from a trusted source. I'll show how to implement this solution using SQL Server but it can work with any relational database (you could even do it using a plain text file).
- Create a table to hold your pads.
CREATE TABLE OneTimePad
(
Pad UNIQUEIDENTIFIER
)
- Populate your table with pads.
SET NOCOUNT ON DECLARE @i BIGINT
SET @i=0
TRUNCATE TABLE OneTimePad
WHILE @i < 100000 BEGIN
INSERT INTO OneTimePad VALUES(NEWID())
SET @i = @i + 1
END
- Send a copy of the pad table to the customer.
- The customer will then append a pad to the url we give them to invoke our catalog engine. Here is a sample sproc to get the next pad:
CREATE PROC GetNextPad
AS
SET NOCOUNT ON
DECLARE @pad UNIQUEIDENTIFIER
BEGIN TRAN
SELECT TOP 1 @pad=Pad FROM OneTimePad;
DELETE FROM OneTimePad WHERE Pad=@pad;
COMMIT
SELECT @pad;
GO
- On our landing page, we check for the presence of the pad parameter. If it's not present, we send them back to the customer site. If the pad is invalid, we tell them to take a hike. Here's the pseudo code:
pad = Request.QueryString["pad"]
if( null == pad ) redirect back to customer site
recordsAffected = exec("DELETE FROM OneTimePad WHERE Pad=?", pad)
if( 0 == recordsAffected ) die "We're unhackable biatch!"
Our site forces end-users to access us through our landing page. We only have to validate them when they get redirected from a customer. Once those steps are in place we have a poor mans referrer authentication that is fairly solid and easily repeatable for other customers/hosted products. We have the option to generate a gajillion keys or setup an FTP based key exchange. The only way to compromise the system is to brute force it (good luck with that) or to compromise the pad database. If the latter occurs, simply regenerate and exchange new pads.
I'm counting on you the reader to poke holes in this proof of concept :)
This year looks to be a fun event. This is the second year in a row that I haven't gone, after going for 3 years in a row. My Mom and Dad were suprised that I wasn't down in Houston for the event, but such is life. No enough money, too much of a pain with the kids, and my back has been killing me so I haven't practiced or even played in the last few weeks.
The event does now allow the pro's to play in their own "Team Qualifier" event. Being a proponent of this, as it clarifies their involvment, I think it is should go pretty well.
RacquetballOnline.tv is also at the event this year. We can all hope that their country funding makes it possible to get some real funding to finance the site and it's webivised events.
Carrabba's also makes an appearance as a sponsor. I freaking love Carrabba's so just thinking about how good the food is going to be at the tournament makes me want to be there. I'm really interested to see how having them at the event helps with the hospitality.
The Hyatt also steps up as a sponsor this year. I think they've sponsored the event since the beginning of the thing, and while I could be wrong, they're akin to the Promus' and current Choice Hotels' in this respect.
I wish all the participants luck and hope they have a great week. If you're reading this, let me know how you did.
I'm starting to seperate each of my blogs into seperate sites. MNP is mainly a sports related site, and as such the technical posts that I have been plopping up here just didn't fit, so I created a seperate blogging site for those. I've also been wanting to bitch about current politics so I created a blog for that. I also created a family blog site for more personal matters.
Having said that, only racquetball or sports related posts will appear here in the future. Hee haw. That is all...
JATRAMMELL posted about the low numbers at the yearly National event.
While the overall numbers are barely going down, the number of top level players is reeeeally low. 14 and 5 players in the Open divisions? Yikes.
Why is this concerning?
Because people who have played the game long enough to make the Open division just aren't playing anymore. If Open players aren't playing anymore then the challenge for up and comers isn't there anymore either. Think about it. We all play the game to have fun, get some exercise and sociallize, but we all participate in something that is intristict to each sport: a challenge.
The goal of each sport is to win, to best your opponent(s) by being better. What happens if you're the best? It gets boring. If you've "done it all", why would you continue (maybe if the money's good)? It would get especially frustrating for some who because of the boredom start to lose to players that earlier they could beat easily.
Our sport also becomes made up of players that aren't "the best" anymore either. With some of the best no longer playing the game, they are arguably being replaced with lower caliber players each year. Not good.
I think the answer doesn't have to do with the tournament format, but more with the fact that organized racquetball is declining. There are less and less people that play tournaments these days and it's showing at the national level.
Link to Meet and play forums
There was! News to me. Looks like the same one they used last year. Nothing really to see there though, moving on...
A few years ago people asked me what I thought about televising racquetball online. Back then, high speed connections were not in most homes. The infastructure wasn't there to support it, so I said it wasn't feasible. I still don't today.
Two websites, theracquetballchannel.com and racquetballonline.tv are trying there best to prove me wrong. Both sites cover the Men and Women's tours individually, and both so far have broadcasted events live for free.
The biggest problem back then was getting a high enough throughput from the event to the people at home to get a clear enough picture of the ball. The ball visibility seems to plague televised events not only in our sport but in others like squash as well.
When I watch a squash match, I have no idea what's going on. I'm sure that people familiar with the game have some idea as they're more familiar with how the ball is moving the strategy of the players shots and all that. I watch a recap of the best of moments, moments that in racquetball get my blood pumping, but in squash I just don't "get it". I think it's because I don't play the game a lot. I see the players, and they seem to be chasing flies around the court.
The same issue plagues online racquetball, and that's why I don't think streaming video is "the answer" or anywhere near a part of it. You just can't see the ball, and while I, with ample experience playing the game, can follow it, no regular joe smoe is going to be able to. The televised events are restricted to then to the die hard players that want to view the latest matches in their homes. If new players are exposed to the sport this way they'll think we are a bunch of people that swat flies together for fun.
I'm currently setting up a build server. While I am a one man team for the websites I build, that might not always be so, and I like to make things as easy as possible. I'd also like to know how to get it going. Cue in any open source piece of software and you're bound to get your hands dirty. The following tutorial is coming from someone who knew nothing about integrating the tools that are in the title, so I had a bit of learning to get this stuff going.
The first thing I did was download CruiseControl.NET to my build machine. The install went fine, yet I had no idea where to begin. Good things there's some documentation! I read through getting everything going, and it turns out you have to modify a config file. The documentation goes over the different blocks that are available, but it was still a little confusing. In the installed "examples" directory there were a few config examples, but nothing that had my setup. This is where I encourage you to do some googling to find a config similar to yours. Here's mine below:
<cruisecontrol>
<project name="Meetandplay.com">
<webURL>http://meetandplay.com/</webURL>
<triggers>
<intervalTrigger seconds="86400" />
</triggers>
<modificationDelaySeconds>10</modificationDelaySeconds>
<!-- SVN to the MNP directory -->
<sourcecontrol type="svn">
<trunkUrl>-repo directory-/repos/</trunkUrl>
<workingDirectory>-project directory-\Repos\MNP</workingDirectory>
</sourcecontrol>
<!-- Build using MSBuild (2005) -->
<tasks>
<msbuild>
<executable>C:\WINNT\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable>
<workingDirectory>-project directory-\Repos\MNP\MNP</workingDirectory>
<projectFile>MNP.sln</projectFile>
<buildArgs>/noconsolelogger /p:Configuration=Debug /v:quiet</buildArgs>
<targets>Build</targets>
<timeout>15</timeout>
<logger>C:\Program Files\CruiseControl.NET\server\Rodemeyer.MsBuildToCCnet.dll</logger>
</msbuild>
</tasks>
</project>
</cruisecontrol>
I want my builds to be run every day or so, as I usually check-in once a day, if that. I also made the logging set to quiet. Not sure exactly why, but a doc said that it saved some logging space. Since I don't really need logging (I think), the less of it, the better.
The big thing that got me stuck here was the <logger> tag. I don't have the MSBuild XML Logger and in typical OSS fashion, it wasn't included with the install, and as an added bonus I couldn't find anywhere on the web to download it. The confluence website also had a few links to download it, yet all of those got the 404 error. I finally saw an "improved logger", called Rodemeyer.MsBuildToCCNet.dll that I was able to download. You follow those instructions on that site and finally that got going. Sounds simple, but just finding and doing that took 4 hours that I'm now hopefully saving you.
Now that my projects were building, I was STILL getting a failed build, even though I knew they were building ok. After reading a SGu blog post that told me that since I had a WAP project I was missing the targets file. Well, that's just what the build log said too. Sgu pointed to K. Scott Allen's blog which in turn told me how to get the .targets file copied to the server (so you don't have to install VS2005 or WAP on your server). How these people figure this crap out is anyone's guess, but I'm glad I didn't have to. After creating these directories and pasting the file, bing bing bing, the green "Success" words lit up my screen and made me feel like a winner again. Now I get to figure out the CCTray, or what looks to be cool widgets or gadgets that sit on your desktop.
My biggest gripe was about how "hard" it was to get things going in a web application project. With 2.0 this problem is already solved. It's not plainly obvious to get to, but a developer from the project pointed me to the SubSonic Tools project (not really sure why it's seperate - kinda seems hidden).
Getting this up and running was pretty straight forward. You download the binary file, close VS 2005 and install the stuff using the included msi installer. You will need to replace your dll with the upgraded dll that is included within these tools (Beta 3 didn't work - even though this is labeled beta 3). From there within VS 2005 you right click on the directory you want to generate files to and choose what you want to do from the SubSonic pull down. Easy as pie, no more messing with Web Pages or compilation issues!
If you have more questions, I suggest downloading and reading the Add-in documentation.
A Data Layer framework is like a game system, you swear it's the best until something better comes out. When I choose a DAL framework to work with, its for an extended period of time. It has to be easy to setup, easy to work with, be robust, and have a future. I have to admit that I haven't worked on huge sites that have millions of visitors, most of my sites are smallish with 100's of users at a time tops so performance isn't really an issue for me. I'm more concerned with how fast I can build things. I want to have a requirement and push it out the door the fastest way possible.
Anyhoo, Subsonic looks to be filling those roles, for the most part. dOOdads did the above really well, but lets compare the two on HOW easy and pain free they are. Caveat: This is in no way a tutorial, so my explainations below are not exact.
How easy is it to setup?
dOOdads
You first include the given project in your own solution. You then need to choose which database you're building towards, select the files to "compile", and then include the MyGeneration_2005.dll in your PL, BLL, and DAL. You then use the MyGeneration tool (freeware, works in tandem with dOOdads) to generate DAL classes, and then the SPROCs those classes will use to perform information CRUD. The MyGeneration tool is pretty nice, if I need to make changes to the generated code, it's pretty straight forward, and took about and hour to get used to.
With dOOdads you have the additional step of generating the SPROCs and then adding those to your database. Since Subsonic doesn't use SPROCs (it can if you want it to), this second step is absent.
SubSonic
Since I have a Web Application Project, SubSonic is harder to setup than dOOdads. I want namespaces on my generated objects, plus I want to run the code generation from my own project. Ripping this from the given SubSonic package was a pain. I couldn't find any documentation, but I went through and figured out how to add namespaces, and plunked in my project's directory in the given textbox. With each new version that comes out, I'm sure my changes will be removed so I'll have to re-add them each time. Blech.
The generator also requests a path, so each time you bring up the BatchClassGenerator page you have to look up the directory you're sending files to. MyGeneration's tool remembers my "last generated to" path so I don't have to type it in. The other downfall of having the generator "in project" is that my solution must compile, otherwise I can't run my project in "Debug" mode and therefore run the generator. I can't "View in browser" if I have administrative rights (or give ASPNET or Network Service write perms on my directory) and hit "Yes" in the continue box to actually bring up the page when VS encounters errors.
Notably, Subsonic seems more built for Web Site Projects than WAPs. It would seem that the only setup you need to do with WSPs is copy the DLL and put a build provider file in your App_Code. Almost makes me want to switch to it...almost.
Winner: dOOdads by just a little. If there was a "build provider" for WAP and code generation was handled in an external tool (like MyGeneration perhaps?) Subsonic would win hands down. If you have a WSP, this is a no brainer, use Subsonic and never touch generation again.
Code Comparision
I like to abstract my code in a business or controller layer (most of the time combining them, so lets call it Biz). dOOdads generates abstract classes which the Biz layer inherits. Mostly these are left blank, and then from your PL you manipulate them. In Subsonic you are given partial classes, which I have placed in my DAL in a "generated" folder. You can create "extentions" to these classes by place other like named partial classes in the directory if you wish. This way makes you use more of a Controller, BLL, and DAL approach, except the BLL is in the partial classes in your DAL.
The two different frameworks also have radically different patterns of access. Subsonic uses the ActiveRecord pattern while dOOdads uses collections for everything. That means if you get a single record it's the same as if you get a group of records. In Subsonic there is a difference, you can either get a single record, or use the collection object to get a collection of objects. I'm sure there are performance benefits to either way, but I've never really tested either method as nothing in either has ever run "slowly". Mind you, I don't have millions of users pounding my websites, if you do, do your own testing :)
Now lets take a look at some code. Here's a look at code doing the same thing, the first is from dOOdads, and the second is from SubSonic. I like to create static classes that return the data I need, so these examples will be from that standpoint.
Returning some records with a Where statement:
dOOdads:
Tip t = new Tip();
t.Where.CategoryID.Value = categoryID;
t.Where.CategoryID.Operator = WhereParameter.Operand.Equal;
t.Query.AddOrderBy(ColumnNames.ModifiedOn, WhereParameter.Dir.DESC);
if (t.Query.Load())
return t;
else
return new Tip();
SubSonic:
return new TipCollection().Where(Data.Tip.Columns.CategoryId, categoryID).OrderByAsc(Data.Tip.Columns.Title).Load();
8 lines down to one. You also get an IEnumerable strongly type collection of Tip objects. Nice. I do wish I could still inherit the Data object so I don't have to specify Data.Tip.Columns.CategoryId, just Columns.CategoryId but I'm just nit picking.
Returning a single record:
dOOdads:
Tip t = new Tip();
if(t.LoadByPrimaryKey(tipId))
return t;
SubSonic:
return new Data.Tip(tipId);
Another one liner. The one-liners in subsonic make me wonder about even using a Controller type class for these types of operations. I mean...one line here, or one line on the PL? Make no difference that I can see.
More complex of a query:
In this one SubSonic code comes first, with dOOdads after placed with corresponding calls (I commented out the dOOdads code)
Query q = Data.Tip.CreateQuery();
/*
Tip t = new Tip();
*/
q.Top = topAmount.ToString();
q.OrderBy = OrderBy.Desc(Data.Tip.Columns.CreatedOn);
/*
t.Query.Top = topAmount;
t.Query.AddOrderBy(ColumnNames.ModifiedOn, WhereParameter.Dir.DESC);
*/
q.AddWhere(Data.Tip.Columns.TipType, latestType);
/*
t.Where.TipType.Value = latestType;
t.Where.TipType.Operator = WhereParameter.Operand.Equal;
*/
if (categoryID != Category.Empty)
{
q.AddWhere(Data.Tip.Columns.CategoryId, categoryID);
/*
t.Where.CategoryID.Value = categoryID;
t.Where.CategoryID.Operator = WhereParameter.Operand.Equal;
*/
}
TipCollection tc = new TipCollection();
tc.Load(Data.Tip.FetchByQuery(q));
return tc;
/*
if (t.Query.Load())
return t;
else
return new Tip();
*/
The only advantage SubSonic seems to have in the extended query department is the Where statements are condensed into one line. Still you get that enumerable collection. In dOOdads you have to "do...while" loop your way through each record. Not entirely a bad thing, but since SubSonic i