Tuesday, August 14, 2007

How easy maintenance will improve performance

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/how_easy_maintenance_will_improve_performance.htm]

We all want our software apps to perform fast, however sometimes we get so obsessed with immediate performance that we neglect the big picture and actually code in a way that degrades performance in the long run. The problem is that optimizing code for performance usually (1) takes more development resources, and (2) makes that code harder to maintain. About the second point - there is often a tradeoff between performance and maintenance: generalized code is easy to maintain, but because it handles all general cases, it is not optimized for speed. For example, it wastes extra resources on tasks that may not be needed for the current case, but are still included so that the code handles the general case.

 

So, the trap becomes a developer may spend all their resources upfront trying to optimize a specific component. However, if that component isn't truly performance-critical, then they've: (1) spent their resources on a non-critical item such that they have less resources for the really critical ones, and (2) created a maintenance issue that will continually drain their resources away from the components where a little amount of effort could really optimize it.

 

What this means is that, in the long-haul, writing maintainable code (which may be slightly slower) will actually yield overall performance gain because you'll continually have move resources to address the real performance problems. Functionality trumps performance - if your code functionally doesn't work, you won't care about performance. So if you're continually being distracted to go and fix bugs in old code because it isn't maintainable, you'll likely shift your mindset from "make this work fast" to "just get it to functionally pass". Of course the trap is that a developer starts out with good intentions - "I was just trying to optimize this code".

 

The optimal approach is to strike a balance between maintainability and performance. Some practical tips:

  • Not all code is equal - obviously code that is frequently called needs much more attention that code that is rarely called.

  • Check where your bottlenecks are. For example, if your database is ground to a halt, and your web servers are relatively free (keep in mind that web servers scale much more easily than database servers), killing yourself to optimize a simple codeBehind page may steal mental resources from optimizing the real issue - a database. Likewise, don't waste time trying to compress all your JavaScript, just to save 5 k of download, if that JavaScript now becomes impossible to debug or maintain.

  • Know what the standard is - Everyone knows that the app should be "fast", but how fast is fast enough? Clichés like "as fast as possible" are what get you into the downward spiral because they encourage you to squander resources on less-important tasks. If you know that given X users, a page should take Y seconds to load, then you can make rational decisions on how to spend your development time wisely.


Living in Chicago and interested in working for a great company? Check out the careers at Paylocity.

Monday, August 13, 2007

Running a batch in a hidden console window

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/running_a_batch_in_a_hidden_console_window.htm]

.Net allows you to run a batch in a hidden console window using System.Diagnostics. You can specify a ProcessStartInfo object (really a strategy pattern), and then run the process. This is useful when trying to integrate to other programs that only provide a command line interface.

This simple utility method also provides the ability to log the console output to some log file (specified with strLogPath). I have a simple unit test simply as a stub to see how the code is called. In this case, I call the "test.bat" file, passing in a single param "abc", ask the calling code to wait until the batch finishes, and then log it to a given path.

 

    [TestMethod]
    public void TestDiagnostics_1()
    {
      RunHiddenExecutable(@"C:\Temp\test.bat", "abc", true, @"C:\temp\log.txt");
    }

    public static void RunHiddenExecutable(string strFileName, string strArguments, bool blnWaitForExit, string strLogPath)
    {
      bool blnOutputLogFile = true;
      if (strLogPath == null || strLogPath.Length == 0)
        blnOutputLogFile = false;
      System.Text.StringBuilder sb = new System.Text.StringBuilder();
      try
      {
        //run in process without showing dialog window:
        ProcessStartInfo psi = new ProcessStartInfo();
        psi.WindowStyle = ProcessWindowStyle.Hidden;
        psi.CreateNoWindow = true;    //Need to include this for Executable version (appears not to be needed if UseShellExecute=true)
        psi.FileName = strFileName;
        psi.Arguments = strArguments;
        psi.UseShellExecute = false;
        psi.RedirectStandardOutput = true;

        Process p = System.Diagnostics.Process.Start(psi);
        sb.Append(p.StandardOutput.ReadToEnd());

        if (blnWaitForExit)
          p.WaitForExit();

      }
      catch (Exception ex)
      {
        sb.Append("Exception Occured:\r\n");
        sb.Append(ex.ToString());
        throw;
      }
      catch
      {
        sb.Append("A non-CLSCompliant exception was thrown.");
        throw;
      }
      finally
      {
        //Implement - log this however you want
        //if (blnOutputLogFile)
        //  Write out file (sb.ToString(), strLogPath);
      } //end of try

    } //end of method

 

