How VB.Net can screw up a newbie without them knowing it.

Posted on March 17, 2009 
Filed Under Uncategorized | Leave a Comment

Let me preface this by saying I have no hate for VB.Net. When used properly, it is just as good as C#. However, VB as a whole has gotten a bad rep because generally the people that flock to it are the ones with no formal programming experience. Thus, they don’t really always know what they are doing. I’ve seen it a lot. I will also admit that I have done a lot of these horrendous things that I should have never done. In fact, this post is about something I did that turned out way bad when we fast forward a few years later (yes, it took that long for me to find this problem.)

Here is the problem and the solution I came up with over two years ago. We have a very large MDI application that has a lot of different forms. We didn’t want users opening up multiple copies of those forms, plus we wanted a neat way to bring certain forms to the foreground if they were already open.

My simple and what I thought was ingenious solution was to create a few functions that just check to see if the form is open… Something like this:

  Public Function IsFormOpen(ByVal oFormName As Form) As Boolean
    Dim bAlreadyShown As Boolean = False
    Dim obj As Form
    For Each obj In Me.MdiChildren
      If obj.Name = oFormName.Name Then
        bAlreadyShown = True
      End If
    Next
    Return bAlreadyShown
  End Function

When I wrote that and it worked, it made sense to me that it would just pass in the type and I could get the name of the type and it would match and the world would be happy…

Now fast forward 2 years and we are getting some complaints of the application/whole computer being slow after running for a while. This smelled of memory leaks (which we have had lots of in the past). So I started doing my memory leak testing and now I am seeing all of our forms still being held open when they should be closed!

So turning off just my code debugging and putting a breakpoint in InitializeComponent() told me a big huge story. A story of how VB “helps” a developer by just doing things for them without them knowing. It seems that before going into the IsFormOpen function call, VB creates an object of that form type and passes in that object. That object never gets destroyed, either. Dispose on that object only gets called when the application exits. Nice, huh? So every time we open a form, we actually are creating two and only opening one. One simple change fixed it:

  Public Function IsFormOpen(ByVal oFormName As System.Type) As Boolean
    Dim bAlreadyShown As Boolean = False
    Dim obj As Form
    For Each obj In Me.MdiChildren
      If obj.Name = oFormName.Name Then
        bAlreadyShown = True
      End If
    Next
    Return bAlreadyShown
  End Function

Incidentally, C# wouldn’t allow the first function to happen. It won’t even compile in any way, shape, or form (at least in the way I was trying to use it). It tells me the obvious, I was trying to use a Type as a variable. VB “helps” you with that…

Linq to SQL in web services

Posted on March 2, 2009 
Filed Under .Net Code, linq, Web Development | 2 Comments

Quite a while ago, I investigated the use of Linq to SQL in our one web service component that feeds a CMS that we package with out software. In my initial investigations, every time I would send an entity through the web service, I would get a circular reference error. This made sense since each relationship is bidirectional. If I have a family entity that has a relationship to a person entity, that person entity also has a reference to the family entity. This goes on infinitely. Those relationships are nice because you can grab one single person and then get data out of the family entity without having to do any joins or write a separate query. So what do you do?

There are a couple of options.

1. Make the data context serialization unidirectional. This will turn off all relationships. You will have to manually do your joins using Linq when the data reaches the other side of the transaction.

