Dennis Burton's Develop Using .NET

Change is optional. Survival is not required.
Tags: fundamentals | tdd | VS tricks

In the previous post, I walk through creating a web site project where C# and Visual Basic code live happily in the same project file. The call to action from that post is to ask "What this could be used for?" If you recall from that post, some of the distinguishing features of VB9 over C#3 are XML Literals and expanded support for LINQ query expressions, but where would I use these features? I can't think of a good reason to use these features in the code-behind of a web site project (user interface layer); places where I would use LINQ expressions, for instance, would be in a business logic layer.

The Process
When creating business logic functionality, under TDD we would first create a test to ensure that this functionality does what it is supposed to. Using a web site project under this process, things start to fall apart. A website project does not create an explicit assembly that you can reference inside of a unit test library project. I created a test assembly in the same solution as the MultiLanguage project. When I tried to add a reference, the project was not available either. Because we can't reference the web project assembly, we can't test it, which means there is no test coverage of the components inside App_Code. This is a deal breaker for me; I sure hope that I have missed something along the way.

The Feature Request
What I really want is a class library project that acts like the website project when it comes to adding files in multiple languages. I want to be able to add a partial class implementation in the primary language of my choice. I also want to be able to add a partial class implementation that will allow me to implement methods on that class in a secondary language if that language is better suited for a particular task. The compiler that works with the website project is clearly able to handle this scenario. How about letting Visual Studio do this for me in class libraries?

Looking Forward
As of right now, the benefits of using both VB and C# do not seem to be worth the cost of the extra steps required to build a testable assembly. C# and VB are not that far apart; there are relatively few differences that make one more effective than the other, and that effectiveness is marginal. That said, there is a lot of language work going on top of the CLR. There are significantly different approaches to problems that are enabled by using a functional languages like F#. There is a totally different set of solutions in the dynamic language world enabled by the DLR in languages like IronRuby and IronPython. I want to be enabled to use the best fit language for a given problem and package up that solution in a way that makes sense. I don't want to have to create a new class library and package that into the deployment process every time I want to use a feature from a different language.

If you are only building web solutions and test coverage is not important to you, than the technique described would meet that need. There is still a hole for non-web solutions. There are still plenty of apps written in WinForms, Console apps, or even just a single library. It would be handy if Visual Studio would provide for the use of this technique in environments other than web site projects.

 

Using C# and VB in the same project file! That is bound to get under the skin of the C# zealots. I prefer to code in C#, but these days it feels much more like I am coding to the framework, other libraries, or CodeRush (if you are addicted like myself). There are times when VB provides some real advantages over C#. Like XML Literals and specific cases of query expressions (like Take and Distinct).

The goal of the sample site
This post will demonstrate how to leverage multiple languages within a website project. The website will contain pages written with both C# and VB as the code behind languages. These pages will contain user controls that have been written in both languages as well as reference classes that have been coded in both languages. All of the pages,user controls, and classes will be contained in a single project file.

Make a website project
From the file menu select New Website... then select ASP.NET Web Site
In the sample I have named the site MultiLanguage. There are several differences between a web site project and a web forms project. An advantage of web site projects that we will take advantage of is the language selection on the web site creation dialog. What I find interesting is that the only effect of this option is to select the code-behind for the default page that is created. In an web forms application, the project file is set up to be bound to the language compiler before the first page is created. Since the goal here is to include different languages in a project, the web site project is the choice for us.

Add web forms
Add two new web forms: one with C# code behind the other with VB code behind. For the sample I chose the names of csPage.aspx and vbPage.aspx, respectively. This is really easy at this point since the new web form dialog has a language drop down that allows the selection of your preferred language. For the sake of feedback, drop a label on the page. In the page load event handler, populate the label with the language used in the code-behind.

<asp:Label ID="languageTag" runat="server" />languageTag.Text="CSharp PageLoad Handler";

Try it out
At this point you should be able to bring up both pages. Already this is quite different than the usual web site. Two pages that are peers in the same project have been created with different languages.

Add user controls
Add two new web user controls: one with C# code-behind the other with VB code-behind. The sample uses the names csUserControl.ascx and vbUserControl.ascx. Just like with the forms, the language can be selected right on the add User Control dialog. Just like in the forms, add a label to the control. Just like in the forms populate the page load event handler for the control with code to update the label with the language used in the code-behind of the control.