See also: How to send the Console output to a string

 


Living in Chicago and interested in working for a great company? Check out the careers at Paylocity.

 

Thursday, August 9, 2007

Open up multiple tabs in IE7 from the command line

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/open_up_multiple_tabs_in_ie7_from_the_command_line.htm]

I had a need to regularly view a quick collection of web pages, somewhat like a dashboard. Rather than manually click a bunch of links from my favorites, I just wanted a single click to open all the pages. I know that you can automate IE from the command line to open a single tab, like so:

 

"%ProgramFiles%\Internet Explorer\iexplore" http://www.msdn.com
 

... but I needed to open several tabs. I could then repeat this line multiple times, but that just opened multiple instances of IE instead of a single instance with multiple tabs.

 

MSDN blogger Tony Schreiner had a perfect solution using Windows Script Host (WSH). You can just have a single script open multiple tabs within a single IE instance, and then call that script with one click from the command line.


Living in Chicago and interested in working for a great company? Check out the careers at Paylocity.

 

Wednesday, August 8, 2007

Coding can be like running a marathon

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/coding_can_be_like_running_a_marathon.htm]

Coding can be like running a marathon - you're in it for the long haul. If you "sprint" upfront (hacks, skipping process, un-maintainable code, one-off copies) just to get it done, you start off ahead. But as you need to make maintenance and architecture changes further down the road, all those hacks have caught up with you and the code has become too hard to change. Each new feature becomes like a runner dragging them self to go one more mile. Both marathoners and software applications need to be prepared to run for a long time.

 


Living in Chicago and interested in working for a great company? Check out the careers at Paylocity.

Tuesday, August 7, 2007

Cloning a temp table schema in SQL

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/cloning_a_temp_table_schema_in_sql.htm]

SQL temp tables can be a functional and performance life-saver because they let you store intermediate results. You can then do resource-heavy calculations on this smaller result set, or just break a complex procedure into multiple, easier-to-handle parts.  Sometimes you'll have two temp tables with the same schema. SQL provides as easy way to clone a temp table (thanks to our Paylocity SQL architect for showing me this). Here's a sample:

CREATE TABLE #Main1 --prePaging
(
  colInt int,
  colString varchar(20)
)

Insert Into #Main1 Values(10, 'aaa')
Insert Into #Main1 Values(20, 'bbb')
Insert Into #Main1 Values(30, 'ccc')

--Dynamically create a close of #Main1, call it #Main2
--This doesn't touch data
Select * Into #Main2 From #Main1 Where 1=2

--show that Main2 exists, and has the right schema
Select * From #Main1
Select * From #Main2

--Now insert some data into Main2
Insert Into #Main2 (colInt, colString) Values(40, '
ddd')
Select * From #Main2

drop table #Main1
drop table #Main2

The key is the line "Select * Into #Main2 From #Main1 Where 1=2", which dynamically creates table Main2 and sets its columns equal to that of Main1.

 


Living in Chicago and interested in working for a great company? Check out the careers at Paylocity.

Monday, August 6, 2007

Easily insert huge amounts of test data

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/easily_insert_huge_amounts_of_test_data.htm]

I've plugged the free MassDataHandler tool before - an open source tool that lets you use XML script to easily insert data. One limitation of the tool (perhaps to be solved in future releases) is the inability to easily insert mass amounts of data.

 