2. Change the parent access modifier of the relationship to Friend (VB) or Internal (C#). This leaves the child relationships intact but removes the parent relationships. There would no longer be a family relationship in the person entity.

I prefer option 2 simply because then I don’t have to rejoin my data every time I fetch it. I don’t need those child to parent relationships because I can build around it by making my web service functions return the family instead of just returning a person, even if I just request the person.

The next issue that I ran into was the updating existing records. Since adding new records takes a detached object and just inserts it as new, that isn’t an issue. The problem comes in the concurrency tracking that Linq to SQL uses. You either time stamp your record, or you allow it to check previous fields for concurrency. We don’t use the time stamp method. The way that most things work in the SQL world is that when you save, you either overwrite what is currently in the table, or you get an concurrency error that makes you input everything over again after getting a refresh from the database. This is not the way we like it. We like a merge scenario where I can make a couple changes to the record and someone else can make some other changes and we the save is made, both our changes make it to the database.

So, how do you fix this? It’s easy with Linq to SQL. It already has the merge saving built in, unlike ADO.Net. (The Entity Framework will do this as well if you set it up right.) Even though Linq to SQL already supports the saving, this won’t work in web services. The problem is that during serialization, the entity loses it’s data context. It’s the data context that actually tracks the changes in the record, not the entity itself. So once the data context loses it’s entity (gets detached), it has to be reattached in order to do the save. The problem with this is that the data context no longer knows what has changed in this entity, so it cannot do any concurrency validation. The key is to use the Attach method and fill in two of it’s parameters, one for current entity and one for the original entity. There are a couple ways you can do this, but I choose to add a base class where I could store the serialized entity (complete with children) and ship it back and forth.

No matter how you slice it, you are going to have the overhead of change tracking eating up bandwidth to and from the web service to the calling application. This can either be incredibly apparent by holding your original values in the session state and just passing it back to the web service, or you can serialize it in the entity object and just pull it out later. I use this method for simplicity. I actually just make a business class (actually, I already had one because all my validation is stored there in a generic, fun sort of way that allows it to just interact with IDataError) and in that business class, I just add a object holder and some functions to set the original value.

Here is a small sample of the base business class:

Imports System.Xml.Serialization

Public Class BaseBusiness

  Private original As Object

  Public Sub SetOriginalValue(ByVal _OriginalValue As Object, ByVal originalType As System.Type)
    Dim sb As New StringBuilder()
    Using strw As New System.IO.StringWriter(sb)
      Dim SXO As New XmlSerializer(originalType)
      SXO.Serialize(strw, _OriginalValue)
      original = sb.ToString()
    End Using
  End Sub

  Public Function HasOriginalValue() As Boolean
    Dim bOriginal As Boolean = False
    If original IsNot Nothing Then
      bOriginal = True
    End If
    Return bOriginal
  End Function

  Public Function GetOriginalValue(ByVal originalType As System.Type) As Object
    If HasOriginalValue() Then
      Dim sXml As String = CStr(original)
      Dim fam As Object
      Using strr As New System.IO.StringReader(sXml)
        Dim SXO As New XmlSerializer(originalType)
        fam = SXO.Deserialize(strr)
        Return (fam)
      End Using
    Else
      Return (Nothing)
    End If
  End Function
End Class

Notice the use of System.Type, this allows me to pass in any data type and serialize it on the fly without having to have special code in my entity class. This makes it appropriate for any class I need to track changes in, even in the entity framework if I so desire.

Here is the top level entity class where I use it:

Imports System.Xml.Serialization

Partial Class Family
  Inherits BaseBusiness

  Private Sub OnLoaded()
    OriginalValue = Me
  End Sub

  Public Property OriginalValue() As Family
    Get
      Return CType((Me.GetOriginalValue(GetType(Family))), Family)
    End Get
    Set(ByVal value As Family)
      Me.SetOriginalValue(value, GetType(Family))
    End Set
  End Property
End Class

That is the only code that will need to be in the entity class in order for it to work. The best part of this is when the data gets shipped back and forth, it’s always accessible as the original entity, not a serialized XML string.

This was just a quick test I did when investigating this stuff today. Eventually, I am going to try to minimize the code in both classes some more and see if I can even strip out the XML Serialization to string and just store it as binary data.

ASP.Net MVC versus Web Forms

Posted on February 5, 2009 
Filed Under Web Development | Leave a Comment

We are going to be converting a fairly good sized project over to ASP.Net soon. The process of planning has already started. I have been investigating our options and trying to decide what would work best for us.

I checked out ASP.Net MVC first because that seems to be all the rage right now. I can definitely see why people are excited. I had a fully functioning site up and running in no time. It’s definitely a great framework and introduces a lot of very nice features. I love the testability it introduces into the whole equation, even the view.

What I don’t like, is the fact that it doesn’t support 3rd party controls. With the exception of Telerik and possibly Infragistics, others are SOL right now until the component vendors get their components up to speed. I know Jeff Handley had posted a few articles about extending the base controls, but I’m talking about some serious controls, like Dev Express’s grids and asp editors. I know there are options, but we will never ditch Dev Express. They have been too good to us. When I can e-mail the CTO with an off the wall management question and have him respond within 8 hours, and submit a message to a general e-mail with some questions about their plans and get a phone call from them within 8 hours, that’s not only a company that makes a great product, but a company that cares (shameless plug for you guys Julian and Mehul).

So I started to investigate other options. When searching for some silverlight info, one of my dudes discovered Visual WebGui. Visual WebGui was pretty slick. You drag and drop onto what looks like a Windows Form and where you drop it is where it renders when viewed through IE or Firefox. They also had a small tutorial on how to take a regular Windows forms application and quickly convert it to a full blown ASP.Net web application. This would have been awesome, except we don’t use Windows Forms controls for anything, not even our forms. Everything is from Dev Express, so it doesn’t convert so hotly. I did some more research and you can get those 3rd party controls to work, but it’s not the greatest or easiest to work with. Their quick solution would eventually turn into a code nightmare. So much for that.

Looks like it’s time to dig out the new routing engine in ASP.Net and start wiring up some of my own code. I really wanted to use MVC, but if the UI is going to suck, our users will absolutely hate us.

Wix

Posted on January 9, 2009 
Filed Under .Net Code, CSharp, Windows Deployment, Wix, XML | 2 Comments

In finally get CruiseControl.Net set up to not only automate our build process, but notify us of any breaking changes on checkin, I discovered some issues in using the standard setup and deployment projects. Not wanting to spend a lot of time on it, I decided to look into Wix and see how it could help me streamline things.

It turns out Wix is a whole lot more work. Instead of a simple point and click interface, I’m presented with hand editing the XML file. Boo.

There are a few decent open source projects to give me a GUI interface so I can get my point and click back, but they turned out to either be for Wix 2 or they had some serious bugs in them.

In the end I used Dark to script my MSI file. While this worked out ok, Wix 3 wouldn’t compile the script without me making some changes. I dug through the script it created and I started to wonder how I was going to automate this. The trick is really not to automate it. The trick is to set up your script once and then leave it alone. Modifications are only made when things change in your build. That’s fine, but what about setting it up for the first time? You have to create a GUID for each component (read, every file if you want the files updateable) that gets installed. This can be time consuming if you have quite a few ancilary files that need to be shipped with the release.

Well, I automated that part. I created myself a small console application that will take a folder and a output file name as arguments. The program will traverse this folder and create an include script. It treats every file, directory, and directory of files as individual components.

In case anyone wants to do this but doesn’t want to take the time to write all the code, here it is.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Reflection;

namespace CreateIncludeScript
{
    class Program
    {
        static int Main(string[] args)
        {
            if (args.Length < 2 || args.Length > 2)
            {
                return (-1);
            }
            string folder = args[0];
            string outputfile = args[1];
            XmlDocument xmlDoc = new XmlDocument();
            XmlNode root = xmlDoc.AppendChild(xmlDoc.CreateElement("Include", "http://schemas.microsoft.com/wix/2006/wi"));

            XmlNode dir = root.AppendChild(xmlDoc.CreateElement("Directory", "http://schemas.microsoft.com/wix/2006/wi"));
            SetAttr(xmlDoc, dir, "Id", "TARGETDIR");
            SetAttr(xmlDoc, dir, "Name", "SourceDir");

            ProcessDirectory(xmlDoc, dir, folder);

            XmlNode feat = root.AppendChild(xmlDoc.CreateElement("Feature", "http://schemas.microsoft.com/wix/2006/wi"));
            SetAttr(xmlDoc, feat, "Id", "DefaultFeature");
            SetAttr(xmlDoc, feat, "Level", "1");
            SetAttr(xmlDoc, feat, "ConfigurableDirectory", "TARGETDIR");
            ProcessFeatures(xmlDoc, feat, dir);

            xmlDoc.Save(outputfile);
            return (0);
        }

        static void SetAttr(XmlDocument doc, XmlNode node, string name, string value)
        {
            XmlAttribute attr = node.Attributes.Append(doc.CreateAttribute(name));
            attr.Value = value;
        }

        static void ProcessDirectory(XmlDocument xmlDoc, XmlNode target, string directory)
        {
            string[] files = Directory.GetFiles(directory);
            string[] dirs = Directory.GetDirectories(directory);

            foreach (string file in files)
            {
                FileInfo finfo = new FileInfo(file);

                XmlNode comp = target.AppendChild(xmlDoc.CreateElement("Component", "http://schemas.microsoft.com/wix/2006/wi"));
                string guid = System.Guid.NewGuid().ToString().ToUpper();
                string id = "C_" + guid.Replace("-", "");
                SetAttr(xmlDoc, comp, "Id", id);
                SetAttr(xmlDoc, comp, "Guid", "{" + guid + "}");

                XmlNode fnode = comp.AppendChild(xmlDoc.CreateElement("File", "http://schemas.microsoft.com/wix/2006/wi"));
                SetAttr(xmlDoc, fnode, "Id", "F_" + guid.Replace("-",""));
                SetAttr(xmlDoc, fnode, "Name", finfo.Name);
                SetAttr(xmlDoc, fnode, "KeyPath", "yes");
                SetAttr(xmlDoc, fnode, "DiskId", "1");
                SetAttr(xmlDoc, fnode, "Source", finfo.FullName);

                if (finfo.Extension.ToLower() == ".dll" || finfo.Extension.ToLower() == ".exe")
                {
                    if (IsDotNetAssembly(finfo.FullName))
                    {
                        SetAttr(xmlDoc, fnode, "Assembly", ".net");
                        SetAttr(xmlDoc, fnode, "AssemblyManifest", "F_" + guid.Replace("-", ""));
                        SetAttr(xmlDoc, fnode, "AssemblyApplication", "F_" + guid.Replace("-", ""));
                    }
                }
            }

            foreach (string dir in dirs)
            {
                DirectoryInfo dinfo = new DirectoryInfo(dir);

                XmlNode dnode = target.AppendChild(xmlDoc.CreateElement("Directory", "http://schemas.microsoft.com/wix/2006/wi"));
                SetAttr(xmlDoc, dnode, "Id", dinfo.Name.Replace("-",""));
                SetAttr(xmlDoc, dnode, "Name", dinfo.Name);

                ProcessDirectory(xmlDoc, dnode, dinfo.FullName);
            }
        }

        static void ProcessFeatures(XmlDocument xmlDoc, XmlNode target, XmlNode root)
        {
            XmlNamespaceManager xmlmgr = new XmlNamespaceManager(xmlDoc.NameTable);
            xmlmgr.AddNamespace("wi", "http://schemas.microsoft.com/wix/2006/wi");
            XmlNodeList comps = root.SelectNodes("descendant::wi:Component", xmlmgr);

            foreach (XmlNode node in comps)
            {
                XmlNode compfeat = target.AppendChild(xmlDoc.CreateElement("ComponentRef", "http://schemas.microsoft.com/wix/2006/wi"));
                SetAttr(xmlDoc, compfeat, "Id", node.Attributes.GetNamedItem("Id").Value);
                XmlNode file = node.SelectSingleNode("wi:File", xmlmgr);
                if (file.Attributes.GetNamedItem("Assembly") != null)
                {
                    SetAttr(xmlDoc, compfeat, "Primary", "yes");
                }
            }
        }

        static bool IsDotNetAssembly(string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                try
                {
                    using (BinaryReader binReader = new BinaryReader(fs))
                    {
                        try
                        {
                            fs.Position = 0x3C; //PE Header start offset
                            uint headerOffset = binReader.ReadUInt32();
                                                         fs.Position = headerOffset + 0x18;
                            UInt16 magicNumber = binReader.ReadUInt16();
                                                         int dictionaryOffset;
                            switch (magicNumber)
                            {
                                case 0x010B: dictionaryOffset = 0x60; break;
                                case 0x020B: dictionaryOffset = 0x70; break;
                                default:
                                    throw new Exception("Invalid Image Format");
                            }

                            //position to RVA 15
                            fs.Position = headerOffset + 0x18 + dictionaryOffset + 0x70;

                            //Read the value
                            uint rva15value = binReader.ReadUInt32();
                            return (rva15value != 0);
                        }
                        finally
                        {
                            binReader.Close();
                        }
                    }
                }
                finally
                {
                    fs.Close();
                }
            }
        }
    }
}

← Previous PageNext Page →