Monday, July 12, 2004

Back in the days of being a trainer, I used to stand all day long.  Back in those days I was accustomed to it.  I'm spending all day in the booth and while it is great and exciting to talk to people about our killer app my feet are dying.  Note to self: next time at a conference wear shoes that not only fit and aren't a 1/2 size too small, but are comfortable also.

Monday, July 12, 2004 3:30:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback

Here I am, continuing in Toronto at the Microsoft Worldwide Partner Conference.  This has been awesome!  While I have a hard time with the 'sales' side of the house (being almost exclusively dev) it is awesome to see the interest and rallying support around our system.  Our web product is receiving rave reviews from customers as well as some very high marks from the CRM team.  We met with Jason Hunt (Technical Product Manager for CRM) and he loves the product.  Additionally, we've met with Brenda Smestad, Barry Givens (who gives us very high reviews and lots of kudos), Graham Sheldon, and others.  It has been awesome!

I've also continued to run into people that I know - which is great!  Today I saw (though he didn't see me) a great friend Dan Peay of Microsoft.  I hope to run into him later and catch up.  Also ran into Michael Reagan (now with Microsoft New York) - what a great guy!  I really remember having a blast with these guys from MS and it really means a lot to me that they not only remember me but have come back to talk and recall the good ol' days.  These are good times!

Monday, July 12, 2004 5:17:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, July 10, 2004

Well, my conference here in Toronto is, to say the least, has been eventful.  To start off, I owe a lot of thanks to customs in Canada.  I didn't realize upon coming that I needed a passport.  It may have been pure dumb luck or whatever, but before coming I remembered to grab mine.  Unfortunately, my passport has been expired for 3 years.  I got a big fat warning prior to leaving that if customs didn't want to let me in the country, I'd spent the night in a cell and be deported.  Needless to say, this had me a little worried upon entering the country.  Well, when I nonchalantly handed my passport and papers to customs the lady (a mighty severe-looking woman I might add) gave my passport a double check, thought about it for a bit, and then just let me in! Whew!  She only asked me what I was in the country for.  That was a relief.

I'm pretty excited to be here at the Microsoft Worldwide Partner Conference.  Though it's very sales-oriented (something that is very hard for me to deal with), I'm having a good time.  We spent the day setting up our booth and meeting with MS partners.  There is a lot of interest revolving around product configurators which is very reassuring and exciting.  I see lots of potential with our product and am extremely excited about our future prospects.

Upon making preparations for this trip/conference I suspected that I might run into friends here - I always do when I come to conferences.  As it turns out my luck still runs hot!  Today I ran into two good friends and associates from my days at Microsoft: Larry Shaw and John Herman.  Additionally I encountered Kristi Schwartz.  It was pretty invigorating to see my good friends.  I hope to see them at the booth to show them what I've been working on and catch up on old times.

Looking forward to a good conference!

Saturday, July 10, 2004 4:19:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, July 08, 2004

I'm very excited to be going to Toronto this week.  I fly out first thing in the morning and I'll be heading to the Microsoft Worldwide Partner Conference.  We've been working feverishly the last several weeks to make sure that everything is ready and in order.  I'm going as a member of Experlogix, Inc.  Aside from the conference, I'm quite thrilled to be traveling to Toronto - I've never been there.  I hope I can get out and see a few cool places, but the schedule will be packed so we'll have to see.

Unfortunately, due to all of this travel, I had to miss the Utah .NET User Group meeting today.  I've heard from my fellow leadership that the meeting was great!  Scott Thornock presented on the topic of Smart Client apps and threw in some WSE 2.0 ...very exciting stuff!  I really wish I could have been there.  Thanks, Scott, for all your efforts!

I've also been away from my family a lot lately which is definitely not the most enjoyable circumstance.  I miss them very much.  When I arrive home next Wednesday we'll have a lot of catching up to do.

Sorry if this seems like rambling, because, well, it is.  It's 2:00 AM (I've been up until 3:30-4:30 every night this week and up at 8:00 so I'm a bit wiped out and a bit scrambled, but we've gotten the majority of things resolved for the conference...<fingersCrossed>the rest is cake!</fingersCrossed>.

