Tom Jaeschke

I'm Tom Jaeschke and you may contact me at tomjaeschke@tomjaeschke.com or (512) 419-8788.

Friday, March 18, 2011

Thank you Headspring!

...for 5+ wonderful years of employment! Dustin Wells, you are the best boss I will ever have! I'm sad that today is my last with your company.

Thursday, February 3, 2011

Dark clouds loom for Mom and Pop hosting enterprises

Shared Hosting Pricing versus Cloud Hosting Pricing

At Headspring, we are embracing Microsoft Azure and have, for example, this year moved our web site to Azure from shared hosting. (Thank you Tim Thomas!) This is one of many examples of our reliance on cloud computing, an ever-deepening trend for us.


Is it expensive? Not really. The most extreme example I can think of in which cloud hosting is expensive compared to an alternative is the difference between hosting one web site in a windows shared hosting environment versus spinning up a Rackspace slice in a Windows environment or undertaking a minimum commitment to Azure. The difference is about $600 per year as of this writing. That is the difference between $100 per year for the shared environment and $700 per year for the cloud environment.


What's more, even if this does seem like a big distinction, note that the gap will only tighten up. Clouds are new and their pricing will fall. Network Solutions can't cut back expenses much more to pass a savings on to you. They are running a lean ship as it is.


What is the trend here? Could there come a day when there is effectively no price difference at all and thus all of the Mom and Pop hosting companies that have come and gone in the past dozen years see their era come to a close? Is a new era in hosting dominated by Microsoft, Rackspace, and a few other key big fish dawning? It seems to me that the days in which any small business owner may offer hosting as a service are slipping away.

Sunday, December 19, 2010

Headspring Holiday Party

I have just left a Headspring Holiday Party and I have come away with these iPhone pictures of Dustin Wells, Jeffrey Palermo, Kevin Hurwitz, Matt Hinze, Glenn Burnside, and Anne Epstein:



Headspring

Thursday, June 24, 2010

At Headspring, I STILL work with some amazing individuals!

I am proud to work with:


Eric Anderson

Jimmy Bogard

Shawna Boyce*

Anne Epstein

Kevin Hurwitz

Mahendra Mavani

Jeffrey Palermo

Pedro Reys

Christa Tuttle*


*of Launch Marketing, which offices with Headspring



Today, most of us toured the new facility that Headspring and Launch Marketing will move into on Monday, the 28th of June (four days from today). Nick Becker, Matt Hinze, Rafael Torres, and Dustin Wells of Headspring could not attend due to circumstances, but I am immensely proud to work with Nick Becker, Matt Hinze, and Rafael Torres and especially to work for Dustin Wells. After the tour we ate at the Mangia Pizza at Mesa and Spicewood Springs and I took these photos...



Eric Anderson, Jimmy Bogard, Shawna Boyce, Anne Epstein, Kevin Hurwitz, Mahendra Mavani, Jeffrey Palermo, Pedro Reys, and Christa Tuttle

Wednesday, June 16, 2010

Mahendra Mavani speaks on Object-Oriented Programming

On 6/15 Matt Hinze presented on TDD at Microsoft's Austin office for what was the very last workshop held at its MoPac location. Be our guest on 7/20 at the new Microsoft office at 10900 Stonelake Boulevard, Building B, Suite 225, Austin, Texas 78759 when Headspring's Mahendra Mavani speaks on OOP. I will be there and I promise a good workshop. (SOLID, IoC, and more!)


Register.

Sunday, June 13, 2010

SEO exposure

I've been trying to learn a little more about SEO (Search Engine Optimization) nuisances in 2010 (and earlier). My findings include:



  1. The contents of a title tag should not exceed 65 characters while the contents of the description tag should be limited to 25 to 150 characters.


  2. http://www.ispionage.com/ and http://www.compete.com/ and http://www.spyfu.com/ and http://www.keywordspy.com/ are tools for gauging what competitors and doing in the name of Adwords.


  3. https://adwords.google.com/select/KeywordToolExternal is good tool for measuring if a potential search term is worth optimizing for and also how competitive it is. We have been using this tool to determine overall traffic for a keyword (within a month's time) and then http://www.google.com/analytics/ to determine how big of a pie slice (within a month's time) we are garnering. The second number divided by the first provides a percentage to track.


  4. http://www.quantcast.com/ is a tool for gauging the audience of an existing web site while http://www.forrester.com/Groundswell/profile_tool.html is a tool for gauging how to reach a demographic by way of social media marketing (use the third slide of http://www.forrester.com/Groundswell/ladder.html to interpret the results the tool gives).


  5. http://mashable.com/ gives Web 2.0 and social media news.


  6. http://www.wordtracker.com/ and http://www.wordze.com/ are keyword tools. Wordze offers a 30 day free trial and also supposedly provides cost-per-click projections with recommended keywords.


  7. Canonical tags may be used to make the most of duplicate content instead of having duplicate content become a negative. Something like this <link rel="canonical" href="http://www.example.com/product.php?item=swedish-fish"/> in the header of a page suggests that the page's content should weigh in favor of the other page referenced instead of a scenario in which the two pages compete with each other.


  8. http://www.prchecker.info/check_page_rank.php allows one to determine a "page rank" for a page and Google has leaked that the better a page ranks the more Google is inclined to allow more pages on a given site to be indexed. Page rank and the number of pages indexed are tied together as metrics.


  9. It may be wise to build a search engine at one's site, to record what the public searchs for, and to pay attention to those searches which yield zero results. What are others NOT finding at your site?


  10. http://www.cogi.com/ is a good transcription service. Headspring has been transcribing audio recordings form our senior players speaking on their expertise and cleaning the transcripts of the recordings up to feed (eventually blog postings and also) whitepaper content such as that found here: http://www.headspringsystems.com/services/custom-application-development/


  11. I now have experience with WordPress, dasBlog, and subtext. If you are going to host your own blog, WordPress is very likely the way to go. dasBlog and subtext are ASP.NET open source alternatives to the one solution which PHP truly excels at. Use dasBlog and subtext when under hosting restraints. dasBlog does write content to files in lieu of using a database which may save one on hosting fees.


  12. One may track Twitter traffic using service such as http://bit.ly/ consistently to wrap URLs offered at Twitter (putting a tracking code at the end of URLs).


  13. verticalresponse.com is good for email campaigns.


  14. http://www.techsmith.com/camtasia.asp is good for recording live presentations which in turn may then draw interest at YouTube and Viddler.


  15. http://www.attentionwizard.com/ may be used to tell where eyes will go on a landing page. http://www.crazyegg.com/ seems to be a comparable tool as are http://www.clicktale.com/, http://www.usertesting.com/, and http://userfly.com/.


Friday, May 7, 2010

Headspring's Eric Anderson to give a workshop on Version Control and Automated Builds on May 18

http://www.headspringsystems.com/community/workshop/ will allow you to sign up for "Version Control and Build Systems for Growing Teams," a workshop by Headspring's Eric Anderson on May 18th at the Microsoft Center in Austin.



Topics to be covered include:

  • Branching strategies in Subversion
  • Branching strategies in DVCS with Mercurial
  • Continuous Integration with Hudson
  • Continuous Integration with CruiseControl.Net
Tom Jaeschke

I'm Tom Jaeschke and you may contact me at tomjaeschke@tomjaeschke.com or (512) 419-8788.

Saturday, April 24, 2010

Pitfalls

I have finished building a simple content management system (see: Yay Tests!). It is the best work I've yet done in my four and one half years at Headspring.



The project was really fun because I got to do some wild stuff in breaking with RESTful routing out of necessity (see: of both CMS routing and forcing browsers to close) and some of the more conventional model-view-controller things which still seem like magic to me (see: easy AJAX?). Beyond MVC, I am proud out the underlying architecture too (see: Fight for the Center).



So what didn't go so well? What are some pitfalls that I will avoid next time (and document here so that you may avoid them too)?



One:
I struggled to get the FCKeditor working. (see: couldn't skate by with a marginally functional FCKeditor)



Two:
I placed an overemphasis on alphabetization. (see: When to Alphabetize)



Three:
I staged a version of the web application on one of Headspring's servers and in doing so I struggled to get ASP.NET MVC running in an IIS6 environment. There are two things to remember to do. First, the MVC .dll has to be added to one's bin folder. Secondly, one needs to set up "wildcard mapping" which may be done by going into the site's properties in IIS:



From the "Home Directory" tab click on "Configuration..." to get this dialog box:



Next, click the "Insert..." button for this dialog box:



C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll needs to be added here and the checkbox for "Verify that file exists" should be unchecked.



Four:
A flash movie plays at the home page of the web application which is public-facing. It was noticed that when persons tried to view the home page at an iPhone that the flash movie just appeared as a broken link. This problem, and other potential woes caused by an inability to use Flash, had to be addressed and it was suggested to me that I display an image in lieu of the flash content if Flash was unavailable. I thus struggled to find a way with JavaScript to determine if the flash plug-in was installed to empower some conditional logic that would sniff for Flash. I had trouble finding a script that worked universally across all browsers. Eventually, I found this solution:





<!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>BTA Systems</title>
<link href="/Content/Site.css" rel="stylesheet" type="text/css" />
<script src="/Scripts/AC_RunActiveContent.js" language="javascript"></script>
<script type="text/javascript" src="/Scripts/jquery-1.4.2.js"></script>
</head>
<body>

<SCRIPT LANGUAGE=JavaScript1.1>
<!--
var MM_contentVersion = 6;
var plugin = (navigator.mimeTypes && navigator.mimeTypes["application/x-shockwave-flash"]) ? navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin : 0;
if ( plugin ) {
var words = navigator.plugins["Shockwave Flash"].description.split(" ");
for (var i = 0; i < words.length; ++i)
{
if (isNaN(parseInt(words[i])))
continue;
var MM_PluginVersion = words[i];
}
var MM_FlashCanPlay = MM_PluginVersion >= MM_contentVersion;
}
else if (navigator.userAgent && navigator.userAgent.indexOf("MSIE")>=0
&& (navigator.appVersion.indexOf("Win") != -1)) {
document.write('<SCR' + 'IPT LANGUAGE=VBScript\> \n'); //FS hide this from IE4.5 Mac by splitting the tag
document.write('on error resume next \n');
document.write('MM_FlashCanPlay = ( IsObject(CreateObject("ShockwaveFlash.ShockwaveFlash." & MM_contentVersion)))\n');
document.write('</SCR' + 'IPT\> \n');
}
if ( MM_FlashCanPlay ) {
var flash = true;
} else{
var flash = false;
}
//-->

</SCRIPT>
<script type="text/javascript">
$(document).ready(function() {
if (flash) {
$('#flash').removeClass('noshow');
} else {
$('#image').removeClass('noshow');
}
});
</script>




<div id="flash" class="div040 noshow">
<script language="javascript">
if (AC_FL_RunContent == 0) {
alert("This page requires AC_RunActiveContent.js.");
} else {
AC_FL_RunContent(
'codebase', 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0',
'width', '959',
'height', '285',
'src', 'BTA',
'quality', 'high',
'pluginspage', 'http://www.macromedia.com/go/getflashplayer',
'align', 'middle',
'play', 'true',
'loop', 'true',
'scale', 'noscale',
'wmode', 'opaque',
'devicefont', 'false',
'id', 'BTA',
'bgcolor', '#007FB3',
'name', 'BTA',
'menu', 'true',
'allowFullScreen', 'false',
'allowScriptAccess', 'sameDomain',
'movie', 'BTA',
'salign', ''
); //end AC code
}
</script>
</div>
<div id="image" class="div040 noshow">

</div>
</body>
</html>




Five:
This NHibernate-related code:





public void updateSettings(Settings settings)
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(settings);
session.Flush();
transaction.Commit();
session.Close();
}
}
}




...ended up replaced by this NHibernate-related code:





public void updateSettings(Settings settings)
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(settings);
session.Flush();
transaction.Commit();
session.Close();
transaction.Dispose();
sessionFactory.Close();
sessionFactory.Dispose();
}
}
}




Do you see the difference between the two? Duh. :) Launch Marketing, who was liaison with the end client, received this message before the replacement.



Six:
The UI displayed a series of tabs for the topmost categories of content. If one tab was "up" it had to be denoted in the HTML. I wrote a class that checked to see if the current context demanded adjusting a particular piece of HTML and I called the class 53 times within three of my Views. Unfortunately, instead of passing the menu system, hydrated from the database, into the implementations of the class, I had the class reach out for data. This made the application run noticeably slow. Why would I make this mistake? I was just trying to pass one less thing to a method. I am participating in a book club which is going through "Clean Code" by Robert C. Martin. Page 40 contains: "The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification - and then shouldn't be used anyway."



Seven:
The content, siteconfig, and logs folders within a dasBlog install must be given permissions to allow for writing. In the absence of the permissions, there is no way to update the blog.



I hope this cheat sheet will spare you the headache I had.

Friday, April 23, 2010

couldn't skate by with a marginally functional FCKeditor

The project I keep blogging about was a self-built CMS. It allowed administrators to edit the content of the web site within the FCKeditor which is a wysiwyg HTML editing tool that dresses up what is otherwise a textarea type input to look like so:



At http://ckeditor.com/download one may download the tool which is apparently a depreciated version of CKeditor. I picked FCKeditor over its baby brother as I've seen it work firsthand before (although I had not done the implementation myself). For the most part it does what it does by way of JavaScript and just, as mentioned, dresses up a textarea to allow persons who don't know HTML to write HTML wysiwyg-style. From the initial download I placed at /Scripts/wysiwyg/ the core files of fckconfig.js, fckeditor.js, fckpackager.xml, fckstyles.xml, and fcktemplates.xml and a folder titled "editor" full of numerous other supporting files for the tool. I did a Google search for how to wire up the tool in an ASP.NET MVC application and, based on my findings, I got it mostly working without too much effort.



When defining the specs for the project, Launch Marketing and I demoed an older project from 2007 which used the now-antiquated BlueShoes editor and set the expectation with the end client that one would be able to create basic HTML and input an image into the HTML based upon images manually placed upon the end client's server. (I planned to implement BlueShoes before I realized it was outdated and I didn't recall a way to upload images within BlueShoes.) The plan was not to build out the uploading of files or the browsing for files at the server by an automated means.



Administrators still needed to be able to add images via the FCKeditor. How does one do so? Well, the leftmost icon on the fourth row of the editor above opens this window:



Ah, hah! An administrator can just type in the URL of the manually placed image and the agreed to specifications will be met!



Did you notice the button which says "Browse Server" like I did? It would be neat if that just worked but unfortunately it threw errors like this one:



Well, it doesn't really have to work, right? The project scope states...



Maybe you see where this is going: The end client noticed the "Browse Server" button too and argued that it should work.



I had hoped I could skate by with things as they stood. My instincts now told me I shouldn't try to skate anymore.



Getting the FCKeditor working completely with upload and browse capabilities was really painful as I could not find any good blog posts to explain what I was missing. I hope this serves as the blog post I couldn't find.




  1. Biggest thing: Most of the FCKeditor does what it does with JavaScript, but you will have to implement something server-side language specific to empower uploading and browsing. Don't waste time searching through the JavaScript for what is wrong. You can start there, but you won't find all of the answers there and you will waste time assuming all of the answers are there. (There is an old joke about the word assume.)
     

  2. Following the path I took, I assumed too much and first started digging through the existing code. In fckconfig.js I found a place to specify the base URL for the site. This is something that does need to be configured.
     

  3. In fckconfig.js I also found a place to specify the scripting language. (Why would that be important???) I set the page extensions to .aspx. This too is something that does need to be configured.
     

  4. /Scripts/wysiwyg/editor/filemanager/connectors/aspx/config.ascx was found in the folder titled "editor" and inside were variables for defining the file paths where FCKeditor will try to upload files to. I modified the paths into something sane.
     

  5. In config.ascx there is also a CheckAuthentication() method which returns false by default. As long as it returns false then config.ascx (the facilitator for uploading and browsing) is worthless. That said, I've read numerous blog postings which caution against simply returning true here suggesting strongly that developers write some sanity checking to see if a user has permissions to make edits before returning true.
     

  6. The file features still didn't work after the five steps above. What was I missing? It turns out that there is a second separate download at http://ckeditor.com/download for enabling the FCKeditor in ASP.NET. One should download the download. The contents of the .zip file look like so:


     

  7. I dumped everything into the root of my UI save for the "bin" folder. I tried to build. AssemblyInfo.cs caused exceptions. I just deleted the file. In the end, I didn't need it.
     

  8. I found in the "FileBrowser" folder Config.cs which had a lot of variables to set which looked comparable to those in config.ascx. It seems config.ascx's stuff overrides that of Config.cs by default however, and I didn’t need to tweak Config.cs whatsoever.
     

  9. Integration with .NET requires three <appSettings> keys in Web.config which look like this:

    <add key="FCKeditor:BasePath" value="~/Scripts/wysiwyg/editor/"/>

    <add key="FCKeditor:UserFilesPath" value="~/Images/"/>

    <add key="FCKeditor:AutoCompleteAbsoluteURL" value="1"/>
     

  10. The keys need to be set properly.
     



On the other side of the ten steps above everything worked!



Skate or Live!

Sunday, April 18, 2010

Fight for the Center



I have recently wrapped up building a simple CMS for Launch Marketing. I see some things that could be improved yet (see: When to Alphabetize), but the undertaking has been a success and I find that I cannot stop blogging of the project as I got to tackle some new challenges.



Something that is not new, which guided my hand, is a core principle at Headspring: Fight for the Center! I first heard these words in a chess lesson as a child, and the lesson in chess seems to apply to application design too. If you can control the center in chess you can control the whole of the board. If you can control the core of your application and keep the pieces at the edges from muddying up the center you are in good standing to keep the application stable and maintainable.



So what defines the core versus the "pieces at the edges?" Headspring's CIO Jeffrey Palermo underlines what the parts are and how to keep them loosely coupled in his blog postings on Onion Architecture. The core should hold the business logic and your objects while the UI and all of the external dependencies are detached into different Visual Studio Projects (Headspring is a .NET shop.). The core itself should not depend on other projects. Objects need to be something more than and something independent from gunk that bubbles up from the database as otherwise the whole of the app becomes tied to a specific hydration means making the implementation of the specific technology hard (impossible) to change out and the business logic tricky to both test and maintain given the way one thing bleeds into another.





I broke four external dependencies away from the core and UI in the name of keeping the application maintainable. They are the database, DateTime.Now, the sending of emails, and the writing of a sitemap XML file for the whole of the web app based on user-created "pages" within the CMS.



I toyed with the notion of somehow trying to detach the reading and writing of cookies from the rest of the UI too. I didn't do so (see: of both CMS routing and forcing browsers to close) and I'm not sure how I might have. I've done some Google searching today for anyone else who might have thought of this, but found nothing. I guess all of the web browser-related functionality stays in the UI project. It is healthy to ask one's self "What can I detach?" however, and I bring up the cookies as an example of posing this question. Per Headspring's CEO Dustin Wells: Question Everything...(within reason)



Regular Expressions are another example of something that is not an external dependency. I had no problem putting this in the core of my project as I know it will be maintainable even in the event of a port to another language:





using System.Text.RegularExpressions;

namespace AdministrableApplication.Core
{
public class EmailValidator
{
public static bool Check(string emailAddress)
{
if (Regex.IsMatch(emailAddress, @"^\w+([-+.']\w+)*@\w+([-+.']\w+)*\.\w+([-+.']\w+)*$"))
{
return true;
} else {
return false;
}
}
}
}



 
That said, one should be careful to not have too many different using statements sprinkled about the core. Such is a sign of external dependencies creeping in.



My UI still seems a little bulky as it has a lot of esoteric whistles and bells within it (see: easy AJAX?) and I wish more of the UI could have been legitimately jammed into the core. It is best to make the core as fat as possible.



Headspring recommends full system tests, but in this project I only wrote unit tests around the core of the app. While I don't recommend this as good practice, I will say in this case having only the core under test really pushed me to jam as much as possible into the core (see: Yay Tests!), and perhaps it is best to at least pretend that you are only testing the core of your app. If you knew that only the core of your application lay under tests wouldn't you try to secure as much as possible within the core keeping other Visual Studio Projects lean and minimalistic?



It's safest at the center.






Anyways, I hope you've enjoyed my chess analogy. It's your move.



credits roll, music begins: One Night In Bangkok

Thursday, April 15, 2010

Yay Tests!

I've built an elementary CMS for a client of a client. There is a backend administrative system for adding pages and specifying their URLs that looks like this:





In the example above the fourth row is selected for editing. Here is a closer look at how the URL is specified:





http://www.example.com/infor/industry/high-tech-and-electronics/ is thus the path to the page. Here are some notes on how the routing was handled: of both CMS routing and forcing browsers to close



A look at the database table that drives how the pages are organized reveals the magic is kept in a column called suggestedPriority. More on the schema here: When to Alphabetize





So obviously records are sorted by suggestedPriority. The arrows in the second image above allow for moving pages up or down relative to a first, second, or third "folder." This allows for organization of the menu system. If one uses a second folder arrow, all of the adjacent pages with identically-named "second folders" should get moved up or down relative to nearest cluster of pages with identical second folder names. This ended up working too well and clustered pages together by matching on only second folder names while not checking for a discrepancy between second folder names and first folder names.



There was bug that was not addressed by unit tests.



Luckily, I had enough other unit tests to quickly lay hands on the problem and address it, writing new unit tests. The speed of finding the bug came largely from knowing what I did have under test already. If I had written the page sorting means without TDD I would have written a mess that I had no confidence in, that I would be afraid to change, and that would be much more time-consuming to troubleshoot.



(This project, which I've been blogging about for weeks, "went live" today.)

Tuesday, April 6, 2010

easy AJAX?

If you haven't tried returning a JsonResult with ASP.NET MVC by way of jQuery you're missing out on what is finally a painless way to undertake AJAX, the only way I've yet done AJAX that hasn't felt like jamming a square peg into a round hole. Instead of feeling dirty after writing what feels like a hack, one may glow with pride after an implementation that feels as natural as posting a form, as though AJAX was actually intended to be part of web development!


Consider this piece of a View:






Details details = ViewData["Details"] as Details;
string errorMessage = ViewData["SubmissionErrors"] as string;
%>
<script type="text/javascript">
$(document).ready(function() {
$.getJSON("/json/", null, function(data) {
var hasFullAccess = data.isAdmin;
if (hasFullAccess == true) {
var errorMessage = "<%= errorMessage %>";
if (errorMessage == "") {
$('#pageEditing').attr('style', 'display: block;');
if ("<%= details.formDisplayed %>" == "True") {
$('#formPreview').removeClass('noshow');
}
if ("<%= details.formExtraFieldDisplayed %>" == "True") {
$('#extraField').removeClass('noshow');
}
} else {
$('#errorMessage').attr('style', 'display: block;');
}
} else {
window.location.replace("/login/");
}
});




 

I reach out http://www.example.com/json/ in line six above to get a Json object with this in it:


{"isAdmin":true}


...or this in it:


{"isAdmin":false}


...to see if a user really has permissions to be viewing the content at the View.


(see my controller and my schema if it helps)

Sunday, April 4, 2010

of both CMS routing and forcing browsers to close

This is a piece on ASP.NET MVC routing which strays into closing browsers with JavaScript. Enjoy!



At Headspring, I recently was asked to build a web site for a client of Launch Marketing which needed to have some minimal CMS functionality. Specifically, administrators needed to be able to add their own pages and populate the pages with content. There was not the need for a multi-tiered permissions system for various user types or the need to allow for CSS re-skinning. It seemed that I could write something simple myself in lieu of repurposing a different, feature-heavy CMS package and I did.



I wanted to give administrators the ability to create their own URLs for pages that were up to three "folders" deep and thus a RESTful approach to routing in which URLs looked like this:



http://www.example.com/page/33/



...was forgone in the name of allowing URLs to appear as such:



http://www.example.com/services/consulting/onsite/



My Global.asax.cs file thus is crafted like so:






using System.Web.Mvc;
using System.Web.Routing;

namespace AdministrableApplication
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801

public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");



routes.MapRoute(
"Default", // Route name
"{id1}/{id2}/{id3}/{id4}", // URL with parameters
new { controller = "Home", action = "Index", id1 = "", id2 = "", id3 = "", id4 = "" } // Parameter defaults
);

}

protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
}




 

Regardless of the URL path, one is routed to one action at one controller. Every piece of the route is a variable. The universal action then decides what to do based upon what it is given. There is thus some branching logic in HomeController.cs which I could stand to refine into a switch/case approach. It is:






using System.Web;
using System.Web.Mvc;
using AdministrableApplication.Core;
using AdministrableApplication.Core.interfaces;
using AdministrableApplication.Objects;
using StructureMap;

namespace AdministrableApplication.Controllers
{
[HandleError]
public class HomeController : Controller
{
public Settings settings;

[ValidateInput(false)]
public ActionResult Index(string id1, string id2, string id3, string id4, FormCollection values)
{
//home
ISettingsRepository settingsRepository = ObjectFactory.GetInstance();
settings = settingsRepository.retrieveSettings();
if (id1 == "") return View();
if (id1 == "favicon.ico") return View();
 

 

 

//administration verification
if (id1.ToLower() == "json")
{
return Json(new {isAdmin = isVerifiedAdministrator()});
}
 

 

 

//administration logout
if (id1.ToLower() == "logout")
{
logout();
return View("Logout");
}
 

 

 

//administration login
if (id1.ToLower() == "login")
{
if (!isVerifiedAdministrator())
{
if (values["accessAttempt"] != null)
{
if (!mayLogin(values["accessAttempt"] as string))
{
return View("Login");
} else {
Response.Redirect("/administration/");
}
} else {
return View("Login");
}
} else {
Response.Redirect("/administration/");
}
}
 

 

 

//administration
ViewData["SubmissionErrors"] = "";
IPageRepository pageRepository = ObjectFactory.GetInstance();
Page[] pages = pageRepository.retrieveAllPages();
ViewData["Pages"] = pages;
string formSubmission = values["formSubmission"] as string;
if (id1.ToLower() == "administration")
{
if (!isVerifiedAdministrator())
{
Response.Redirect("/login/");
}
if (id2 == "" id2 == "favicon.ico")
{
if (formSubmission == "administration")
{
ViewData["SubmissionErrors"] = FormProcessor.AttemptAndReportAnyErrors(values);
pages = pageRepository.retrieveAllPages();
ViewData["Pages"] = pages;
}
if (ViewData["SubmissionErrors"] as string == "The password has changed. ") return View("Logout");
return View("Administration");
}
 

 

 

//administration of pages
Page page = pageRepository.retrievePage(id2.ToLower(), id3.ToLower(), id4.ToLower());
if (page == null) return View("Administration");
ViewData["Page"] = page;
IDetailsRepository detailsRepository = ObjectFactory.GetInstance();
Details details = detailsRepository.retrieveDetails(page.pageId);
ViewData["Details"] = details;
if (formSubmission == "administration")
{
ViewData["SubmissionErrors"] = FormProcessor.AttemptAndReportAnyErrors(values);
details = detailsRepository.retrieveDetails(page.pageId);
ViewData["Details"] = details;
}
return View("Proxy");
 

 

 

//pages
} else {
Page page = pageRepository.retrievePage(id1.ToLower(), id2.ToLower(), id3.ToLower());
if (page == null) return View();
ViewData["Page"] = page;
IDetailsRepository detailsRepository = ObjectFactory.GetInstance();
Details details = detailsRepository.retrieveDetails(page.pageId);
ViewData["Details"] = details;
ViewData["SubmissionSuccess"] = false;
if (formSubmission == "communication")
{
string errorMessage = FormProcessor.AttemptAndReportAnyErrors(values);
ViewData["SubmissionErrors"] = errorMessage;
if (errorMessage == "") Response.Redirect(values["formRedirectsTo"] as string);
}
return View("Page");
}
}
 

 

 

public bool isVerifiedAdministrator()
{
bool isAuthenticated = false;
HttpCookie applicationPassword = Request.Cookies["passwordCookie"];
if (applicationPassword != null)
{
if (EncryptionEnactor.Garble(applicationPassword.Value) == settings.globalPassword)
{
isAuthenticated = true;
}
}
return isAuthenticated;
}
 

 

 

public void logout()
{
HttpCookie cookie = new HttpCookie("passwordCookie", "");
IClockRepository clockRepository = ObjectFactory.GetInstance();
cookie.Expires = clockRepository.retrieveClock().whenCookiesShouldExpire;
Response.Cookies.Add(cookie);
}
 

 

 

public bool mayLogin(string potentialPassword)
{
bool isAuthenticated = false;
if (EncryptionEnactor.Garble(potentialPassword) == settings.globalPassword)
{
isAuthenticated = true;
HttpCookie cookie = new HttpCookie("passwordCookie", potentialPassword);
IClockRepository clockRepository = ObjectFactory.GetInstance();
cookie.Expires = clockRepository.retrieveClock().whenCookiesShouldExpire;
Response.Cookies.Add(cookie);
}
return isAuthenticated;
}
}
}




 

The first piece of the route will take the user to a "home page" if left blank or will do something special if it is given as json, logout, login, or administration. Otherwise, we end up at a line of code reading:



return View("Page");



Cool. But, how do we know what content to populate the Page view with? Well, thirteen lines prior is:



Page page = pageRepository.retrievePage(id1.ToLower(), id2.ToLower(), id3.ToLower());



The pieces of the route are thus used to try to match against parameters for a Page object that has persistence to a database. (This posting has a little bit more on the database end of things: When to Alphabetize)



When one goes to http://www.example.com/services/consulting/onsite/ one will get content for /services/consulting/onsite/



What is more, when administering the site, an administrator may go to http://www.example.com/administration/services/consulting/onsite/ to edit the content that appears at http://www.example.com/services/consulting/onsite/



Yeah!



And now, to switch topics: As I've already given the code for HomeController.cs and as it obviously has a lot of cookie-based login/logout functionality within it I thought I might as well touch on a hurdle I faced herein.



I take administrators out of administration by taking them to http://www.example.com/logout/ which first alters their cookies and then returns a view that has this in it:






<@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
<title>Exit</title>
</asp:Content>

<asp:content id="Content2" runat="server" contentplaceholderid="MainContent">
<script type="text/javascript">
$(document).ready(function() {
window.open('', '_self', '');
window.close();
window.location.replace("/");
});
</script>
</asp:content>




 

Note the three lines of JavaScript. The first two will close browsers and the third, a redirect to the "home page," should, in theory, never be run given the line preceding it.



Why do I need to close the browsers? Why can't I leave the web site up after altering the cookie that keeps a user logged in? In Internet Explorer, at least within Cassini testing, I found that I could alter the cookie that is used to validate an administrator to contain no copy for password matching and then nonetheless access things that only administrators should be able to see by way of the URL line due to page caching. I couldn't make changes as an administrator as the emptiness in the cookie would be noticed upon a post, but I could still view things that I shouldn't have been allowed to view.



Forcing closed Internet Explorer closed this loophole.



What about the line of code that takes a user to the "home page" and should in theory never be run? This is for Firefox which both did not seem to have the caching issue that Internet Explorer did and did not want to close. I've read numerous blog postings on how to force shut Firefox, but nothing I read wanted to work for me so I had to create an exception to the rule for Firefox.



Note that I am trying to close browsers without a dialog box popping up saying: "The Web page you are viewing is trying to close the window. Do you want to close this window?" I can't seem to escape the dialog box in Internet Explorer 6, but then I also haven't tried too hard to support IE6. (It's dead.)

Friday, April 2, 2010

When to Alphabetize

I am participating in a book club which is reading "Clean Code" by Robert C. Martin. Chapter 2 deals with good names and there is a line on page 20 which begins: It is very helpful if names for very similar things sort together alphabetically...



I am wrapping up a project in which I tried to keep things tidy with much alphabetization and on the other side of the experience I concluded that I really should not have done any alphabetization attempts because I found myself compromising good names in favor of marginally good names which happened to alphabetize well. Then, I read the sentence fragment mentioned above and felt conflicted.



The project is a CMS-driven web site. The simple CMS is of my own concoction. It allows administrators to create as many pages as they please for the web site and to populate the pages with content.



It has all of three persistence objects. Here is what the whole of the SQL for prepping the application looks like:





BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Details
(
detailsId uniqueidentifier NOT NULL,
formDisplayed bit NOT NULL,
formExtraFieldDisplayed bit NOT NULL,
formExtraFieldName varchar(255) NULL,
formName varchar(255) NULL,
formRedirectsTo varchar(255) NULL,
formSendsUserAnEmail bit NOT NULL,
h1 varchar(255) NULL,
h2 varchar(255) NULL,
htmlMain text NULL,
htmlSidebar text NULL,
htmlSupplement text NULL,
metatagDescription varchar(255) NULL,
metatagKeywords varchar(255) NULL,
metatagTitle varchar(255) NULL,
pageId uniqueidentifier NOT NULL
) ON [PRIMARY]
TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE dbo.Details ADD CONSTRAINT
PK_Details PRIMARY KEY CLUSTERED
(
detailsId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
COMMIT
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Page
(
folderFirst varchar(255) NOT NULL,
folderSecond varchar(255) NOT NULL,
folderThird varchar(255) NOT NULL,
linkName varchar(255) NOT NULL,
pageId uniqueidentifier NOT NULL,
suggestedPriority int NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Page ADD CONSTRAINT
PK_Page PRIMARY KEY CLUSTERED
(
pageId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
COMMIT
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
COMMIT
select Has_Perms_By_Name(N'dbo.Page', 'Object', 'ALTER')
as ALT_Per, Has_Perms_By_Name(N'dbo.Page', 'Object', 'VIEW DEFINITION')
as View_def_Per, Has_Perms_By_Name(N'dbo.Page', 'Object', 'CONTROL') as Contr_Per BEGIN TRANSACTION
GO
ALTER TABLE dbo.Details ADD CONSTRAINT
FK_Details_Page FOREIGN KEY
(
PageId
) REFERENCES dbo.Page
(
PageId
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
COMMIT
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Settings
(
emailConfirmationBody text NOT NULL,
emailConfirmationSubjectLine varchar(255) NOT NULL,
emailDetailsSubjectLine varchar(255) NOT NULL,
emailFrom varchar(255) NOT NULL,
emailTo varchar(255) NOT NULL,
globalPassword varchar(255) NOT NULL,
homeCalloutBodyI text NULL,
homeCalloutBodyII text NULL,
homeCalloutBodyIII text NULL,
homeCalloutBodyIV text NULL,
homeCalloutBodyV text NULL,
homeCalloutHeadI varchar(255) NULL,
homeCalloutHeadII varchar(255) NULL,
homeCalloutHeadIII varchar(255) NULL,
homeCalloutHeadIV varchar(255) NULL,
homeCalloutHeadV varchar(255) NULL,
homeCalloutLinkI varchar(255) NULL,
homeCalloutLinkII varchar(255) NULL,
homeCalloutLinkIII varchar(255) NULL,
homeCalloutLinkIV varchar(255) NULL,
homeCalloutLinkV varchar(255) NULL,
homeDescription varchar(255) NOT NULL,
homeKeywords varchar(255) NOT NULL,
homeTitle varchar(255) NOT NULL,
homeUrl varchar(255) NOT NULL,
locationOfSitemap varchar(255) NOT NULL,
settingsId uniqueidentifier NOT NULL
) ON [PRIMARY]
TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE dbo.Settings ADD CONSTRAINT
PK_Settings PRIMARY KEY CLUSTERED
(
settingsId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
COMMIT
INSERT INTO Settings (emailConfirmationBody,emailConfirmationSubjectLine,emailDetailsSubjectLine,emailFrom,emailTo,globalPassword,homeCalloutBodyI,homeCalloutBodyII,homeCalloutBodyIII,homeCalloutBodyIV,homeCalloutBodyV,homeCalloutHeadI,homeCalloutHeadII,homeCalloutHeadIII,homeCalloutHeadIV,homeCalloutHeadV,homeCalloutLinkI,homeCalloutLinkII,homeCalloutLinkIII,homeCalloutLinkIV,homeCalloutLinkV,homeDescription,homeKeywords,homeTitle,homeUrl,locationOfSitemap,settingsId) VALUES ('Thank you for your information. Someone will contact you shortly.','Thank You For Your Information','Form Completion at Web Site','info@headspring.com','tom@headspring.com','e352fab0f0796d8b74cd862d4efbe145','homeCalloutBodyI','homeCalloutBodyII','homeCalloutBodyIII','homeCalloutBodyIV','homeCalloutBodyV','homeCalloutHeadI','homeCalloutHeadII','homeCalloutHeadIII','homeCalloutHeadIV','homeCalloutHeadV','/i/','/ii/','/iii/','/iv/','/v/','This is our website.','home','Home','http://www.example.com','sitemap.xml','451d856c-9e40-47cd-a659-9d3d0137fe70')







 
My objects: Settings is a grab bag of things which did not fit into the other two objects. Page suggests an individual web page within the application. I query all of the Page records to create a menu system. Each Page has a Details child which is the heavy part of the Page that is only needed on a case by case basis and not needed or wanted when querying Page records in mass to build a menu.



Note in Details the following:




htmlMain text NULL,
htmlSidebar text NULL,
htmlSupplement text NULL,



 
Following today's book club meeting I have concluded...



What I did right: grouping htmlMain, htmlSidebar, and htmlSupplement together instead of naming these bodyCopy, sidebar, and footer



What I did wrong: naming htmlSupplement as htmlSupplement instead of htmlFooter (htmlFooter is a better, more descriptive name for htmlSupplement. I decided to call the parameter htmlSupplement out of a desire to have the html parameters listed in order of perceived importance with htmlMain being the most important content on a Page, htmlSidebar being next in star power, and htmlSupplement being a distant third. Herein I compromised a good designation in the name of alphabetization.)

Tom Jaeschke

I'm Tom Jaeschke and you may contact me at tomjaeschke@tomjaeschke.com or (512) 419-8788.

Monday, March 1, 2010

Agile Training At Month's End

For truly top-notch Agile .NET training, please investigate Headspring's Agile Boot Camp™ for .NET Part I. See http://www.headspringsystems.com/services/agile-training/agile-boot-camp-part-I/ for details. I have taken the class myself and I highly recommend it.

Monday, January 18, 2010

Software Development Outsourcing

I'm glad to be a part of Headspring, an impressive, Austin-based company offering software development outsourcing and agile training.

Monday, December 28, 2009

Encore Performance

Headspring is offering an "encore performance" of our workshop on Inversion of Control. On January 19th, Matt Hinze will present anew his "Decoupling Layers using Inversion of Control" training at Austin's Microsoft Technology Center. Follow the link to register: http://www.headspringsystems.com/community/workshop/

Monday, November 30, 2009

Matt Hinze of Headspring to host a workshop on "Decoupling Layers using Inversion of Control"

A deep dive into IoC and dependency injection will be offered on December 15th, 2009 in Austin, Texas at the Microsoft Technology Center. Register here: http://www.headspringsystems.com/community/workshop/



The following will be detailed:

  • Inversion of Control
    • A concept
    • Relinquishing the responsibility of managing dependencies
  • Dependency Injection
    • A pattern
    • Constructor injection
    • Property injection
  • Some Benefits Enumerated
    • Testing: Makes it easy to isolate classes under test and to test interactions between classes by allowing the tester to provide a special implementation
    • Reuse: Enables code reuse and prevents duplication by enabling the sharing of functional parts
    • Simplicity: Since each component is only concerned with its responsibility, it can focus on that simple job
    • Construction velocity: Speed is quickened because of the above things. That's what we're here for, right?


The training starts at 2:00 P.M.

Wednesday, September 16, 2009

Correction

XAMLExport is not the proper name for Mike Swanson's tool. See: http://www.mikeswanson.com/xamlexport/

Thursday, September 10, 2009

WYSIWYG crutch

While I had two classes in college on HTML, I didn't really start producing sophisticated HTML until I was exposed to Macromedia Dreamweaver. With Dreamweaver's easy, GUI-based means of laying out tables, my game got better, and, years later, when it came to pass that tableless design was to replace table-based design as the in vogue, industry standard for laying out web pages, I had progressively learned enough HTML to be able to step away from the WYSIWYG crutch of Dreamweaver and venture into the waters of CSS without a comparable crutch. That said, Dreamweaver got me to where I am presently. I needed the crutch initially.



Presently, I'm learning a little XAML, and I am again finding the value in a WYSIWYG crutch. I found a means to design in Adobe Illustrator (which I have used for over a decade and am very comfortable with) and then export from Illustrator to XAML. I use and recommend Mike Swanson's XAMLExport which may be had at here: http://www.mikeswanson.com/XAMLExport



Here is an example of producing XAML with XAMLExport in sixteen steps.




One. To begin with I create a new Illustrator file and first consider what size I want the whole of the Silverlight movie I am to create to be. I decide I want my movie to by 300 pixels in width and 200 pixels in height. To prepare for this in Illustrator I'm going to make a rectangle which will serve as a background for the entire movie. To be sure the rectangle exports at the appropriate pixel dimensions, I: First, select the rectangle tool from the toolbar, and second click to create a rectangle instead of dragging out a rectangle freehand. In clicking, a dialog box appears requesting dimensions for the rectangle. I give 300 points for the width and 200 points for the height. When my Illustrator file is exported to XAML, point values will be cast to pixel values.




Two. My rectangle is created.




Three. Next I create a new layer in Illustrator and lay out some vector shapes on it. The image of the sun shown here would be a pain to write in XAML, but hours of work have become seconds of work with the help of Illustrator. (I'm building in Illustrator instead of Expression Blend as I'm already strong in Illustrator.)




Four. I want to add an image to my Silverlight movie. Unfortunately, there is no way to carry an image over to XAML with XAMLExport so I will instead make a rectangle to serve as a placeholder for the image. Again, dimensions are important, so I make a new layer, select the rectangle tool, and then click to type numeric values for rectangle dimensions.




Five. A rectangle is made.




Six. I decide I want my background to have a gradient fill and I give it one. The gradient effect will carry over to the XAML. At this point, I am done with my Illustrator file. I select Export... under the File menu and then pick XAML for Silverlight (*.XAML) as the "Save as type" value at the dialog box which appears. I save out a file with a .xaml extension.




Seven. I close Illustrator and open Visual Studio. I create a Silverlight movie. I open the default MainPage.xaml file that is made at the movie's generation.




Eight. I copy the contents of the .xaml file I made in Illustrator into the Grid control at MainPage.xaml. Ideally, I could make the d:DesignWidth and d:DesignHeight values in the UserControl tag match the 300 x 200 dimensions of my content at this point too.




Nine. I preview my movie. It works! Everything is lovely... well, save for the missing image.




Ten. Within the XAML, I look for the box to replace with an image. Every separate layer I made in Illustrator became a different Canvas in the XAML generated (with notes to clarify which layer was which). The individual shapes I made in Illustrator were cast to Paths. I find the Canvas that corresponds to "Layer 3" where I built my placeholder rectangle.




Eleven. I make a new Canvas below the "Layer 3" Canvas and I put an Image in the new Canvas.




Twelve. I test anew. Looks like my Image hugs the upper left corner of the Canvas it is in.




Thirteen. I add a Margin parameter to my new Canvas.




Fourteen. How did I know what values to use for the Margin? A closer look at the Data parameter for my placeholder box reveals four vertices. I find the number pair for the upper left corner and use the pair for the first two values in my Margin parameter. (The second two values can simply be zeros as they are extraneous given that the image will hug the upper left corner of the Canvas.)




Fifteen. I remove the "Layer 3" Canvas as I no longer need my placeholder. (I only needed the placeholder box to set the Margin parameter at the Canvas holding my Image.)




Sixteen. I preview my movie once more. All is well.

Thursday, August 20, 2009

Blossoming!

Headspring Systems is growing nicely. The software firm is now a Microsoft Gold Certified Partner (see: http://www.headspringsystems.com/about/pressreleases/Default.aspx?id=8.20.09) and has won a slot on Inc. Magazine's Inc. 500 (see: http://www.headspringsystems.com/about/pressreleases/Default.aspx?id=8.12.09).
Tom Jaeschke

I'm Tom Jaeschke and you may contact me at tomjaeschke@tomjaeschke.com or (512) 419-8788.

Friday, August 7, 2009

Enter for a chance to win an iPod Touch

Headspring Systems is having a quarterly drawing and giving away an iPod to the drawing's winner! Get your name in the fish bowl before the end of September at http://www.headspringsystems.com/community/promotional/

Tuesday, June 23, 2009

Custom Application Development

In the time I've been at Headspring Systems I've watched the organization blossom into the premiere shop for custom application development statewide in Texas. Headspring is the fourth small business which I have been a part of, but the first to move beyond a dream of what might be to become an enviable enterprise, the best player in its space.



Perhaps the fourth time is the charm. Lauren Bacall was Humphrey Bogart's fourth wife after all.



I'll stop digressing :) - Kudos to everyone who made Headspring what it is today!

Thursday, June 11, 2009

Model-View-Controller 6/16/2009

Headspring Systems is hosting a free, half-day deep dive into ASP.NET MVC in just five days time at the Microsoft Technology Center at 9606 North MoPac Expressway in Austin, Texas 78759 (i.e. the North Austin locale where the Austin .NET Users Group meets). Register for our event here.



Our adventures in custom application development have left us frustrated with the web forms means of crafting user interfaces, an archaic carryover from Visual Basic's environment, and especially at the fact that gobs of code that cannot be secured behind unit tests snowball into unwieldy messes in the code behinds. Fortunately, Microsoft's model-view-controller pattern now offers a superior alternative.



Obviously, there is going to be a bit of a learning curve involved in understanding a new way to approach web design for .NET, but perhaps we can make it less painful for you.



At 1 PM on Tuesday, June 16, 2009, Headspring's Jeffrey Palermo and Eric Hexter will offer their expertise on MVC and take questions. Look for their presentations to be comparable to those they gave at this year's Austin Code Camp and the presentations of our prior half-day free training on 2/19/2009. Again, register here to attend.

Monday, April 13, 2009

No Ubiquitous Language at The Cold Side

Most recently, I've been asked to work to boost SEO traffic to Headspring's own web site, and, in learning the ropes of site optimization (thank you Eric Hexter), I have gotten to write some copy for headspringsystems.com. Trying my hand at copy writing has been enjoyable and I'm pleased with a landing page at http://www.headspringsystems.com/services/agile-training/domain-driven-design-training.aspx that I wrote some verbiage for. I used some of my own life experience for this. There was a time in 2002 when I spent five months midyear as the "Cold Side Person" at one of Austin's more famous cheesesteak chains. I found the means of communicating orders by way of in-house notation... "challenging"

Monday, March 30, 2009

Welcome Eric Anderson

Headspring Systems is pleased to welcome Eric Anderson to our team. He is a valued addition to the core of developers spearheading one of our most prominent projects. To learn more about Eric, visit his blog at http://testinfected.blogspot.com/

Thursday, February 26, 2009

Idiot-Proof Inversion of Control Example

What follows is what I hope to be a dumbed-down example of how to enact IoC (Inversion of Control) that anyone may easily follow. My example uses StructureMap. There are a great many tools that one may utilize for IoC. I'm using StructureMap as Headspring has standardized around StructureMap.



Alright, we will use an example Visual Studio 2008 C# web application with one web form (Default.aspx) and three classes. The function of the application is to firstly display a TextBox, a Button, and a Label, and secondly to put the number of characters in the TextBox into the Label whenever the Button is pressed. Thus, if I enter "123 Happy Street" into the TextBox and press the Button, I should see "11" appear in the Label.





So how does the web form translate a random string into a numeric count of particular characters? The code behind (Default.aspx.cs) looks like this:



using System;

namespace inversiontest
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

protected void Button1_Click(object sender, EventArgs e)
{
string myCopy = TextBox1.Text;
NonLetterPurge myCutter = new NonLetterPurge();
CharacterCount myCounter = new CharacterCount();
StringHandler myHandler = new StringHandler();
int letterCount = myHandler.runProcess(myCutter, myCounter, myCopy);
Label1.Text = letterCount.ToString();
}
}
}



The three classes being used look like this:



NonLetterPurge.cs:
namespace inversiontest
{
public class NonLetterPurge
{
public string dropUndesirableCharacters(string blobOfCopy)
{
string cleanBlob = "";
foreach(char blobCharacter in blobOfCopy)
{
if (char.IsLetter(blobCharacter))
{
cleanBlob = cleanBlob + blobCharacter;
}
}
return cleanBlob;
}
}
}




CharacterCount.cs:
namespace inversiontest
{
public class CharacterCount
{
public int convertStringToNumber(string blobOfCopy)
{
return blobOfCopy.Length;
}
}
}




StringHandler.cs:
namespace inversiontest
{
public class StringHandler
{
public int runProcess(NonLetterPurge cutter, CharacterCount counter, string copy)
{
string revisedCopy = cutter.dropUndesirableCharacters(copy);
return counter.convertStringToNumber(revisedCopy);
}
}
}




With the four pieces of C# above (one code behind and three classes), my application performs as it should. However, all of my classes are tightly coupled and I have to new up three classes just to use one.



The following details the introduction of Inversion of Control methodologies to the application described above.



First, I download the latest version of StructureMap (version 2.5.3) at http://sourceforge.net/project/showfiles.php?group_id=104740 and once I had the StructureMap.dll I placed it in my bin directory and then created a reference to it in my project within Visual Studio.



Secondly, I significantly rewrote my application. For each of the three classes I created an interface, and the classes themselves were altered as well (as was the way they were enacted from the web form's code behind). In the end there were seven files containing C# which were: CharacterCount.cs, Default.aspx.cs, ICharacterCount.cs (an interface for CharacterCount), INonLetterPurge.cs (an interface for NonLetterPurge), IStringHandler.cs (an interface for StringHandler), NonLetterPurge.cs, and StringHandler.cs. These items contained the following code:



CharacterCount.cs:
namespace inversiontest
{
public class CharacterCount : ICharacterCount
{
public int convertStringToNumber(string blobOfCopy)
{
return blobOfCopy.Length;
}
}
}




Default.aspx.cs:
using System;
using StructureMap;

namespace inversiontest
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ObjectFactory.Initialize(x =>
{
x.ForRequestedType<ICharacterCount>().TheDefaultIsConcreteType<CharacterCount>();
x.ForRequestedType<IStringHandler>().TheDefaultIsConcreteType<StringHandler>();
x.ForRequestedType<INonLetterPurge>().TheDefaultIsConcreteType<NonLetterPurge>();
});
}

protected void Button1_Click(object sender, EventArgs e)
{
string myCopy = TextBox1.Text;
var myHandler = ObjectFactory.GetInstance();
int myNumber = myHandler.runProcess(myCopy);
Label1.Text = myNumber.ToString();
}
}
}




ICharacterCount.cs:
namespace inversiontest
{
public interface ICharacterCount
{
int convertStringToNumber(string blobOfCopy);
}
}




INonLetterPurge.cs:
namespace inversiontest
{
public interface INonLetterPurge
{
string dropUndesirableCharacters(string blobOfCopy);
}
}




IStringHandler.cs:
namespace inversiontest
{
public interface IStringHandler
{
int runProcess(string copy);
}
}




NonLetterPurge.cs:
namespace inversiontest
{
public class NonLetterPurge : INonLetterPurge
{
public string dropUndesirableCharacters(string blobOfCopy)
{
string cleanBlob = "";
foreach(char blobCharacter in blobOfCopy)
{
if (char.IsLetter(blobCharacter))
{
cleanBlob = cleanBlob + blobCharacter;
}
}
return cleanBlob;
}
}
}




StringHandler.cs
namespace inversiontest
{
public class StringHandler : IStringHandler
{
private readonly ICharacterCount _characterCount;
private readonly INonLetterPurge _nonLetterPurge;

public StringHandler(ICharacterCount characterCount, INonLetterPurge nonLetterPurge)
{
_characterCount = characterCount;
_nonLetterPurge = nonLetterPurge;
}

public int runProcess(string copy)
{
string revisedCopy = _nonLetterPurge.dropUndesirableCharacters(copy);
return _characterCount.convertStringToNumber(revisedCopy);
}
}
}




The application now runs and works as it did before, but much has changed behind the scenes. Notice that the code that runs when the button is clicked which once read...



string myCopy = TextBox1.Text;
NonLetterPurge myCutter = new NonLetterPurge();
CharacterCount myCounter = new CharacterCount();
StringHandler myHandler = new StringHandler();
int letterCount = myHandler.runProcess(myCutter, myCounter, myCopy);
Label1.Text = letterCount.ToString();


...has been simplified to:


      
string myCopy = TextBox1.Text;
var myHandler = ObjectFactory.GetInstance();
int myNumber = myHandler.runProcess(myCopy);
Label1.Text = myNumber.ToString();


The copy in the Page Load event wires up the interfaces in preparation of their use. It looks like so:



ObjectFactory.Initialize(x =>
{
x.ForRequestedType<ICharacterCount>().TheDefaultIsConcreteType<CharacterCount>();
x.ForRequestedType<IStringHandler>().TheDefaultIsConcreteType<StringHandler>();
x.ForRequestedType<INonLetterPurge>().TheDefaultIsConcreteType<NonLetterPurge>();
});


Obviously, it would be ideal to not have to repeat this manner of wiring across the Page Load events of many web forms in a more complicated application. Couldn't these sorts of associations just be enacted once globally at the launch of an application? Yes, if I remove the lines of code above, I can enact the same effect by creating an XML configuration file called StructureMap.config. In this file I will have the following:


<StructureMap>

<PluginFamily Type="inversiontest.ICharacterCount" Assembly="inversiontest" DefaultKey="Default">
<Plugin Assembly="inversiontest" Type="inversiontest.CharacterCount" ConcreteKey="Default" />
</PluginFamily>

<PluginFamily Type="inversiontest.INonLetterPurge" Assembly="inversiontest" DefaultKey="Default">
<Plugin Assembly="inversiontest" Type="inversiontest.NonLetterPurge" ConcreteKey="Default" />
</PluginFamily>

<PluginFamily Type="inversiontest.IStringHandler" Assembly="inversiontest" DefaultKey="Default">
<Plugin Assembly="inversiontest" Type="inversiontest.StringHandler" ConcreteKey="Default" />
</PluginFamily>

</StructureMap>

Wednesday, February 4, 2009

Headspring Systems has launched its web site anew.

Headspring Systems has refined its web presence to reflect the company's standing as a premiere Agile software development/training/coaching firm. Headspring invites you to tour the enterprise at http://www.headspringsystems.com/ and hopes to illuminate a better way to undertake software engagements.
Tom Jaeschke

I'm Tom Jaeschke and you may contact me at tomjaeschke@tomjaeschke.com or (512) 419-8788.

Sunday, November 16, 2008

How to Create Tarantino Objects


Strictly speaking, the Tarantino Project is a grab bag of useful .dll files for technologies such as NHibernate and StructureMap which are conveniently collected in one place (http://code.google.com/p/tarantino/) and which may be used in a .NET application.


That said, it is fairly safe to predict that a Tarantino application will be established in an onion architecture in which a given solution will have separate projects for Core, Core.IntegrationTests, Core.UnitTests, Database, Infrastructure, Infrastructure.IntegrationTests, Infrastructure.UnitTests, UI, and, perhaps, UI.Controls. It took me a little while to truly feel comfortable in an onion environment and to understand how all of the Tarantino tools come into play. Now that I "get it" however, I am sold.


For others who find their eyes tearing at cutting their first Tarantino onion I offer this suggestion: Set up a dummy Tarantino application and create an object. You will feel much better once you have done so.


Recently, I worked on a web site which sends email notifications whenever a visitor completes a web form. I thought it might be wise to record the data involved for each sending to a database. What follows, are the steps I undertook to do so. I feel this scenario illustrates fairly well how to create a Tarantino object.





  1. The first step I undertook was to decide on a name for my object. I chose "OutgoingEmailAttempt." In an Agile setting the name should match the term the customers use for the correlating concept so that there is no translation needed between terms discussed aloud and terms nested in code (where the name of the object will appear in all of the file and class names associated with the object, to ensure logical designations which are not painful for anyone to understand).


    With a name in mind, I then created a database table that was also called "OutgoingEmailAttempt" in Microsoft SQL Server Management Studio Express. I was careful to use the "Generate Change Script" feature to save the SQL from the table creation, and within my Tarantino application, I put the .sql file in the Database project within the "Update" folder.


    Why is it important to retain the SQL scripts? If one has a record of the SQL scripts used to create a schema, one may thus recreate the schema elsewhere. Tarantino applications typically employ multiple instances of their database schemas. There will be a separate database running in each individual developer's work environment, a separate database in a staging environment, a separate database in the production environment, etc. It is common to have a database table devoted to a list of scripts which have already been run and a build process in which scripts which are newly introduced get run before being logged to the table themselves. By way of this configuration, when the SQL for creating a database table for an object is checked into the code repository, the table will soon exist at each individual developer's work environment, a separate database in a staging environment, a separate database in the production environment, etc.


    The columns in my "OutgoingEmailAttempt" table each corresponded to a property for the "OutgoingEmailAttempt" object. I created varchar data type columns named NameOfParty (who completes a web form), EmailAddressOfParty, PhoneNumberOfParty, CommentsOfParty, IpAddressOfParty, InHouseNotation (for denoting why the email alert was sent), and InHouseEmailAddress (which clarifies which address the email alert was sent to). In the schema design: The columns should have the same names the object properties will ultimately have and the names should make sense. I also made a datetime data type column called ContactTime, and, most importantly, I made a uniqueidentifier data type column dubbed OutgoingEmailAttemptId. In every Tarantino application I've yet seen, the name of the uniqueidentifier column must be: the name of the object + "Id"








  2. The second file made was a class for the object itself titled "OutgoingEmailAttempt.cs" in the Core project under the "Model" folder. An object class such as this one should be made largely void of code so that a test for the class may be written before the real guts of the class are fleshed out. Try to write a class just complete enough to be referenced by a test and still compile without throwing an error.






  3. The third file is "OutgoingEmailAttemptTester.cs" in the Core.UnitTests project under the "Model" folder.


    The tester class may entail numerous unit tests. For any one test, the first driver in ping pong pairing must refine the object class in question enough to make a test against it fail without throwing an error and must also refine the test itself enough to end with a failing test which is strong, while the second driver is charged with making the test past. The pairing partners should work back and forth in this manner to write as many tests as are needed.


    Things which one should obviously add to an object class and test against are property accessors. In my class I wrote property accessors for NameOfParty, EmailAddressOfParty, PhoneNumberOfParty, CommentsOfParty, IpAddressOfParty, InHouseNotation, InHouseEmailAddress, and ContactTime. I used names identical to names given for corresponding columns at the database schema. I made InHouseEmailAddress a string variable, ContactTime a DateTime variable, and so on. I did not write a property accessor for OutgoingEmailAttemptId because my OutgoingEmailAttemptTester.cs class inherits from another class, PersistentObject.cs, which manages the "Id" accessor and a few other things common to all objects which inherit from it.






  4. The next step was to create a NHibernate mapping file to allow the Tarantino application to interact with the database table which was just created. I made OutgoingEmailAttempt.hbm.xml in the Infrastructure project within the "Mappings" folder (which, in turn, sits in the "DataAccess" folder). When undertaking such a process, it is best not to focus on getting the mappings accurate initially, but rather to just set up a placeholder file. The accuracy will be hammered out in the next step.


    It is crucial to set the "Build Action" property of an .hbm.xml file to "Embedded Resource" instead of the default value of "Compile." This may be done by right-clicking on the file in the Solution Explorer within Visual Studio and selecting "Properties" from the menu which appears.






  5. The fifth file I created was an integration test class called "OutgoingEmailAttemptMappingsTester.cs" in the Infrastructure.IntegrationTests project within the "Mappings" folder (which, in turn, sits in the "DataAccess" folder). Upon the successful completion of a run of a mappings test to add an entry, one should be able to see a row created at the corresponding database table. Obviously, if the mappings are inaccurate in the .hbm.xml file, the mappings test will fail when run and the .hbm.xml file will have to be refined in order to make the test pass.


    When undertaking ping pong pairing, one developer, the driver, will write, first, a hollow .hbm.xml file and, secondly, a mappings test which cannot pass (due to the emptiness of the file it tests), while the driver's partner, the navigator, will observe and speak aloud thoughts and suggestions. Once the driver and the navigator agree that the test is solid, then the two developers switch roles. The navigator becomes the driver and refines the .hbm.xml so that it will pass the mappings test, while the previous driver becomes the new navigator and offers verbal direction and a critical eye.


    I'm not going to dive into specific syntax for C# (the language used in Headspring Systems' Tarantino applications) or NHibernate in this article, but I also don't really have to. When creating the files for a new Tarantino object one can easily gauge a sense of what needs to be written by looking at the corresponding files for other objects in either the same solution or another Tarantino application. When in doubt, look to the existing work of others for guidance.






  6. In the Infrastructure project, I created "OutgoingEmailAttemptRepository.cs" within the "Repositories" folder (which, in turn, sits in the "DataAccess" folder). Repository classes, facilitate the casting of database data to objects and likewise the pushing of object data to databases.






  7. In the Infrastructure.UnitTests project, I created "OutgoingEmailAttemptRepositoryTester.cs" within the "Repositories" folder (which, in turn, sits in the "DataAccess" folder). As you might imagine, this class tests my repository class. At this point in the article, you may also be able to imagine how two programmers may write both a repository class and the unit tests to test it by way of ping pong programming.






  8. The last files I made were some web pages in the UI to use my new OutgoingEmailAttempt object and one interface.


    My Tarantino application is set up so that the UI project references both the Core and Infrastructure projects and the Infrastructure project references the Core project. So then, how may "OutgoingEmailAttempt.cs" utilize "OutgoingEmailAttemptRepository.cs" for database integration? The answer is by way of StructureMap and the creation of an interface within the Core project. I made "IOutgoingEmailAttemptRepository.cs" within the "Repositories" folder which sits within the "Model" folder within the Core project.


    "OutgoingEmailAttemptRepository.cs" was made to inherit from "IOutgoingEmailAttemptRepository.cs" and both files reference StructureMap in their using statements.





Conclusion: The steps above detail the sum of what needed to be done in establishing an object in a Tarantino application. Outside of the UI files I made to actually use the object I created, I needed to create the eight files I've detailed. The steps are well worth taking. I feel this is a much better approach to writing a .NET application than making a few god classes in the App_Code folder. ;)

Monday, October 27, 2008

At Headspring Systems, I work with some amazing individuals!

Hello, my name is Tom Jaeschke and I am paradoxically both the senior employee of and the most junior developer at Headspring Systems. As almost everyone at the organization has a blog, I thought that it would only be appropriate if I tried to start a blog myself, so here it is. I cannot offer any unique tips and tricks on .NET practices that my colleagues haven't already done a better job of outlining in their blogs, so I’m not yet sure what form my own blog will take.



I would like to say that I am honored deeply to work at the same company as Nicholas Becker, Jimmy Bogard, Blake Caraway, Eric Hexter, Matt Hinze, Kevin Hurwitz, Ken Jackson, Jeffrey Palermo, and Jeff Wilson. I am amazed at my fortune, as I could never have imagined a finer team to be associated with.



If you have not heard these names yet, please allow me to make the introduction. The following pictures were taken with my iPhone at a developers meeting over lunch on October 10, 2008. As you can tell, the pictures were not staged or planned. :)





In closing this entry, I would like to say, that, more than any other reason I could point to, Headspring's owner, Dustin Wells, is the number one reason why I, or anyone else, should be happy to work at Headspring. I've had some really bad bosses, but he is not one of them. He is the best boss I will ever have. Kudos to you Dustin!



I can recall the humble beginnings of Headspring before Nicholas Becker, Jimmy Bogard, Blake Caraway, Eric Hexter, Matt Hinze, Kevin Hurwitz, Ken Jackson, Jeffrey Palermo, and Jeff Wilson and I could not be happier for Dustin for the excellent team he has grown in the three years I have been working for him.

To learn more about me, please visit my web site at tomjaeschke.com

Friday, July 18, 2008

Welcome To Headspring Systems: Blake Caraway and Duane DeRouen

I'd like to welcome two new hires to Headspring Systems, a Mr. Blake Caraway (now a Senior Consultant) and a Mr. Duane DeRouen (now our Vice-President of Client Services). Both gentlemen were working in vital roles as contractors within Headspring's team at Dell, but now have been hired on direct with Headspring as of July 2008. Congratulations to both.