Archive for April, 2008

April 21st 2008

Applying for technical jobs? A rough guide

For the last three years I’ve been involved in our company’s development team hiring process.  We’ve defined it, refined it, reworked it, and reworked it again.  We’ve made mistakes and made great hires.  I thought I’d share a few thoughts on the subject which you may find useful. 

For every job posting online, we get 100+ applications.  For every diamond in the rough, there are many more applications we wish we didn’t spend time on.  We only want to hire great developers and have a rule: “no false positives”.  Although we may miss a “diamond in the rough” application, we try to make the time we spend hiring as effective as possible.  Usually we “ding” anyone who:

  1. Doesn’t have a cover letter.
  2. Has numerous spelling errors on their resume or cover letter.
  3. Submitted an application that takes us more than 30 seconds to determine what position you’re applying for and where you’re coming from (e.g. current job is “help desk”, but applying for a programmer position; went to school for art history).  For a better explanation check Rand In Repose’s posting.

So how do you avoid these “dings”?

  1. Have someone proof read your resume
  2. Check out other peoples resumes, e.g.  Resumes That Knock ‘Em Dead and see formats that appear appropriate for the field you work in.
  3. Use a cover letter customized to the position.  Cover Letters That Knock ‘Em Dead has a number of samples to get an idea of the tone and content of what your cover letter should contain.  Make sure to have someone proof read your cover letter before you apply.
  4. Put things on your resume that HR people, recruiters, and automated systems can find.  I don’t mean highlight or boldface all your references to technologies (who started that trend anyways?), use certifications, associations, conferences, and trainings.  HR people aren’t developers.  They don’t know the difference between C# and VB.net or smalltalk and Ruby.  They know keywords they’ve been asked to look for, so make their job easier. By getting certifications, you may not be advancing your education so much as easing the first barrier to entry at any organization - survive the resume screening process.  Certs, conferences, and user groups indicate you’re serious about what you do for a career and advancing yourself professionally. HR people like this.

You’ve submitted your cover letter and resume, and you get a call back.  The usual process is something like this:

  1. HR person/recruiter calls to qualify the application.  They want to sell you on the job - the company, the job itself, the benefits and also determine if this is a good fit for both parties.  They’ll ask the standard personnel questions and more specific questions they’re told to ask. for example
    1. Why are you looking for a job?
    2. When can you start?
    3. What salary are you looking for?
    4. What kind of job are you looking for?
    5. What are your professional goals? -this one is very important. I can’t tell you how many people have applied for developer positions and told me “I want to get into business analysis/project management/developer management” - the job posting was specifically for a software engineer. Why did you apply?
    6. Suppose you and someone have similar resumes. What makes you different?
    7. What books have you read recently?
    8. What blogs do you subscribe to?

    Be prepared for all of these.  The last three are really where we begin to distinguish ambition/character from professional qualifications. They’re good indicators (like education and certification) about what it is you want to do, how motivated you are, and a rough guide to your technical skills.

  2. The technical phone screen.  This person is usually the hiring manager.  His goal is to further qualify you as a potential hire and to help sell you on the job with more details. Expect more technical questions and questions on your experience with certain technologies. 
  3. In person interview.

Hopefully you made it through the screening process and are asked for a personal interview.   Get there early - “if you’re on time, you’re late”.  Once there, relax, use the bathroom, and review your information again- who the company is, what they do, the job description, and what information you’ve given them (resume, cover letter, references etc.).

Steve Yegge has a good description of the general onsite interview process and a whole lot more at “get that job at google.” From his posting:

Every “experienced” interviewer has a set of pet subjects and possibly specific questions that he or she feels is an accurate gauge of a candidate’s abilities. The question sets for any two interviewers can be widely different and even entirely non-overlapping.

 A classic example found everywhere is: Interviewer A always asks about C++ trivia, filesystems, network protocols and discrete math. Interviewer B always asks about Java trivia, design patterns, unit testing, web frameworks, and software project management. For any given candidate with both A and B on the interview loop, A and B are likely to give very different votes. A and B would probably not even hire each other, given a chance, but they both happened to go through interviewer C, who asked them both about data structures, unix utilities, and processes versus threads, and A and B both happened to squeak by.

 That’s almost always what happens when you get an offer from a tech company. You just happened to squeak by. Because of the inherently flawed nature of the interviewing process, it’s highly likely that someone on the loop will be unimpressed with you, even if you are Alan Turing. Especially if you’re Alan Turing, in fact, since it means you obviously don’t know C++.

 The bottom line is, if you go to an interview at any software company, you should plan for the contingency that you might get genuinely unlucky, and wind up with one or more people from your Interview Anti-Loop on your interview loop. If this happens, you will struggle, then be told that you were not a fit at this time, and then you will feel bad. Just as long as you don’t feel meta-bad, everything is OK. You should feel good that you feel bad after this happens, because hey, it means you’re human.

It helps to understand the perspective of the employer on the interview process. Joel Spolsky is an excellent resource on hiring developers.  His book entitled “Smart And Gets Things done“ and blog posting The Guerrilla Guide to Interviewing are essential resources for companies trying to find great talent. From his posting:

You should always try to have at least six people interview each candidate that gets hired, including at least five who would be peers of that candidate (that is, other programmers, not managers). You know the kind of company that just has some salty old manager interview each candidate, and that decision is the only one that matters? These companies don’t have very good people working there. It’s too easy to fake out one interview, especially when a non-programmer interviews a programmer.
 
 If even two of the six interviewers thinks that a person is not worth hiring, don’t hire them. That means you can technically end the “day” of interviews after the first two if the candidate is not going to be hired, which is not a bad idea, but to avoid cruelty you may not want to tell the candidate in advance how many people will be interviewing them. I have heard of companies that allow any interviewer to reject a candidate. This strikes me as a little bit too aggressive; I would probably allow any senior person to reject a candidate but would not reject someone just because one junior person didn’t like them.

Look for passion. Smart people are passionate about the projects they work on. They get very excited talking about the subject. They talk quickly, and get animated. Being passionately negative can be just as good a sign. “My last boss wanted to do everything on VAX computers because it was all he understood. What a dope!” There are far too many people around that can work on something and not really care one way or the other. It’s hard to get people like this motivated about anything.

Bad candidates just don’t care and will not get enthusiastic at all during the interview. A really good sign that a candidate is passionate about something is that when they are talking about it, they will forget for a moment that they are in an interview.