Thursday, July 08, 2004 7:55:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, July 07, 2004
I cannot conceivably conclude to concur that you can't use 'can not' in a query.  Nor can one cater to those that coax coders to create crafty quotes.  Can't we all concede that cannot is the correct way to convey can not, which is wrong?
Wednesday, July 07, 2004 6:34:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I realize that's a pretty boring title, but let me explain:

I have a few websites that I've created over the past several years that I use quite frequently.  These include:

  • A bug tracking system that I developed a few years ago that I'm in the process of overhauling
  • A custom media server that streams audio content based off of custom, user-defined playlists from my personal collection of WMA and miscellaneous files
  • A special family gallery of photographs

Needless to say I am not the only user of these sites.  Various friends, associates, and family members frequent the sites (despite their work-in-progress nature).  However, there is a catch:  anonymous access is not allowed.  If a user wants to view the contents of a site, he must log in.  With the myriad of individuals out there that access these sites it would be impractical, not to mention extremely labor-intensive and would expose my network to security risks to provide a Windows logon for each user...that would be insanity.

For these and other reasons I opted for a Forms-over-SSL-based authentication mechanism to gain access to these sites.  The forms authentication backends ultimately to a SQL Server database that maintains the users and their encrypted credentials.  Ok, nothing magic here.  Simple, routine computing.  But this is where the fun begins.

A user may have access to one, two, or all of the sites.  I don't want to store multiple credentials for the same user (potentially with all the same values), one for each site.  Heaven forbid that we create a user credentials database for each site!  I wanted a single repository for all credentials.  Ok, still nothing magic - everyone has to solve these kinds of problems.  Despite these relatively mundane and trivial tasks, the whole reason I bring this up is because I think my solution is pretty cool.  Note: I haven't yet tested this solution for scalability nor have I profiled it.  I'm not expecting a boatload of traffic, so this suits my personal needs fine, but I would test this for any real-world application...don't just take my word for it that it's good.

On the backend I have a SQL Server 2000 database that maintains a master list of websites and logins and mappings between the two among some other stuff.  Stored procedures exist that allow for the management of these tables as well as the validating that a user has permissions to a particular site.

Over this database exists a set of web services that make calls to the stored procedures.  These web services (by their very nature) are accessible to the web sites but instead of accessing them directly, the web sites rely on a set of helper classes that abstract the web service interfaces.  For security reasons, the web services are hidden completely behind a firewall with no outside access.  Additionally, the only user with access to the database procedures (and only the procedures) is the user associated with the web service virtual directory in IIS.

An example of the web service call that validates the user is the following:

[WebMethod(Description="Checks to determine if the login id and password are associated with a particular website.")]
public bool IsValidLogin(string loginID, string password, string webSite) {
   string pwdHash = FormsAuthentication.HashPasswordForStoringInConfigFile(password, "SHA1");

   using ( SqlCommand cm = new SqlCommand("dsProc_ssoIsValidLogin", getConnection()) ) {
      cm.CommandType = CommandType.StoredProcedure;
      cm.Parameters.Add("@LoginID", SqlDbType.NVarChar, 25).Value = loginID;
      cm.Parameters.Add("@Password", SqlDbType.NVarChar, 40).Value = pwdHash;
      cm.Parameters.Add("@Website", SqlDbType.NVarChar, 30).Value = webSite;
      return (int)cm.ExecuteScalar() > 0;
   }
}

As can be ascertained, each website to which the users can gain access has a name.  The userid/password combination is matched up to the website name.  In the case of a match, a value of True is returned.

I mentioned a second ago that there is a set of helper classes that provide the interfaces to the Web Services.  Now with Visual Studio .NET I could have just as easily created a web reference and called it good.  However, I don't know if you may have had the same experiences that I've had, but I tend to open the References.cs file (which is automatically generated when the web reference is created) and edit it.  Mainly the edits involve the line that reads

