Wednesday, March 30, 2005

A few years ago I gave a presentation to our local .NET User Group on creating Add-Ins for Visual Studio .NET using the .NET framework.  That presentation was a lot of fun and very well received.  Creating Add-Ins isn't rocket science by any stretch but it's not without its couple gotchas.

Visual Studio .NET provides a project wizard that accelerates the creation of Add-Ins but it by no means necessary.  If you use the wizard to create your project, it will yield two projects: your Add-In project and a deployment project.  The deployment project is not necessary either, but provides a convenient mechanism from within the IDE to install and uninstall your Add-In.  When creating your Add-In using the wizard you'll want to be careful to name your Add-In appropriately; should you decide to rename your Add-In later you'll need to make several changes.

The most basic of Add-Ins consists simply of a class that implements the IDTExtensibility2 interface.  This interface, available via a reference to Extensibility.dll, provides a set of methods useful for interacting with the Add-In's host environment.  The wizard will create a class called Connect for you that already has the method stubs for each of the interface's methods.  Being a masochist when it comes to code, I prefer to write it out by hand, but the wizard surely gives you a head start when creating your Add-Ins.

Despite being created using the .NET framework, IDTExtensibility2 Add-Ins need to be exposed to their host environments as COM objects (which makes sense being that the IDTExtensibility2 interface is a COM interface).  Therefore, it is necessary to take some action to facilitate this interoperation.

1.  Apply the GuidAttribute to your assembly; this effectively becomes the TypeLibID for your assembly.
2.  Assign a ProgIdAttribute to your class to define a unique, friendly name through which COM-based clients can instantiate your Add-In (e.g. CoCreateInstance, CreateObject, etc)
3.  Assign a GuidAttribute to your class to establish the CLSID.
4.  Apply the ClassInterfaceAttribute to your class with a value of ClassInterfaceType.AutoDispatch; this is not technically required, but I prefer to do this.
5.  Create a strong name (via sn.exe) and apply it to the assembly via the AssemblyKeyFileAttribute.
6.  Assign a version to the project via the AssemblyVersionAttribute.

If you are building your Add-In from within Visual Studio, open the project properties and make sure the 'Register for COM Interop' is set to true.  If, on the otherhand, you prefer command-line builds as I frequently do, you'll need to register it manually with the Assembly Registration Utility (RegAsm.exe).

For deeper integration with the host, you'll probably need to know more about the environment in which the Add-In will run.  The host environment will dictate in many ways the references you set in your project.

The IDTExtensibility2.OnConnection method, for example, accepts among other two parameters named application and addInInst.  For an Add-In that targets the VS.NET environment, the application object is really an EnvDTE._DTE object and the addInInst is an EnvDTE.AddIn object as illustrated in the following example:

using EnvDTE;
using Extensibility;

private _DTE   _app;
private AddIn  _addIn;

void IDTExtensibility2.OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom) {
   _app = (_DTE)application;
   _addIn = (AddIn)addInInst;  
}

However, if you're dealing with an Office Add-In, you have some other things to consider.  First is the question of references.  You may be tempted to simply add a reference to the appropriate COM library (e.g. Microsoft Excel 11.0 Object Library), but unless you installed Office properly, that is probably not the right course of action.

When developing Add-Ins for Office, you want to set a reference to the appropriate Primary Interop Assembly (PIA).  If the PIA is not registered in the GAC or inaccessible, VS.NET will, upon selecting the COM object library, create an interop assembly (e.g. Interop.Excel.dll) in your project folder.  This is not really a good thing; among other things it's not signed so if you signed your assembly it won't work.

You might be tempted to create your own using the Type Library Importer tool (TlbImp.exe).  In fact, you could do something similar to the following to create your own, signed interop assembly (in this case, for Outlook):

tlbimp msoutl.olb /out:Devstone.Interop.Outlook.dll
/namespace:Devstone.Interop.Outlook /asmversion:1.0.0.0 /keyfile:c:\junk\mytestkey.snk

In fact, this would work, but it's not the recommended way to approach the problem.

The recommendation is to establish a reference to the vendor-supplied PIA.  Only the publisher of a type library can produce a true PIA.  Fortunately, Microsoft has created PIAs for each of the Office applications, but they are not installed by default in a Typical or Custom installation of Office.  You must manually select them.

Once installed, you can set your reference to the COM type library using the standard mechanisms.  Visual Studio .NET will find the PIA registered in the GAC and will set a reference to the PIA rather than dynamically generating the interop assembly.  VS.NET will also set a reference to Microsoft.Office.Core.dll (which is the Microsoft Office 11.0 Object Library in the case of Office 2003).

The code from above may resemble the following for an Outlook Add-In:

using Microsoft.Office.Core;
using outlook = Microsoft.Office.Interop.Outlook;
using Extensibility;

