Thursday, December 18, 2008

PMP, Agile and a true development process

Over the last couple of years, the PMP title has become the latest fashion accessory. Unfortunately, most of the people I've dealt with have tried to enforce every facet of PMP on even the smallest of projects. They've also mostly been freshly minted so they really don't have any experience. All this has lead to projects mired in paperwork, behind schedule and ticked-off users.

Over the years, I'd developed my own style that seemed to work and got a few requests to teach my style to some of the other developers. Problem was, I'd never really formalized anything. I just kinda did things. So over the last couple of months, I've been working on formalizing some things.

I spent some time looking into the PMP and can see where it would bring extensive value to lots of projects, especially construction or manufacturing. There are pieces that I can see would be beneficial to the contractual side of software development. The main thought I had was that PMP would never directly lead to even one line of code! Worse, a manager would never be able to tell the true status of a software project really because they don't work like that!

PMP is based on dependencies between segments and developers actively try to avoid those dependencies to prevent fragile designs. It also considers something complete when we know that change is inevitable! The specs from yesterday are not the specs for today. That library we completed yesterday can, and probably will, change today!

I'd looked at Test Driven Development and found I was doing some of the techniques but not all. I'd also looked at Agile software development and found the same thing. Further research into both lead me to a book that pretty much had everything I used. The book? Agile Principles, Patterns, and Practices in C# by Robert C. Martin and Micah Martin.

This book truly lays out a process for developing software. Manager, architect and developer will find what they need. For the developer, it has how to get stared coding now while still being able to answer the managers questions (can you say Status Report). The architect will find all of those things you've been thinking or saying for years neatly. Design patterns, UML, code smells and my favorite, design smells are all found in this book.

The best part about this book is not what is says to do but what it says not to do. The mantra carried throughout this book is "Don't produce a ton of paper that will be thrown away or change tomorrow". Since I hate wasting time on needless documentation, this book spoke my language. Before you say that your bosses will never go along, remember this. I work in a shop that defines anal. They believe that if you don't have it in triplicate, it's not real. This book gave me all the ammo I needed.

Another book you may want to invest in is Head First Design Patterns by Eric Freeman, Bert Bates, Elisabeth Freeman and Kathy Sierra. This presents the top design patterns in a funny manner. Head First books are definitely not your standard tech books.

Between these two books, I have been able to "formalize" my style and have actually enhanced it. Things that I did because they seemed right, I now can name and articulate the reasons for using or not using them.

What about PMP? Is PMP bad? No. Should I avoid PMP like the plague? No, but don't do it because it's cool. Pick the parts of PMP that bring value to the project and use them. If someone can't show or explain the benefit of a document, don't produce it! Most of all, skip the PMP checklist.

Why I switched to C# (or How I Became Darth Vader)

Made the decision to switch to C# recently. The main reason: I got tired of finding code written in C# and then having to convert it to VB. It really wasn't that painful except when I'd forget the parenthesis that VB so nicely takes care of (this should be the first thing you check when you get an error). Maybe it's just because it's new but I actually like it better in most respects.

If you're going to make the same switch, here's some resources:

Julia Lerman's talk on C# for VB Developers

C# Essentials, 2nd Edition is a great book that assumes you are an experienced developer and quickly shows all of the basic functionality you need.

Technorati Tags: ,,

Tuesday, November 18, 2008

Regular expressions for passwords

This is a link post for some of the regular expressions that can be used for passwords.  Specifically, you can use them for asp.net's passwordStrengthRegularExpression. Note: Be sure to test them *before* you use them in a production app. The table indicates the minimum length, if it requires at least one upper, lower, digit or special character.

Expression Len Upper Lower Digit Special
^.*(?=.{10,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$ 10 Y Y Y Y
^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$ 8 Y Y Y N
^.*(?=.{10,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$ 10 Y Y Y Y

Links for Linq Joins

Some articles on joins for Linq. This one is basic, just to grok the concept. This post from CodeGuru is a little more detailed and explains things a little better. Multiple join conditions are shown here. This post uses extension methods to handle a date. I can definitely see some uses for this.

There are some interesting things you can do like finding all of the overlapping date ranges in a table.

Thursday, October 30, 2008

Windows Live Toolbar broke my Shift+Tab

Finally!!!!!! For months my shift+tab in forms didn't work. IE or Firefox. Drove me crazy!!!!! Google a little this morning and found an entry that said uninstall Windows Live Toolbar. Thought "what the heck". It worked!!! Not only did fix IE but it also fixed FF. Don't understand. Don't care. Problem solved.

Monday, October 27, 2008

Adding commas to numbers with JavaScript

I recently went to add commas to a number on entry. Found a nice script here. Only thing I changed was to make it set the value in the function. I also liked his solution for adding separators other than commas.

The code:

function addCommas(obj) {
    nStr = obj.value;
    nStr += '';
    x = nStr.split('.');
    x1 = x[0];
    x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    obj.value = x1 + x2;
}

Tuesday, September 9, 2008

Parse CSV or other separated strings

I had to work with Comma Separated Values (CSV) files the last few days and updated some code to make it easier to use with LINQ. Quick review, a CSV file is a file that has all of the values separated by a comma. Values that contain a comma are typically qualified with double quotes. For example:

Abraham, Lincoln, 03/4/1861, 04/15/1865, "Republican, National Union"

Basically, I created a class called SeparatedString that performs all the functionality of parsing the data so that I can retrieve it later. When the Text property is set it figures out the location of all relevant commas and loads them into a List. The GetValue method then uses the index to grab the occurrence of the comma that precedes the value to be retrieved.

Public Class SeparatedString
    ''' <summary>
    ''' Contains the position of all separator characters in the string
    ''' </summary>
    ''' <remarks></remarks>
    Private _Separators As New List(Of Integer)

    ''' <summary>
    ''' The CSV text of the string.
    ''' </summary>
    ''' <remarks></remarks>
    Private _Text As String
    Public Property Text() As String
        Get
            Return _Text
        End Get
        Set(ByVal value As String)
            _Text = value
            LoadSeparatorPositions()
        End Set
    End Property

    Private _Qualifier As Char = Chr(34)
    ''' <summary>
    ''' The character used to qualify text strings.
    ''' </summary>
    ''' <value>The character used to qualify text strings.</value>
    ''' <returns>Char</returns>
    ''' <remarks>
    ''' <para>
    ''' Default value: double quotes - chr(34)
    ''' </para>
    ''' Depending on the tool used to create the string, text
    ''' strings may be qualified by a character, typically double quotes.
    ''' <example>
    ''' "John", "Quincy", "Adams", 01/01/2003, 54
    ''' </example>
    ''' </remarks>
    Public Property Qualifier() As Char
        Get
            Return _Qualifier
        End Get
        Set(ByVal value As Char)
            _Qualifier = value
        End Set
    End Property

    Private _Separator As Char = ","
    ''' <summary>
    ''' The character used to separate values.
    ''' </summary>
    ''' <value>The character used to separate values.</value>
    ''' <returns>Char</returns>
    ''' <remarks>
    ''' <para>
    ''' Default value: comma
    ''' </para>
    ''' <example>
    ''' "John", "Quincy", "Adams", 01/01/2003, 54
    ''' </example>
    ''' </remarks>
    Public Property Separator() As Char
        Get
            Return _Separator
        End Get
        Set(ByVal value As Char)
            _Separator = value
        End Set
    End Property

    ''' <summary>
    ''' Loads the list of separator positions.
    ''' </summary>
    ''' <remarks>
    ''' The _Separators list is cleared and loaded with the
    ''' position of all separators. If the string is empty
    ''' or contains no unqualified separators, the list is
    ''' empty.
    ''' <para>
    ''' NOTE: Separators found within qualifiers are not
    ''' stored.
    ''' </para>
    ''' </remarks>
    Private Sub LoadSeparatorPositions()
        _Separators.Clear()
        If _Text.Trim.Length < 1 Then Return

        Dim IsInQualifier = False
        Dim CurChar As Char
        'Dim x = 0
        For x = 0 To _Text.Length - 1
            CurChar = _Text.Substring(x, 1)
            If CurChar = _Separator And Not IsInQualifier Then _Separators.Add(x)
            If CurChar = _Qualifier Then IsInQualifier = Not IsInQualifier
        Next
    End Sub

    ''' <summary>
    ''' Gets the value at the specified separator position.
    ''' </summary>
    ''' <param name="occur">The occurance of the separator that begins the value.</param>
    ''' <returns>String</returns>
    ''' <remarks>
    ''' NOTE: The list of values is zero based unless the string begins with a separator.
    ''' <example>
    ''' <![CDATA[
    ''' Dim sStr As New SeparatedString(Chr(34) & "John" & Chr(34) & ", " & Chr(34) & "Quincy" & Chr(34) & ", " & Chr(34) & "Adams" & Chr(34) & ", 07/11/1767, 80")
    ''' Console.WriteLine("First Name: " & sStr.GetValue(0))
    ''' Console.WriteLine("Middle Name: " & sStr.GetValue(1))
    ''' Console.WriteLine("Last Name: " & sStr.GetValue(2))
    ''' Console.WriteLine("Date Of Birth: " & sStr.GetValue(3))
    ''' Console.WriteLine("Age: " & sStr.GetValue(4))
    ''' ]]> 
    ''' ' You'll need to remove the CDATA start and end.
    ''' '
    ''' 'This example produces the following results:
    ''' 'First Name: John
    ''' 'Middle Name: Quincy
    ''' 'Last Name: Adams
    ''' 'Date Of Birth: 07/11/1767
    ''' 'Age: 80
    ''' </example>
    ''' </remarks>
    Public Function GetValue(ByVal occur As Integer) As String
        If occur > _Separators.Count Then Return String.Empty

        Dim start = 0
        If occur > 0 Then start = _Separators(occur - 1) + 1

        Dim value As String = ""
        If occur < _Separators.Count Then
            value = _Text.Substring(start, _Separators(occur) - start)
        Else
            value = _Text.Substring(start)
        End If
        value = value.Trim
        value = value.Replace(_Qualifier, "")
        If value.Length < 1 Then Return String.Empty
        Return value
    End Function

    ''' <summary>
    ''' Creates a new instance of the SeparatedString class.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        Separator = ","
        Qualifier = Chr(34)
    End Sub

    ''' <summary>
    ''' Creates a new instance of the SeparatedString class with the specified value.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New(ByVal value As String)
        Text = value
    End Sub

    ''' <summary>
    ''' Creates a new instance of the SeparatedString class with the specified value, separator and qualifier.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New(ByVal value As String, ByVal sep As Char, ByVal qual As Char)
        Separator = sep
        Qualifier = qual
        Text = value
    End Sub
End Class

You can simply use the class out of the box or inherit from SeparatedString and create a class that has meaningful properties that can be used with LINQ.

Public Class President
    Inherits SDG.FileHandler.SeparatedString

    Public Const FirstNameKey As Int16 = 0
    Public Const LastNameKey As Int16 = 1
    Public Const TermStartKey As Int16 = 2
    Public Const TermEndKey As Int16 = 3
    Public Const PartyKey As Int16 = 4

    Public ReadOnly Property FirstName() As String
        Get
            Return GetValue(FirstNameKey)
        End Get
    End Property

    Public ReadOnly Property LastName() As String
        Get
            Return GetValue(LastNameKey)
        End Get
    End Property

    Public ReadOnly Property Party() As String
        Get
            Return GetValue(PartyKey)
        End Get
    End Property

    Public ReadOnly Property TermEnd() As Date
        Get
            If IsDate(GetValue(TermEndKey)) Then Return GetValue(TermEndKey)
            Return Date.MinValue
        End Get
    End Property

    Public ReadOnly Property TermStart() As Date
        Get
            If IsDate(GetValue(TermStartKey)) Then Return GetValue(TermStartKey)
            Return Date.MinValue
        End Get
    End Property

    Public Sub New()
        MyBase.New()
    End Sub

    Public Sub New(ByVal value As String)
        MyBase.New(value)
    End Sub
End Class

Putting it all together, I simply read the file and create a List. Since LINQ can be used on generic Lists, we're all set. The following reads a CSV file of US Presidents and returns those inaugurated in January.

Dim presidents As New List(Of President)

Using dataFile = New StreamReader(TextBox1.Text)
    Dim line As String
    Do
        line = dataFile.ReadLine
        If line Is Nothing Then Exit Do
        If line.Trim.Length < 2 Then Exit Do
        presidents.Add(New President(line))
    Loop Until line Is Nothing
End Using

Dim query = From p In presidents Where p.TermStart.Month = 1 Select p

GridView1.DataSource = query.ToList
        

You can change the separator and/or qualifier. I made a conscious decision to not provide a  mechanism to override the check for a qualifier. Simply use a character that will not appear in your file to get around this check.

Wednesday, August 13, 2008

Simple Age Calculation

I was helping someone with some SQL stuff and needed to give them a function to calculate someone's age.  Rooted around a bunch of places and finally just ended up re-writing it. So this is mostly to have it on hand. This function is needed because DateDiff doesn't check if they've had their birthday this year.

CREATE FUNCTION CalculateAge (
    @Start smalldatetime
    , @End smalldatetime
    )
RETURNS int
AS
BEGIN
    -- Declare the return variable here
    DECLARE @Age int

    SET @Age = DateDiff(year, @Start, @End)
    IF DateAdd(year, @Age, @Start) > @End
        SET @Age = @Age - 1

    RETURN @Age

END
GO

VB Version

Public Function Age(ByVal value As Date, ByVal d As Date) As Integer
    Age = DateDiff(DateInterval.Year, value, d)
    If value.AddYears(Age) > d Then Age = Age - 1
    Return Age
End Function

Friday, July 18, 2008

Use JavaScript on Controls in a GridView, DetailsView or FormView (even in a MasterPage with an UpdatePanel)

Ever want to use JavaScript on a Textbox inside a DetailsView? It can be done, you just need to overcome two issues. The first is that .Net will change the ID from "MyTextBox" to something like "ct100_ContentPlaceholder1_DetailsView1_MyTextBox". The second is that these controls may exist only in Edit or Insert mode.

The first thing we want to do is get the ID of MyTextbox that gets sent to the browser. Since MyTextbox only exists when we're editing or inserting, we need to use the PreRender event. We check to make sure we are in Edit/Insert mode and then use FindControl to get a handle on the textbox. Next we use the ScriptManager RegisterHiddenField method to register a hidden control that has the ClientID as its value.

Protected Sub DetailsView1_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles DetailsView1.PreRender
    If DetailsView1.CurrentMode = DetailsViewMode.Edit Or DetailsView1.CurrentMode = DetailsViewMode.Insert Then
        Dim txt As TextBox = DetailsView1.FindControl("MyTextbox")
        ScriptManager.RegisterHiddenField(DetailsUpdatePanel, "MyTextboxClientID", txt.ClientID)
    End If
End Sub

There's several important things to notice here. First, ScriptManager is used to register the hidden field since I'm assuming you are in an UpdatePanel. Look at ClientScript to add the hidden field if you're not using an UpdatePanel. Also, notice that I pass the UpdatePanel as the first parameter instead of the page.

Second, we dynamically register the hidden field. We don't use a HiddenField control because it will suffer from the same naming issue in a MasterPage.

Third, the hidden field is named MyTextboxClientID. We'll need that name in the JS to get our control. The following function takes the name and returns the control. If the hidden field or the control aren't present, it returns null. This keeps an error from getting thrown when we're not in Edit/Insert mode.

// Gets a contained control from a
// hidden field.
// name - The name of the hidden field
//        that has the ClientID name.
function GetContainedControl(name)
{
    var hdn = document.getElementById(name);
    if (hdn!=null)
        {return document.getElementById(hdn.value);}
    return null;
}

Here's how to call GetContainedControl. Notice we pass the name of the hidden field and not the name of the control we want to find. We check to see if it's null and we're ready to go.

var txt = GetContainedControl("MyTextboxClientID")
if (txt != null)
{
    // Do stuff here
}

Lastly, I'd recommend using constants for these names so that you don't get a name mismatch between the code behind and the JS. Here's the revised lines to use a constant.

Protected Const MyTextboxClientID As String = "MyTextboxClientID"

Protected Sub DetailsView1_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles DetailsView1.PreRender
    If DetailsView1.CurrentMode = DetailsViewMode.Edit Or DetailsView1.CurrentMode = DetailsViewMode.Insert Then
        Dim txt As TextBox = DetailsView1.FindControl("MyTextbox")
        ScriptManager.RegisterHiddenField(DetailsUpdatePanel, MyTextboxClientID, txt.ClientID)
    End If
End Sub

And the JavaScript.

var txt = GetContainedControl('<% = MyTextboxClientID %>')
if (txt != null)
{
    // Do stuff here
}

Hope that helps!!!!

Thursday, July 10, 2008

LINQ To SQL Row not found or changed

LINQ To SQL likes to use TimeStamp columns for version tracking and makes it really easy to do so. However, in a lot of the apps I work on, we need to keep track of when the last change was made so we have a DateTime column titled LastUpdated. Using Steve Michelotti's post, I was able to use this column for row version control.

Everything was fine until I was using a DetailsView and got the message "Row not found or changed". This message is LINQ's way of telling you that the row has been changed and you now have a concurrence conflict. Since I was working on my local box, I knew this wasn't the case. Pulling up SQL Profiler, I was able to see that LINQ was sending the LastUpdated date back without milliseconds. Since this isn't the same as the value on the row, it thinks a change has occurred.

I was displaying the LastUpdated field in a readonly column so it shouldn't have been getting changed. I also had it as DataKey and could see that it was getting passed back. After farting around for a while, I finally figured out that it was the Label control in the EditItemTemplate that was causing the problem.

<asp:Label ID="Label3" runat="server" Text='<%# Bind("LastUpdated") %>'></asp:Label>

The Bind in the above causes the DetailsView to use the value displayed in the label as the value for the update. Bind is two way even though the column is marked ReadOnly and ReadOnly really means "Don't let the user change anything." It doesn't mean "Don't let the value change."

Simply changing the Bind to Eval solves the problem as below.

<asp:Label ID="Label1" runat="server" Text='<%# Eval("LastUpdated") %>'></asp:Label>

I haven't tested but this should affect all of the other controls such as GridView and FormView.

Cheers

Wednesday, July 2, 2008

JavaScript DateDiff Functions

Here's some JavaScript functions that I use like VB's DateDiff function. I've seen some other functions but most take the number of days and end up close but not quite. They also don't correctly handle leap years.

DateDiffYears will return the number of years between two dates while DateDiffMonths will return the number of months. Couple of things to remember. DateDiffYears is often used to determine age so it won't go negative but will return zero. Thus, if the end date precedes the start date you'll get zero as the result. Also, pass start and end as date objects rather than strings. LAstly, you need DateDiffYears to use DateDiffMonths.

// Calcuates the number of years between two dates.
// start - a date object that defines the beginning of the range
// end - a date object that defines the end of the range
function DateDiffYears(start, end)
{
    var years = end.getFullYear() - start.getFullYear();
    var month1 = start.getMonth()
    var month2 = end.getMonth()
    var day1 = start.getDate()
    var day2 = end.getDate()
    if (month2 < month1)
        {
            years -= 1;
        }
    else
        {
            if (month2 = month1)
                {
                    if (day2 < day1)
                        {years -= 1;}
                }
        };
    
    if (years < 0)
        {years=0;}
        
    return years;
}

// Calcuates the number of months between two dates.
// start - a date object that defines the beginning of the range
// end - a date object that defines the end of the range
function DateDiffMonths(start, end)
{
    var years1 = DateDiffYears(start, end);
    var years2 = end.getFullYear() - start.getFullYear();
    var months = years1 * 12
    var month1 = start.getMonth()
    var month2 = end.getMonth()
    var day1 = start.getDate()
    var day2 = end.getDate()
    if (years1 != years2)
        {
            months = months + 11 - (month1 - month2);
        }
    else
        {
            months = months + (month2 - month1);
            if (day2 < day1)
                        {months -= 1;}
        };
        
    return months;
}

Someday I will combine them into one DateDiff function but that's a later entry.

Technorati Tags: ,,

Friday, June 27, 2008

Use a Web.Config connection string in your Data Access Layer with LINQ To SQL

My applications have the Business and Data Access Layers in a separate DLL from the presentation. This makes it so that everything that wants to do a certain function uses the same library. I also like to pre-compile my web sites which means that I can't get at the app.config if I need to make changes to the connection string (moving around dev, test, prod, etc). I found this from Jason Gaylord that worked quite well.

As I worked with it, I began to think that there might be a better way to handle it. My answer was a simple factory pattern class to create my datacontext. I'd like to claim the idea but I really got it from the Enterprise Application Block's DatabaseFactory.

Instead of adding an additional constructor I simply create a class that has the responsibility for creating the datacontext. The CreateDataContext method determines the correct connection string and then uses the predefined constructors. I place this class in the code file for the LINQ To SQL since I consider it part of the DAL.

Public Class AdventureWorksDataContextFactory    
    Public Shared Function CreateDataContext() As AdventureWorksDataContext        
        Dim connStr = ConfigurationManager.ConnectionStrings("AdventureWorksConnectionString").ConnectionString        
        Return New AdventureWorksDataContext(connStr)    
    End Function
End Class

When I want to create my datacontext I simply need to issue the following command.

Dim dc = AdventureWorksDataContextFactory.CreateDataContext

The nice part about this is that I can have my method do all sorts of stuff and it's neatly separated from the datacontext itself. I can overload the method if needed.

Enjoy.

Subscribe in a reader

Friday, June 13, 2008

Silverlight Master Page

Since I'm old school and so are most of my users, I wanted to create a simple Silverlight app that had a title bar across the top, nav menu on the left side and a content area on the right. You can modify this for mutiple zones and do some pretty slick stuff. Note: This is Silverlight 2 Beta 2 and VS 2008.

image

The key is to create a Canvas that gets changed when you want to make changes. Here's the code for the above -- notice the Canvas named ContentHolder.

<UserControl x:Class="SilverLightMasterPageTest.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:SilverLightMasterPageTest">
    <Grid x:Name="LayoutRoot" Background="#FF5C7590">
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Border Grid.Row="0" CornerRadius="10" Background="#FFDEDEDE" Margin="2,2,2,2" Padding="10,2,10,2">
            <TextBlock Text="Master Page Test" VerticalAlignment="Center" Foreground="#FF14517B" />
        </Border>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="110" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <local:NavBar Grid.Column="0" />
            <!-- This canvas is what we change -->
            <Canvas x:Name="ContentHolder" Grid.Column="1">
                <local:Page1 />
            </Canvas>
        </Grid>
    </Grid>
</UserControl>

We need to use a Canvas because it has the Children property that allows us to add / remove child controls. This code shows how to use the Children property to change the page.

Partial Public Class Page
    Inherits UserControl

    Public Sub New()
        InitializeComponent()
    End Sub

    Public Sub ChangePage(ByVal newPage As UserControl)
        ' Removes the currently displayed page
        ' (including from memory)
        Me.ContentHolder.Children.RemoveAt(0)
        ' Adds our new page
        Me.ContentHolder.Children.Add(newPage)
    End Sub
End Class

The Remove option gets rid of the currently displayed control while Add shows the new control. Now all we need to do is have our navigation bar create the correct control and call ChangePage. This is encapsulated in PagePicker (in the code behind for NavBar).

' Encapsulate the logic to select
' the page to display
Private Sub PagePicker(ByVal pageId As PageIds)
    ' Make sure this goes to the top page so that you can get
    ' to your change method.
    ' We're nested in two grids at this point so we need
    ' to go back up the tree.
    Dim pg As Page = CType(CType(Me.Parent, Grid).Parent, Grid).Parent
    Dim newPage As UserControl = Nothing
    Select Case pageId
        Case PageIds.Page1
            newPage = New Page1
        Case PageIds.Page2
            newPage = New Page2
        Case PageIds.Page3
            newPage = New Page3
        Case PageIds.Page4
            newPage = New Page4
    End Select
    ' This is where the actual change
    ' occurs.
    pg.ChangePage(newPage)
End Sub

Private Sub Page1Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    PagePicker(PageIds.Page1)
End Sub

Private Sub Page2Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    PagePicker(PageIds.Page2)
End Sub

Private Sub Page3Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    PagePicker(PageIds.Page3)
End Sub

Private Sub Page4Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    PagePicker(PageIds.Page4)
End Sub

' Just makes it easy to identify
' our pages
Private Enum PageIds
    Page1
    Page2
    Page3
    Page4
End Enum

Notice that PagePicker has to get a reference to the top control so that it can call ChangePage. This will probably be one of the things that bites you as you change the layout. Here's the XAML for NavBar and one of the pages.

<UserControl x:Class="SilverLightMasterPageTest.NavBar"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <Border CornerRadius="10" Background="#FFDEDEDE" Width="110" Padding="4,4,4,4">
        <StackPanel>
            <Button x:Name="Page1Button" Content="Page 1" Foreground="#FF14517B" 
                BorderThickness="0" BorderBrush="#FFDEDEDE" Click="Page1Button_Click" />
            <Button x:Name="Page2Button" Content="Page 2" Foreground="#FF14517B" 
                BorderThickness="0" BorderBrush="#FFDEDEDE" Click="Page2Button_Click" />
            <Button x:Name="Page3Button" Content="Page 3" Foreground="#FF14517B" 
                BorderThickness="0" BorderBrush="#FFDEDEDE" Click="Page3Button_Click" />
            <Button x:Name="Page4Button" Content="Page 4" Foreground="#FF14517B" 
                BorderThickness="0" BorderBrush="#FFDEDEDE" Click="Page4Button_Click" />
        </StackPanel>
    </Border>
</UserControl>
<UserControl x:Class="SilverLightMasterPageTest.Page1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot">
        <Border CornerRadius="10" Background="AliceBlue" Width="300"
         HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,10,10,10" Padding="5" >
            <TextBlock Text="This is page 1" FontSize="22" />
        </Border>
    </Grid>
</UserControl>

Monday, March 31, 2008

Det. Eddie Ibarra -- Hero of the Week

Det. Eddie Ibarra is my hero of the week. Because he solved a homicide in the First 48? No. Because he's a Steelers fan living in Dallas. He was featured on the show the other night and his cube was covered in Steelers stuff. There was even a picture of his kid in a Big Ben jersey. Way to go Eddie!!!!

How long is it until the first game.

Saturday, March 15, 2008

Easy Way To Maintain Div Scroll Position On Postback

There are numerous ways to maintain scroll position on a postback that all involve javascript coding. Unfortuantely, it always seems like they don't work in the situation I need. Well, the other day, I found a solution that had been right in front of my nose.

I was working on a page where I wanted a list of people on the left and the details for the selected person on the right. The list was inside a scrolling div so they'd be able to scroll the list to find the individual.  Everything was wrapped in an UpdatePanel  and I had everything set up right. However, when a postback occurred -- boom -- back to the top of the div.

My solution was two UpdatePanels. One inside my scrolling div and the other holding the DetailsView. The UpdateMode is set to Always for both so when the update fires in one, it also fires in the other. Since UpdatePanel is inside the scolling div, the div (and its current scroll position don't update).

Viola, a scrolling div that maintains position without any code.

Technorati Tags: ,,

Tuesday, February 19, 2008

Custom Classes and LINQ To SQL Group By

I spent a good part of my morning trying to get LTS to return my custom class as a result of a group by instead of an anonymous type. Most of the samples I found on the web for Group By returned anonymous types and the ones that didn't were from early versions and I couldn't get them to work.

The problem I was having was getting the column I was grouping on mapped. I created a class called DescriptionCounts that had two properties Description (string) and Count (integer). The following code will correctly map a group by query to this class.

Dim i = From b In dc.Barriers _  
  Group By b.BarrierType.Description Into g = Group _ 
  Select New DescriptionCounts _ 
  With {.Description = Description, .Count = g.Count}

The main thing to notice is that when the Description property gets mapped, it is mapped to a variable named Description (.Description = Description -- notice that there is no prefix) and not to a property of b.

All of the aggregate functions require that you use the Group command (g = Group).

Technorati Tags: ,

Wednesday, January 23, 2008

LINQ To SQL Attach

The Attach method allows updating with a disconnected object that has been updated. The easiest version to use is:

Public Sub UpdateCustomer(updatedCustomer As Customer) 
  Dim dc As New MyDataContext() 
  dc.Customers.Attach(updatedCustomer, True) 
  dc.SubmitChanges
End Sub

The only problem is that this generates a SQL statement that updates all columns rather than just the on that has changed. In other words, we used a little extra bandwidth.

The reason this happens is because when we disconnected the object, we lost all the built in change tracking provided by LINQ (for now).

Option 2 is to get a new copy of the object and update it.

Public Sub UpdateCustomer(updatedCustomer as customer)

  Dim dc as New MyDataContext
  Dim originalCustomer as Customer = from c in
    dc.Customers Where c.PrimaryKey =
    updatedCustomer.PrimaryKey

  originalCustomer.CustomerName =
    updatedCustomer.CustomerName
  originalCustomer.CustomerAddress =
    updatedCustomer.CustomerAddress
  originalCustomer.CustomerCity=
    updatedCustomer.CustomerCity
  originalCustomer.CustomerState=
    updatedCustomer.CustomerState
  originalCustomer.CustomerPostalCode=
    updatedCustomer.CustomerPostalCode

  dc.SubmitChanges
End Sub

If you're wondering how this changes anything since it looks like we're changing all of the fields, look at the code LINQ generates for the properties. You'll notice that it checks to see if the value has changed and if so fires all of the update events. If it hasn't, it basically ignores the change.

The problem here is that we have to code all of the updates ourselves. We also have a round trip to the DB to get the object and then another one to update the object.

A slight modification will reduce the code we write but doesn't reduce the number of DB trips.

Public Sub UpdateCustomer(updatedCustomer as customer)
  Dim originalDC as New MyDataContext
  Dim originalCustomer as Customer = from c in
    dc.Customers Where c.PrimaryKey =
    updatedCustomer.PrimaryKey

  'We have to disconnect the original object
  ' from it's datacontext
  originalDC = Nothing

  Dim newDC as New MyDataContext
  newDC.Customers.Attach(updatedCustomer,
    originalCustomer)
  newDC.SubmitChanges

End Sub

This version of Attach uses the original version to compare against the new version for changes.

If we want to send only the changes we have made and avoid the extra round trip, we need to store a copy of the object before changes are made.

This example is very basic so that you can get the idea.

Public Class Customers

Private Const CustomerCacheKey As
  String = "Customer{0}"

Public Function GetCustomer(customerKey as integer)
  As Customer

  Dim CacheKey As String = String.Format
    (CustomerCacheKey, customerKey.ToString)

  Dim dc As New MyDataContext

  GetCustomer = (From c In dc.Customers Where 
    c.CustomerKey = customerKey Select
    c).SingleOrDefault
  HttpContext.Current.Cache.Add(CacheKey, GetCustomer,
    Nothing, DateTime.Now.AddSeconds(60),
    Cache.NoSlidingExpiration, CacheItemPriority.High,
    Nothing)
End Function

Public Sub UpdateCustomer(updatedCustomer as Customer)

  Dim CacheKey As String = String.Format
    (CustomerCacheKey,
    updatedCustomer.CustomerKey.ToString)

  Dim dc As New MyDataContext
  Dim originalCustomer As Customer =
    HttpContext.Current.Cache(CacheKey)

  dc.Customers.Attach(updatedCustomer, 
    originalCustomer)
  dc.SubmitChanges
End Sub

End Class

The real problem -- you've got to code all over everywhere to handle keeping a copy. If you're in a GridView or other list control, you don't know which one they picked and do you really want to cache a copy of every object. The above example is very weak because the cache is shared by everyone and can become stale very quickly. Someone may update the copy stored in the cache by calling your business layer method (of course you could use the cached copy instead of getting an updated version). I think you get the idea -- we're increasing the complexity in a different layer of our application. Can you say maintenance nightmare?!

So, when is it appropriate to use the different mechanisms? Here's some guidelines (remember, only you know your situation and can determine what's best).

1. Use the basic Attach(MyObject, True) in most cases. It presents the simplest code and is the most easily maintained. The only real cost is the extra data that goes over the wire.

2. Use option 2 where concurrency concerns out weigh the cost of the extra round trip. This allows a better level of control and flexibility and is just plain easier to code and maintain.

3. The last option is best if the payload of the generated SQL update statement above is too large. I would ask if the update payload is so large, doesn't that mean that your cache copy is really large also and now you have two copies?!

Just remember whatever you decide, do it because you have reviewed the options and made a decision as to what fits your environment best.

Oh, what about the basic Attach(MyObject) method? Well, all you're doing here is attaching the object and telling LINQ that it is in the original state. So, when you SubmitChanges nothing will go back to the DB because as far as LINQ is concerned nothing has changed.

Tuesday, January 22, 2008

Visual Studio 2008 & SPAN tags

Visual Studio doesn't seem to like SPAN tags. I've already reported a bug where it adds duplicates SPAN tags to templated fields in a GridView or DetailsView that is within a span tags. Now, it's moving my span tags around.

I had two span tags to define left and right columns with inline-block. They were contained within the same div. Somehow, VS decided that the left span belonged to the div above and moved it all on it's own. If it keeps happening, I'll see if I can narrow the circumstances down and get a bug report filed.

Thursday, January 17, 2008

VS 2008 GridView Rendering Bug

Found an annoying bug in VS today. If you have a GridView contained in a Span tag on a ContentPage and edit the fields, VS will add span tags around the fields you change. Of course, when your done making your changes and click OK, you'll get a rendering error. Simply edit the source and remove the spurious span tags.

LINQ To SQL OnValidate doesn't fire

Technorati Tags:

I spent half an hour tracking this down today and couldn't find it based a google search of the blog title. So.....If your OnValidate code isn't firing in your LINQ to SQL, your probably working with an out-of-date signature. Somewhere between Beta 2 and RTM, they snuck in a slight change by adding a parameter to indicate what kicked off the method.

Make your signature similar to:

Private Sub OnValidate(ByVal action as ChangeAction)

The ChangeAction enumeration values are Insert, Update, Delete and None. So if you need special code for the insert versus the update, you can code an IF statement appropriately.

You can find all of the breaking changes here.

Tuesday, January 15, 2008

Steelers out, rooting for anybody but the Pats

They just have gotten so arrogant. They're good but I don't they're that good. They did beat some good teams but usually when those teams were struggling (SD in particular). I might feel differently if they were a little humble about it.

On the other hand, I'd love to Favre get another ring. He's been a great player and stayed with the team/town that made him. Can he beat NE? If they played in the frozen tundra, definitely yes. Neutral field, maybe.

Sunday, January 13, 2008

LINQ To SQL, Business Layer and LinqDataSource (Round 2)

Played around with the LDS and can really only see using the LDS for really thin apps where you don't want a lot of structure (maintenance tools, prototypes, etc.) Of course, this is the same time I get my Visual Studio magazine and it covers the topic very well. Read Roger Jennings article on what's missing in VS 2008, the LINQ to SQL section talks about this issue in depth.

Meanwhile, I found this great blog from Rick Strahl on returning IQueryable that will do a lot of what I want and still get the benefit of only pulling back what I want.

Monday, January 7, 2008

LINQ To SQL, Business Layer and LinqDataSource

In playing with LTS I keep coming back to the question "Should the DataContext be exposed to the presentation so that you can use the LinqDataSource?" Does this violate the separation of layers by moving business (domain) logic into the presentation tier?

Dinesh and Scott Guthrie seem to advocate exposure but I could be interpreting them wrong. On the flip side, Rick Strahl posted some stuff that would seem to say no. Note: I do see that he is more concerned with a disconnected state than with the datacontext).

My current thoughts are that this is a six of one/half dozen issue. Seems like I get the business rules (validation, etc) I want separated from the data. Should be maintainable. So for now, I'm going to use it on some legacy applications that are well defined where this would be a definite time saver. We'll see....