<asp:Label ID="languageTag" runat="server" />languageTag.Text = "CSharp UserControl PageLoad Handler";

Include the controls
Drag both controls onto each of the language-specific pages. No additional work is required to wire up events on the controls, as they are self contained for this example.

<uc1:vbUserControl ID="vbUserControl1" runat="server" />
<uc2:csUserControl ID="csUserControl1" runat="server" />

Try it out
Bring up both pages again. Now for any given page multiple languages are being used to render it!

Add classes
So far, the steps involved in adding multiple languages to pages and user controls have been discoverable using Visual Studio directly. Adding classes is slightly different. The App_Code folder will enable us to pull off multipule-language classes. Add an App_Code folder if you do not have one already: right-click the project, select Add ASP.NET folder, there will be an App_Code selection. In the App_Code folder, add a new class for both VB and C#. Here again, the language selection still lets you choose a language. As a quick way to get some feedback from each class, provide an override of ToString that will say what language the class is written in.

public override string ToString()
{
  return "CSharp class text";
}
Public Overrides Function ToString() As String
  Return "VisualBasic class text"
End Function

Add the classes to the forms

Add labels to the language specific pages, one for each language specific class. In the page load event handler for the page, instantiate one instance of each class and populate the new labels on the page with the ToString call from each language specific class

csClass mycsClass = new csClass();
vbClass myvbClass = new vbClass();
csClassText.Text = mycsClass.ToString();
vbClassText.Text = myvbClass.ToString();

However, if you bring up one of the pages you will be greeted with a compiler error that the two classes use a different language.

Adding multiple language support to App_Code
In order to use multiple languages inside App_Code you will need to add sub folders to App_Code. I'll name these folders csharp and visualbasic in the sample. Now, notify the compiler that these subfolders should be compiled separately. This can be done by adding a codeSubDirectories section under the compilation area of the web.config.

<codeSubDirectories>
    <add directoryName="csharp"/>
    <add directoryName="visualbasic"/>
</codeSubDirectories>

Move the VB class from App_Code to this visualbasic folder, and the C# class to the csharp folder, then try to view one of the pages.

Try it out
That is it! Now we have forms, user controls, and classes all using both VB and C# included in the same project file.

 

Now what
The first thing to ask when seeing a new technique, is "What would I use this for?" Being a fan of TDD, I have issues with this approach, but I will leave that for the next post. If you don't care much about test coverage, this technique is useful for leveraging features specific to a language to get the job done as efficiently as possible.

Sample Project

MultiLanguage.zip (7.61 KB)

PowerShell has been a topic of interest for me for the last couple of weeks. I have been squeezing in some reading on it here and there for about a little over a week. Out of the blue pops up one of those issues at work that does not involve making an elegant object model, but rather digging through log files. How cool is that? For more years than I care to mention, it seems like every time I start looking into a new topic, something comes up where that research saves a ton of time. So there I am whipping up this script, when SharpReader pops up toast that says Using an IDE to write PowerShell Scripts. If you are doing any PowerShell work at all, go ahead, stop reading this post and go get that tool. The post will be here when you get back. The PowerShell Suite has been discounted for 2 weeks associated with Hanselman's post and there is a free version for non-commercial use.

The issue was that users in Germany were getting access denied during a particular time frame. Since this time frame overlapped with with the nightly import of user information, there was some speculation that there was a collision with the import. We wanted to get a list of users that got the access denied and correlate that with the users that were imported. That leaves us with 500MB for 2 hours on each server. 8 web servers and a 4 hour period in question yields 16GB worth of log files to mine. Definitely an automation task.

The log file looks like:

... cut
[8364/18416][Tue Feb 07 04:30:27 2008][..\..\..\CSmHttpPlugin.cpp:402][INFO:1] PLUGIN: ProcessResource - Resolved Url '/security/accessdenied.aspx?ReturnUrl=index.aspx'.
[8364/18416][Tue Feb 07 04:30:27 2008][..\..\..\CSmHttpPlugin.cpp:515][INFO:1] PLUGIN: ProcessResource - Resolved Method 'GET'.
[8364/18416][Tue Feb 07 04:30:27 2008][..\..\..\CSmHttpPlugin.cpp:566][INFO:2] PLUGIN: ProcessResource - Resolved cookie domain '.SITENAMEWASHERE.com'.
[8364/18416][Tue Feb 07 04:30:27 2008][..\..\..\CSmHttpPlugin.cpp:3727][INFO:1] PLUGIN: EstablishSession - Decoded SMSESSION cookie -  - User = 'uid=USERIDHERE,dcxdealercode=DEALERCODEWASHERE,ou=dealerships,ou=dealers,o=DOMAINWASHERE.com', IP address = 'IPADDRESSWASHERE'.
... cut

What we needed was the occurrence of access denied where the source was the home page. A few lines later in the log, the SiteMinder user information was present. The script then needs two modes: looking for access denied and looking for the user info. Get-Content provides reader like functionality kicking out a line of text at a time from the provided file. Piping to a ForEach calls the associated script block for each line of text in the file. Use some Regex magic to find the correct string and extract information from the user information line. The result of the Regex is where I ran into the biggest hang up. PowerShell has an automatic variable $matches that is populated when a match is found (when a match is found not when a match is attempted). So I had to clear out the $matches variable after processing a match. The only output was the parsed user information that could be passed off to Brian, who deals with the imports. It is a pretty simple piece of script that looks like:

param ( $infile )

$lookingForSiteMinderCookie = $FALSE
$matches = $null
$Get-Content $infile | ForEach-Object {
  if( $lookingForSiteMinderCookie )
  {
    if ( $_ -match '^.*\[.*\](\[.*\])\[.*\]\[.*\].*uid=([a-zA-Z0-9]*).*dcxdealercode=([0-9]*).*$' )
    {
      "TimeStamp: {0}`tUser: {1}`tDealer: {2}" -f $matches[1],$matches[2],$matches[3]
      $matches = $null
      $lookingForSiteMinderCookie = $FALSE
    }
  }
  else
  {
    if ( $_ -match '^.*accessdenied\.aspx\?ReturnUrl=\%2fhome\%2fmain\.aspx.*$' )
    { 
      $matches = $null
      $lookingForSiteMinderCookie = $TRUE 
    }
  }
}

 

This was saved as LogParse.ps1.

From the PowerShell command line:

  • Use Get-ChildItem with a filter that matches the log file names to get a list of log files
  • Iterate through each file using ForEach calling LogParse.
  • Output to Out-File to save the results

Get-ChildItem logfilepattern | ForEach-Object { .\LogParse $_ } | Out-File deniedUsers.results

I have found PowerShell to be a very useful tool for doing some quick and dirty tasks. I can certainly see that with a bit more experience with it, a command line .net interpreter could be very handy. Check it out. You can rarely go wrong by having another tool in the toolbox. I do want to point out that this was primarily an exercise in using Powershell because I wanted to learn it better. The power of PowerShell is not going to come in text processing where there are many tools that have been around for a very long time that do that job very well. Many shells use strings when piping between commands. Passing a string object is not that much better. The real power comes when you start using objects and even more when you use objects in the pipeline. That is the feature unique to PowerShell.

Tags: fundamentals

Equality comparisons in .net behave differently by default for reference types and value types. Value types are considered equal when the state of the object is equal. Reference types are considered equal when the reference points to the same location in memory. Therefore two different instances of a reference type would not be considered equal even if the internal state is the same. A quick unit test can be used to show this.

The class we will be using for comparison:

using System;

namespace Equality
{
  class MyClass
  {
    public MyClass(int id)
    { _id = id; }

    private int _id;
    public int Id
    {
      get { return _id; }
      set { _id = value; }
    }
  }
}

The test fixture and the first test:

using System;
using MbUnit.Framework;

namespace Equality.Test
{
  [TestFixture]
  class EqualityTest
  {
    const int equalId = 10;
    const int notEqualId = 12;

    MyClass testClass = new MyClass(equalId);
    MyClass equalClass = new MyClass(equalId);
    MyClass notEqualClass = new MyClass(notEqualId);

    [Test]
    public void EqualsMethod_Test()
    {
      Assert.IsTrue(testClass.Equals(equalClass),"Equal by identity but not by reference");
      Assert.IsFalse(testClass.Equals(notEqualClass), "Not equal by either identity or reference");
    }
  }
}

Test Results:

------ Test started: Assembly: Equality.exe ------
Starting the MbUnit Test Execution
Exploring Equality, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
MbUnit 1.0.2700.29885 Addin
Found 1 tests
[failure] EqualityTest.EqualsMethod_Test
TestCase 'EqualityTest.EqualsMethod_Test' failed: Equal by identity but not by reference
MbUnit.Core.Exceptions.AssertionException
Message: Equal by identity but not by reference
...
0 passed, 1 failed, 0 skipped, took 1.63 seconds.

Since all reference types derive from System.Object, we can change this behavior by overriding the Equals method to do a comparison that means something to the object. In this case we will use the Id property. In this case we will first test to make sure that the object being compared is of the correct type and then we will compare the Id property. As an interesting excercise, take a look at the override of the Equals method in the System.ValueType class using Reflector. This implementation uses reflection to determine what items can hold state. It then does a comparison for each item (using .Equals in the end so that reference types work correctly).

  public override bool Equals(object obj)
  {
    MyClass testClass = obj as MyClass;
    return Id == testClass.Id;
  }

Results:

------ Test started: Assembly: Equality.exe ------
Starting the MbUnit Test Execution
Exploring Equality, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
MbUnit 1.0.2700.29885 Addin
Found 1 tests
[success] EqualityTest.EqualsMethod_Test
1 passed, 0 failed, 0 skipped, took 1.59 seconds.

Now we have the Equals method expressing a meaning for equality that matches our understanding of the object. This is a nice advantage when using framework classes such as collections. Collection methods like Contains will call the object's Equals method. Simply overriding the Equals method gives us many more tools to work with without much effort. One thing that should be mentioned here is the warning that occurs when you override Equals. You will be greeted with a compiler warning about overriding Equals without overriding GetHashCode. GetHashCode is defined as a method that should return a number unique for a given instance of the object. We are using the Id property for that purpose.

public override int GetHashCode()
{ return Id; }

Now it would be really nice to have some consitancy with other mechanims used to compare equality. Another common mechanism for equality testing is the == operator. Lets put together a test that will see if this complies with our objects definition of equality.

[Test]
public void EqualOperator_Test()
{
  Assert.IsTrue(testClass == equalClass, "Equal by identity but not by reference");
  Assert.IsFalse(testClass == notEqualClass, "Not equal by either identity or reference");
}

Results:

------ Test started: Assembly: Equality.exe ------
Starting the MbUnit Test Execution
Exploring Equality, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
MbUnit 1.0.2700.29885 Addin
Found 2 tests
[success] EqualityTest.EqualsMethod_Test
[failure] EqualityTest.EqualOperator_Test
TestCase 'EqualityTest.EqualOperator_Test' failed: Equal by identity but not by reference
Message: Equal by identity but not by reference
...
1 passed, 1 failed, 0 skipped, took 1.78 seconds.


 

Looks like we have a bit more work to do. What we need to do now is provide the operator == method. We will take advantage of the fact that we have already defined equality in the Equals method on our object. (Note that this is a static method that takes two objects as parameters. One side effect of this is that you do not get to make static members virtual and override them.)

public static bool operator==(MyClass lhs, MyClass rhs)
{
return lhs.Equals(rhs); }

Now we fire up the test to see where we stand and we are greeted with an error message. The compiler is telling us that when == is implemented that != is required as well. After you finish wondering why someone by default would not implement a != method as an inverse of the == method, implement the != method using our Equals method or the == operator. Of course we need to add some tests to ensure that != is doing what we expect.

[Test]
public void NotEqualOperator_EqualTest()
{
  Assert.IsTrue(testClass != notEqualClass, "Not equal by either identity or reference");
  Assert.IsFalse(testClass != equalClass, "Equal by identity but not reference");
}

public static bool operator !=(MyClass lhs, MyClass rhs)
{ return !lhs.Equals(rhs); }

It can be handy for users of your classes to be able to count on basic functionality such as equality. It also will help leverage the framework for collection operations as well. Another thing very similar in nature to this exercise that will help leverage the framework is implementing the IComparable or IComparable<T> interface.

Other Considerations

In Bill Wagner's book Effective C#, Item 10 mentions some issues with overriding GetHashCode. In short, the framework has a very efficient way to come up with the hash code, in many cases you will not come up with something better. If you do not have something as simple as an identity column that is assigned from the database, consider this advice carefully.

 

Dennis Burton

View Dennis Burton's profile on LinkedIn
Follow me on twitter
Rate my presentations
Google Code repository

Community Events

Windows Azure Boot Camp Lansing GiveCamp