When interviewing we want to give the best impression possible. This is both easier and harder than it may seem.  According to Dave Munger in this post and Malcolm Gladwell in his book Blink(well worth reading), you’ll have about six seconds to make that first impression.  Presenting yourself with confidence and a humble smile will go a very long way to set the right tone for your interview.

Each individual interview will have several distinct sections.

  1. You’ll meet and shake hands.
    A recruiter once told me that at this moment, I should lean forward a little and stand on my toes.  He said it would make me stand up straighter and more importantly it would make me feel a little silly. This would in turn make me smile and relax. When I relaxed, the interviewer would relax.  After interviewing with 4 companies and 20 different people I will tell you, he was exactly right.  As the interviewer of 50+ people for two different companies, I can tell you the opposite is true- the candidates who don’t present themselves well usually have a harder interview as I try to draw them out.
  2. The interviewer will tell you who they are and what they do. 
  3. They’ll ask you some questions. 
    Be polite, be eager, and be positive during all of this. 
  4. Finally they’ll ask you if you have any questions.
    You should always have questions. Questions show you care about the position. It helps draw the interviewer into the conversation.  It’s also one of the only places during the interview process where you’ll get an idea what the job would actually be like.  Questions like: 

    • Who would I be working with? (Are they of your caliber? Are they as motivated as you are?)
    • What would be the first project/team I’d work on here?
    • What would I need to do to meet or exceed expectations after 6 months? (OK this one is a little cheesy, but sometimes effective)
    • Who would be my manager?
    • What feedback loop do you have for my performance?
    • What is the primary focus of the position (you may get a different answer depending on who you ask) - technical? interfacing with users? project management?

So you’re interview is done, and you’re on your way out.  Don’t forget to thank everyone you met, including their “staff”.  Take a moment when you finally get out of there and write down some notes of your impressions (you’ll forget things by the time you get home) what people said, the specifics of the actual job, etc. 

A few final notes:

  • 2 weeks notice to your current employer is standard - if they expect you to leave without notice, it says something about how much they care about people.
  • Always get time to think about the offer- “I have to speak with my family” is a valid answer.  Anyone who goes for the hard sell is sketchy.
  • If things don’t pan out, it’s probably because someone (and it only takes one) thought you weren’t the best fit.  That may be true, it may not, but better no fit than a bad one. 
  • Big companies can afford a longer ramp up time and better training.  Smaller ones are generally looking for you to hit the ground running.  Don’t be upset if someone thought you’d require a little too much training to be useful to them- it’s them, not you :)

No Comments yet »

April 21st 2008

YSlow and ASP.NET - Expires Header - BuildProvider (Part 3 of 3)

For the last part of our quest to enable caching of static content by separating it out into directories by version, we want to provide an easy and intuitive way for developers to reference static content from the code behind. e.g.

Dim linkIcon As New System.Web.UI.WebControls.Image
linkIcon.ImageUrl = StaticContent.Images.rss_gif

Under the covers StaticContent.Images.rss_gif resolves to a method call -StaticContent.PathBuilder.ConvertImageUrl(”rss.gif”) (the method is covered in part 2).
To do this we need a buildProvider (Note: this is not supported in Web Application Projects (WAP) see notes at the end).
The code is mostly written for us on Dave Transom’s excellent post.
The slightly modified code is here.
Now that we have a buildProvider we wire it into the build using the web.config:

<compilation debug="true" strict="true" explicit="true">
	<buildProviders>
		<add extension=".hrefs" type="Savo.Framework.Web.StaticContent.BuildProvider"/>
	</buildProviders>
</compilation>

To configure our BuildProvider create a file with the extension “.hrefs” in your App_Code directory, using some variation on the following:

<?xml version="1.0" encoding="utf-8" ?>
<settings
	namespace=""
	className="StaticContent"
	minDepth="1"
	maxDepth="100"
	lowercaseUrls="true"
	includeExtension="true"
	truncatePathToStartingDepth="true"
	resourceResolutionFormat='StaticContent.PathBuilder.ConvertStaticContentUrl("{0}")'
	directoryNamesToIgnore="StaticContent">
	<files include="\.(gif|jpg|png|css)$" exclude="">
	<folders include="StaticContent" exclude="App_|Bin|\.svn">
</settings>

If you’re using a “Web Application Project”, this method won’t work as per this MS post.  My suggestion is to use a pre-build event (although this has a few extra gotchas) or another framework like slink though I haven’t tried either method.

No Comments yet »

April 21st 2008

YSlow and ASP.NET - Expires Header - Expression Builders (Part 2 of 3)

Continuing our task of caching indefinitely static content by versioning it, we want to build the framework so that instead of code like this:

<asp:image imageurl="~/images/rose.jpg" runat="server" id="image1">

we have this:

<asp:Image id="image1" imageurl="<%$ Image: rose.jpg %>" runat="server"/>

The key difference is that we can dynamically assign where the image path for rose.jpg resolves in the latter case. Specifically, we can resolve to applicationPath/versionX.X.X/Images/rose.jpg (or whatever url scheme meets your fancy).

We need two things. First we create an expressionBuilder object and then tie the custom expressionBuilder into the build. I’m using the VS “WebSite” project, but this should work for “Web Application Projects”.

The following code draws heavily from this post on Dave Reed’s excellent blog infinitiesloop.

Imports System.CodeDom
Imports System.Web         

Namespace StaticContent         

	Public NotInheritable Class PathBuilder         

		Private Shared CssPath As String = String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}/StaticContent/{1}/Css/{{0}}", System.Web.HttpContext.Current.Request.ApplicationPath, System.Configuration.ConfigurationManager.AppSettings("versionNumber")))
		Private Shared ImagePath As String = String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}/StaticContent/{1}/Images/{{0}}", System.Web.HttpContext.Current.Request.ApplicationPath, System.Configuration.ConfigurationManager.AppSettings("versionNumber")))
		Private Shared JavascriptPath As String = String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}/StaticContent/{1}/Javascripts/{{0}}", System.Web.HttpContext.Current.Request.ApplicationPath, System.Configuration.ConfigurationManager.AppSettings("versionNumber")))         

		Public Shared Function ConvertCssUrl(ByVal path As String) As String
			Return String.Format(System.Globalization.CultureInfo.InvariantCulture, CssPath, path)
		End Function         

		Public Shared Function ConvertImageUrl(ByVal path As String) As String
			Return String.Format(System.Globalization.CultureInfo.InvariantCulture, ImagePath, path)
		End Function         

		Public Shared Function ConvertJavascriptUrl(ByVal path As String) As String
			Return String.Format(System.Globalization.CultureInfo.InvariantCulture, JavascriptPath, path)
		End Function         

		Private Sub New()
		End Sub         

	End Class         

	MustInherit Class StaticContentExpressionBuilder
		Inherits Compilation.ExpressionBuilder         

		Protected Shared evaluationExpression As String         

		Protected MustOverride ReadOnly Property StaticContentExpression() As String         

		Public Overrides Function ParseExpression(ByVal expression As String, ByVal propertyType As System.Type, ByVal context As Compilation.ExpressionBuilderContext) As Object
			Return String.Format(System.Globalization.CultureInfo.InvariantCulture, StaticContentExpression, expression)
		End Function         

		Public Overloads Overrides Function GetCodeExpression(ByVal entry As System.Web.UI.BoundPropertyEntry, ByVal parsedData As Object, ByVal context As System.Web.Compilation.ExpressionBuilderContext) As System.CodeDom.CodeExpression
			Return New CodeSnippetExpression(parsedData.ToString())
		End Function         

	End Class         

	<Compilation.ExpressionPrefix("Js")> _
 Class JavascriptExpressionBuilder
		Inherits StaticContentExpressionBuilder         

		Protected Overrides ReadOnly Property StaticContentExpression() As String
			Get
				Return "StaticContent.PathBuilder.ConvertJavascriptUrl( ""{0}"" )"
			End Get
		End Property         

	End Class         

	<Compilation.ExpressionPrefix("Image")> _
	Class ImageExpressionBuilder
		Inherits StaticContentExpressionBuilder         

		Protected Overrides ReadOnly Property StaticContentExpression() As String
			Get
				Return "StaticContent.PathBuilder.ConvertImageUrl( ""{0}"" )"
			End Get
		End Property         

	End Class         

	<Compilation.ExpressionPrefix("Css")> _
	Class CssExpressionBuilder
		Inherits StaticContentExpressionBuilder         

		Protected Overrides ReadOnly Property StaticContentExpression() As String
			Get
				Return "StaticContent.PathBuilder.ConvertCssUrl( ""{0}"" )"
			End Get
		End Property         

	End Class         

End Namespace

I’ve chosen to implement several types of static content (css, images, and js) but that’s just a matter of convenience. Clearly this could all be done with a single expression. Note the reason I have placed the content type specific methods on PathBuilder is that I need to call these methods in part three (code-behind) where accessing the expressionBuilder assemblies would be pretty awkward.

To tie our new expression builders into the build we need to add some entries to the web.config.

<compilation debug="true" strict="true" explicit="true">
	<expressionBuilders>
		<add expressionPrefix="Css" type="StaticContent.CssExpressionBuilder"/>
		<add expressionPrefix="Image" type="StaticContent.ImageExpressionBuilder"/>
		<add expressionPrefix="Js" type="StaticContent.JavascriptExpressionBuilder"/>
	</expressionBuilders>
</compilation>

That’s it. Now we can do things in our markup like:

<asp:image imageurl="Image: rose.jpg" runat="server" id="image1">
<link rel="stylesheet" href="Css: Fonts.css %>" type="text/css" id="FontsCss" runat="server">

the image “rose.jpg” and css file “Fonts.css” will be rendered to the page as urls:
“applicationPath/StaticContent/1.0.0/Images/rose.jpg” and “applicationPath/StaticContent/1.0.0/Css/Fonts.css” respectively.

1 Comment »

April 20th 2008

YSlow and ASP.NET - Concatenating and Minifying Css and Js Resources

In our code we reference several js libraries (prototype, scriptaculous, custom libraries etc.) and we break our css out into several files (layout, fonts, etc.). We encouraged our developers to use whitespace, comments, descriptive names, and separate files to make code as clear as possible. Clear code, however, results in longer download times for web clients.  We wanted to follow the suggestions from the YUI folks to minify and concatenate our resources so that clear code and fast download times could co-exist.

The answer was our automated build. It would have the job of minifying the js and css. It would concatenate the resources together into two files - one for js and one for css.

To use concatenated resources in our code while preserving the ability of developers we work, we changed the the markup section of our master slightly to:

<head runat="server">
	<!-- css -->
	<!--	PLEASE NOTE: CSS and JS ARE COMBINED ON AUTOMATED BUILD
				Please add entries there as necessary!!
	-->
	<link rel="stylesheet" href="Css/Fonts.css" type="text/css" id="FontsCss" runat="server">
	<link rel="stylesheet" href="Css/Structure.css" type="text/css" id="StructureCss" runat="server">
	<link rel="stylesheet" href="Css/Hyperlinks.css;" type="text/css" id="HyperlinksCss" runat="server">
	<link rel="stylesheet" href="Css/Menus.css" type="text/css" id="MenusCss" runat="server">
	<link rel="stylesheet" href="Css/Concatenated.css;" type="text/css" id="ConcatenatedCss" runat="server" visible="false">
	<!-- javascript -->
	<ScriptReference:ScriptRef src="Js/prototype.js" runat="server" ID="UiPrototypeJs">
	<!-- note there are multiple files downloaded after scriptaculous loads, these files should already be included in the concatenated.js if appropriate-->
	<ScriptReference:ScriptRef src="Js/library/scriptaculous.js" runat="server" ID="ScriptaculousJs">
	<ScriptReference:ScriptRef src="Js/library/application.js" runat="server" ID="ApplicationJs">
	<ScriptReference:ScriptRef src="Js/library/Concatenated.js;" runat="server" ID="ConcatenatedScript" Visible="false">
</head>

Note: asp.net doesn’t have a server control for script elements, so we roll our own server control:

Imports System
Imports System.Web.UI       

	Public Class ScriptRef
		Inherits WebControls.Literal       

		Private Const _scriptRefMarkup As String = "<script src="" mce_src=""{0}"" type=""text/javascript""></script>"       

		Public Property Src() As String
			Get
				Return Me.Text
			End Get
			Set(ByVal value As String)
				If Not value Is Nothing Then
					Me.Text = String.Format(Globalization.CultureInfo.InvariantCulture, _scriptRefMarkup, value.Replace("~", System.Web.HttpContext.Current.Request.ApplicationPath))
				End If
			End Set
		End Property       

	End Class

At runtime we can now choose whether to show the css/js files or our concatenated version:

Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
	MyBase.OnInit(e)       

	Dim useConcatenatedJsAndCss As Boolean = False
	If Not String.IsNullOrEmpty(System.Configuration.ConfigurationManager.AppSettings("useConcatenatedJsAndCss")) AndAlso System.Boolean.TryParse(System.Configuration.ConfigurationManager.AppSettings("useConcatenatedJsAndCss"), useConcatenatedJsAndCss) AndAlso useConcatenatedJsAndCss Then
		If useConcatenatedJsAndCss Then
			'NOTE: Whatever we set to visible false here has to be added to the
			'build to be included in the Concatenated css
			FontsCss.Visible = False
			StructureCss.Visible = False
			HyperlinksCss.Visible = False
			MenuCss.Visible = False       

			PrototypeJs.Visible = False
			ScriptaculousJs.Visible = False
			ApplicationJs.Visible = False       

			UiConcatenatedCss.Visible = True
			UiConcatenatedScript.Visible = True
		End If
	End If
End Sub

using the config setting “useConcatenatedJsAndCss”

	<appSettings>
		<add key="useConcatenatedJsAndCss" value="false"/>
	</appSettings>

For minification, we used the YUICompressor. Download it to a place where the build can find it.

<target name="CleanCss">
	<foreach item="File" in="${PathToYourWebSiteBuildOutput}/Css" property="filename">
		<property name="extension" value="${path::get-extension(filename)}">
		<property name="extension2" value="${string::to-lower(extension)}">
		<property name="isCss" value="${string::ends-with(extension2, 'css')}">
		<if test="${isCss}">
			<exec program="java">
				<arg value="-jar">
				<arg value="${YUICompressorPath}">
				<arg value="-o">
				<arg value="${filename}.min">
				<arg value="${filename}">
				<arg value="--warn">
				<arg value="--charset">
				<arg value="Cp1252">
			</exec>
			<move file="${filename}.min" tofile="${filename}" overwrite="true">
		</if>
	</foreach>
</target>       

<target name="CleanJs">
	<foreach item="File" property="filename">
		<in>
			<items>
				<include name="${PathToYourWebSiteBuildOutput}/Javascripts/**/*.js">
			</items>
		</in>
		<do>
			<property name="extension" value="${path::get-extension(filename)}">
			<property name="extension2" value="${string::to-lower(extension)}">
			<property name="isJs" value="${string::ends-with(extension2,'js')}">
			<if test="${isJs}">
				<exec program="java">
					<arg value="-jar">
					<arg value="${YUICompressorPath}">
					<arg value="-o">
					<arg value="${filename}.min">
					<arg value="${filename}">
					<arg value="--preserve-semi">
					<arg value="--charset">
					<arg value="Cp1252">
				</exec>
				<move file="${filename}.min" tofile="${filename}" overwrite="true">
			</if>
		</do>
	</foreach>
</target>

Note: when calling jsmin from the commandline we need to specify the character set for the file we’re compressing or use the default format (ANSI).

For concatenation we can use the build task “concat”. When concatenating files, order is important so we enumerate the files explicitly:

<target name="CombineCss">
	<!-- ordering of concat filesets is nondeterministic, so we concat separately -->
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Css\Concatenated.css" append="false">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Css/Fonts.css">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Css\Concatenated.css" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Css/structure.css">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Css\Concatenated.css" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Css/Hyperlinks.css">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Css\Concatenated.css" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Css/Menus.css">
		</fileset>
	</concat>
</target>     

<target name="CombineJs">
	<!-- ordering of concat filesets is nondeterministic, so we concat separately -->
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Javascripts\library\Concatenated.js" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Javascripts/library/prototype.js">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Javascripts\library\Concatenated.js" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Javascripts/library/scriptaculous.js">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Javascripts\library\Concatenated.js" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Javascripts/library/crir.js">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Javascripts\library\Concatenated.js" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Javascripts/library/builder.js">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Javascripts\library\Concatenated.js" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Javascripts/library/effects.js">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Javascripts\library\Concatenated.js" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Javascripts/library/dragdrop.js">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Javascripts\library\Concatenated.js" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Javascripts/library/controls.js">
		</fileset>
	</concat>
	<concat destfile="${WebSiteStaticContentVersionDirectoryPath}\Javascripts\library\Concatenated.js" append="true">
		<fileset>
			<include name="${WebSiteStaticContentVersionDirectoryPath}/Javascripts/library/slider.js">
		</fileset>
	</concat>
</target>

1 Comment »

April 20th 2008

YSlow and ASP.NET - Expires Header (Part 1 of 3)

At our company there’s a focus on getting our asp.net web application to perform in IE6/7 and FF.  Our site uses javascript and css heavily which results in less than optimal performance.  In an effort to improve performance we took proactive steps by:

  • the size of viewstate and http response on all asp.net requests
  • logging the number of queries used on each http request
  • using the excellent tool YSlow to get insight into the actual content rendered

In future posts I plan to touch upon changes we made in response to these results including minimizing our javascript and css files with YUICompressor and concatenating the results to reduce the number of http requests through our continuous integration process.