private outlook.Application  _app;
private COMAddIn             _addIn;

void IDTExtensibility2.OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom) {
   _app = (outlook.Application)application;
   _addIn = (COMAddIn)addInInst;
}

The next step is the matter of registering the Add-In with the target Office application.  This is done in the registry.  If you've created the Add-In project using the wizard and have the deployment project, it contains these settings for you.  Otherwise, you may have to create them manually.  For development and testing purposes, this key must exist.  So this is what I do:

1.  Create the AddIns key in HKCU\Software\Microsoft\Office\Outlook
2.  Create a new key in the Addins key with the same value as the ProgIdAttribute assigned to the Add-In class
3.  In the ProgId key, create a DWORD value named LoadBehavior and give it a value of 3
4.  Create a string value named FriendlyName and give it the name as you would like it to appear within Office
5.  Create a string value named Description and assign it a value accordingly
6.  Export the ProgId key to a .reg file for later use
7.  Add the .reg file to my VS.NET Add-In project for quick access and so it automatically incorporates itself into source control

After that, you're pretty much off the races.  Creating Add-Ins can be a lot of fun and also pretty rewarding.

Happy coding!

Wednesday, March 30, 2005 4:08:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Wednesday, March 23, 2005

Have you ever double-clicked an XML document sitting on your local computer and have it open in Internet Explorer only to be greeted with the Information Bar?  Have you created HTML files and tried to preview them and had the same thing occur?  It gets a little frustrating, especially if you don't know why IE's doing it in the first place.  The reason IE is doing this is, more than likely, the page has or requires script to run.

With IE 6.0, Microsoft has (correctly, in my opinion) locked down security setting for the Local Machine Zone (LMZ) such that content run from the LMZ is not fully trusted (as it once was).  Attackers discovered that if they could get a file on your local machine (via exploit, convincing you to save, etc) their pages would be able to run fully trusted.  And because most users run their computers as Administrators (ack!), the saved content would have full permissions on the computer, giving the attacker a wide-open field to play in.  Now, however, rather than execute the file unrestricted, IE will pop up the Information Bar giving the user the opportunity to grant or deny permissions.

This can get a little frustrating, however, if you're developing HTML content (for documentation or to run from a CD) or XML documents and you want to test them out frequently.  As it turns out, you can tag your active documents with what's known as the Mark of the Web (MOTW) which IE will recognize and will elevate the page's permissions based on the url specified.

Basically, what it comes down to is a short HTML comment placed near the top of the document with IE willl parse.  It will extract the specified URL and compare it against the user's zone settings to grant it the proper level of permissions (i.e. Trusted, Restricted, Internet, etc).  Here's what it looks like:

<!-- saved from url=(0024)http://www.devstone.com/ -->

The 0024 is a decimal value identifying the number of characters to evaluate in reading the URL.  Alternatively you can specify the following to grant generic Internet Zone permissions to the document:

<!-- saved from url=(0014)about:internet -->

The recommended practice to to include this tag within documents that rely on scripting, frames, etc.  Recently, I have found it to be particularly beneficial in creating an HTML-based navigation system for a CD containing videos and media.  Now, users can pop in the CD/DVD and view the web pages without the annoying Information Bar popping up and ruining the experience.  Likewise, you can apply the tag to an XML document and view the document locally without the constant interruption.

Wednesday, March 23, 2005 3:57:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, March 18, 2005

I've seen plenty of hype about this product over the past months and today I decided to give in and give it a try.  I frequently hear people recommending that I run a spyware utility to make sure that my system is not infected with spyware, but haven't done it for one reason or another.

I downloaded and installed the utility.  I don't run my machine as an administrator, so in order to install it I had to run it as an administrator.  Rather than running the application as THE administrator account, I temporarily promoted my account to the Administrators group (through a slick tool by Aaron Margosis).  Interestingly, the application errored out upon first run as me as well as when I attempted to run it as Administrator.  I had to run it as me promoted to the Administrators group (I suspect that this is because the default owner of the application was me as an admin rather than the Administrators group).  Once I successfully started it, I performed an analysis of my system and I'm happy to see that I had ZERO spyware applications and registry keys/values on my machine.

I can now go along happily and feel even more secure in my computing ;)

Friday, March 18, 2005 8:52:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, March 17, 2005

As I have mentioned before, I very much enjoy working with ReSharper by JetBrains.  We recently purchased version 1.5 and I really like it.  It does have some interesting quirks when dealing with compilation directives.  It seems that ReSharper, in providing its own custom Intellisense, continues to evaluate compilation blocks that are excluded from the current build.  For example, ReSharper gets confused when you have something like this:

