Monday, December 27, 2010

Quickly Testing and Changing Code

Sometimes when running software it is hard to make a particular code path execute.  Like if you want to ensure your code behaves correctly when bad data is passed to your function.  I love Visual Studio and the debugging capabilities are one of the things I love about it.  Using the watch window the values of variables can be changed to check those code paths, and by right clicking on a line one can set the next statement of execution wherever they want.

If we have code like this

int customerId = getCustomer("bob");
bool log = Settings.ShouldLog();
if(customerId < 0 && log)
{
    log.LogError("bob is invalid");
}

We can make the log code run by setting a breakpoint on the if line and then changing customerId to -1 and log to true in the watch window.  If we want to change the code in the if block and retest changing these values every time is repetitive.  Changing the breakpoint on the "if" to a conditional breakpoint allows additional code to execute when the breakpoint is evaluated.  We can leverage this feature to change the breakpoint condition to "customerId = -1."  Each time the breakpoint is evaluated customerId will be set to -1.  This is great, but in our example we have two variables which need to be set properly to satisfy the if condition.  To change both variables the conditional breakpoint can be changed to a lambda like this: () => { customerId == -1; log == false;};

I've found this technique can help me rapidly iterate on code changes to ensure edge case scenarios or hard to reproduce data scenarios execute properly for code that isn't under test.

Monday, December 20, 2010

How hard is it to change a column datatype?

One of our clients initially specified one of their columns as an int, and after being deployed for a month realized it needed to support other characters.  I found a couple of ways to do this in SQL Server 2008.

We'll use this sample table:

CREATE TABLE dbo.Orders
      (  OrderId int NOT NULL IDENTITY (1,1),
         OrderNumber int NOT NULL
      ) 
INSERT INTO Orders
      SELECT 234
     
SELECT * FROM Orders



The way I normally do things is by generating a change script in SQL Server Management Studio.  Here is the SQL output by that process.

CREATE TABLE dbo.Tmp_Orders
      (
      OrderId int NOT NULL IDENTITY (1, 1),
      OrderNumber varchar(50) NOT NULL
      )  ON [PRIMARY]