The specific challenge I hope to illustrate concerns the default asp.net http headers for content expiration.  By default, asp.net and IIS serve resources with “Cache-Control: private”.  When you close your browser (or the current tab) the content may be cached (depending on your browser (FF and IE6/7 each handle this differently). When the user returns to your site he will either re-request the file and receive an HTTP 304 (”not modified since”) response or download the entire file again. 

In the case where your site has lots of images, js, or css files, you can end up issuing a lot of needless http requests for recurring users.  If instead you were able to set the http cache header to something in the far future, then the browser wouldn’t request the file in the first place.  Scott Hanselman has a good discussion of this behavior here.

In order to cache items at the client you need a way to version them e.g. “~/images/logo.jpg” becomes “v1.0.0/images/logo.jpg”. This way when the resources change they have new paths. These new paths are served up in your markup- the img.src attribute is now different than anything in cache and the browser must therefore request the new version (whose expiration header is also set to a far future date).

The catch is that while asp.net uses the ResolveUrl method and natively changes “~/images/logo.jpg” to “/myapplication/images/logo.jpg”, there is no such mechanism built into the framework to make it translate “~/images/logo.jpg” to “/myapplication/v1.0.0/images/logo.jpg”.

This is a fun problem to solve. It has two parts.

First we must create a framework so that static content can be served easily from the markup by substituting “~/images/logo.jpg” for “<%$ Images: logo.jpg %>”. 

Part 2 will be addressed using expressionBuilders  

Secondly we need to create the framework so that in the code behind, we can replace img.url = “~/images/logo.jpg” with img.url = StaticContent.images.logo_jpg.

Part 3 will be addressed using a build provider.

Both parts will require changes to our build process not covered here.  The continuous integration process should include in its build archive a directory of the static content named by the version number (e.g. v1.0.0) and updating a application configuration property with the specific version so that the application can reference the static content directory properly. 

The Continuous Integration changes will not be covered here because they are very specific to one’s own continuous integration process.  We currently use nant with xmlPreprocess to generate our configuration files for each version and environment (prod, dev, qa, etc.), but there are a ton of ways to do this, all outside of asp.net. Using a small ruby script we generate an additional master settings file to incorporate the latest version number in the master configuration.

No Comments yet »

April 20th 2008

What I wish I knew about Service Broker before I started using it

My current work involves asyncronous communication between multiple servers. Servers in the web farm submit jobs for processing by various backend server farms. Over time our implementation evolved from a simple stored procedure call that polled a table to a much more scalable and fault tolerant MSMQ implementation.

We moved to MSMQ because polling SQL Server tables leads to locking issues with multiple pollers and larger data sets.  Unfortunately, the move to MSMQ separated messages from data, requiring complicated logic to simulate distrubuted transactions across queues and the database. We moved to SQL Service Broker because it is designed to handle multiple pollers, utilizes t-sql,and calls are made inside the current t-sql transaction.

Unfortunately the documentation for Service Broker isn’t all that intuitive and the Microsoft tools (Sql Management Studio in particular) don’t really make things easier. Learning about Service Broker takes time and a healthy dose of experimentation.  Our implementation has been live now for several months and is working well.

Here are a few things we learned along the way:

  • you can see what messages are in a queue by selecting from it - select * from myQueue.
  • the system views sys.service_queues and sys.transmission_queue are your friends. If messages are sent but don’t show up in the queue, check the reason column of the transmission queue.  Most of the time it is because the database master key permissions aren’t set correctly, particularly if the database has been restored. 
  • permissions need to be granted on queues and services to send and receive from them:
    --Grant the user access to the service
    GRANT SEND ON SERVICE::[Your_Service_Name] TO [domain\john.doe] ;
    –Grant the user send rights
    GRANT RECEIVE ON [Your_Queue_Name] TO [domain\john.doe] ;
    –Grant the user receive rights
    GRANT SEND ON [Your_Queue_Name] TO [domain\john.doe] ;
  • when using Activation on queues the procedure actions are included in the current transaction/
  • sending/receiving messages are included in the current transaction.
  • sql management studio does not script broker objects “naturally” - when you script database objects through the UI, service broker objects aren’t included. When you script service broker objects, permissions aren’t included
  • Alter database your_database set new_broker

    will clear all your queues

  • messages in queues are encrypted by default. All messages are hex encoded.
  • if you use a common data store for each endpoint, keep messages as terse as possible. The actual content of the messages is less important than the message itself. If you need additional data, you can retrieve it from the database.

Service Broker has more features than any standard user is likely to encounter.  Asyncronous symptoms can be complicated; building a secure, scalable, fault tolerant system takes a lot of forethought.  Until Microsoft better integrates its product we have blogs, technical articles, forums, etc. to help augment the existing information. Good luck.

No Comments yet »

April 20th 2008

T-SQL Split - An xml based approach

When we select information from our database we have to be careful to filter only results that the user has access to, e.g. an user has access to user information in a fixed list of departments:

Select * from Employee where department_id in (1,3,6)

The problem is comes when we try to parameterize our query because T-SQL requires a parameter for each item in the “IN” clause.  When we use our OR/M (nHibernate) and the setParameterList method, it creates a query like this:

sp_executeSql(n'Select * from Employee where department_id in (@p0,@p1,@p2)',
'@p0 int, @p1 int, @p2 int',
@p0=1,@p1=3,@p2=6);

This has a few issues:

  1. As the list size increases, query performance degrades due to the number of parameters.
  2. We risk throwing a “Too many parameters were provided in this RPC request. The maximum is 2100″ error depending on the number of items in the list(s) the query uses.
  3. These queries cannot be cached effectively because of the variations in parameters.

We considered building a list of comma delimited ids, passing that to SQL as an nvarchar parameter, and using a user defined function like this:

Select * from SomeTable where id in (dbo.fnSplit(@ids))

After some investigation, however, we found no standard way to implement the fnSplit function. Everybody has their own version optimized for list size, ordering, and format.

Instead we serialized the list to xml (pretty easy in.net) and deserialized it using:

CREATE FUNCTION [dbo].[fn_SplitIntsFromXmlNoPrimaryKey](@input varchar(max))
RETURNS @retArray TABLE (id int)
AS
BEGIN
	DECLARE @xml xml
	SET @xml = @input
	insert into @retArray
	SELECT   T.id.value(’.', ‘int’) AS id
	FROM @xml.nodes(’/ArrayOfInt/int’) T(id)
	RETURN
END
select * from [dbo].[fn_SplitIntsFromXmlNoPrimaryKey] (
‘
  33
  72
  74
  76
  78
  80
80
 ‘)

For larger numbers of ids, performance degraded due to the table scan:
fnSplitWithNoPrimaryKey
So we optimized the function by introducing a primary key on the ints:

CREATE FUNCTION [dbo].[fn_SplitIntsFromXml](@input varchar(max))
RETURNS @retArray TABLE (id int Primary Key)
AS
BEGIN
	DECLARE @xml xml
	SET @xml = @input
	insert into @retArray
	SELECT   T.id.value(’.', ‘int’) AS id
	FROM @xml.nodes(’/ArrayOfInt/int’) T(id)
	RETURN
END
select * from [dbo].[fn_SplitIntsFromXml] (
‘
  33
  72
  74
  76
  78
  80
 ‘)

fnSplitWithPrimaryKey
For several thousand ids, the performance difference was quite dramatic at 1 second versus 13 seconds. Although our testing was light it was conclusive- a primary key allows SQL to avoid using a heap for storing/searching the table valued function result and this is much faster. Since we’re using a single parameter per IN clause, we are no longer limited by the RPC parameter limit, the query plan can be cached, and performance is within limits.
Happy coding.

No Comments yet »

April 19th 2008

Concatenating values in t-sql select statements

In a previous life, I worked on purchasing software.  One of the performance problems that continually plagued us stemmed from a fundamental decision we made about how we tracked the line items of an order:

orderLineId

orderId

qty

product

1

123

1

Product A

2

123

1

Product A

3

123

1

Product B

4

123

1

Product C

Each line has quantity one because throughout the application users would changed where a portion of an order was billed, shipped or assembled.  It seemed easier to simply store each as an individual line of quantity one than worry about how splitting lines would percolate data changes in the application.

What happens when you have quantity 100? How do you present that to the user?  How do you persist changes back to the database?  We realized the need to group like items for the user to avoid making him select 100 rows manually.  This led to, “How do we associate the one row in the UI with the corresponding 100 rows in the database?”.

The answer was relatively simple. Instead of storing a single id for the row, we stored all 100 ids comma delimited. 

In grouping like items, we had to add a sub-query to select all the ids of the line item records aggregated for the result record.

In short:

select STUFF(
(
  SELECT ',' + convert(varchar(12), object_id)
  FROM sys.objects
  FOR xml PATH('')
), 1, 1, '')

giving a result set of:

4,5,7,8,13,15,…

We’re selecting the id and converting it to a string.
the For xml PATH(”) indicates that our output should be xml, but that the “path” for each record is an empty string.
Given a result set of:

4
5
7
8
13
15

we “convert” it to an xml path of “” and get

,4,5,7,8,13,15

the STUFF function simply removes the first prepended “,” from our result.

Using this we were able to take user changes from the UI and apply them to the records in the database through a single UPDATE statement using the clause “where orderLineId in (@lineIds)”, @lineIds = our delimited listing.

Happy coding.

2 Comments »

April 19th 2008

Detecting and avoiding “A potentially dangerous Request.Form value was detected from the client” in asp.net

By default asp.net uses the setting validateRequests=”true”. This can lead to a paradox described here http://www.asp.net/learn/whitepapers/request-validation/. You can turn off request validation for your page or site, but that removes the basic protection asp.net provides against xss attacks. When you leave request validation on and a user enters text like “a<b”, the request will error out long before it reaches your actual code.

As the article asp.net article suggests, one should always htmlEncode and htmlDecode inputs posted to and rendered from the server. In our case, our own employees had discovered our failure to do this. They promptly exploited it to give custom styling to data items. This made the customers happy, triggered a nightmarish conflict between those who considered this a “feature” and those who knew it was a “bug”. Once the users took enough advantage of this loophole, it became entrenched.

When we finally got a chance to do a major refactor, our first thought was, “well, let’s do as the article suggests and put htmlDecode and htmlEncode on every input where we bind controls against values from our database.” - this seemed tedious and did nothing to prevent future developers from opening new xss holes as we developed new features.

Our next idea was use of asp.net control adapters to override the default asp.net controls and perform the htmlEncode and htmlDecode automatically. This way, developers wouldn’t have to remember to do it themselves. Unfortunately, control adapters only apply to markup (ascx/aspx) and not to controls created/added in the codebehind.

We finally opted to leave it the asp.net request validation on and:

  1. add validation to every input to prevent markup from being posted
  2. add validation in our business layer to prevent markup from entering our database
  3. strip out characters from user inputs that make it invalid
  4. use a third party control for entering rich text(markup). The control would handle the html encoding of the user input for us in these special cases and filter out invalid html tags.

It helps to work with super smart people, one of whom happened to write an a nice validation framework that used another developer’s excellent code gen., so steps 1 and 2 were pretty easy once we came up with the proper regex.

First we need to find out what validation asp.net is doing against form values. This code can be found using reflector in the System.Web.CrossSiteScripting class:

internal static bool IsDangerousString(string s, out int matchIndex)
{
    matchIndex = 0;
    int startIndex = 0;
    while (true) {
        int num2 = s.IndexOfAny(startingChars, startIndex);
        if (num2 < 0) {
            return false;
        }
        if (num2 == (s.Length - 1)) {
            return false;
        }
        matchIndex = num2;
        char ch = s[num2];
        if (ch != ‘&’) {
            if ((ch == ‘<’) && ((IsAtoZ(s[num2 + 1]) || (s[num2 + 1] == ‘!’)) || (s[num2 + 1] == ‘/’))) {
                return true;
            }
        } else if (s[num2 + 1] == ‘#’) {
            return true;
        }
        startIndex = num2 + 1;
    }
}

If a word begins with an “&#” or “<” preceding an alpha, “!” or “/” character, then the value is considered invalid by asp.net.

For part 1 we created a custom validator that utilized the following javascript function:

function NoXmlValidation(sender, args)
{
var re = new RegExp('(&#)|(<(?=(!|\\w|\\%|/)))', 'g');
args.IsValid = true;
if (args.Value && args.Value.match(re)) {
args.IsValid = false;
}
return;
}

Not all forms can be validated client side and prevent postback- specifically if you’re using validation groups and/or update panels, you can have major issues when the validators don’t fire and the postback generates the asp.net request validation error.
To work around this, we hooked into the ms client side application framework. When an html form is submitted we go through the inputs and strip out characters that violate our xss validation.

First the javascript code:

//called before onSubmit, validation should be called before, although the way we hooked into
//the event through the .net framework means that the code is also in there afterwards too
//but will never get called.
//This method goes through all the inputs and removes the characters that would cause a requestValidation exception on postback
//note: this actually modifies the element values, not just the data posted
function OnNormalSubmit() {
 // get all textarea and text inputs
 var inputs = $A(document.getElementsByTagName('input')).findAll(function(item) {return (item.type.toLowerCase() === 'text'); }).concat($A(document.getElementsByTagName('textarea')));
 var re = new RegExp('(&#)|(<(?=(!|\\w|\\%|/)))', 'g');
 // iterate through the all and replace invalid values
 inputs.each(
 		function(elt) {
 				elt.value=elt.value.replace(re,'');
 				if ( elt.Validators ) {
 					elt.Validators.each(
 						function(elt2) {
 							if ( elt2.isvalid !== undefined && !elt2.isvalid ) {
 								$(elt2).hide();
 							}
 						}
 					);
 				}
 			}
 	);
}

Note: the sample above uses the “$A“, “each“, and “findAll“ methods in the prototype library.  ”for” loops could be used instead, however, by using prototype we can abstract out the low level details and focus on the algorithm itself.

Now we need do is hook this method up to be called during the onPostback() method.
In your pagebase or master, put in following code:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
 If Not IsPostBack Then
 'register code for onSubmit validation to prevent requestValidationError. Note: must call ValidatorOnSubmit before calling custom code- otherwise the postback isn't prevented when the form is invalid.
 '.net puts our code before the standard ValidatorOnSubmit call, so we need to call it first manually.         

 ScriptManager.RegisterOnSubmitStatement(Me.Page, Me.GetType, "onSubmitScript", "if (typeof(ValidatorOnSubmit) === ""function"" && ValidatorOnSubmit() == false) return false; " + Microsoft.VisualBasic.vbCr + "if( self['OnNormalSubmit'] ) { OnNormalSubmit(); return true; }” + Microsoft.VisualBasic.vbCr)         

 End If
End Sub

No Comments yet »

April 19th 2008

Detecting authentication cookie expiration on asyncronous requests

As functionality moves from postbacks and server side processing to the client via update panels and ajax behaviors, a common situation can occur when the user’s authentication cookie has expired and the user executes an asynchronous request. 

Normally with a synchronous request, the web server will return a response with HTTP code 301 (redirect) and the browser will redirect the user to the login page.  However, for asynchronous requests, however, the server simply responds with an HTTP code of 500 (error).
The ms ajax framework renders asynchronous requests as JSON rather than the standard SOAP.  This makes consuming server resources via javascript much easier.  The problem is that the ms ajax framework doesn’t distinguish between unhandled errors and authentication timeouts when processing requests as JSON.

The code that controls this is the class System.Web.Script.Services.RestHandler of the system.web.extensions assembly.

internal static void ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData) {
	try
	{
		if (!s_permissionSetChecked) 	{
			s_permissionSet = (NamedPermissionSet) typeof(HttpRuntime).GetProperty("NamedPermissionSet", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null, null);
			s_permissionSetChecked = true;
		}
		if (s_permissionSet != null) 	{
			s_permissionSet.PermitOnly();
		}
		IDictionary rawParams = GetRawParams(methodData, context);
		InvokeMethod(context, methodData, rawParams);
	} catch (Exception exception) {
		//THIS IS WHERE .NET CATCHES ALL EXCEPTIONS AND RETURNS THE 500 error
		WriteExceptionJsonString(context, exception);
	}
}                  

internal static void WriteExceptionJsonString(HttpContext context, Exception ex) {
	context.Response.ClearHeaders();
	context.Response.ClearContent();
	context.Response.Clear();
	context.Response.StatusCode = 500;
	context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(500);
	context.Response.ContentType = “application/json”;
	context.Response.AddHeader(”jsonerror”, “true”);
	using (StreamWriter writer = new StreamWriter(context.Response.OutputStream, new UTF8Encoding(false)))
	{
		if (ex is TargetInvocationException) {
			ex = ex.InnerException;
		}
		if (context.IsCustomErrorEnabled) {
			writer.Write(JavaScriptSerializer.SerializeInternal(new WebServiceError(AtlasWeb.WebService_Error, string.Empty, string.Empty)));
		} else {
			writer.Write(JavaScriptSerializer.SerializeInternal(new WebServiceError(ex.Message, ex.StackTrace, ex.GetType().FullName)));
		}
		writer.Flush();
	}
}

To get around this issue we need to detect JSON responses with HTTP code 500 and no authentication cookie in order to return our own slightly modified response. This is done with an http module.

Imports System                  

	Public Class SessionModule
		Implements IHttpModule                  

#Region " IHttpModule Implementation "                  

		Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
			If context Is Nothing Then
				Throw New System.ArgumentNullException("context")
			End If                  

			AddHandler context.EndRequest, AddressOf OnEndRequest                  

		End Sub                  

#End Region                  

#Region " Private Methods "                  

		Private Sub OnEndRequest(ByVal sender As Object, ByVal e As EventArgs)
			'if this is a web service call with no auth cookie, send a different response than the generic error so that we can handle it appropriately
			'not the "customErrors" section defines (if not "off") that generic messages will be sent (including web services)
			'since it's a scriptHandler for asmx (for json/atlas) we can't get around that constraint with web service extensions (they never get hit)
			'so we do it here instead.
			'NOTE: we're returning a matching json object that asp.net is already configured to return (copied from reflector):
			'System.Web.Script.Services.RestHandler->WriteExceptionJsonString
			If System.IO.Path.GetExtension(System.Web.HttpContext.Current.Request.FilePath).ToLowerInvariant = ".asmx" AndAlso HttpContext.Current.Request.Cookies.Get(System.Web.Security.FormsAuthentication.FormsCookieName) Is Nothing Then
				WriteAccessDeniedExceptionJsonString(HttpContext.Current)
			End If
		End Sub                  

		Private Shared Sub WriteAccessDeniedExceptionJsonString(ByVal context As HttpContext)
			context.Response.ClearHeaders()
			context.Response.ClearContent()
			context.Response.Clear()
			context.Response.StatusCode = 500
			context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(500)
			context.Response.ContentType = "application/json"
			context.Response.AddHeader("jsonerror", "true")
			Using writer As New System.IO.StreamWriter(context.Response.OutputStream, New UTF8Encoding(False))
				writer.Write(New WebServiceError("Authentication failed.", String.Empty, String.Empty).ToJson)
				writer.Flush()
			End Using
		End Sub                  

#End Region                  

#Region " Auth Cookie missing on Web Service request response "                  

		'this class is used to replicate the response from the .net framework when an json web service call
		'is denied because of missing auth ticket
		Private Class WebServiceError                  

#Region " Declarations "                  

			Private Const JsonTemplate As String = "{{""Message"":""{0}"",""StackTrace"":""{1}"",""ExceptionType"":""{2}""}}"
			Private _exceptionType As String
			Private _message As String
			Private _stackTrace As String                  

#End Region                  

#Region " Constructors "                  

			Public Sub New(ByVal msg As String, ByVal stack As String, ByVal type As String)
				_message = msg
				_stackTrace = stack
				_exceptionType = type
			End Sub                  

#End Region                  

#Region " Public Methods "                  

			Public Function ToJson() As String
				Return String.Format(System.Globalization.CultureInfo.InvariantCulture, JsonTemplate, _message, _stackTrace, _exceptionType)
			End Function                  

#End Region                  

		End Class                  

#End Region                  

	End Class

Don’t forget to wire the http module into the asp.net pipeline using the httpModules section of the web.config.

Now, on the client side we need to hook a method into the asp.net ajax client side framework to listen for any http request that returns with our extra information (HTTP code=500 and Message=”Authentication failed”) and redirect the user accordingly. We can do this using the ms ajax application framework completedRequested event:

// attaches a handler that is fired first after web service is returned, before specific handler is called
// specifically, if the web service errored because of authentication failure (auth cookie expired)
// then redirect the page to the login page.
// note the login page is hard coded is assumed to be in the path site/app path/login
Sys.Net.WebRequestManager.add_completedRequest(On_WebRequestCompleted);                  

function On_WebRequestCompleted(sender, eventArgs)
{
  if ( sender.get_statusCode() === 500 ) {
   if (sender.get_object().Message === "Authentication failed.") {
		window.location.href = window.location.protocol + "//" + window.location.host + window.location.pathname.substring(0,window.location.pathname.indexOf('/',1)) + '/login.aspx?redirectUrl=' + encodeURIComponent(window.location.pathname + window.location.search);
    }
  }
}

This will redirect the user to page “login.aspx” and contain the current url as the redirectUrl querystring parameter.
That’s it. Now when the user’s authentication expires he will be redirected to the login page as expected.

1 Comment »

April 6th 2008

ScrollTo using the ajax control toolkit animation framework

I’ve been digging deep into the asp.net ajax javascript framework and wanted to share one of the cool controls i was able to make using the asp.net ajax library and the AjaxControlToolkit animation framework. The guys over at codeplex have put together a pretty nice animation framework. It’s been pretty hard for me to find any useful reference material on the animation framework outside of using it really server side. I wasn’t able to find anything about creating your own custom animation classes although the animation framework has obviously a ton of sample code. Eventually i found my best resource to be VS2008 javascript intellisense with the aniamtion framework referenced into my class. Enough of the details, i could rant all day an may do so later but lets get into some of the cool stuff.

javascript references for intellisense

/// 
/// 
/// 
/// 
/// 
/// 
/// 

/// 

specifically this sample doesn’t use the intellisense from all of these references but this is generally what i start with as my js references for any javascript class i’m looking to create. And yes, the intellisense builder requires that the js file references be in an order of dependence. Ok, now we can finally move on to what i’m actually trying to blog about. The scroll to animation.

Type.registerNamespace("boudreau");boudreau.scrollTo = function(element) {
    boudreau.scrollTo.initializeBase(this, [element]);
       this.animation = null;
}
boudreau.scrollTo.prototype = {
    initialize: function() {
    ///
    /// creates our animation and adds a click handler.
    ///
        boudreau.scrollTo.callBaseMethod(this, ‘initialize’);
        this.animation = new $AA.InterpolatedAnimation(this.get_element(), 2, 25, “scrollTop”);
// Important: Notice that the value is passed as a function.
        // This overrides the animation’s getAnimatedValue function.
        this.animation.getAnimatedValue = this.__getAnimatedValue;
    },

dispose: function() {
    ///
    /// Does the cleanup work
    //
        this.animation.dispose();
        boudreau.scrollTo.callBaseMethod(this, ‘dispose’);
    },

scrollTo : function( target ) {
    ///
    /// scrolls the container to to the target element.
    ///

        var container = this.get_element();
        // If we wanted the item to scroll to the middle of the
        // container we set offset = middle.
        var middle = ($common.getContentSize(container).height / 2.0 );

        // For now we just use a 40 pixel offset so it isn’t hugging
        // the top of the container.
        var offset = 40;

        //set start and ending positions of the scroll window.
        this.animation.set_startValue(container.scrollTop);
        this.animation.set_endValue(target.offsetTop - 40);

        //let the framework do it’s job.
        this.animation.play();

    },

__getAnimatedValue : funtion ( percentage ) {
        ///
        ///  This method is run in the context of the animation object.  Therefore it’s
        ///  important to understand that when this function is executing that “this”
        ///  referes to the animation object, NOT the boudreau.scrollTo object.
        ///  Besides that this function performs a simple linear interpolation.
        ///
        ///
        /// It’s important to note the reason we have to define this function is that it’s a
        ///  MustOverride on the InterpolatedAnimation object.
        ///  The reason i used InterpolatedAnimation is so that i could
        ///  implement a non-linear animation.
        ///

        return this.get_startValue() + (this.get_endValue() - this.get_startValue()) * (percentage * / 100);

    }

}

boudreau.scrollTo.registerClass(’boudreau.scrollTo’, Sys.UI.Behavior);

if (typeof(Sys) !== ‘undefined’) Sys.Application.notifyScriptLoaded();

That’s the code.
If you aren’t comfortable with prototypical development in javascript then you’ll you should brush up on that. [Blog Link, WWW Link?] But if you are then lets dive into the details.

The Behavior
scrollTo object derives from microsoft’s Sys.UI.Behavior which is used to extend the functionality of a particular DOM element. This behavior acts on the scrollable container you define as the target element. If someone asked me what this behavior did i’d tell them ” It’s a javascript object that holds a reference to a DOM element, the container, (This is what we get from microsoft ajax library.) The javascript object knows hows to scroll, with animation, to a given target element inside the container. ” If you can decipher that you’ve got it. On to the gory details.

The constructor - new $AA.InterpolatedAnimation()
In the init method we define a new InterpolatedAnimation first off $AA is a shorthand for AjaxControlToolkit.Animation, it saves us some 20 chars to type, and download for that matter. The constructor used above takes some 7 parameters but we are only passing in 4 because i’m happy with the default values for the others for now. The first, the target element, is the object that should contain the scrollBar you want to animate. The second and third are fps and duration respectively (which i won’t discuss cause they are pretty self explanitory.) the fourth parameter is the property of the the target element you want to animate using the InterpolatedAnimation. Kind of important to understand what’s going on here, this parameter is what the interpolated animation updates when it is told to .play(). So, in our case we want to animate the scrollTop property of the target element. scrollTop is just a javascript property that gets/sets the top of the content area in a scrollable window. Alright, that’s creating the animation. Hopefully you see what’s going on here, lets recap. We are creating an interpolated animation object inside of our behavior and telling it what property on what element we want to animate. Next we’ll look at the scrollTo function and see how the behavior does the work.

The worker - scrollTo()
the scrollTo() method does all the work of finding and scrolling to the correct distance inside the container. At the end we hope to see that our target element is 40 px below the scrollTop of the container content window. First we define the properties on the animation that will be interpolated, the start and end scrollTop value of the container. These two properties on the InterpolatedAnimation are the values you want the animation to interpolate across. (remember how we set scrollTop as the property in the constructor.) Our InterpolatedAnimation is going to interpolate the values between start_value and end_value on the container element (the scrollable element) and update the property scrollTop with the value at each step in the animation. This is how it works, if you don’t understand the previous explanation read it again, it’s the magic and hopefully you can tell there is no real magic at all!

// For now we just use a 40 pixel offset so it isn't hugging
// the top of the container.
var offset = 40;

//set start and ending positions of the scroll window.
this.animation.set_startValue(container.scrollTop);
this.animation.set_endValue(target.offsetTop - 40);

this.animation.play();

So that’s pretty much it, after you

$create(boudreau.scrollTo, null,null,null, $get(scrollableElementId))

a scrollTo behavior on a scrollable element you use

$find(scrollableElementId).scrollTo(element_to_scroll_to_in_container)

to produce a nice smooth scroll to the target element inside the scrollable container.

No Comments yet »