Friday, December 3, 2010


I've been intending to do this one for a while now. Build automation. Personally I try to automate wherever I can. I'm not at the level of CruiseControl(.Net) because frankly the shops where I've worked to date have only a fledgling Agile practice.

A little while back I stepped into a project where there was no automation. Releases were fairly haphazard and required a lot of tweaking to install into each deployment environment. A bigger issue was the amount of effort required to regenerate the .netTiers data/service layers when a column was added to a table. To be effective this stuff needs to be a push button exercise. In this project there was so much deferred maintenance on the templates it could take a day of manual tweaks to apply the necessary changes to the generated code before it would successfully compile - this was occurring instead of applying those changes to the actual templates that generated the code. And you should be familiar with how accurate and error-free tweaking generated code manually is right? I'm rambling, the moral of the story is that automation salves a myriad of ills and improves not only your ability to do your job but delivering installers that reliably install your application also vastly improves your relationship with the people responsible for looking after the servers your application runs on.

So this first post is to promote and for future reference record the sheer usefulness of PowerShell for getting your wares to where they need to be. Recently I've been refactoring a special application that keeps its data in XML files instead of, I don't know, an actual database? And for my first trick I need to separate one "manual" into two. Fortunately PowerShell knows XML in a very cool way. Time to look at a little code.

   1: # update manuals.xml
   2: function update_manual_xml {
   3:     $docFile =  Join-Path $APP_DATA "manuals.xml"
   4:     Write-Host "Updating $docFile"
   6:     if (test-path $docFile)
   7:     {
   8:         $doc = [xml](Get-Content $docFile)
  10:         $eisaXml = $doc.manuals.manual | Where-Object { $_.code -eq "eisa" }
  11:         $eiAdminEmail = $eisaXml.sections.section[0].GetAttribute("AdminEmail")
  12:         $snAdminEmail = $eisaXml.sections.section[1].GetAttribute("AdminEmail")
  14:         [xml]$ei = '<manual AdminEmail="' + $eiAdminEmail + '" HideEffectiveDate="true">
  15:                         <code>ei</code>
  16:                         <title>Manual Title</title>
  17:                         <homePageText><![CDATA[Manual description]]></homePageText>
  18:                         <sections>
  19:                             <section>
  20:                                 <number>1</number>
  21:                                 <title>Section Title</title>
  22:                             </section>
  23:                         </sections>
  24:                     </manual>'
  25:         [xml]$sn = '<manual AdminEmail="' + $snAdminEmail + '" HideEffectiveDate="true">
  26:                         <code>sn</code>
  27:                         <title>Another Manual Title</title>
  28:                         <homePageText><![CDATA[And description]]></homePageText>
  29:                         <sections>
  30:                             <section>
  31:                                 <number>1</number>
  32:                                 <title>Section Title</title>
  33:                             </section>
  34:                         </sections>
  35:                     </manual>'
  36:         $eiMan = $doc.ImportNode($ei.manual, $true)
  37:         $doc.manuals.AppendChild($eiMan)
  38:         $snMan = $doc.ImportNode($sn.manual, $true)
  39:         $doc.manuals.AppendChild($snMan)
  40:         $doc.manuals.RemoveChild($eisaXml)
  41:         $doc.Save($docFile)
  42:     } else {
  43:         Write-Host "File not found: $docFile"
  44:     }
  45: }

How great is that? Working with XML in PowerShell is easy. I'm not going to claim I'm writing great code here. More like just enough to get a one off upgrade sorted. The script should be largely self-explanatory but in case you’re after a little more detail I’ll go through the important bits. In line 3 I’m putting together the path to the manuals XML file using the Join-Path function. In this case the path is different for every location – in Test it’s on the C:\ drive, in Production it’s E:\ so for each case I load $APP_DATA to point to the website’s App_Data directory which is where the XML files are stored.

Line 6, it’s always good to check the file exists before trying to use it. Then the meat course in line 8, load all that XML into something I can manipulate, using [xml] to convert the file contents into an XML DOM object. In line 10 I’m looking for an <eisa /> element which contains the two manuals I need to separate. In lines 14 and 25 I’m creating the XML for the new manuals. And finally lines 36 – 40 where I import the new manuals and remove the manual I’m replacing.