this.Url = "http://localhost/.....";

because this url differs between development and production.  I usually edit it to read the value from a configuration file (Web.config).  It's a pain to have to edit that file only to have to reedit it when the web reference is refreshed - it's easy to forget to do it, that's for sure.  Not that my web services change their contracts often but you know.

That's one of the reasons I decided to create the set of helper classes, but the main driving reason was that I wanted a consistent, simplified, and shared object that didn't behave like a web service.  I didn't want to have to create an instance of the soap client to call a method and then dispose of it.  I wanted this class to be sharable across all of the web sites that needed the same functionality.

The solution I came up with was to create a class called SignOnValidator that has a single static method called IsValidLogin that takes the user's login id and password as parameters.  This method then resolves the name of the website by reflecting on the calling assembly to find a custom attribute called SignOnWebSiteNameAttribute instead of relying on a parameter passed in.  Then it creates an instance of an internal class that manages the call to the web service proper, returning the result.  I might need to do some optimization to the code, but here it is (unoptimized):

using System;
using System.Configuration;
using System.Reflection;
using System.Web.Services;
using System.Web.Services.Protocols;

[assembly:AssemblyVersion("1.0.0.0")]
[assembly:AssemblyCopyright("Copyright © 2004, Devstone Software")]

namespace Devstone.Web.WebAuth {

   public sealed class SignOnValidator {

      private SignOnValidator() { /* no public constructor */ }

      public static bool IsValidLogin(string loginID, string password) {
         string siteName;

         // get the instance of the attribute in the calling assembly
         Assembly caller = Assembly.GetCallingAssembly();
         if ( caller.IsDefined(typeof(SignOnWebSiteNameAttribute), false) ) {
            // retrieve the SignOnName for the calling application
            SignOnWebSiteNameAttribute attrib = caller.GetCustomAttributes(typeof(SignOnWebSiteNameAttribute), false)[0] as SignOnWebSiteNameAttribute;
            siteName = attrib.SiteName;
         }
         else {
            throw new ArgumentException("Unable to authenticate the user.  Please make sure that the calling assembly has the SignOnWebSiteNameAttribute specified.");
         }

         using ( SignOnWebService svc = new SignOnWebService() ) {
            return svc.IsValidLogin(loginID, password, siteName);
         }
      }

   } // SignOnValidator class


