Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Getting the currently executing Control in ASP.NET


:P
On this page:

I’m in the middle of building a localization extender control that allows customizing how localization is applied in a data driven manner. One of the things the control does is track controls and they’re localization settings in data files with IDs that match the page or controls they live on and the control Id of that page or control (or master).

 

So one of the things I needed to do is to find – at runtime – the currently executing page OR user control or master page. After I spent about 20 minute building some nasty string translation code that pulled values out of Control.TemplateSourceDirectory and the Control.ToString() members I realized there’s an easier solution by using:

 

public static string GetControlAppRelativePath(Control Ctl)

{

    return Path = Ctl.TemplateControl.AppRelativeVirtualPath.Replace("~/","");

}

 

This returns App_templates/Standard/WebStoreMaster.master for example if the control is or is executing in the Master page above. I strip off the leaders because in most cases I’ll want just the relative path and if necessary I can prepend the Request.ApplicationPath (/wwStore for example) to provide a full server path.

 

Design Time Too Please

At design-time I like to get this information too, and that’s a bit more tricky and requires getting a reference to IDE instance. I really hate the fact that Microsoft enabled the ISite interface on controls, but neglected to provide some way to get a reference to the current DTE object through this interface? Why the hell is that not available? Or maybe it is but I sure as heck couldn’t find it…

 

Instead you have to resolve to some serious hack code to get an instance of the IDE. The easy way is to use:

 

(EnvDTE._DTE)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");

 

Unfortunately, this returns ANY instance of Visual Studio that is running, not particularly the one that is hosting your application. If you do the above for example, if you’re debugging your add-in you get into trouble because instead of finding the attached instance of VS that you is hosting your control, usually the  code will find the instance that is running the debugger so you’re not debugging the right code – AaaaacK!

 

Thankfully my good friend Kevin McNeish of Oak Leaf Enterprises and the Mere Mortals Framework had been down that path before and he provided me with this friendly COM madness code (provided with his permission here):

 

using EnvDTE;

using System;

using System.Collections;

using System.Runtime.InteropServices;

using System.Windows.Forms;

using VSLangProj;

using System.IO;

 

 

namespace Westwind.Globalization.Design

{

/// <summary>

/// Provides automation access to the current solution in design mode

/// </summary>

public class VisualStudioSolution

{

      /// <summary>

      /// Gets the running object table

      /// </summary>

      /// <param name="res"></param>

      /// <param name="ROT"></param>

      /// <returns></returns>

      [DllImport("ole32.dll", EntryPoint = "GetRunningObjectTable")]

      public static extern uint GetRunningObjectTable(uint res, out

                  UCOMIRunningObjectTable ROT);

 

      /// <summary>

      /// Creates Bind Ctx

      /// </summary>

      /// <param name="res"></param>

      /// <param name="ctx"></param>

      /// <returns></returns>

      [DllImport("ole32.dll", EntryPoint = "CreateBindCtx")]

      public static extern uint CreateBindCtx(uint res, out UCOMIBindCtx ctx);

 

      const uint S_OK = 0;

 

 

    private static EnvDTE._DTE DTE = null;

 

      /// <summary>

      /// Returns a reference to the Development Tools Extensibility (DTE) object

      /// </summary>

      /// <returns>DTE object</returns>

      public static EnvDTE._DTE GetDTE()

      {

 

        if (DTE != null)

            return DTE;

 

        // Get the DTE

 

            // The following method works fine IF you only have one instance of VS .NET open...

            // It returns the first created instance of VS .NET...not necessarily the current instance!

//                return (EnvDTE._DTE)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");

           

            // This code was adapted from Girish Hegde's on-line article

            // "Adding or Changing Code using FileCodeModel in Visual Studio .NET

            EnvDTE.DTE dte = null;

 

            // Get the ROT

            UCOMIRunningObjectTable rot;

            uint uret = GetRunningObjectTable(0, out rot);

            if (uret == S_OK)

            {

                  // Get an enumerator to access the registered objects

                  UCOMIEnumMoniker EnumMon;

                  rot.EnumRunning(out EnumMon);

                  if (EnumMon != null)

                  {

                        // Just grab a bunch of them at once, 100 should be

                        // plenty for a test

                        const int NumMons = 100;

                        UCOMIMoniker [] aMons = new UCOMIMoniker [NumMons];

                        int Fetched = 0;

                        EnumMon.Next(NumMons, aMons, out Fetched);

 

                        // Set up a binding context so we can access the monikers

                        string Name;

                        UCOMIBindCtx ctx;

                        uret = CreateBindCtx(0, out ctx);

 

                        // Create the ROT name of the _DTE object using the process id

                        System.Diagnostics.Process currentProcess =

                              System.Diagnostics.Process.GetCurrentProcess();

 

                        string dteName = "VisualStudio.DTE.";

                        string processID = ":" + currentProcess.Id.ToString();

 

                        // for each moniker retrieved

                        for (int i=0; i < Fetched; i++)

                        {

                              // Get the display string

                              aMons[i].GetDisplayName(ctx, null, out Name);

 

                              // If this is the one we are interested in...

                              if (Name.IndexOf(dteName) != -1 && Name.IndexOf(processID) != -1)

                              {

                                    object temp;

                                    rot.GetObject(aMons[i], out temp);

 

                                    dte = (EnvDTE.DTE) temp;

                                    break;

                              }

                        }                            

                  }

            }

 

        // *** RAS: Added Cache the reference if found

        DTE = dte;

 

            return dte;

      }

}

 

Mmmm… don’t you wish you were back to coding COM code in C++? <g>

 

Anyway the GetDTE() method provides an instance to the Visual Studio IDE from which you can then get the active project from which you can get the ActiveDocument (in the same class:

 

/// <summary>

/// Retuns an instance of the Active VS.NET Project COM object

/// </summary>

/// <returns></returns>

public static Project GetActiveProject()

{

    EnvDTE._DTE dte = GetDTE();

    if (dte == null)

        return null;

 

    if (dte.ActiveDocument == null)

        return null;

 

    Project proj = dte.ActiveDocument.ProjectItem.ContainingProject;

    if (proj == null)

        return null;

 

    return proj;

}

 

/// <summary>

/// Returns a path to the active document

/// </summary>

/// <returns></returns>

public static string GetActiveDocumentVirtualPath()

{

    Project proj = GetActiveProject();

    if (proj == null)

        return null;

 

    FileInfo fi = new FileInfo(proj.FullName);

    string ProjectPath = fi.DirectoryName + "\\";

 

    return DTE.ActiveDocument.FullName.ToLower().Replace(ProjectPath.ToLower(), "/").Replace("\\", "/");

}

 

 

Notice that in order to get the ActiveDocument’s relative path I have to do a little work by translating the ActiveDocument path into a relative path by looking at the project path and then stripping off the base path, but the end result is that you get the same value back as with the GetControlAppRelativePath() function I showed above…

 

Inside of the actual control the way to use this code is as easy as:

 

protected override void OnInit(EventArgs e)

{

    base.OnInit(e);

 

    if (this.HttpContext.Current == null)

    {

        this.DesignerInit();

    }

}

 

protected void DesignerInit()

{

    string Path = VisualStudioSolution.GetActiveDocumentVirtualPath();

    if (Path != null)

        this.PageId = Path;

 

}

 

And life is good… both design time and runtime can pick up this information.

 

While I was mucking around with the DTE stuff and searching for info, there was a lot of discussion how to access web.config as well (which I probably will need to do too to figure out a connection string so the designer can show data for example). Once you have a DTE reference it’s pretty easy to do with code like this (also from the class above):


/// <summary>

/// Returns a file path to web.config

/// </summary>

/// <returns></returns>

public static string GetWebConfig()

{

    Project proj = GetActiveProject();

    if (proj == null)

        return null;

 

    foreach (ProjectItem Item in proj.ProjectItems)

    {

       

        if (Item.Name.ToLower() == "web.config")

        {

FileInfo fi = new FileInfo(proj.FullName);

      return fi.DirectoryName + "\\" + Item.Name;

  }

    }

 

    return null;

}


The Voices of Reason


 

Atanas Korchev
August 19, 2006

# re: Getting the currently executing Control in ASP.NET

You can check out the IWebApplication interface introduced in VS2005 (if you don't need VS2002/VS2003 support). I prefer it much more than messing with the EnvDTE.

For example here is how to get the path to web.config:
http://flimflan.com/blog/AccessingWebconfigAtDesignTimeInNET20.aspx

Rick Strahl
August 20, 2006

# re: Getting the currently executing Control in ASP.NET

Atanas: Ah yes another thing that I hadn't even heeard about. I'm glad to see this sort of interface. Looks like that will make life a lot easier for a number of things. Hmmm... I wonder if you can get at this interface through an Add-in as well.

Jason Haley
August 20, 2006

# Interesting Finds: August 19 and 20, 2006

# DotNetSlackers: Getting the currently executing Control in ASP.NET


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024