I also like to produce installers for each of the environments where I need to deploy an application. In my next post I'm going to document some MSBuild code to automate building an app for each configuration. Once those installers are built I use PowerShell to put them where the tech guys expect them to be, including creating the correct directory structure based on the application version number defined in Web.config:

   1: $cwm = "D:\Work\AppRoot\"
   2: $cwmPackages = Join-Path $cwm "ProjectRoot\obj"
   3: $newVersions = "\\\repo\"
   4: $environments = @{ Path = "Staging"; },
   5:                 @{ Path = "Development"; },
   6:                 @{ Path = "Test"; },
   7:                 @{ Path = "Production"; }
   9: function get_version {
  10:     $webConfig = Join-Path $cwm "Web.config"
  11:     Write-Host "Web.config $webConfig"
  12:     if (-not(test-path $webConfig)) {
  13:         Write-Host("Can't locate Web.config")
  14:         exit
  15:     }
  17:     $configDoc = [xml](Get-Content $webConfig)
  18:     $webVersionXml = $configDoc.configuration.appSettings.add | Where-Object { $_.key -eq "Version" }
  19:     return $webVersionXml.GetAttribute("value")
  20: }
  22: function create_new_version_folders {
  23:     $deployPath = Join-Path $newVersions $webVersion
  24:     if (-not(test-path $deployPath)) {
  25:         mkdir $deployPath > $null
  26:     }
  28:     # create subfolders
  29:     foreach ($target in $environments) {
  30:         $targetPath = Join-Path $deployPath $target.Path
  31:         if (-not(test-path $targetPath)) {
  32:             mkdir $targetPath > $null
  33:         }
  34:     }
  36:     return $deployPath
  37: }
  39: function copy_package {
  40:     Param($deployPath)
  42:     if (-not(test-path $cwmPackages)) {
  43:         Write-Host("Can't locate packages")
  44:         exit
  45:     }
  47:     foreach ($target in $environments) {
  48:         $sourcePath = Join-Path $cwmPackages ($target.Path + "\Package")
  49:         $targetPath = Join-Path $deployPath $target.Path
  50:         $targetTemp = Join-Path $targetPath "\Package\PackageTmp"
  52:         Write-Host "Copying package to $targetPath"
  53:         cp $sourcePath $targetPath -Force -recurse
  54:         rm $targetTemp -Force -recurse
  55:     }
  56: }
  58: #
  59: # Main
  60: #
  61: Write-Host "Using path $cwm"
  62: push-location $cwm
  64: # 1. Get version from web config e.g. 
  65: $webVersion = get_version
  66: Write-Host "WebsiteVersion: $webVersion"
  68: # 2. Create new version folder if not exists
  69: $deployPath = create_new_version_folders
  71: # 3. Copy package
  72: copy_package $deployPath
  74: # finally reset path
  75: pop-location
  76: exit

In the first couple of lines I’m declaring where to find the packages and a list of the configurations that target each of the different deployment environments. In line 9 is my function to retrieve the current version number from Web.config, again using the PowerShell [xml] facility. create_new_version_folders in line 22 does what its name suggests and creates the file structure where the installers will end up. And finally copy_package copies each installer to the correct location in the new directory structure.

For other projects I modify this script to copy e.g SQL scripts used to update the database for a particular version of the application. This can include modifying the SQL to match up any linked servers used in a particular deployment environment.

The amount of time that saves me is significant and makes my day just a little bit better each time. The converse is also true, whenever I find myself having to do something manually more than once I curse myself for not having spent the time to automate it the first time.

Technorati Tags: ,

Wednesday, November 17, 2010

Pain Points

Over at ms-joe Joe has a compiled a list of pain points that developers have suggested typically give concern when trying to stay on top of all the new and exciting stuff that pours out of Redmond and other forges. I don't normally follow Joe's blog and I got there I think from Alvin Ashcraft's Morning Dew. Whatever, I took it upon myself to, tongue somewhat in cheek, respond to the 35 items in the list. Clearly my response didn't pass moderation - it appeared briefly but was pulled almost straight away. Fair enough, I'm not about to take umbrage but I did feel that what I had to say might at least generate some feedback. I didn't save my original response so now I get to do it over. The items are copied from Joe's site and I'm not sure if I'm breaking any copyright rules by posting but I am giving full attribution to Joe for the questions. The responses/opinions are all mine.