Say you want a table to have 10,000 rows so you can do some SQL performance tuning. Ideally you could specify some algorithm to dictate the number of rows and how each row of data is unique. For example, you may want to say "Insert 10,000 rows of company data, using different company names of the form 'co' + i.ToString(), such as co1, co2, co3, etc...".

 

You can easily do this. First you could use the MDH to insert the parent data. Then for specific high-volume tables, you could use the SQL while loop to specify the insert strategy, like so:

Declare @i int select @i = 1  WHILE (@i <= 10000) BEGIN    --Define the dynamic data to insert   Declare @co varchar(10)   select @co = 'co' + cast(@i as varchar(4))    --Do the SQL insert   Insert into MyTable (    [co],     [SomeColumn] )   Values(    1,     @co,     'someData' );    --increment the counter   select @i = (@i + 1) END

The would quickly insert 10,000 rows of test data into MyTable. You could customize the technique for other tables, adding multiple inserts in the loop, or adjusting for a multi-column unique index.

 


Living in Chicago and interested in working for a great company? Check out the careers at Paylocity.

Sunday, August 5, 2007

Migrating legacy apps to Asp.net AJAX

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/migrating_legacy_apps_to_aspnet_ajax.htm]

We've all heard how awesome Ajax is, so the practical question is how do I start using it? It's easy enough if you're building an app from scratch, but it's always harder when migrating a legacy app. Here are some things I've discovered while trying to migrate a 4-year ASP 2.0 web app (which was itself migrated from .Net 1.1).

 

Step 1: Update the web.config. Because a legacy app probably already has a big config, you'll need to merge sections in. While you can get the full web.config file from creating a new Ajax app, I've copied it here for convenience and greyed out the nodes likely to already exist. The relevant sections are

  • configSection.sectionGroup

  • page.controls

  • compilation.assemblies

  • httpHandlers

  • httpModules

  • system.web.extensions

  • system.webServer

xml version="1.0"?>
<configuration>
    <configSections>

        <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
            <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication"/>
                <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                    <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="Everywhere"/>
                    <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication"/>
                    <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" allowDefinition="MachineToApplication"/>
                sectionGroup>
            sectionGroup>
        sectionGroup>
    configSections>
    <system.web>
        <pages>
            <controls>

                <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
            controls>
        pages>

       
        <compilation debug="true">
            <assemblies>

                <add assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
            assemblies>
        compilation>
        <httpHandlers>

            <remove verb="*" path="*.asmx"/>
            <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
            <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
            <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
        httpHandlers>
        <httpModules>

            <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        httpModules>
    system.web>

    <system.web.extensions>
        <scripting>
            <webServices>
               
               
               
               
               
               
            webServices>
           
        scripting>
    system.web.extensions>
    <system.webServer>
        <validation validateIntegratedModeConfiguration="false"/>
        <modules>
            <add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        modules>
        <handlers>
            <remove name="WebServiceHandlerFactory-Integrated"/>
            <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
            <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
            <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        handlers>
    system.webServer>
configuration
>

 

 

Step 1b. Beware of the web.config node: <xhtmlConformance mode="Legacy" />. This is inserted by the ASP.Net 1.1 to 2.0 migration. Setting this to legacy will mess up the HTML output that Ajax needs. ScottGu explains more here. If you have this node, you'll want to set it to "Transitional", however that could likely break a lot of your javascript. Note that as an intermediate step, you can still switch this node to "Transitional" in preparation for migrating to Ajax sometime in the future.

 

Step 2: Ensure that the System.Web.Extensions assembly is correctly referenced by your app. The MS Ajax download installs this in the GAC. You may need to copy it locally depending on how your application deploys.

 

 

It's really that easy. I was concerned that there would be some nuance - you'd need to modify some hidden file in a hidden folder, each page would need tweaking, or it just wouldn't work. I was pleasantly surprised how easy it was to migrate.

 

Once you have an ASP.Net Ajax app, then you can start using all the wonderful AJAX things like the amazing update panel, or the Ajax controls.

 


Living in Chicago and interested in working for a great company? Check out the careers at Paylocity.