   [WebServiceBinding(Name="SignOnValidatorSoap", Namespace="
http://signon.devstone.com/")]
   internal sealed class SignOnWebService : SoapHttpClientProtocol {
      internal SignOnWebService() {
         string url = ConfigurationSettings.AppSettings["LogonServiceUrl"];
         if ( null == url )
            throw new ArgumentException("Unable to resolve the URL for the logon service.  Please make sure your application's configuration settings are correct.");
         else
            this.Url = url;
      }

      [SoapDocumentMethod("
http://signon.devstone.com/IsValidLogin",
       RequestNamespace="
http://signon.devstone.com/",
       ResponseNamespace="
http://signon.devstone.com/",
       Use=System.Web.Services.Description.SoapBindingUse.Literal,
       ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
      public bool IsValidLogin(string loginID, string password, string webSite) {
         object[] results = this.Invoke("IsValidLogin", new object[] { loginID, password, webSite});
         return ((bool)(results[0]));
      }

   } // SignOnWebService class

// Devstone.Web.WebAuth namespace

Then of course there's the question of the SignOnWebSiteNameAttribute.  Each website that utilizes this sign on mechanism will have to define this attribute, but in my book this simplifies management of the site names.

[assembly: SignOnWebSiteName("mysitename")]

Done.  Everything else will be taken care of because the helper class will automatically resolve the proper site name on invocation.  Oh, and if you're wanting the code for the attribute class, here it is:

using System;

namespace Devstone.Web.WebAuth {

  
[AttributeUsage(AttributeTargets.Assembly)]
   public sealed class SignOnWebSiteNameAttribute : Attribute {
      private string _siteName = null;

      public SignOnWebSiteNameAttribute(string siteName) {
         _siteName = siteName;
      }

      public string SiteName {
         get { return _siteName; }
      }
   } // SignOnWebSiteNameAttribute class

// Devstone.Web.WebAuth namespace

I really like this solution despite the fact that I have to maintain changes made in the web service manually in the SignOnValidator class.  Apart from everything else, I found a good example for using a custom Attribute!  I really think this will simplify the management of website users while providing a very simple programming model for validating users.  Not to mention the benefit that a user has a single login id, name, password, and email across all sites - I hate trying to remember 50 million user IDs and passwords

Wednesday, July 07, 2004 3:34:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Monday, July 05, 2004

I don't think that I'm unique in this experience (I've spoken with others that have the same experience), but have you ever solved some of your most difficult problems in your sleep?  It happens repetitively to me.  Time after time, especially after slaving away and not getting anywhere on an issue (primarily code issues) I solve the problem in the middle of the night while I'm sleeping.  I hate it when I wake up, fully aware that the problem was solved, but am unable to recall the solution - it's infuriating!

Case in point: I am working with a friend from California on an ASP.NET product and am banging my head against some pretty intense issues.  Just the other night I was (while sleeping) running into the issue yet again, but solved it rapidly.  When I awoke shortly thereafter I knew the problem had been solved but couldn't remember.  But this morning while working on it came to solve the problem and had a deja vú moments and the dream came back to me.

Maybe that's why I like to sleep as much as I do.  Or perhaps it's my body telling me that it's time to solve problems and it wants to shut down.  I guess I should listen to it more often rather than kill myself working into the wee hours of the morning.

Monday, July 05, 2004 7:56:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, July 04, 2004

On this day, the United States of America's Independence Day, I am overcome with gratitude for those that sacrificed so much for the country in which I live.  What a wonderful, blessed country!  It is because of their willingness to place their beliefs and even their very lives on the line that I live in such a beautiful place.  I commit to do what I can to live up to their legacy.

Sunday, July 04, 2004 2:25:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, July 02, 2004

In a recent post Eric Gunnerson points out some important concepts involving .NET assemblies and how they should be packaged to yield the best performance.  Basically, the fewer the better.  Each assembly that your application must load increases it's load time.  That said, one should not simply package all of his classes into a single, massive dll.  That would have a very negative and far-reaching impact.  Way back when I got into .NET development (back when it was it Beta 2) I wanted to understand what assemblies were all about.  A very important conclusion that I came to, and EricGu doesn't fail to bring it up, is that of versioning and security.  All code in the assembly versions at the same rate.  More importantly, however, are the security implications.  All code within an assembly has the same identity (and therefore the same level of trust).  If your assembly has to perform various types of actions (such as file IO, registry access, P/Invoke, all code will ultimately have the ability to perform all of those tasks - which may not be what you're wanting).

Aside from the performance and security implications, there may be very good reasons to partition your assemblies into smaller units of code.  Some components might need to be registered into the GAC, while others might need to reside remotely.  The concept of application layers and tiers comes to mind.  While application tiering is a logical design construct, it can have physical implications.  To enforce business requirements, you might not want the UI (user interface) tier to call directly into the DA (data access) tier (there are exceptions to this cut and dry rule).  Were all your classes in a single dll, there would really not be anything stopping them from doing so.  Of course, even then all the developer has to do is set a reference to your DAL (and with the proper know-how and permissions) go to town.

Developing an assembly as a single gargantuan project may not be the right thing to do.  Sometimes your developers are not geographically together or it simply makes more sense to create and test smaller projects.  Well, if you use the Visual Studio .NET environment for compilation, a project = an assembly.  There's not really a way around that.  Barry Gervin beat me to the punch on writing about this, but it's pretty easy to compile from the command prompt.  What you must do is compile your project files to a NetModule.  Then using the AL (Assembly Linker) utility, link the NetModules together into a single assembly.  (Note, this also makes it quite possible to combine, say VB.NET and C# in the same assembly).

Friday, July 02, 2004 3:33:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback