Sunday, June 08, 2008

I've recently developed an application that has the requirement of not having any external dependencies.  That is, the application must, for a variety of reasons, be a stand-alone executable without any bundled libraries or assemblies.  I've already developed some of the functionality that this application relies on, however, and it's packaged in other small utility libraries.  Referencing these assemblies would be the natural decision if I wanted to leverage the functionality.  But that breaks rule #1: no external dependencies.

That aside, I might consider having my project link to the source code files to the other libraries and effectively compile the code directly into my application.  That might, in a simplistic case, be reasonable, but it could easily be unwieldy and unmanagement in the long term.  Not only would I be bringing in classes from another quite distinct namespace into my project, but there's no real tie between them.  A developer may change the source file in a way that breaks my application, introducing other previously-unexpected dependencies.  The list goes on and on as to why this may be a bad idea.

The next thought that came to mind was "What if I were to take the assembly I would otherwise reference and embed it within my application as a resource?  I could load it dynamically at runtime without physically deploying a separate library."  This approach does require a little more work on the part of the developer.

This isn't a new concept.  In fact, several years ago I'd written some code that does this in a similar fashion.  I'm pleased with how effortless it was, however, and I managed to throw this code together in about 20 minutes.

For purposes of the illustration and to test the concept, I created a blank solution in Visual Studio 2008 called 'LoadAssemblyFromResource'.  To this solution, I then added three new projects: EmbeddedAsm (class library), HostAsm (class library), and ClientApp (console application).  EmbeddedAsm is the assembly that will be embedded within HostAsm.  HostAsm represents my application that can't have any external dependencies.  ClientApp is my test harness that will invoke methods on HostAsm.  In turn, HostAsm will leverage the functionality in EmbeddedAsm at runtime.

To set this up, I first created EmbeddedAsm and set the output folder for all configurations to bin\.  Then I and compiled it.  Having functionality within it was not necessary at this point.  Basically, I simply needed to be able to reference the output.  Also, I like having a single target to reference.  This makes the embedding of the assembly easier without having to deal with the bin\Debug or bin\Release directories.

Then, in HostAsm I created a folder called Resources.  I right-clicked that folder and chose 'Add --> Existing Item'.  I then browsed to the bin\ folder of EmbeddedAsm and selected the .dll.  Important: I selected Add As Link rather than Add to add the assembly to the Resources folder.  This enables me to make updates to the EmbeddedAsm and have the HostAsm be updated with the changes.  Were I to have selected Add a copy of the .dll at that point in time would be copied into my Resources folder and any updates to the EmbeddedAsm would be ignored; I'd have to re-add it.

ClientApp has a reference to HostAsm.

The final step in the setup, then, was to right-click on the solution (in the Solution Explorer) and select Project Build Order.  Because HostAsm doesn't actually have a reference to EmbeddedAsm, I had to ensure that it has a dependency on in (on the Dependencies tab).  This forces Visual Studio to compile EmbeddedAsm first so when it gets around to compiling HostAsm it embeds the latest version.

Now for the code...

In EmbeddedAsm I created a very simple class with a simple method:

EmbeddedAsm:

namespace EmbeddedAsm {
   public class CustomClass {
      public string GetMessage() {
         return "Hello from CustomClass";
      }
   }
}

I wrote HostAsm to be a little more robust and reusable.  Rather than simply loading the embedded assembly and running with it, I wanted to put together a bit of a framework (albeit a simple one) to handle the loading of multiple embedded assemblies.

HostAsm:

namespace HostAsm {

   internal static class AssemblyLoader {
      private static Dictionary<string, Assembly> _loadedAssemblies = new Dictionary<string, Assembly>();

      internal static Assembly LoadAssembly(string assemblyName) {
         if ( _loadedAssemblies.ContainsKey(assemblyName) )
            return _loadedAssemblies[assemblyName];

         byte[] bytes;
         using ( Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("HostAsm.Resources." + assemblyName) ) {
            bytes = new byte[(int)stream.Length];
            stream.Read(bytes, 0, bytes.Length);
         }

         Assembly asm = Assembly.Load(bytes);
         if ( null == asm )
            throw new ArgumentException("Unable to load assembly: " + assemblyName);

         _loadedAssemblies.Add(assemblyName, asm);
         return asm;
      }


      internal static Type GetType(string assemblyName, string typeName) {
         Assembly asm = LoadAssembly(assemblyName);

         Type type = asm.GetType(typeName);
         if ( null == type )
            throw new ArgumentException(string.Format("Unable to locate type {0} in assembly {1}", typeName, assemblyName));

         return type;
      }
   }

}

The AssemblyLoader class is responsible for the loading of assemblies (naturally) and for retrieving types from the embedded assemblies.  It loads the assemblies by accessing the manifest resource stream of the executing assembly (HostAsm) and fully-qualifying the reference to the embedded library.  Note that the assembly's root namespace is 'HostAsm' and the embedded library is in the Resources folder so the fully-qualified name to the .dll is HostAsm.Resources.EmbeddedAsm.dll.  We then use the Assembly.Load() method to load the assembly from bytes read from the stream.  The assembly is catalogued so we can avoid quickly retrieve an already-loaded library quickly in a subsequent call.  The assembly is then returned to the caller.

The approach I took for accessing and calling the types in the embedded assembly is that of a proxy class.  In this case, the proxy class is coded such that it's methods match those in the class to be called from the embedded assembly.  Also, I wanted to genericize the proxy type so I could, in a repeatable form, invoke methods on other types in a similar fashion.  Therefore, I created a ProxyClassBase type which abstracts away the reflection plumbing necessary to make the calls.  These classes also exist in the HostAsm library.

ProxyClassBase.cs

namespace HostAsm {

   internal abstract class ProxyClassBase {
      protected ProxyClassBase(string assemblyName, string typeName) {
         InstanceType = AssemblyLoader.GetType(assemblyName, typeName);
         Instance = Activator.CreateInstance(InstanceType);
      }

      protected Type InstanceType;
      protected object Instance;

      protected T InvokePublicMethod<T>(string methodName) {
         return (T)InstanceType.GetMethod(methodName).Invoke(Instance, null);
      }
   }

}

CustomClassProxy.cs

namespace HostAsm {

   /// <summary>
   /// Proxy class for EmbeddedAsm.CustomClass.
   /// </summary>

   internal class CustomClass : ProxyClassBase {
      public CustomClass()
         : base("EmbeddedAsm.dll", "EmbeddedAsm.CustomClass") {
      }

      public string GetMessage() {
         return InvokePublicMethod<string>("GetMessage");
      }
   }

}

Note that the CustomClass proxy class invokes the base class's .ctor which in turn uses the functionality in the AssemblyLoader class to encapsulate the creation and management of the type being wrapped.  The method on the CustomClass proxy matches that of the actual CustomClass in EmbeddedAsm except that it defers to the base class's implementation to invoke the method via Reflection.

Then, within my HostAsm I can simply consume the method as though the object were local and participated in the same namespace of my application:

namespace HostAsm {

   public class Worker {
      public void DoWork() {
         CustomClass cc = new CustomClass();
         Console.WriteLine(cc.GetMessage());
      }
   }

}

It's actually quite easy and straightforward.  Not only does this approach help me in my predicament of not being able to support any external dependencies, but I can see how it might be useful in situations where assemblies are packaged and deployed together, used as Add-Ins, etc.  It's pretty cool stuff.

Sunday, June 08, 2008 3:03:26 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, May 30, 2008

I've had an issue lately that has kicked me around...that is, until tonight :).  I have TFS 2008 set up on a remote server that I connect to via a dedicated VPN tunnel.  This particular server participates in a Windows Server 2003 domain but my workstation is not a member of the domain.  That said, my primary development environment is a Virtual PC machine that is on the domain.  Therefore, the credentials that I use on my VPC are domain credentials whereas the credentials that I use to sign on to my workstation are local credentials - they do not match.

Back when I initially set up TFS connectivity was a snap.  Visual Studio (actually the Team Explorer 2008) on my VPC (being on the domain as though it were local) simply connects to the TFS server and I can work with the data easily.  I wanted, however, to connect to the same TFS server from my workstation.  This proved to be more of a battle than I had expected.

Almost invariably, upon attempting to connect to the server in the Team Explorer I would get the seemingly infamous "TF31002: Unable to connect to this Team Foundation Server".  What was most perplexing was that I did actually connect a couple of times but when I restarted Visual Studio (or even after just a few seconds) my connection was lost.  I could ping the server and I could access the web service (http://servername:port/services/v1.0/serverstatus.asmx) just fine, but I could not connect in Visual Studio.

I spent hours trying every trick I could think of to no avail.  I could not get it to prompt me for credentials (even though I had selected that option in Internet Explorer's Intranet security settings) and my network credentials established for the TFS server didn't seem to have any effect when accessing it through Visual Studio.  I won't go into any more details about how I troubleshot the problem, but suffice it to say, NOTHING I did seemed to affect the outcome at all.

In my research into the problem I saw that a few people had resolved this issue by uninstalling their McAfee AV software.  I don't use McAfee, but I do have AVG 8.0 installed.  This proved to be the tipping point to solving the problem.  I had disabled some AV settings when I was first attempting to fix the issue, but it didn't seem to have any effect so I wrote it off.  Well, it turns out I was just turning the wrong knobs.

The solution that worked for me was this:

    1. Set up a network password (Control Panel - User Accounts) for the remote server using my domain credentials.
    2. Open the AVG User Interface -> Tools -> Advanced settings.
      1. Expand the Web Shield option and select Web Protection.
      2. Uncheck the 'Enable Web protection' option.

After that, everything was smooth as glass. :)

Friday, May 30, 2008 12:32:53 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Sunday, May 25, 2008

This marks a significant moment in my blog's lifetime.  Today I (finally) took the time to migrate the blog from .Text .95 to DasBlog 2.0.

I have long desired to move off of .Text for a couple of reasons: 1) it has long not been updated (yes, I know about the SubText branch) and 2) it runs on the .NET Framework 1.1.  In and of itself, .NET 1.1 is not bad.  In fact, I think it was a pretty rockin' platform 7 years ago.

I have recently acquired Back in February I acquired a replacement server machine for two workstations that I was using as my web servers.  This machine has, except for a few moments of glory, sat still, quiet, and off in my office just waiting to run.  Well, today, I finally had a moment to dedicate to get the DasBlog engine running and all of my websites transferred over to the new machine.  This new box is running Windows Server 2008 and IIS 7.0 (another reason I wanted to move my blog over to the newer .NET platform).  Thus far, DasBlog is running great and I'm very much enjoying it.

Despite conducting several searches I couldn't find any real help or canned utilities for taking .Text content and migrating it.  Pretty much everything led to a dead-end (404) or a SourceForge project that had no downloads.  I did, however, stumble upon some code written by Scott Hanselman that illustrated in a few simple steps the process of creating DasText entries and associated comments.  I've taken this code and tailored it for .Text.  If you're migrating from a .Text with multiple blogs you'll have to customize it a bit, but it should be pretty simple.

I've updated the code to extract blog posts, associated categories, and comments.  NOTE: This code was not written to be elegant; it's really just a brute-forced-extraction of the data.  That's all I was interested in.  I only ever needed to run it once and then I was done.

NOTE: THIS CODE IS OBSOLETE AND HAS BEEN REPLACED AND UPDATED IN THIS POST.

using System;
using
System.Collections.Generic;
using
System.Data;
using
System.Data.SqlClient;
using
newtelligence.DasBlog.Runtime;

namespace
ConvDotTextToDasBlog {
   class Program {
      static void Main(string[] args) {
         IBlogDataService dataService = BlogDataServiceFactory.GetService(AppDomain.CurrentDomain.BaseDirectory, null);
         string connStr = @"Initial Catalog=Blog; Data Source=(local); Integrated Security=True";
         using ( SqlConnection conn = new SqlConnection(connStr) ) {
            conn.Open();
            using ( SqlCommand cmPosts = new SqlCommand("SELECT COUNT(ID) FROM blog_Content WHERE PostType=1; SELECT * FROM blog_Content WHERE PostType=1"
, conn) ) {
               using ( SqlDataReader drPosts = cmPosts.ExecuteReader() ) {
                  drPosts.Read();
                  int totalCount = drPosts.GetInt32(0);
                  drPosts.NextResult();
                  int currIndex = 0;
                  while ( drPosts.Read() ) {
                     int postId = drPosts.GetInt32(0);
                     string postTitle = drPosts.IsDBNull(1) ? string.Empty : drPosts.GetString(1);
                     DateTime dtCreated = drPosts.GetDateTime(2);
                     DateTime dtModified = drPosts.GetDateTime(10);
                     string postText = drPosts.GetString(12);
                     string postAuthor = drPosts.GetString(5);
                     Console.WriteLine("Processing Post #{0} ({1} of {2})"
, postId, ++currIndex, totalCount);
                     Entry entry = new Entry();
                     entry.CreatedLocalTime = dtCreated;
                     entry.ModifiedLocalTime = dtModified;
                     entry.Title = ( postTitle.Length > 0 ? postTitle : postText.Substring(0, Math.Min(20, postText.Length)) );
                     entry.Content = postText;
                     entry.EntryId = postId.ToString();
                     entry.Categories = getPostCategories(postId, connStr);
                     entry.Author = postAuthor;
                     dataService.SaveEntry(entry);
                  }
               }
            }

            using ( SqlCommand cmComments = new SqlCommand("SELECT COUNT(ID) FROM blog_Content WHERE PostType=3; SELECT * FROM blog_Content WHERE PostType=3"
, conn) ) {
               using ( SqlDataReader drComments = cmComments.ExecuteReader() ) {
                  drComments.Read();
                  int totalCount = drComments.GetInt32(0);
                  drComments.NextResult();
                  int currIndex = 0;
                  while ( drComments.Read() ) {
                     int commentId = drComments.GetInt32(0);
                     int refPostId = drComments.GetInt32(13);
                     DateTime dtCreated = drComments.GetDateTime(2);
                     string commentAuthorName = drComments.IsDBNull(5) ? string.Empty : drComments.GetString(5);
                     string commentAuthorIp = drComments.IsDBNull(7) ? string.Empty : drComments.GetString(7);
                     string commentAuthorUrl = drComments.IsDBNull(11) ? string.Empty : drComments.GetString(11);
                     string commentText = drComments.GetString(12);
                     Console.WriteLine("Processing Comment #{0} ({1} of {2})"
, commentId, ++currIndex, totalCount);
                     Comment comment = new Comment();
                     comment.CreatedLocalTime = dtCreated;
                     comment.ModifiedLocalTime = dtCreated;
                     comment.TargetEntryId = refPostId.ToString();
                     comment.Author = commentAuthorName;
                     comment.AuthorHomepage = commentAuthorUrl;
                     comment.AuthorIPAddress = commentAuthorIp;
                     comment.Content = commentText;
                     dataService.AddComment(comment);
                  }
               }
            }
         }
      }

      private static string getPostCategories(int postId, string connStr) {
         string sql = "SELECT cat.Title FROM blog_Links AS links INNER JOIN blog_LinkCategories AS cat ON links.CategoryID = cat.CategoryID WHERE links.PostID = @PostID"
;
         List<string> categories = new List<string>();
         using ( SqlConnection cn = new SqlConnection(connStr) )
         using ( SqlCommand cm = new SqlCommand(sql, cn) ) {
            cn.Open();
            cm.Parameters.Add("@PostID"
, SqlDbType.Int).Value = postId;
            using ( SqlDataReader dr = cm.ExecuteReader(CommandBehavior.CloseConnection) ) {
               while ( dr.Read() )
                  categories.Add(dr.GetString(0));
            }
         }
         return string.Join(";"
, categories.ToArray());
      }
   }
}

For the most part the site is up and running, but I'd like to migrate my rating system and my download manager to this new platform.  This hasn't been straightforward as .Text is SQL Server-based and DasBlog is file-based, so I'll have to come up with something...but I look forward to exploring the DasBlog object model to see if I can come up with something that works.

.NET | DasBlog | Journal
Sunday, May 25, 2008 7:43:52 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Friday, May 09, 2008

I've just posted my code that I wrote both in preparation for my presentation and during my presentation on Windows Communication Foundation at the Utah Spring Code Camp 2008 a few weeks back.  You can download the code here or via the link on my blog page.  I had a great time at Code Camp and look forward to the next one; we've had a blast the last few years putting them on.

I feel this presentation was unique in that I took an approach on WCF that I hadn't seen done before, and being that I had never presented on WCF before I wanted to take a different slant on it.  Rather than start with how to create WCF services and consume them by generating a proxy class and using configuration files (which is a snap really), I started with how to consume WCF services via code and what's entailed.  Knowing it from the code side first helps me appreciate the flexibility, elegance, beauty, and simplicity of the configuration file route.  I think starting to understand WCF from the configuration file route is fine, but you miss out on some of the finer details of how it works.  Both techniques have their places, of course.

Friday, May 09, 2008 4:57:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, May 08, 2008

We were planning on conducting the Utah .NET User Group meeting tonight for our regular monthly meeting, but due to a variety of circumstances, we are needing to postpone our user group meeting to next Tuesday.  This is the email sent out to the group:

We sincerely apologize for the extreme lateness of this notice, but due to a variety of circumstances beyond our control we are needing to postpone our regularly scheduled Utah .NET User Group meeting to this coming Tuesday, May 13th.

We hope that this doesn't disrupt your plans and/or schedules too much and that we can convene at our regularly scheduled time.

Thank you for your understanding.

Please pass the word along to anyone who you may know that was planning on attending the meeting tonight so that we might all be informed of the change

Topic: Windows Server 2008
Date: Tuesday, May 13th, 2008
Time: 6:00 PM
Place: Digital Draw Network - Suite 300 (10897 South River Front Parkway, South Jordan, UT)
Thursday, May 08, 2008 7:42:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, April 27, 2008

I'm adding this simply for future reference, so please ignore if you'd like. :-)

You may have user accounts on your system (XP or Vista) that will never be used to login to your machine.  These may represent accounts created specifically for services or are simply logons for remote users to access otherwise secured resources.  It bugs me that through the Windows GUI you can't determine whether the account should show up on the Welcome Screen.  At least you can't do it intuitively; I hear you can do it through group membership.  Maybe I'll try creating a 'Hidden Users' group and adding users to it, removing them from the 'Users' group.  I don't know if that will work yet.

But you can remove a user from the Vista Welcome Screen simply do the following:

1. Open the registry (regedit.exe) to HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList, creating keys as necessary.  For example, I needed to create both the SpecialAccounts and the UserList key.
2. Create a DWORD value for each user you want to omit/remove from the Welcome Screen where the name of the value is the user name.
3. Assign it a value of '0'.
4. Reboot.

This same technique works in Windows XP, though I'd always used tools like TweakUi to accomplish this.  It's nice to know the manual way.

Sunday, April 27, 2008 4:34:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Tuesday, April 22, 2008

Sticking with the tradition established over the past few years, we have another great opportunity to touch base with developers across the valley at our bi-annual Utah Code Camp.  The Spring Code Camp will be held this coming Saturday (April 26th from 9:00 AM to 5:00 PM) at Neumont University.

These events have been getting better and better each time.  There is a great group of speakers this time as well (not that we didn't have a good group in years past :), but it seems that the Code Camp is diversifying as well - which is great!  We have talks on Scrum, CakePHP, Developing on Windows Vista, TSQL, Windows Services, XNA development, Silverlight, Mac OSX programming, and much more!  Personally, I'll be sharing my thoughts on Windows Communication Foundation (WCF).

So, come on out and join the vibrant Salt Lake community of developers and have a great time!

What: Utah Spring Code Camp
When: April 26th, 2008, 9:00-5:00
Where: Neumont University (10701 South River Front Parkway, South Jordan, UT 84095)
Registration:
http://utcodecamp.com/event/

Tuesday, April 22, 2008 2:02:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Thursday, April 10, 2008

That's right, folks, we have the distinct opportunity to once again gather as a community of developers and share our insights and thoughts on the world of .NET.  The time has come once again to convene for our monthly .NET User Group Meeting!

This month's meeting is sure to be an exciting one as we're hosting a Heroes Community Launch! With this particular event we have volunteers from the group willing to share on three distinct topics: Visual Studio 2008, and SQL Server 2008. You won't want to miss it!

In the coming months we'll take the various topics and do some additional deep dives into each one.

Come on out, invite your friends and co-workers and be among the first to receive technical demos, readiness kits, and much more. We have several Not-For-Resale copies of each product to be given out as well to some lucky winners!

Time: 6:00 PM
Date: Thursday, April 10th, 2008
Place: Digital Draw Network (10897 South River Front Parkway, South Jordan, Suite 300)

 

Thursday, April 10, 2008 4:25:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback