March 2006 - Posts

How to load a DSL domain model instance file programmatically

Note: this entry has moved.

Say you have a custom DSL file in a solution. Say you want to process the model on that file using GAT or an AddIn or even your custom application code at runtime. You need to deserialize that XML (yes, it’s all XML in the end) into an instance of your domain model (root).

You will need two pieces of information: the .NET type of the domain model (that is, the parent of your domain model root concept) and its the corresponding designer. The following code does the trick, allowing you to deserialize a model and return the first instance of the given domain model type (i.e. the model root):

public static TRootConcept GetModelRoot<TRootConcept>(string modelFile, Type modelType, Type designerType)
{
 // Create a new store to deserialize the instance to.
 Store newStore = new Store();
 Type[] metaTypes = new Type[] {
  typeof(Microsoft.VisualStudio.Modeling.Diagrams.CoreDesignSurface),
  typeof(Microsoft.VisualStudio.Modeling.Utilities.UtilitiesModel),
  modelType,
  designerType };
 // Load these types into the store, so that it knows about them for deserialization
 newStore.LoadMetaModels(metaTypes);
 foreach (Type subStoreType in metaTypes)
 {
  // TODO: this will not be required in RC+ versions of DSL tools.
  Activator.CreateInstance(subStoreType, newStore);
 }
 // Be version resilient
 int majorVersion;
 int minorVersion;
 GetModelVersion(modelFile, out majorVersion, out minorVersion);
 // Deserialize the file into the store
 using (Stream fileStream = File.OpenRead(modelFile))
 {
  XmlSerialization.DeserializeStore(newStore, fileStream, majorVersion, minorVersion, null, null);
 }
 // Locate the attribute that will give you the Guid of the element to find
 MetaObjectAttribute metaObject = Generics.GetCustomAttribute<MetaObjectAttribute>(typeof(TRootConcept));
 if (metaObject == null)
 {
  throw new ArgumentException(String.Format(
   CultureInfo.CurrentCulture,
   Properties.Resources.MetaInformationForElementNotFound,
   elementType));
 }
 // Return the first one we find
 foreach (object element in newStore.ElementDirectory.GetElements(metaObject.Id))
 {
  return element;
 }
 return null;
}

The GetModelVersion helper method simply reads the major/minor values from the XML itself:

private static void GetModelVersion(string modelFile, out int majorVersion, out int minorVersion)
{
 // Get versions from model:
 //<om:MetaModel MajorVersion="1" MinorVersion="1" xmlns:om="
http://Microsoft.VisualStudio.Modeling">
 using (XmlReader reader = XmlReader.Create(modelFile))
 {
  reader.MoveToContent();
  if (reader.LocalName != "MetaModel")
  {
   throw new InvalidOperationException(Properties.Resources.FileDoesNotContainModel);
  }
  majorVersion = Int32.Parse(reader.GetAttribute("MajorVersion"), CultureInfo.CurrentCulture);
  minorVersion = Int32.Parse(reader.GetAttribute("MinorVersion"), CultureInfo.CurrentCulture);
 }

Update: here's how to do it with the June 06 bits.

How to add a VS assembly reference without hardcoding its path

Note: this entry has moved.

Sometimes, you want to add a reference to an assembly provided by VS (that is, something inside the %ProgramFiles%\Microsoft VisualStudio 8.0 folder) that doesn't show up in the Add Reference dialog (there are lots of very interesting stuff you can use that fall under these category, such as Microsoft.Data.ConnectionUI which I used in the GAX TechEd HoL and allows you to create an "Add Data Connection" clone complete with support for .NET providers, connection string builders, test connection, etc.).

The trick is to browse for the reference as usual using the Add Reference dialog, save the project and open it in notepad (or your favorite XML editor). Look for your reference, and replace the hardcoded path to the IDE folder with the $(DevEnvDir) macro variable, like so:

<Reference Include="Microsoft.Data.ConnectionUI, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
  <HintPath>$(DevEnvDir)Microsoft.Data.ConnectionUI.dll</HintPath>
  <SpecificVersion>False</SpecificVersion>
  <Private>False</Private>
</Reference>

The  $(DevEnvDir) variable points to the %ProgramFiles%\Microsoft VisualStudio 8.0\Common7\IDE folder, but you can always use relative path “movements” to go up and around other folders under the root VS install path.

Yes, I know… VS should do that automatically… Go vote the bug so that we have it fixed in Orcas ;-)

Update: I’ve just found that you can also use $(VsInstallDir), which points to  %ProgramFiles%\Microsoft VisualStudio 8.0

More Posts