#if ( DEBUG )
   public class DevstoneTestControlBase : UserControl {
#else
   public abstract class DevstoneTestControlBase : UserControl {
#endif
      // actual class implementation here...
   }

I periodically decorate my classes this way (especially control-based, UI components) so that in a design, debug environment I can use a designer (which relies on publically creatable classes in order to render the UI) but to mark the classes as abstract when compiled in a 'Release' form so that the proper behaviors are enforced.

When you do this, however, ReSharper throws fits because it cannot resolve protected or public members in the derived class back to the base class.  However, with just a bit of cleverness you can overcome this ReSharper shortcoming and still have your cake and eat it too.

#if ( !DEBUG )
   abstract
#endif
   public class DevstoneTestControlBase : UserControl {
      // class implementation here...
   }

This way, the 'Release-only' modifiers are moved out of the declaration (but still valid when evaluated) and only class declaration exists.  While there are still other nuances with how ReSharper evaluates compilation directives (having different using clauses conditionally, or declaring variables within them, for example), I'm glad to be able to overcome this one.

Thursday, March 17, 2005 5:54:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, March 16, 2005

I just updated to the latest CAPTCHA control 1.3 by Miguel Jimenez  from version 1.2.   The upgrade was EXTREMELY seamless, just dropped the .dll in the \bin folder and removed the HttpHandler from the web.config...that was it!

It's great to see other .NET developers contributing such quality stuff to the community.  I was experiencing the error in which the HttpHandler would cause the browser to prompt to save a file rather than allowing a user onto the site...nice to see that one fixed.  This is great stuff Miguel!

Wednesday, March 16, 2005 9:42:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Within the ASP.NET framework there is built-in support for what is commonly known as Forms Authentication.  Forms Authentication provides a mechanism for the pages within the website to authenticate the caller by validating data (i.e. credentials, PIN, etc) against some data store (such as a database).  I won't bore anyone with a discussion on Forms Authentication as there are many references to be found and it's a pretty well-known topic.  I will, however, entertain another related thought.

When using Forms Authentication, an unauthenticated user will be directed to a designated login page.  ASP.NET will, in the request querystring, identify the page that was initially requested in the ReturnUrl variable thus:

http://localhost/myweb/login.aspx?ReturnUrl=%2fmyweb%2findex.aspx

Upon successful validation of the users credentials the login page will typically call the FormsAuthentication.RedirectFromLoginPage() method.  This method will redirect the user to the page designated by the ReturnUrl property.  Usually this is the desired and anticipated behavior.

There may be times, however, when you want to force a particular application to always (or at least to conditionally) redirect to a designated starting page regardless of the ReturnUrl value.  Probably the best way to accomplish this goal is to utilize an HttpModule that performs simple url rewriting.  The HttpApplication associated with the website in question has an event called AuthorizeRequest which is the prime location to perform the url rewriting.  You could add a handler for this event within the global.asax's Global class (and there's really nothing wrong with this approach) but I prefer to create my own HttpModule to isolate the functionality and compartmentalize it.  This is basically what it comes down to in its simplest form:

public class LoginRewriter : IHttpModule {

   void IHttpModule.Dispose() { }

   void IHttpModule.Init(HttpApplication app) {
      app.AuthorizeRequest += new EventHandler(authorizeRequest);
   }

   private void authorizeRequest(object sender, EventArgs e) {
      rewriteLoginPath(sender as HttpApplication);
   }

   private void rewriteLoginPath(HttpApplication app) {
      if ( !app.Request.IsAuthenticated ) {
         app.Context.RewritePath(“~/Login.aspx?ReturnUrl=~/StartPage.aspx“);
      }
   }

}

This simple module alters the requested path for all non-authenticated requests to point to StartPage.aspx page.  Therefore, despite the request (valid or invalid), upon successfully authenticating, the user will always be redirected to StartPage.aspx.  The last remaining step is to wire the module up in the web.config file:

<httpModules>
   <add type=”MyWeb.LoginRewriter, MyWeb” name=”LoginRewriter” />
</httpModules>

And there you have it...easy as pie!

Wednesday, March 16, 2005 9:23:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, March 14, 2005

Last month I posted an entry in which I identified my musical preferences while writing code.  I happened to browse some referrers today and noticed an interesting link.  Apparently if you enter the terms weird and programmers into Google, my blog (that post in particular) is #1 on the list.  While I don't entirely disagree with the results,  I thought it was pretty funny.

...the things people search for while surfing ;)

Monday, March 14, 2005 12:11:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

This one might cause an interesting discussion, but I thought I'd throw it out there anyway.

Over the past several months I've toyed (without yet purchasing) a few 'productivity tools', namely ReSharper by JetBrains and CodeRush by DevExpress.  Honestly I was extremely pleased with both tools.  Each tool seamlessly integrates with Visual Studio .NET as an add-in.  Each provides mechanisms to quickly refactor code, perform syntax checking, efficiently navigate around the code base, and much more.