1.) Choosing between Web Forms, MVC, WebMatrix or something else entirely.

Real developers use MVC. If, like me, you are presently stuck with legacy Web Forms I feel your pain. MVC is the future and it is a huge improvement over Web Forms. I understand WebMatrix to be targeted at hobbiest devs who might more traditionally have gravitated towards Access.

2.) Choose Windows Forms, WPF, Silverlight or something else for desktop development.

WPF. I make this assertion not from experience with either WPF or Silverlight but long experience with Windows Forms. At this point there is enough of a question mark over Silverlight that I wouldn't go there and it is hampered for the desktop by it's sandboxing. For me the future lies with HTML5 and in particular the mobile space. HTML5, CSS, JavaScript, jQTouch, WebKit provide a natural cross platform environment for mobile development. There are only a couple of cross platform options in this space, it just makes sense.

3.) Choosing a data access method (ADO.NET, LINQ to Objects, LINQ to SQL, Entity Framework, NHibernate, embedded SQL) and how to use that choice in application architecture. What are the good practices, should I use binding? Etc.?

For small projects use LINQ to SQL, for large projects use LINQ to EF. In both cases use DTOs between layer boundaries. There are other players in this space including LightSpeed, SubSonic, NHibernate (stay away from NetTiers). Choosing an ORM is a difficult choice, and there are plenty of good options. If you have a clear separation between your business logic and data access layers then you should at least have some ability to revisit that choice but in reality I haven't seen anyone change ORMs mid-project.

4.) Choosing between ASMX or WCF (When SHOULD I use WCF)

You should always use WCF, WCF is good.

5.) Determining the right practices and patterns for development.

See item 1. I like REST, MVC, and AJAX via jQuery. This stuff works and provides a good separation between your different layers. I'm just now putting the finishing touches to an iPhone app written in HTML5/jQTouch/jQuery that uses JSON to talk to a WCF REST service hosted in IIS with an EF/SQL Server data store. It all seems very natural although I'm sure the Ruby community might have words to say about that claim.

6.) Finding examples of non-trivial applications with great documentation and tutorials.

TekPub. I find I'm fairly constrained in the time I have available to look at new stuff and I like to read so I tend to pick and choose and I rely on StackOverflow if I'm stuck on something specific.

7.) How to build, consume, and manage Reusable Code (Libraries, Frameworks, Controls).

Read Dependency Injection in .Net. I'm supposed to be writing a review for it but those darn time constraints keep getting in the way. I'm about halfway done and despite already having experience with DI/IOC I am learning plenty. Again, the Ruby community have words to share on DI but if you're doing c# you owe it to yourself to read this, it will inform your code and app design.

8.) Microsoft guidance focuses on HOW, but not when and why. Proved “Best Practices”.

See item 7.

9.) Moving from WebForms Controls to JavaScript (jQuery) (How, Why, and When to choose which.)

If you're not familiar with JavaScript you need to spend some time learning the basics. I think TekPub has some good JS stuff and there are a number of good books. For a .Netter JS contains some surprises. Doug Crockford's JavaScript: The Good Parts is a good read that will set you up to avoid some of those traps.

In many ways moving from WebForms to jQuery is going to put you a lot closer to the wire than you might have been used to. I like that, ymmv. I really disliked how the MS tooling in WebForms was all about trying to abstract away the browser. If you followed the prescription then everything was ok, but if you wanted to to something outside that it all seemed to get very hard. There is no question there is a learning curve associated with moving to MVC and jQuery but it will tend to make sense. With Web Forms the learning curve was all about how to get around the limitations of the abstraction.

10.) Show me how to do Grids and all other WeForms control functions in jQuery.

Check out Encosia and working with jQuery templates.

11.) Should I choose jQuery or ASP.NET AJAX (and how do I use jQuery to do all the things that I used to do in ASP.NET AJAX and the Ajax Control Toolkit.

Choose jQuery and refer to item 10.

12.) Deciding whether to use Silverlight in an ASP.Net app.

Avoid Silverlight for the web. Like Flash it is not web friendly, it is not search engine friendly. I know of a bank that is using it for an intranet app and they are pleased with it so it does have a place and being able to use your existing C# skills is a plus.

13.) Complete guidance on REST with samples of working will all major REST APIs (Twitter, Facebook, etc.)

That's a tall order. I haven't looked for REST guidance for a while. There is some good stuff out there and the basics are simple. Use uri's to identify your resources, POST = Create, GET = Read, PUT = Update, DELETE = Delete.

14.) How to do OO Design & Coding with .NET (I never really had to learn)

See item 7.

15.) Changing our development practices to follow more of a TDD model.

Read Uncle Bob's Agile Principles, Patterns, and Practices in C#. Prentice Hall. 2006

16.) Source code management tool options. VSTS and cheaper solutions (CVN)

How big is your shop and how deep are your pockets? SVN (including TortoiseSVN for explorer integration and AnkhSVN for Visual Studio integration) is a good free option, VSTS is also a good option but I think it is still the case that if you want to go over the 5 CALs that come with your MSDN subscription you're in for a hefty bill over and above what you're already paying for MSDN. I haven't used Git but I'm considering it to get my source into an offsite repository. Avoid CVS.

17.) How do implement a true and Abstract DAL

Item 7, use DTOs

18.) How to build a good BLL with validation (required fields, etc...) in front of an Entity Framework DAL.

If you have abstracted your DAL then the fact you're using EF is irrelevant.

19.) I need a complete strategy for SEO. Microsoft has some tools, but no complete story!

Your basic strategy for SEO is to write quality validating HTML that the crawlers can index. Everything else is snake oil.

20.) How do I design and build for high performance and how to I troubleshoot performance problems.

If you're Facebook you've got a team working on this for you. Otherwise design and build using what you already know. If you find there are performance issues, capture timing information by inserting NLog logging statements at suspected bottlenecks. There are other tools available, including a logging tool that can be applied without having to rebuild your app but I've not used it and I can't remember its name right now. There are also code profiling tools that can help but the basics are to start thinking about where the bottleneck might be and trying to prove it by judicious use of logging. Don't assume. Don't make random changes to your application based on where you think the issue might lie.

21.) Understanding stateless application development. WebForms handled “state” for me. MVC and WebMatrix don’t.

I'm not sure I understand the issue here. Is it the remembered state of controls or session state? Either way I don't feel knowledgable enough to give technical guidance but frankly this is web 101 stuff and you're a smart individual, google it.

22.) Building a business case for management to migrate a Classic ASP (VBScript) (90% of app) to ASP.Net MVC instead of ASP.Net WebForms (10% of app).

Don't kid yourself, this is not a migration, it is a rewrite either way. What are the advantages of MVC? For a developer a huge bonus is the testability and if you're rewriting an existing app you really want to make sure you capture those existing business rules in a test suite. Did I mention MVC produces better quality HTML and how, in turn, good quality HTML is better for SEO?

23.) Building a business case for migrating Classic ASP to ASP.Net (Choosing MVC or WebForms)

See item 22 above. Getting off Classic is also about dwindling skill-sets, support costs, and security.

24.) How to understand and get started with ASP.NET MVC. (I’ve been an ASP.NET developer for years and have looked at the MVC samples but it’s too complex and requires advanced OO skills.)

Hello world! Sheesh...

25.) Application deployment is a mess. Which versions of .NET, IIS, SQL ? How to automate deployment? What works side-by-side? How to manage SQL schema updates.

Latest .Net Framework absolutely. Same if possible for IIS and SQL Server. We're past version 1.0 issues with MS. Sure there will be edge cases but on the whole it's a solid platform. For packaging and deployment use the newest options and automate your build using MSBuild and PowerShell. Always automate your build.

26.) Should I use Stored Procedures or not now that we have LINQ and EF? How do I manage versioning of Stored Procs if I use them.

Use Stored Procs when it makes sense to do so, e.g. I recently wrote a SP to use a recursive CTE to flatten hierarchical parent/child relationships in a table - easy for a SP, more complex in code. Otherwise use the ORM, that is what it is for.

27.) I need to understand and use the ASP.Net security model, providers, etc.

Good luck with that. If you just remember that MS tooling is mostly about authenticating users against a MS credential store, i.e AD, you should be OK. Sometimes things aren't what they appear to be.

28.) I need to understand How the .net framework versions work (and don't work) together.

Assume they do, you will probably be correct.

29.) I need detailed guidance about how to make my (web) application multi lingual. Should I use resource files, a SQL Database?

I don't know but I would lean towards resource files unless your requirements are very complex.

30.) ASP.NET Membership and Profile management is far too weak. Show my how to take both to the next level and highly customize them.

If you stop drinking the koolaid you're on your own. Not really,
check codeplex or other OSS, someone has done this already.

31.) My team has major confusion over dataset binding in controls versus lists / listview, use of business objects and all the choices. Which controls are best to use for what user interface types and what is the best way to use data with those controls.

I'm a simple guy, I like to avoid dataset binding and instead use DTOs and lists. I find it makes automated testing that much easier. I'm probably missing out on some convenience but I think I gain in understanding what is happening.

32.) Help me learn how to deal with service binding and endpoint configuration headaches in my ASP.NET application; especially when developing on localhost (http) and deploying to a secure environment (https)... resulting in "works on my machine" syndrome.

I empathize completely.

33.) I need to understand the UpdatePanel (AJAX) and how it affects the page life cycle, security, and interaction between multiple UpdatePanels on same Page. Should I even be using the UpdatePanel? Is it going away? What is Microsoft’s plan for their AJAX stuff which seems to be going away and being replaced by jQuery.

Update panels are going away, ding dong the witch is dead. Use jQuery.

34.) Now that we’re doing more client side coding I need to really understand ViewState. It seems like a black box.

No you don't. Step away from the black box, use MVC and jQuery, there is no ViewState.

35.) How should I keeping up with new releases? How do I decide when to move to new versions.

Always move to new versions. You don't want to be in a situation where your app lags a couple of e.g. VS versions behind. If there is stuff in your app that needs to upgrade you should do that sooner rather than later. Later may mean a lot more effort.

So there you have it, as best as I can recall, somewhat tongue in cheek. I'm open to having my opinions adjusted.

- Posted using BlogPress from my iPad

Sunday, August 29, 2010


Over at World of VS there's a scaled out online poll taking place about what extensions would be useful additions to Visual Studio. There are a couple I really like and I've voted accordingly. My own suggestion is something I personally would find extremely useful and that is being able to manipulate MSBuild files in Visual Studio with full intellisense, including community MSBuild tasks. I've been a little underwhelmed by the love and I guess the question I have is what level of automation do Microsoft developers use in their projects and what tool is used if it isn't MSBuild?

To me automation is a no-brainer - being able to kick off builds in Team Build that not only compile the source but also produce installers for my target deployment environments, e.g. test, staging, and production. That means that whenever a developer checks in some source code we end up with a consistent set of installers built using a known set of referenced assemblies. At the end of a sprint when you really want to demonstrate the work you've completed do you really want to waste effort figuring out why the build that was generated on one developer's machine doesn't install when it installed perfectly from a different machine's build last week?

I also use MSBuild to update version numbers in a dozen configuration files, a job that previously was done by hand and resulted in many wasted hours and broken builds. I use MSBuild to precompile a website and copy the output to my local test site, including making the necessary modifications to config files for connection strings. I realise that Visual Studio is now far better at creating config files that match the target environment but just having that ability to press a button and know that if something is broken it has everything to do with some bad code I wrote and not the installer.

The lack of support in Visual Studio for MSBuild, apart from the standard XML editor, is a hole that needs filling. I know enough about MSBuild to be able to get it to do what I need but while it is a core tool I rely on I don't use it often enough to be able to write out build targets without having to reach for Stack Overflow or Google. Once my build file is done I rarely have to revisit it which means my aging neurons aren't providing a feed the next time I need to automate a build task. Maybe next time I'll take the time to have a look at Attrice's Microsoft Build Sidekick.

Sunday, August 15, 2010

Custom Authentication in WCF

I'm building an HTML5 iPhone application that accesses a WCF RESTful web service hosted by IIS. The stumbling block for me has been authentication. I decided to go with Basic Authentication over HTTPS as a simple and secure enough approach for authenticating my users. But as with most Microsoft tech it doesn't pay to wander too far from the track. If you want to authenticate Windows users then I suspect everything would more or less just work. I'm working with an existing credential store in a SQL Server database so I needed something a little different. I'm not going to go into detail about how I eventually got it sorted - this is really just to acknowledge Dominick Baier over at for shining a light on the process.

A few things to note for future reference. WCF doesn't use HttpContext.Current.User to identify the current user (although this can be enabled). Instead use ServiceSecurityContext.Current.PrimaryIdentity.Name. If following along with Dominick's example and setting up the HttpContextPrincipalPolicy it's quite valid to keep using the Thread.CurrentPrincipal to identify the user. And if the identity of the user is not reflected in Thread.CurrentPrincipal then something else is configured incorrectly. In my case the policy wasn't being loaded because I had it defined under the default behavior and not the MunqServiceBehavior I set up in an earlier post, see config section below:

1 <behaviors>
2   <serviceBehaviors>
3      <behavior name="MunqServiceBehavior">
4         <serviceMetadata httpGetEnabled="true" />
5         <serviceDebug includeExceptionDetailInFaults="false" />
6         <MunqInstanceProvider />
7         <serviceAuthorization principalPermissionMode="Custom"> <!-- set to Custom to enable auth policies below -->
8           <authorizationPolicies>
9           <!--
10             sync ServiceSecurityContext.PrimaryIdentity with Context.User.Identity and Thread.CurrentPrincipal with Context.User
11         -->
12         <add policyType="CustomAuthenticationModule.HttpContextIdentityPolicy, CustomAuthenticationModule" />
13         <add policyType="CustomAuthenticationModule.HttpContextPrincipalPolicy, CustomAuthenticationModule" />
14        </authorizationPolicies>
15       </serviceAuthorization>
16     </behavior>
17     <behavior name="">
18        <serviceMetadata httpGetEnabled="true" />
19        <serviceDebug includeExceptionDetailInFaults="false" />
20     </behavior>
21    </serviceBehaviors>
22 </behaviors>

Then I couldn't figure out why the authentication module itself wasn't loading. Turns out it's a known issue with Cassini, the Visual Studio web server. For IIS7 we're supposed to be using the <system.webServer> section but Cassini uses the old IIS6 syntax. The work around is to define the module in both locations:

1 <system.web>
2   <compilation debug="true" targetFramework="4.0" />
3   <httpModules>
4     <!-- added here and in system.webServer to load module using Cassini -->
5     <add name="CustomAuthenticationModule"
6         type="CustomAuthenticationModule.CustomAuthentication, CustomAuthenticationModule"/>
7   </httpModules>
8 </system.web>
10 <system.webServer>
11   <validation validateIntegratedModeConfiguration="false"/>
12   <modules runAllManagedModulesForAllRequests="true">
13     <remove name="CustomAuthenticationModule"/>
14     <add name="UrlRoutingModule"
15         type="System.Web.Routing.UrlRoutingModule, System.Web, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
16     <add name="CustomAuthenticationModule"
17         type="CustomAuthenticationModule.CustomAuthentication, CustomAuthenticationModule"/>
18   </modules>
19 </system.webServer>

So getting that sorted helped but the next issue was particularly annoying. I set breakpoints in my code and I could see the custom authentication taking place but after successfully authenticating the user and exiting my AuthenticateRequest handler it would immediately hit the EndRequest handler with a 401 - Unauthorized error. Apparently it pays to turn off security in the standard endpoint in order to use a custom handler. Presumably after executing my handler it was trundling off to a standard handler which wasn't then able to authenticate the user trying to access the service and returning access denied.

1 <standardEndpoints>
2   <webHttpEndpoint>
3      <!--
4       Configure the WCF REST service base address via the global.asax.cs file and the default endpoint
5       via the attributes on the <standardEndpoint> element below
6     -->
7     <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true">
8       <security mode="None"/> <!-- turn off security to use custom module -->
9     </standardEndpoint>
10  </webHttpEndpoint>
11 </standardEndpoints>

As a postscript and after working for more than a few years developing an Internet Banking site I'm extremely wary of roll-your-own authentication schemes. It's very difficult to get authentication right and the amount of effort required is typically a reflection of the overall risk of getting it wrong. For a bank that can include not just the risk of exposing a customer's accounts or even losing actual money. Banks have for a number of years implemented rules to minimise those losses by restricting the amount of money that can be transferred online and ensuring inter-bank transfers have a built-in delay period. The risk to a bank is also about the public perception of safety and bad press around online banking can result in substantial loss of custom. I walked away from a job with a bank where I felt strongly that inadequate steps were taken to ensure the security of the online application I was developing. I still think I made the right decision and in case anyone is wondering, it most definitely wasn't the PSIS.