GO
ALTER TABLE dbo.Tmp_Orders SET (LOCK_ESCALATION = TABLE)
GO
SET IDENTITY_INSERT dbo.Tmp_Orders ON
GO
IF EXISTS(SELECT * FROM dbo.Orders)
       EXEC('INSERT INTO dbo.Tmp_Orders (OrderId, OrderNumber)
            SELECT OrderId, CONVERT(varchar(50), OrderNumber) FROM dbo.Orders WITH (HOLDLOCK TABLOCKX)')
GO
SET IDENTITY_INSERT dbo.Tmp_Orders OFF
GO
DROP TABLE dbo.Orders
GO
EXECUTE sp_rename N'dbo.Tmp_Orders', N'Orders', 'OBJECT'
GO


I wasn't thrilled about copying the entire table for our whole dataset.  I thought the change script should create a new column, copy the data to it, remove the old column, and rename the new column to the old column's name.  Googling about for a way to do this I found it was even easier in SQL Server:


ALTER table dbo.Orders ALTER Column OrderNumber varchar(50)


It's a good old one liner :)

Monday, December 13, 2010

Oh how I love Visual Studio

Over the past month I've been working with a lot of JavaScript and I was really missing these features from Visual Studio:

  1. No find in solution - I was actually opening up Visual Studio and using the find feature to search my JavaScript directory structure to find things.
  2. No solution explorer - I had to keep switching over to Windows Explorer to see the files I wanted to open.
  3. No AhnkSVN - I love having source control integrated, so I don't have to worry about adding files, ignoring files, etc.
I found out I can get all my Visual Studio love by creating a web project at the root of my JavaScript Solution.  It gave me back all the features I missed without any problems.

Huzzah!


Monday, December 6, 2010

OpenID on DotNetNuke

I hate remembering passwords and I don't like creating accounts on sites and worrying they are storing my password incorrectly.  I love OpenID because it solves those two issues for me by enabling OpenID sites to use an already existing account for login.  For example I use my gmail account on Facebook and StackOverflow to login.

This post walks through enabling OpenID after installing DotNetNuke.
  1. Click login and login as host
  2. Admin Menu --> Extensions menu option
  3. Authentication System section
  4. click the pencil icon next to 'DNN_OpenIDAuthentication'
  5. check the Enabled? checkbox
  6. Click the 'Update Authentication Settings' to apply the settings.
  7. logout
  8. click the login button
  9. select the OpenID tab
  10. In the OpenID: field enter the url for your OpenID provider (google's url is https://www.google.com/accounts/o8/id)
  11. Click the Login button
  12. Under the 'Register a new account' section change the first name, last name, display name, and email address.
  13. Save the changes, logout, and log back in to ensure it worked.
DotNetNuke leverages the ASP.NET membership provider infrastructure and I plan on incorporating it into future projects I work on.  The biggest issue I see with the technology is that most implementations today require entering your OpenID provider URL.  I think this is bad user interface.  I like how StackOverflow shows pictures of many providers allowing a user to select their provider without needing to memorize or lookup a URL.

Monday, November 29, 2010

Installing .NET Nuke

I read DotNetNuke supports OpenID out of the box and I wanted to try it out.  This post covers the installation of DNN.  A future post will cover configuring OpenID.

Environment:
  • Windows 7 64 bit
  • IIS 7.5
  • SQL Server 2008 R2

Downloaded DotNetNuke Community Edition version 5.05.01

Listened to the DotNetNuke Installation Webinar while doing the following:

Right click properties on the zip file. Uncheck Unblock.

Unzipped the community edition.

read .\Documentation\Readme.txt

Open SQL Server Management Studio connect to .\

Created a new database on my local machine named DotNetNukeTest.  Set initial size to 20 MB.

Expand the security node.  Right click Logins 
Click 'New Login...'
Select SQL Server Authentication

login Name: DotNetNuke
password:IloveGravityWorks
Uncheck Enforce password policy
select User Mapping map to DotNetNukeTest and set role membership to db_owner

Moved the folder to c:\www\

Opened Internet Information Services Manager

Expanded the Sites node.

Right click Sites, Add Site
  Site Name: DotNetNukeTest
  Change Application Pool back to DefaultAppPool
  Physical path: C:\www\DotNetNuke_Community_05.05.01_Install

  Port: 8081

right click C:\www\DotNetNuke_Community_05.05.01_Install -> Properties
select Security tab
click Edit
Click Add
Ensure From this location: is set to your computer name
Object name --> IIS AppPool\DefaultAppPool
OK
Grant the DefaultAppPool user modify permissions
OK
OK

Browse to http://localhost:8081 in Chrome

Select Custom install (I always like doing this for new software)

Next through File Permissions Summary.

Select 'SQL Server 2005/2008 Database' instead of SQL Server 2005/2008 (Express File).
Server: "."
Database: "DotNetNukeTest"

Uncheck 'Integrated Security"
User ID: "DotNetNuke"
Password:'IloveGravityWorks'

I fix my SQL Server authentication and then click Next

change the password, and email fields.  I leave SMTP alone.

on the Install Optional Modules I select all modules:
Announcements (04.00.03), Blog (04.00.00), Documents (04.01.00), Events (05.01.04), FAQs (04.04.00), Feedback (05.00.02), FormAndList (05.01.03), Forum (04.05.03), Help (03.00.02), HTML (Community (05.04.03), IFrame (04.04.00), Links (04.00.01), Map (01.00.09), Media (03.03.00), Messaging (01.01.00), NewsFeeds (04.00.01), Reports (05.01.00), Repository (03.01.15), Store (02.01.36), Survey (04.60.00), Taxonomy (01.01.00), Telerik (05.05.00), UsersOnline (05.01.00), Wiki (04.02.00), XML (04.03.05)
Next
Next through Skins and Containers
Next through Language Packs
Next through Authentication Systems:
Next through Providers
For Portal Administrator set password, email address, and Portal Title.
Click 'Start building your new Site!'

Monday, November 22, 2010

Blacklisted Oh No! - More reliable email

I've had email issues for about 1.5 years.  Often times when sending email to a new contact my email doesn't arrive in their inbox because it gets put in their spam filter or doesn't even reach their inbox at all.

At a party this summer I spoke with some network guys who suggested googling email black list and checking to see if my domain was black listed.

The first hit returned mxtoolbox, and a search for my domain did show I was listed once :(
http://www.mxtoolbox.com/SuperTool.aspx?action=blacklist:davidsilvasmith.com

Clicking on the entry I was taken to the listing website which had a button to whitelist myself.

198.173.106.90 was found in NOMOREFUNN!
no-more-funn.moensted.dk -> 127.0.0.7
31/03/02 importet from fiveten after SPAM


Checking back a day later, my domain was no longer listed, and now 2 months later, my emails seem to be getting through to recipients.




Monday, November 15, 2010

Troubleshooting a failed login with correct user name and password

After setting up a new Database in SQL Server 2008 R2 and configuring DotNetNuke (DNN), an ASP.NET application, to connect to the new database I got received this message:
Connection Error(s):
Index #: 0
Source: .Net SqlClient Data Provider
Class: 14
Number: 18456
Message: Sql login failed

I open up SQL Server Management Studio and reset the user password.  Try again from DNN, same error.

I try connecting via SQL Server Management Studio.  Login failed.

I look in event Viewer and see
Login failed for user 'dotnetnuke'. Reason: An attempt to login using SQL authentication failed. Server is configured for Windows authentication only. [CLIENT: <local machine>]

I open up up SQL Server Management Studio and connect to '.'

I right click the SQL Server 10.50.1600 node and

  1. Select properties
  2. Select Security Page
  3. Under Server authentication change it to 'SQL Server and Windows Authentication mode'


Right click the SQL Server 10.50.1600 node again and select 'Restart' from the sub menu.

Back to the application and login is successful.

Monday, October 11, 2010

Access Denied on Adding a Database

Today I ran into a problem where I couldn't run a create script to create a local database in SQL Server Management Studio 2008 running on 64 bit Windows 7 Professional.  This is the error I received:

Msg 5123, Level 16, State 1, Line 1
CREATE FILE encountered operating system error 5(Access is denied.) while attempting to open or create the physical file 'c:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\xxx.mdf'.
Msg 1802, Level 16, State 4, Line 1
CREATE DATABASE failed. Some file names listed could not be created. Check related errors.

This is how it was fixed on my machine:

  1. Fire up SysInternals Process Monitor
  2. Open the 'Filter' menu
  3. Click 'Filter...'
  4. From the first dropdown select 'Path' in the second dropdown select 'contains'.
  5. For the value enter '\data'
  6. Click the 'OK' button to add the filter.
  7. Retry the operation
  8. Open up one of the NAME NOT FOUND entries with a Detail of Access Denied.
  9. Click the Process tab
  10. Note the process is sqlsvr.exe and the user is NT Authority\Network Service
  11. Navigate to the Data directory in Windows Explorer
  12. Click the Security tab
  13. Click the 'Edit' Button
  14. On the Permissions for 'Data' click the 'Add' button.
  15. For the object name enter 'Network Service'
  16. Click 'OK'
  17. Disregard any errors regarding not being able to set the permissions on files.
  18. Retry the operation in Sql Server Management Studio and it should work!

Monday, October 4, 2010

Unable to rename database in SQL Server Management Studio 2008

Today I wanted to rename a SQL Server 2008 database so I would have a rollback if my create from script went bad.

Upon rename SQL Server Management Studio 2008 gave me the error 'The database could not be exclusively locked to perform the operation. (Microsoft SQL Server, Error: 5030)'

I executed this in a query window connected to the master database:
sp_who2

Looking at the DBName column in the results for each match of the db I was trying to rename I executed 'kill xxx' where xxx was equal to the SPID of the row.

After doing this for all the rows rename succeeded immediately.

Monday, September 27, 2010

Back to Lansing!

Thursday September 9th was my last day at Sonoma Partners, the best Customer Relationship Management (CRM) Dynamics parter in the business.  Sonoma delivers projects on time, on scope, and on budget sometimes receiving amazing unsolicited feedback from satisfied customers.

Amelia Marschall and Jeff McWherter gave me the opportunity to work with talented individuals such as Scott Gowell, and Lauren Colton helping individuals in the greater Lansing area overcome technical challenges and giving back to the community by helping fight extreme poverty and cure preventable diseases, or  working to double the number of college graduates in Michigan.

I started at Gravity Works Design and Development on Monday September 13th.  I've already delivered some great value for customers and had some great times.  I'm looking forward to helping grow the Lansing and Michigan economies.

Monday, June 7, 2010

Windbg Ignoring Sympath when called programatically

Working with mdbglib this weekend my call to .sympath was registering but then none of the symbol files could be found.  This post will show how to resolve this issue and the process I followed to solve it.

Environment Windows Server R2 64 bit.  The app is compiled for x86 and running as a 32 bit app.

  1. lm e
    This listed modules which had errors.
  2. reload -f
    Force a reload of all modules.  Noticed none of them were loading from my path.
  3. Hooked up SysInternals Process Monitor filtered to just my process name and just disk activity.  cleared the output right before doing reload -f
    NOticed it was attempting to load modules from the same directory I was running from.
  4. restarted the application
  5. !sym noisy
    To noisy load symbols.
  6. .reload

    DBGHELP: SymSrv load failure: symsrv.dll
    DBGHELP: wntdll.pdb - file not found
    *** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\SysWOW64\ntdll.dll - 
    DBGHELP: ntdll - export symbols
    DBGHELP: wuser32.pdb - file not found
    *** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\syswow64\USER32.dll - 
    DBGHELP: USER32 - export symbols
    DBGHELP: System.Windows.Forms.pdb - file not found
    *** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v2.0.50727_32\System.Windows.Forms\fedf1ba58dced4f0b3f8c457648ceed9\System.Windows.Forms.ni.dll
    *** ERROR: Module load completed but symbols could not be loaded for C:\Windows\assembly\NativeImages_v2.0.50727_32\System.Windows.Forms\fedf1ba58dced4f0b3f8c457648ceed9\System.Windows.Forms.ni.dll
    DBGHELP: System_Windows_Forms_ni - no symbols loaded

  7. Copied symsrv.dll into my local directory even though it is already in the app path.
  8. Restarted, !sym noisy, .reload
    same results
  9. Filter Process monitor where the path ends in symsrv.dll
    Noticed the process is trying to load symsrv.dll from C:\Windows\SysWOW64\
  10. Copied symsrv.dll to C:\Windows\SysWOW64
    HACK!
  11. restarted, !sym noisy, .reload
    success!
  12. .chain
    Shows dbghelp.dll is loaded from c:\Windows\system32\dbghelp.dll
    MSDN writes symsrv must be installed in the same directory as the copy of dbghelp.dll being loaded.
  13. .extpath C:\Program Files (x86)\Debugging Tools for Windows (x86)\
    sets dbgeng's extension search path to where symsrv.dll is.
  14. .chain
    dbghelp.dll is still loaded from the system32 directory.
  15. Prior to running the debugger I set the environmental variable _NT_DEBUGGER_EXTENSION_PATH to C:\Program Files (x86)\Debugging Tools for Windows (x86)\
  16. Restart, .chain
    dbghelp is loaded from the correct directory! but symsrv.dll is still broken :(
    Looking at the stack in Process monitor shows dbgeng.dll is being loaded from C:\Windows\SysWOW64\dbgeng.dll which calls dbghelp.dll in the same directory.
    Changing process monitor to filter for requests ending in dbgeng.dll shows when the program runs it is looking in the executing applications directory for the dll, not finding it, and then loading it from C:\Windows\SysWOW64\dbgeng.dll
  17. Head over to pinvoke.net to grab the loadlibrary signature and put it in my application.

    [DllImport("kernel32", SetLastError = true)]
    static extern IntPtr LoadLibrary(string lpFileName);

  18. Call LoadLibrary from C# prior to loading my debugger and now the correct dll is bing loaded, but the debugging commands don't return any values :(
  19. Reading the LoadLibrary documentation and then the dll search order documentation I think calling SetDllDirectory may do what I want.
  20. Back to pinvoke.net to grab this signature
    [DllImport("kernel32.dll", SetLastError=true)]
    static extern bool SetDllDirectory(string lpPathName); 
  21. Call SetDllDirectory prior to loading up the debugger and everything now works as expected!

Here is the code:

internal static class UnsafeNativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool SetDllDirectory(string lpPathName);
}



public static class Utility
{
    public static void ProvideWindbgInstallationDirectory(string path)
    {
        if (Directory.Exists(path) == false)
        {
            throw new DirectoryNotFoundException(path);
        }
        #warning add a check that the files we want exist.  maybe even that they are the correct version?
        bool setPath = UnsafeNativeMethods.SetDllDirectory(path);
        if (setPath == false)
        {
            int error = Marshal.GetLastWin32Error();
            throw new InvalidOperationException("Setting the installation directory failed with error code: " + error.ToString());

        }
    }
}



Monday, March 29, 2010

Using WindDbg and Sosex to Identify an ASP.NET Deadlock

This post will show how to use create an ASP.NET deadlock and then then use WinDbg and sosex to identify it the idea is to practice this before needing it when debugging a production server :)


The environment for my test is

  1. Create a new web application and put this in the code behind for Default.aspx:
    public partial class _Default : Page
    {
        /// <summary>
        /// Locks on page load.
        /// </summary>
        protected void Page_Load(object sender, EventArgs e)
        {
            object lockA = new object();

            object lockB = new object();

            ThreadStart firstJob = new ThreadStart(() => this.DualLockingCall(lockA, lockB));
            Thread firstThread = new Thread(firstJob);
            firstThread.Name = "firstThread";
            firstThread.Start();

            ThreadStart secondJob = new ThreadStart(() => this.DualLockingCall(lockB, lockA));
            Thread secondThread = new Thread(secondJob);
            secondThread.Name = "secondThread";
            secondThread.Start();

            Thread.Sleep(Timeout.Infinite);
        }

        /// <summary>
        /// lock on a then lock on b.
        /// </summary>
        /// <param name="onThis">The first instance to lock on.</param>
        /// <param name="onThat">The second instance to lock on.</param>
        private void DualLockingCall(object onThis, object onThat)
        {
            lock (onThis)
            {
                lock (onThat)
                {
                    Thread.Sleep(TimeSpan.FromSeconds(5));
                }
            }
        }
    }
  2. Navigate to the url like localhost/projectName/Default.aspx
  3. Open a command prompt and navigate to the debugging tools for windows directory.
  4. Run this command adplus -pn w3wp.exe -o c:\ -hang
    1. The hang command takes a memory dump immediately.  It doesn't wait for a hang.
  5. Note the output directory.  It should be the last line adplus printed out.
  6. Run windbg and press ctrl+d to open a memory dump.  Navigate to the dump adplus just created.
  7. type .loadby sos mscorwks - This will load the sos.dll from the same directory mscorwks was loaded from.
  8. type .load pathToSosex\sosex.dll
  9. type !dlk
  10. On my machine I get this output:
    *** WARNING: Unable to verify checksum for LockItUp.DLL
    *** ERROR: Module load completed but symbols could not be loaded for LockItUp.DLL
    Deadlock detected:
    CLR thread 8 holds sync block 0000000002583228 OBJ:00000001bf1f2be0[System.Object]
                 waits sync block 00000000025831e0 OBJ:00000001bf1f2bf8[System.Object]
    CLR thread 9 holds sync block 00000000025831e0 OBJ:00000001bf1f2bf8[System.Object]
                 waits sync block 0000000002583228 OBJ:00000001bf1f2be0[System.Object]
    CLR Thread 8 is waiting at LockItUp._Default.DualLockingCall(System.Object, System.Object)(+0x25 IL)(+0x74 Native)
    CLR Thread 9 is waiting at LockItUp._Default.DualLockingCall(System.Object, System.Object)(+0x25 IL)(+0x74 Native)

    1 deadlock detected.
Easy cheesy in this simulated test eh?



Monday, March 22, 2010

A Test of My Mettle

I attended my first Toastmaster's meeting last Wednesday and experienced special guest Ed Hearn deliver an inspirational thought provoking speech entitled: "The Courage to Try."

Mr. Hearn gave examples of individuals achieving seemingly impossible goals such as Wilma Rudolph winning three Olympic Gold medals despite having a severe physical disability as a child.  In each example the actors overcame daunting obstacles to realize fantastic goals.

During the speech I was thinking about people's opportunities in life, and how I did not have to struggle to complete High School, College, or get a professional job.  I've taken advantage of opportunities which were nearly given to me, but I haven't made my own opportunities.  Mr. Hearn's speech combined with some of my other experiences have made me realize the goals I attempt in life are low risk or low effort. When I take on large (1,000+ hours) personal projects I give up on the projects after investing a small (10-700 hours) amount of effort into them compared to the amount of effort required for completion.  I'm assuming I give up because I don't see results and overvalue the opportunity cost.  IE.  I think "Hmmmmmm...... I've spent 50 hours on this which I could have spent completing items x,y, and z on my todo list instead.  It may take another 500 hours to achieve my goal, or I may not even be able to achieve my goal," and sack the project.

I am not sure if this new thinking will bear fruit.  Unless I can talk myself into believing there is significant value in the act of trying I'm not sure I can justify the cost of the attempt.  Maybe because I plan for the worst and hope for the best.  Under this mentality I assume the 1000+ hours invested will not achieve the stated objective.

Monday, March 15, 2010

Viewing ASP.NET Adaptive Rendering

I remembered when ASP.NET launched hearing about adaptive rendering.  Using ASP.NET I did not notice different renderings.  This post shows how one can observe ASP.NET adaptive renderings using fiddler.

The software used for this post is:
  • Visual Studio 2008 SP1
  • Windows Server 2008 R2 Standard
  • Fiddler v2.2.8.6 64-bit
  • IE 8.0
  1. Using Visual Studio create a new ASP.NET Web Application
  2. Modify Default.aspx.cs as follows:
    public partial class _Default : Page


    {
        /// <summary>
        /// Ignores this pages children and demonstrates different HtmlWriters based on
        /// different request strings.
        /// </summary>
        /// <param name="writer">An HtmlWriter passed by ASP.NET.</param>
        protected override void Render(HtmlTextWriter writer)
        {
            writer.Write(HttpContext.Current.Request.Browser.TagWriter.ToString());

            writer.AddStyleAttribute(HtmlTextWriterStyle.Color, "Red");
            writer.RenderBeginTag(HtmlTextWriterTag.P);
            writer.Write("Red text");
            writer.RenderEndTag();
        }
    }
  3. Run the project and note the port it runs on.  In my case the url is http://localhost:56449.  Running in IE 8 I see:
    System.Web.UI.HtmlTextWriter


    Red text
  4. In Fiddler enter Request Builder, build and execute the following request:
    Get http://ipv4.fiddler:YOURPORT HTTP/1.1
    User-Agent: Mozilla(compatible;MSIE 3.0;)
     Host: ipv4.fiddler:YOURPORT
  5. In the Web Sessions click the session resulting from your request
  6. Select the Inspectors tab and select TextView.  On my machine I see:
    System.Web.UI.Html32TextWriter<p><font color="Red">Red text</font></p>
    Since we specified our user-agent as MSIE 3.0 ASP.NET is rendering the style color using the font tag.
  7. Go back to Fiddler Request Builder, build and execute the following request:
    Get http://ipv4.fiddler:YOURPORT HTTP/1.1
    User-Agent: Mozilla(compatible;MSIE 4.0;)
     Host: ipv4.fiddler:YOURPORT
  8. In the Web Sessions click the session resulting from your request
  9. Select the Inspectors tab and select TextView. On my machine I see:
    System.Web.UI.HtmlTextWriter<p style="color:Red;">Red text</p>
  10. There are a couple of differences between the two responses:
    1. HtmlTextWriter was used whereas in the previous request it was an Html32TextWriter.
    2. ASP.NET renedered the style color using CSS instead of the font tag.

To undestand the mechanics making this happen 4 Guys From Rolla have an article describing the details.

Above I used ipv4.fiddler instead of localhost to enable seeing traffic sent to localhost as described in Fiddler's help.

Monday, March 1, 2010

Equal but different JavaScript style

Someone at work pointed out this surprise in JavaScript: [] == ![] evaluates to true.

Reading this I thought it was stating something is equal to the opposite of itself.  For non technical readers can things be equal without being the same?  Currencies?  People?  In English do people sometimes say same but mean equal?

In programming worlds things which are not the same might be equal, but I expect something that is the opposite of itself to definitely not be equal to itself.  For example 1 is not equal to !1  I decided to discover how can this be?

I started by firing up a debugger and looking at the types [] and ![].  ![] is false*.

Then I went to the ECMA-262 spec:

Section 11.9.1 The Equals Operator (==) points us to 11.9.3 abstract equality comparison.

11.9.3 The Abstract Equality Comparison Algorithm
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
7.If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).

9.3 ToNumber
  Given Argument Type Boolean The result is 1 if the argument is true. The result is +0 if the argument is false.

Going back to 11.9.3

9. If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.

Section 9.1 ToPrimitive:
For input type Object

Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8.

Section 8.12.8 DefaultValue

When the [[DefaultValue]] internal method of O is called with hint Number, the following steps are taken:

  1. Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".
  2. If IsCallable(valueOf) is true then,
    1. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
    2. If val is a primitive value, return val.
  3. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".
  4. If IsCallable(toString) is true then,
    1. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
    2. If str is a primitive value, return str.
  5. Throw a TypeError exception.

valueOf defined in Section 15.2.4.4 does not return a primitive so toString() from step 3 is called resulting in ""

Going back to 11.9.3

5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.

Section 9.3.1 ToNumber Applied to the String Type informs us that whitespace returns 0.

The leaves us with ([].toString() == "") == (![] == 0) which can be confirmed in a debugger and also results in [] == ![] resulting in true.



*To know why ![] is false
See section 11.4.9

11.4.9 Logical NOT Operator ( ! )
The production UnaryExpression : ! UnaryExpression is evaluated as follows:
1.Let expr be the result of evaluating UnaryExpression.
2.Let oldValue be ToBoolean(GetValue(expr)).
3.If oldValue is true, return false.
4.Return true.

Step 2 ToBoolean(GetValue(expr))
Takes us to Section 9.2 ToBoolean where: for Argument Type Object the result is true.

Monday, February 15, 2010

Integrating FxCop into Visual Studio

At my previous company we had Visual Studio Team System (VSTS) which has Code Analysis (FxCop) integrated into Visual Studio. To turn it on one enters project properties and clicks on the Code Analysis tab. Each rule or group of rules is turned on by checking a box and the rules execute on every build of the project. I love FxCop because it teaches me how to be a better programmer, finds issues in my code, and makes my APIs more user friendly. I was disappointed upon discovering Code Analysis is a VSTS feature.

This post shows two options for getting FxCop 1.36 running on a each Visual Studio 2008 build without maintaining an FxCop project separate from a C# project.  On the downside it will run the same set of rules for each project, which works for my scenario but may pose problems when working with legacy code and new development because the legacy code will probably violate many rules.

This instructions assume the following environment:
  • MSbuild Version 3.5.30729.4926
  • 64 bit Windows (otherwise you will need to modify the C:\Program Files (x86) paths )
  • Visual Studio 2008
  • FxCop 1.36
Option #1: Integrating FxCop at the project level.
  1. Install FxCop
  2. Create a file: C:\Program Files (x86)\MSBuild\v3.5\FxCop.targets with the following text:
    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <!-- Add FxCop to the list of targets to build -->
        <BuildDependsOn>$(BuildDependsOn);FxCop</BuildDependsOn>
      </PropertyGroup>
      <!-- Define the FxCop target we added above.-->
      <Target Name="FxCop">
        <Message Text="$(MSBuildToolsPath)" />
        <!-- The Condition attribute allows others to use the same project file without breaking their build when FxCop is not installed-->
        <Exec Command="&quot;$(ProgramFiles)\Microsoft FxCop 1.36\FxCopCmd.exe&quot; /file:&quot;$(TargetPath)&quot; /console"
              Condition="Exists('$(ProgramFiles)\Microsoft FxCop 1.36\FxCopCmd.exe')" />
        <!-- When running on a 64 bit machine the $(ProgramFiles) variable will be set to c:\program files (x86) when running a 32 bit process which luckily for us Visual Studio and FxCop are.-->
      </Target>
    </Project>
  3. Open the Visual Studio
  4. File --> New Project (ctrl+shift+n)
  5. Select Other Project Types --> Visual Studio Solutions --> Blank Solution
  6. Name the solution FxCopIntegrated and press OK
  7. In Solution Explorer (ctrl +alt + L) right click the solution and select add new project.
  8. Select Visual C# --> Windows --> Class Library
  9. Name the library CodeBandit and press OK.
  10. In Solution Explorer right click the CodeBandit project and click unload project.
  11. If Visual Studio prompts to save files click "yes"
  12. In Solution Explorer right click the CodeBandit project and click "Edit CodeBandit.csproj"
  13. Navigate to the line after this (ctrl+g 52 on my machine):
    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  14.  Add this line to reference the file created in step 2:
    <Import Project="$(MSBuildExtensionsPath)\v3.5\FxCop.targets"/>
  15. In Solution Explorer right click the CodeBandit project and click reload project.
  16. If Visual Studio asks to close CodeBandit.csproj click "yes" and "yes" to save changes if prompted.
  17. Build the solution.  On my system I received these warnings:
    CA2210 : Microsoft.Design : Sign 'CodeBandit.dll' with a strong name key.
    CA1014 : Microsoft.Design : Mark 'CodeBandit.dll' with CLSCompliant(true) because it exposes externally visible types.
Option #2 Integrating FxCop across all solutions.
  1. Follow the steps from Option #1 to ensure everything is setup and working.
  2. Create a file: C:\Program Files (x86)\MSBuild\v3.5\Custom.After.Microsoft.Common.targets with the following text:
    <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <Import Project="FxCop.Targets"/>
    </Project>
  3. Thats it, all the work was performed in Option #1 :)
  4. To verify this add a new VB project to the FxCopIntegrated solution.
  5. Building the newly added project I get these warnings:
    CA1020 : Microsoft.Design : Consider merging the types defined in 'ClassLibrary2' with another namespace.
    CA2210 : Microsoft.Design : Sign 'ClassLibrary2.dll' with a strong name key.
    CA1014 : Microsoft.Design : Mark 'ClassLibrary2.dll' with CLSCompliant(true) because it exposes externally visible types.
    CA1824 : Microsoft.Performance : Because assembly 'ClassLibrary2.dll' contains a ResX-based resource file, mark it with the NeutralResourcesLanguage attribute, specifying the language of the resources within the assembly. This could improve lookup performance the first time a resource is retrieved.
Thanks to John Robbins for pointing me to an old post by the Code Analysis team showing how to call FxCop in a post build event and having it run over the project without maintaining a separate FxCop project file.

Monday, February 8, 2010

Redirecting Assembly Bindings in .NET 3.5

I spent some time last week digging into assembly binding redirection.  This blog post provides working examples of assembly redirection through config files and publisher policies.

This example will use the following command line utilities and versions:
  • csc - C# compiler (Version 3.5.30729.4926)
  • sn - .Strong Name Utility (Version 3.5.30729.1)
  • gacutil - Global Assembly Cache Tool (Version 3.5.30729.1)
  • al - assembly linker (Version 8.00.50727.42)
Helpful utilities not covered in this article:
To get started lets create a program calling a method in another assembly which outputs the callee's assembly version.  These steps also create assemblies for use in the later examples.
  1. Open a Visual Studio Command Prompt and navigate to a working directory where some files can be created.
  2. Create and save a file named demo.cs containing the following text:
    using System;
    using System.Reflection;

    [assembly: AssemblyVersionAttribute("2.0.0.0")]

    public static class Demo
    {
        public static void Main()
        {
            Console.WriteLine("Version " + Assembly.GetExecutingAssembly().GetName().Version.ToString());
        }
    }
  3. from a command prompt type "sn -k key.snk"
  4. type "csc /target:library /keyfile:key.snk demo.cs"
  5. type "echo f | xcopy demo.dll .\bin\v2\demo.dll"
  6. Change the AssemblyVersionAttribute in demo.cs replacing "2.0" with "3.0"
  7. csc /target:library /keyfile:key.snk demo.cs
  8. echo f | xcopy demo.dll .\bin\v3\demo.dll
  9. Change the AssemblyVersionAttribute in demo.cs replacing "3.0" with "4.0"
  10. csc /target:library /keyfile:key.snk demo.cs
  11. echo f | xcopy demo.dll .\bin\v4\demo.dll
  12. Change the AssemblyVersionAttribute in demo.cs replacing "4.0" with "1.0"
  13. csc /target:libarary /keyfile:key.snk demo.cs
    1. echo f | xcopy demo.dll .\bin\v1\demo.dll
  14. Create and save a file named demo.cs containing the following text:
    public static class Program
    {
        public static void Main()
        {
            Demo.Main();
        }
    }
  15. csc /reference:demo.dll program.cs
  16. program.exe
  17. The output should read: Version 1.0.0.0
Redirecting 1.0 to 2.0 version in the GAC.
  1. gacutil /i .\bin\v2\demo.dll
  2. sn -T demo.dll
  3. Note the Public key token as we'll need that in the assemblyIdentity publicKeyToken attribute in the next step.
  4. Create program.exe.config with the following text:
    <?xml version="1.0"?>
    <
    configuration>
     <
    runtime>
      <
    assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <
    dependentAssembly>
        <
    assemblyIdentity name="demo" publicKeyToken="keyFromSn-T" />
        <
    bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
       </
    dependentAssembly>
      </
    assemblyBinding>
     </
    runtime>
    </
    configuration>
  5. program.exe
  6. The output should read:  Version 2.0.0.0
Redirecting 1.0 to Version 3.0 using a URL:
  1. Replace the program.exe.config text with the following:
    <?xml version="1.0"?>
    <configuration>
     <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
         <assemblyIdentity name="demo" publicKeyToken="keyFromSn-T" />
         <bindingRedirect oldVersion="1.0.0.0newVersion="3.0.0.0"/>
            <!--to use an absolute filepath use href="file://D:/Projects/...." or it can point to a web resource: href="http://www.fake.com/demo.dll"-->
         <codeBase version="3.0.0.0" href="./bin/v3/demo.dll" />
        </dependentAssembly>
      </assemblyBinding>
     </runtime>
    </configuration>
  2. program.exe
  3. The output should read: Version 3.0.0.0
Redirecting 1.0 to Version 4.0 in the GAC using a publisher policy.
  1. Create a policy.config file in the working directory with this text:
    <?xml version="1.0"?>
    <configuration>
     <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
        <assemblyIdentity name="demo" publicKeyToken="keyFromSn-T" />
        <bindingRedirect oldVersion="1.0.0.0" newVersion="4.0.0.0" />
       </dependentAssembly>
      </assemblyBinding>
     </runtime>
    </configuration>
  2. al /link:policy.config /out:policy.1.0.demo.dll /keyfile:key.snk
  3. gacutil /i policy.1.0.demo.dll
  4. gacutil /i .\bin\v4\demo.dll
  5. delete the program.exe.config in the working directory or erase the bindingredirect element.  The bindingredirects seem to be applied first and will break this example.
  6. program.exe
  7. The output should read: Version 4.0.0.0