I found CodeRush to be extremely powerful.  Its feature set is so extensive, exhaustive, and customizable that it's bind-moggling.  I absolutely love its powerful code templates.  In just a few minutes I had created a template called singleton that upon typing the text 'singleton' it would automatically expand to provide me with code properly formatted, structured, and named (according to the template).  CodeRush has some pretty innovate and stylish features.  I very much like how it highlights code blocks, identifies method and switch exit points with the smooth animations, template usages with the semi-transparent arrows, and much more.  CodeRush has a lot going on.

I feel, however, that the sheer extensiveness of CodeRush's capabilities is overwhelming.  I feel, also, that it's just a little too helpful in an invasive way.  Maybe it's just me and how I enter my code, but I'm constantly having to go back and correct times when it would insert templated text erroneously.  In fact, I spent about 3-4 hrs (I might be low-balling that estimate) tweaking it and adjusting each of the templates to a) my coding standards and conventions and b) run at the appropriate times and not unexpectedly.  Even then I wasn't able to fully fix the issues.  All in all, I am very impressed with CodeRush, but for me it gets in the way too much and I end up spending too much time undoing its helpfulness.  (I'd be interested to see how people compare/contrast CodeRush's helpfulness to Microsoft Word's same inclination...I know people that curse products that do this (especially if they come out of Redmond) whereas they don't hold the same opinions for other products...interesting ;))

ReSharper, on the other hand, is a much simpler tool (not nearly the # of gizmos, shortcuts, and templates) but indeed powerful.  ReSharper provides its own intellisense that I really liked (it can pick up changes in other projects in a multi-project solution, for example).  Also, ReSharper has some very nice inline enhancements; for example it will identify unused using statements, insert using clauses as necessary, et al.  ReSharper also enhances the IDE by providing some functionality that will be present in Whidbey such as 'Close all' and 'Close all except...' context menu options.

It wasn't without its idiosyncrasies, however.  I frequently utilize conditional compilation directives in my code and it has a hard time understanding variables and types declares within directive blocks (e.g. #if DEBUG...).  I'm not sure if this (lacking) capability has been fixed in 1.5 or slated for 2.0+.  Also, sharing ReSharper settings among developers on a team is a bit cumbersome (involving copying a directory containing settings).

At the end of the day I would personally recommend ReSharper (I plan on purchasing it here shortly) as it is much less invasive, allowing me to code to my personal style and taste while still providing me with advanced (and time-saving) enhancements.  I was noticing today that JetBrains is (for a limited time) providing a free upgrade to 2.0 when it becomes available with a purchase of ReSharper for $99....sounds like a deal to me!

Monday, March 14, 2005 6:42:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Thursday, March 10, 2005

We had our monthly .NET User Group meeting tonight and it was great!  We had a good turnout as well which is always exciting.  This month's meeting was sponsored by DRIVE Development out of Ogden, Utah.

  • Scott Golightly won the coding contest, receiving a copy of Infragistics NetAdvantage Volume 2003 w/Subscription
  • Justin Long was nominated and elected our new User Group secretary.
  • Clint Lord (I don't know if Clint has a blog) gave a great presentation on O/R Mapping.

All in all, a great event!  Thanks all for coming.

Thursday, March 10, 2005 3:03:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Now that I'm back home and semi-settled in (I arrived home last night after an 11-day trip) I finally had the chance to get my new rating control (ver 1.0.1.0) up on my blog.  What was reassuring was that it went in seamlessly...not a single hitch.  I simply ran the db script against the server, copied in the new .dll and updated the web.config and just like that it was up and running (in total, it took about 3 minutes).  I also took the time to put the 'Top Rated Posts' control on the home page.  Because I have so few rated articles I set the minimum # of votes to 1, but over time, I'd like to have to increase that number.

Don't forget, the control is free and available for use on your .Text blog site.

Thursday, March 10, 2005 2:55:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, March 08, 2005

This isn't something I would normally blog about, but in this case I can't really make an exception.

Tonight we went out to eat and had what was perhaps the best meal that I've ever had...period.  We went to a small Italian restaurant called Buon Appetito on India Street in San Diego.  I had the cheese and spinach ravioli and the house salad.  Bar none both were fantastic!  I have never had such delicious ravioli in my life and the salad was perfect (love the dressing).

The next time I'm in San Diego I will undoubtedly make the stop again.

Tuesday, March 08, 2005 4:28:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, March 05, 2005
Well, here we are at the Microsoft Partner Briefing Convergence 2005 conference.  It's pretty exciting.  Last year we debuted our product offering at the Microsoft Partner Briefing in Toronto, Canada and it was a blast.  It was so very exciting to see the energy behind CRM.  We've had TONS of excitement behind our product offering since and we're expect the same or more from this conference in San Diego.  In you're in town attending the conference don't forget to stop by booth #717!
Saturday, March 05, 2005 9:34:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback