|
Fri
Nov 7 2008 |
Zebra striping |
|
From the article Zebra Striping: Does it Really Help?: - no difference in speed or accuracy If you are going to use zebra striping, keep it subtle. |
|
Thu
Nov 6 2008 |
User Interface Design for Programmers |
|
From Joel Spolsky’s article User Interface Design for Programmers: - A user interface is well-designed when the program behaves exactly how the user thought it would. |
|
Sun
Aug 31 2008 |
Build ASP.NET WebService from the Command-line |
|
This NAnt target will build the project into a new folder with a (single) precompiled DLL. All source files and other non-deployment files are removed. <target name="publish.webservice"> <exec basedir="." program="${msbuild.exe}" commandline=" ${web.project.file} /nologo /p:OutDir=${publish.webservice.dir}\bin\ /p:WebProjectOutputDir=${publish.webservice.dir}" workingdir="." failonerror="true" /> </target> |
|
Wed
Apr 30 2008 |
Upgrading to WordPress 2.5 on BlueHost |
|
WordPress 2.5 was released last month. Why can’t we upgrade using Fantastico? It turns out BlueHost has started their own Fanastico competitor called SimpleScripts. I doubt we’ll be seeing any new Fantastico updates. While Fantastico has had WordPress 2.5 scripts for at least three weeks, BlueHost’s tech support claims they are waiting on Fantastico to release updated scripts. This is the first problem. The second problem is the recommended migration procedure here and here. It involves FTP clients and pulling database configuration bits from php files. This does not leave a good first impression of a product that is supposed to provide one-click installation and upgrades. I ended up using the Wordpress Automatic Upgrader Plugin. I still needed to use an FTP client to install the plugin but now I am not dependent on Fanastico or SimpleScripts. This plugin downloads the latest files directly from WordPress so updates should be available as soon as WordPress releases them. I encountered two problems while upgrading to 2.5:
On the positive side, SimpleScripts appears to be a more polished product compared to Fantastico. |
|
Tue
Apr 29 2008 |
Reset Sequence From Table |
|
This procedure will lookup the maximum value used in a table then set the sequence to this value + 1. CREATE OR REPLACE PROCEDURE reset_sequence_from_table ( seq_name IN VARCHAR2, column_name IN VARCHAR2, table_name IN VARCHAR2) AS cval INTEGER; BEGIN EXECUTE IMMEDIATE 'SELECT MAX(' || column_name ||') FROM ' || table_name INTO cval; cval := cval + 1; RESET_SEQUENCE(seq_name, cval); END reset_sequence_from_table; -- http://www.psoug.org/reference/sequences.html CREATE OR REPLACE PROCEDURE reset_sequence ( seq_name IN VARCHAR2, startvalue IN PLS_INTEGER) AS cval INTEGER; inc_by VARCHAR2(25); BEGIN EXECUTE IMMEDIATE 'ALTER SEQUENCE ' ||seq_name||' MINVALUE 0'; EXECUTE IMMEDIATE 'SELECT ' ||seq_name ||'.NEXTVAL FROM dual' INTO cval; cval := cval - startvalue + 1; IF cval < 0 THEN inc_by := ' INCREMENT BY '; cval:= ABS(cval); ELSE inc_by := ' INCREMENT BY -'; END IF; EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || seq_name || inc_by || cval; EXECUTE IMMEDIATE 'SELECT ' ||seq_name ||'.NEXTVAL FROM dual' INTO cval; EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || seq_name || ' INCREMENT BY 1'; END reset_sequence; |
|
Sun
Apr 6 2008 |
Canon Elph on Steroids |
|
The open source project CHDK has built firmware enhancements for many of Canon’s compact cameras. Some features: - RAW support Some first impressions… The UI is organized but far from user friendly. Lightroom cannot open the RAW files. At high shutter speeds the flash power was too high and there was no flash compensation. I ended up holding a white envelope in front of the flash. I tried taking some high speed photos with the new firmware on my Canon SD500. The best photo was taken with the stock firmware. The high speed shutter wasn’t needed for this shot - the focus and timing were more important. These photos were taken in daylight with no additional lighting beyond the flash. Canon SD500 with CHDK (1/4000s) I think I will keep it on my camera for the extra information on the display and the scripting. A few links: installing, download, usage, more usage info |
|
Fri
Mar 28 2008 |
Processing |
|
A friend told me I needed to write more blog posts. I’m not sure if he was interested in what I have to say or just didn’t want to link to a dead blog. Either way I will put down a few more posts! Take a look at the work from Robert Hodgin at flight404.com. He is using Processing, a programming language created for experimenting with computer graphics. At the core of these images and videos is a model following simple rules of physics (e.g. two positively charged particles will move away from each other) that is affected by the music (e.g. particle size increases with volume). The final image or video isn’t designed, it is the result of programming these rules and letting the model play itself out. Solar (high definition version, alternate version with lyrics) |
|
Fri
Aug 3 2007 |
Global exception handling |
|
Some general notes on exception handling:
In ASP.NET void Application_Error(object sender, EventArgs e) { Exception ex = Server.GetLastError(); if (ex is System.Web.HttpUnhandledException) { ex = ex.InnerException; } LogHelper.Log.Error("Unhandled exception", ex); Server.Transfer("~/Error.aspx"); } 2. Create an Error.aspx page: 3. If you are using Forms Authentication, add the following to your web.config. It ensures unauthenticated users can see the Error.aspx page in case an error occurs during logon/logoff. You should do the same for any images and stylesheets as well. <configuration> <location path="Error.aspx"> <system.web> <authorization> <allow users="?"/> </authorization> </system.web> </location> </configuration> 4. If you are using AJAX, you have to add an event handler on every page (are there any better solutions?): protected void ScriptManager1_AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e) { LogHelper.Log.Error("Unhandled exception during asyncpostback", e.Exception); ScriptManager1.AsyncPostBackErrorMessage = "An unexpected error occured. Please try again or contact the Help Desk."; } ASP.NET Web Services using System; using System.Web.Services.Protocols; using TDR.Common.Utility; namespace MySolution.MyProject.SoapExtensions { public class LoggingSoapExtension : SoapExtension { public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) { return null; } public override object GetInitializer(Type serviceType) { return null; } public override void Initialize(object initializer) { } public override void ProcessMessage(SoapMessage message) { if (message.Stage == SoapMessageStage.AfterSerialize) { if (message.Exception != null) { LogManager.Log.Error(message.Exception.Message, message.Exception); } } } } } 2. Add this to your web.config: <system.web> <webServices> <soapExtensionTypes> <add type="MySolution.MyProject.SoapExtensions.LoggingSoapExtension,MySolution" priority="1" group="1" /> </soapExtensionTypes> </webServices> </system.web> Note: SoapExtensions are not called when invoking the a webservice from the built-in test webpage. WinForms, Console Apps and Services static void Main() { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler( CurrentDomain_UnhandledException); } /// <summary> /// Log unhandled exceptions that occur anywhere in the appdomain. /// </summary> static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { try { LogHelper.Log.Error("Unhandled exception.", e.ExceptionObject as Exception); } catch { } } |
|
Fri
Aug 3 2007 |
MSBuild Automated Deploy Scripts for Web Applications |
|
These are suggested steps for creating batch scripts that will deploy a solution to DEV, TEST and PROD. If this article looks like it is more complicated than it should be, I agree. I think batch scripts with an XML read/write utility or NAnt would be a better choice:
Here is the MyApp.build file: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- MY WEB APPLICATION DEPLOY SCRIPT To deploy this application use the batch files DeployToXXX.bat --> <Import Project="..\..\Utilities\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/> <PropertyGroup> <TempFolder>..\..\_tempfolder</TempFolder> <DevFolder>\\mydevserver\wwwroot\myapp</DevFolder> <TestFolder>\\mytestserver\wwwroot\myapp</TestFolder> </PropertyGroup> <Target Name="DeployToDev"> <AspNetCompiler VirtualPath="MyApp" PhysicalPath="..\ " TargetPath="$(TempFolder)" Clean="true" Force="true" Debug="false" Updateable="false" /> <XmlRead XmlFileName="$(TempFolder)\web.config" XPath="/configuration/appSettings/add[@key='DEV.Log4NetConnectionString']/@value"> <Output TaskParameter="Value" PropertyName="Log4NetConnectionString"/> </XmlRead> <XmlUpdate XmlFileName="$(TempFolder)\web.config" XPath="/configuration/appSettings/add[@key='Environment']/@value" Value="DEV" /> <XmlUpdate XmlFileName="$(TempFolder)\web.config" XPath="/configuration/log4net/appender[@type='log4net.Appender.ADONetAppender']/connectionString/@value" Value="$(Log4NetConnectionString)" /> <XmlUpdate XmlFileName="$(TempFolder)\web.config" XPath="/configuration/system.web/compilation/@debug" Value="false" /> <CreateItem Include="$(DevFolder)\**\*" <Output TaskParameter="Include" ItemName="FilesToCleanFromTarget" /> </CreateItem> <Delete Files="@(FilesToCleanFromTarget)" /> <CreateItem Include="$(TempFolder)\**\*" Exclude="$(TempFolder)\Deploy\*"> <Output TaskParameter="Include" ItemName="FilesToDeploy"/> </CreateItem> <Copy SourceFiles="@(FilesToDeploy)" DestinationFiles="@(FilesToDeploy->'$(DevFolder)\%(RecursiveDir)%(Filename)%(Extension)')" /> <RemoveDir Directories="$(TempFolder)" /> </Target> <Target Name="DeployToTest"> <CreateItem Include="$(DevFolder)\**\*" Exclude="$(DevFolder)\Deploy\*"> <Output TaskParameter="Include" ItemName="FilesToDeploy"/> </CreateItem> <Copy SourceFiles="@(FilesToDeploy)" DestinationFiles="@(FilesToDeploy->'$(TestFolder)\%(RecursiveDir)%(Filename)%(Extension)')" /> <XmlRead XmlFileName="$(TestFolder)\web.config" XPath="/configuration/appSettings/add[@key='TEST.Log4NetConnectionString']/@value"> <Output TaskParameter="Value" PropertyName="Log4NetConnectionString"/> </XmlRead> <XmlUpdate XmlFileName="$(TestFolder)\web.config" XPath="/configuration/appSettings/add[@key='Environment']/@value" Value="TEST" /> <XmlUpdate XmlFileName="$(TestFolder)\web.config" XPath="/configuration/log4net/appender[@type='log4net.Appender.ADONetAppender']/connectionString/@value" Value="$(Log4NetConnectionString)" /> <XmlUpdate XmlFileName="$(TestFolder)\web.config" XPath="/configuration/system.web/compilation/@debug" Value="false" /> </Target> <Target Name="DeployToProd"> <Copy SourceFiles="$(TestFolder)\web.config" DestinationFiles="$(TestFolder)\Web.config.test" /> <XmlRead XmlFileName="$(TestFolder)\web.config" XPath="/configuration/appSettings/add[@key='PROD.Log4NetConnectionString']/@value"> <Output TaskParameter="Value" PropertyName="Log4NetConnectionString"/> </XmlRead> <XmlUpdate XmlFileName="$(TestFolder)\web.config" XPath="/configuration/appSettings/add[@key='Environment']/@value" Value="PROD" /> <XmlUpdate XmlFileName="$(TestFolder)\web.config" XPath="/configuration/log4net/appender[@type='log4net.Appender.ADONetAppender']/connectionString/@value" Value="$(Log4NetConnectionString)" /> <XmlUpdate XmlFileName="$(TestFolder)\web.config" XPath="/configuration/system.web/compilation/@debug" Value="false" /> <Prompt Text="Please ask the production administrator to copy MyApp from test to production. Press Enter when complete."/> <Copy SourceFiles="$(TestFolder)\Web.config.test" DestinationFiles="$(TestFolder)\Web.config" /> <Delete Files="$(TestFolder)\Web.config.test" /> </Target> </Project> Then to run use the following batch script: @echo off echo. echo -------------------------------------------------- echo. echo Deploy the Application to Dev echo. echo Ensure you have retrieved the latest version echo from source control. echo. echo -------------------------------------------------- echo. call "%VS80COMNTOOLS%\vsvars32.bat" set MSBuildCommunityTasksPath=..\..\Utilities\MSBuildCommunityTasks msbuild deploy.build /target:DeployToDev pause This requires the MSBuildCommunityTasks DLLs. Download, install, copy the DLLs to your project directory under Utilities, checkin to VSS and then uninstall. Placing the DLLs in VSS ensures subsequent developers can use the project without hacking their way through a bunch of dependencies. |
|
Fri
Aug 3 2007 |
Multi-Environment Config |
|
The following class allows you to easily switch between DEV, TEST and PROD appConfig settings. Settings are stored in one location (your web.config or app.config file). You can define settings global to all environments and override settings for each environment as neccessary. 1. In your app, access your settings with: MessageBox.Show("The current environment is " + Config.Name); MessageBox.Show("The connection timeout is " + Config.ConnectionTimeout); 2. Setup your web.config or app.config as follows, with an Environment appSetting: <?xml version="1.0"?> <configuration> <appSettings> <!-- ENVIRONMENT Use the "Environment" key below to select the environment (DEV | TEST | PROD). For settings common to all environments, add a normal appSetting key (e.g. "Name"). To change a setting for a specific environment, add an appSetting key with the environment name (e.g. "DEV.Name"). An environment-specific setting will override a common setting. In the example below, the DEV environment will have a connection timeout of 2 minutes while the TEST and PROD environments will use 90 minutes. --> <add key="Environment" value="DEV"/> <add key="DEV.Name" value="Development"/> <add key="TEST.Name" value="Test"/> <add key="PROD.Name" value="Production"/> <add key="ConnectionTimeout" value="90"/> <add key="DEV.ConnectionTimeout" value="2"/> </appSettings> </configuration> 3. Add the following Config.cs class to your project. using System; using System.Configuration; using System.Collections.Generic; using System.Text; /// <summary> /// A type-safe wapper around the project's config file. This class ensures /// all appSetting keys are located in one place and should provide more /// readable code. This also allow settings to be easily moved to a database /// or other store. /// </summary> public class Config { /// <summary> /// Retrieve a value from the config file. The "environment" appsetting /// configures the environment (e.g. Fort Hills development, Edmonton test). /// Prefix appsetting with this environment value to specifiy an appsetting /// that is specific to the environment. /// /// Note: Do NOT make this method public as we want to provide a /// type-safe wrapper and ensure all constants (the appSetting keys) /// are located in one place (this class). /// </summary> private static string GetValue(string key) { string env = ConfigurationManager.AppSettings["environment"]; string fullKey = env + "." + key; string value = ConfigurationManager.AppSettings[fullKey]; if (value == null || value == "") value = ConfigurationManager.AppSettings[key]; if (value == null) value = ""; return value; } /// <summary> /// Name of the environment /// </summary> public static string Name { get { return GetValue("Name").Trim(); } } /// <summary> /// Note we convert this to a TimeSpan so that users of this class /// don't need to know if the timeout is specified in seconds, /// minutes or hours. When adding new settings convert them /// to a data type that will be most useful to users of this class. /// </summary> public static TimeSpan ConnectionTimeout { get { return TimeSpan.FromMinutes( Convert.ToDouble(GetValue("ConnectionTimeout"))); } } } |