应用范围:ExtensibleStorageManager

Revit平台:所有

Revit版本:2012.0

首次发布日期:2012.0

编程语言:c#

技能等级:高级

类别:元素

类型:ExternalCommand, ExternalApplication

主题:在Revit元素中创建,读取,更新和删除第三方可扩展存储。

简介:

这个应用程序通过创建一个包装器类和控制对话框来演示ExtensibleStorage类和相关api,这些包装器类和控制对话框创建模式,将它们序列化和反序列化为XML,并提取和显示这些模式的实体中的所有数据。这是一个高级样品。如果您不熟悉ExtensibleStorage,请首先查看“DynamicModelUpdate”示例。

相关类:

Autodesk.Revit.DB.ExtensibleStorage.Schema

Autodesk.Revit.DB.ExtensibleStorage.SchemaBuilder

Autodesk.Revit.DB.ExtensibleStorage.Field

Autodesk.Revit.DB.ExtensibleStorage.FieldBuilder

Autodesk.Revit.DB.ExtensibleStorage.Entity

Autodesk.Revit.DB.Element

项目文件:

SchemaWrapperTools

SchemaWrapper.cs

包含SchemaDataWrapper类,该类包含SchemaDataWrapper对象

以及用于创建、保存和读取模式的高级操作

SchemaDataWrapper.cs

包含一个SchemaDataWrapper类,该类包含FieldData对象和顶级模式信息的集合。

FieldData.cs

包含一个FieldData类,它将数据包装在架构字段中。

ExtensibleStorageManager

Application.cs - 包含创建命令 Ribbon 按钮的主要 IExternalApplication

Command.cs - 包含启动控制对话框 UICommand 的主要 IExternalCommand

\User\StorageCommand.cs - 包含用于创建和分析架构和实体的高级命令

\User\UICommand.xaml - 主要的对话框布局

\User\UICommand.xaml.cs - 主要的对话框实现 - 使用 StorageCommand.cs 中的命令

\User\UIData.xaml - 数据窗口布局

\User\UIData.xaml.cs - 数据窗口实现

描述:

ExtensibleStorage Manager应用程序包含两个项目

 

·SchemaWrapperTools

  一个C#库,封装了Autodesk.Revit.DB.ExtensibleStorage SchemaSchemaBuilderFieldFieldBuilder,并提供以下支持:

  - 将模式数据串行化到磁盘并从磁盘反串行化。

  - 显示包含特定模式数据的实体中的所有数据。

 

·ExtensibleStorageManager,一个Revit IExternalCommandIExternalApplication,启动一个WPF对话框,允许您与SchemaWrapper类交互,并查看序列化到Revit文档元素中的数据和模式。

说明:

1)构建并安装ExtensibleStorageManager IExternalApplication

2)创建一个新的Revit文档。

3)选择ExtensibleStorageManager功能区中的按钮。

4)在对话框中点击“New Id”按钮。

5)为您的新模式命名,并给出描述,选择读/写权限。

6)选择两个“创建新模式”按钮之一。 (现在先选择第一个“简单”选项。)

7)为要导出的模式XML文件选择路径,然后单击“确定”按钮。

8Revit现在将构建一个小模式,并向其添加数据,然后将在窗口中显示模式和数据。

9)关闭对话框,保存Revit文档,然后关闭并重新打开它。

10)再次启动ExtensibleStorageManager对话框。

11)选择“Create SchemaWrapper from Xml”按钮。

12)选择您在步骤7中指定的XML文件。

13)注意,模式已从XML重新创建,并显示在窗口中。

14)选择“Look up schema and extract entity”按钮。这将查询您在步骤7中保存的数据,并在窗口中显示它。

15)选择“Edit Existing Data - Simple”按钮以查询并编辑您在步骤7中设置的数据。

16)选择“Create SchemaWrapper from Schema”以从模式ID文本字段中的GUID创建新的SchemaWrapper。您可以使用它来分析或串行化您没有创建的其他文档中的模式。

注意:

此示例非常频繁地使用泛型和反射来查询类型、创建类型、调用方法、查询泛型参数,并基于在运行时发现的类型创建泛型容器类。这种使用反射的方法可能一开始会有些棘手,但它允许用户XML串行化包含任意数量、任意数据类型、任何受支持的数据类型的任何受支持的容器类型或任意数量的不同子模式的模式。建议先创建简单的模式,只有几个字段,并查看SchemaWrapper如何从它们创建Schema对象和XML,然后再转向对象列表、对象映射和子模式或子模式列表或映射。

源代码:

完整的源代码请加入QQ群649037449,在群文件中下载RevitSDK.exe,解压后在文件夹中搜索本文中应用程序名称即可获得完整源码

SchemaWrapper.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc. All rights reserved.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.

//
// AUTODESK PROVIDES THIS PROGRAM 'AS IS' AND WITH ALL ITS FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.ExtensibleStorage;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.IO;
using System.Reflection;
using System.Diagnostics;

namespace SchemaWrapperTools
{
   /// <summary>
   /// This class provides a simpler level of access to the
   /// Autodesk.Revit.DB.ExtensibleStorage Schema and SchemaBuilder objects
   /// and also provides easy serialization of schema data to xml for the user.
   /// </summary>
   [Serializable]
   public class SchemaWrapper
   {
      #region Constructors and class Factories
      /// <summary>
      /// For serialization only -- Do not use.
      /// </summary>
      internal SchemaWrapper() { }

      /// <summary>
      /// Creates a new SchemaWrapper from an existing schema.  
      /// </summary>
      /// <param name="schema">A schema to create a wrapper from</param>
      /// <returns>A new SchemaWrapper containing all data in the schema</returns>
      public static SchemaWrapper FromSchema(Schema schema)
      {
          SchemaWrapper swReturn = new SchemaWrapper(schema);

          foreach (Field currentField in schema.ListFields())
          {
             //We need to add call AddField on the SchemaWrapper we are creating for each field in the source schema.
             //Since we do not know the data type of the field yet, we need to get the generic method first and
             //then query the field data types from the field and instantiate a new generic method with those types as parameters.
              
             //Get the "AddField" method.
             MethodInfo addFieldmethod = typeof(SchemaWrapper).GetMethod("AddField", new Type[] { typeof(string), typeof(UnitType), typeof(SchemaWrapper) });
              Type[] methodGenericParameters = null;
              
              //Get the generic type parameters.  The type will either be a single type, an IList<> of a single type, or an IDictionary<> of a key type and a value type.
              if (currentField.ContainerType == ContainerType.Simple)
                 methodGenericParameters = new Type[] { currentField.ValueType };
              else if (currentField.ContainerType == ContainerType.Array)
                 methodGenericParameters = new Type[] { typeof(IList<int>).GetGenericTypeDefinition().MakeGenericType(new Type[] { currentField.ValueType }) };
              else
                 methodGenericParameters = new Type[] { typeof(Dictionary<int, int>).GetGenericTypeDefinition().MakeGenericType(new Type[] { currentField.KeyType, currentField.ValueType }) };
              
              //Instantiate a new generic method from the "AddField" method we got before with the generic parameters we got
              //from the current field we are querying.
              MethodInfo genericAddFieldMethodInstantiated = addFieldmethod.MakeGenericMethod(methodGenericParameters);
              SchemaWrapper swSub = null;  //Our subSchema is null by default unless the field is of type "Entity," in which case
              //we will call "FromSchema" again on the field's subSchema.
              if (currentField.ValueType == typeof(Entity))
              {
                 Schema subSchema = Schema.Lookup(currentField.SubSchemaGUID);
                  swSub = SchemaWrapper.FromSchema(subSchema);  
              }
              //Invoke the "AddField" method with the generic parameters from the current field.
              genericAddFieldMethodInstantiated.Invoke(swReturn, new object[] { currentField.FieldName, currentField.UnitType, swSub });  
          }
          //Note that we don't call SchemaWrapper.FinishSchema here. 
          //The Autodesk.Revit.DB.ExtensibleStorage.Schema object already exists and is registered -- we're just populating the outer wrapper.
          return swReturn;
      }
      

      /// <summary>
      /// Creates a new SchemaWrapper from a Guid
      /// </summary>
      /// <param name="schemaId">The schemaId guid</param>
      /// <returns>A new SchemaWrapper</returns>
      public static SchemaWrapper NewSchema(Guid schemaId, AccessLevel readAccess, AccessLevel writeAccess, string vendorId, string applicationId, string name, string description)
      {
         return new SchemaWrapper(schemaId, readAccess, writeAccess, vendorId, applicationId, name, description);
      }

      /// <summary>
      /// Creates a new SchemaWrapper from an XML file on disk
      /// </summary>
      /// <param name="xmlDataPath">The path to the XML file containing a schema definition</param>
      /// <returns>A new SchemaWrapper</returns>
      public static SchemaWrapper FromXml(string xmlDataPath)
      {
         XmlSerializer sampleSchemaInXml = new XmlSerializer(typeof(SchemaWrapper));
         Stream streamXmlIn = new FileStream(xmlDataPath, FileMode.Open, FileAccess.Read, FileShare.Read);
         SchemaWrapper wrapperIn = null;
         try
         {
            wrapperIn = sampleSchemaInXml.Deserialize(streamXmlIn) as SchemaWrapper;
         }
         catch (Exception ex)
         {
            Debug.WriteLine("Could not deserialize schema file." + ex.ToString());
            return null;
         }
         wrapperIn.SetRevitAssembly();
         streamXmlIn.Close();
         try
         {
            wrapperIn.FinishSchema();
         }
         catch (Exception ex)
         {
            Debug.WriteLine("Could not create schema." + ex.ToString());
            return null;
         }
         return wrapperIn;
      }

      /// <summary>
      /// Constructor used by class factories
      /// </summary>
      private SchemaWrapper(Guid schemaId, AccessLevel readAccess, AccessLevel writeAccess, string vendorId, string applicationId, string schemaName, string schemaDescription)
      {
            m_SchemaDataWrapper = new SchemaDataWrapper(schemaId, readAccess, writeAccess, vendorId, applicationId, schemaName, schemaDescription);
            SetRevitAssembly();
      }

      private SchemaWrapper(Schema schema) : this(schema.GUID, schema.ReadAccessLevel, schema.WriteAccessLevel, schema.VendorId, schema.ApplicationGUID.ToString(), schema.SchemaName, schema.Documentation)
      {
         this.SetSchema(schema);
      }

      #endregion

      #region Active schema manipulation

      /// <summary>
      /// Adds a new field to the SchemaWrapper
      /// </summary>
      /// <typeparam name="TypeName">Currently supported types:  int, short, float, double, bool, string, ElementId, XYZ, UV, Guid, Entity, IDictionary<>, IList<>.  IDictionary<> does not support floating point types, XYZ, UV, or Entity as Key parameters.</typeparam>
      /// <param name="name">The name of the field</param>
      /// <param name="unit">The unit type of the field.  Defintion required for floating point, XYZ, and UV types</param>
      /// <param name="subSchema">A subSchemaWrapper, if the field is of type Entity</param>
       public void AddField<TypeName>(string name, UnitType unit, SchemaWrapper subSchema)
       {
           m_SchemaDataWrapper.AddData(name, typeof(TypeName), unit, subSchema);
       }

      /// <summary>
      /// Create a new Autodesk.Revit.DB.ExtensibleStorage.SchemaBuilder and Schema from
      /// the data in the SchemaWrapper.
      /// </summary>
      public void FinishSchema()
      {

         //Create the Autodesk.Revit.DB.ExtensibleStorage.SchemaBuilder that actually builds the schema
         m_SchemaBuilder = new SchemaBuilder(new Guid(m_SchemaDataWrapper.SchemaId));
         

         //We will add a new field to our schema from each FieldData object in our SchemaWrapper
         foreach (FieldData currentFieldData in m_SchemaDataWrapper.DataList)
         {

            //If the current field's type is a supported system type (int, string, etc...),
            //we can instantiate it with Type.GetType().  If the current field's type is a supported Revit API type
            //(XYZ, elementID, etc...), we need to call GetType from the RevitAPI.dll assembly object.
            Type fieldType = null;
            try
            {
               fieldType = Type.GetType(currentFieldData.Type, true, true);
            }
               
            catch (Exception ex)  //Get the type from the Revit API assembly if it is not a system type.
            {
               Debug.WriteLine(ex.ToString());
               try
               {
                  //Get the field from the Revit API assembly.
                  fieldType = m_Assembly.GetType(currentFieldData.Type);
               }

               catch (Exception exx)
               {
                  Debug.WriteLine("Error getting type: " + exx.ToString());
                  throw;
               }
              
            }

        
            //Now that we have the data type of field we need to add, we need to call either
            //SchemaBuilder.AddSimpleField, AddArrayField, or AddMapField.
            
            FieldBuilder currentFieldBuilder = null;
            Guid subSchemaId = Guid.Empty;
            Type[] genericParams = null;
            
            
            if (currentFieldData.SubSchema != null)
               subSchemaId = new Guid(currentFieldData.SubSchema.Data.SchemaId);
            
            //If our data type is a generic, it is an IList<> or an IDictionary<>, so it's an array or map type
            if (fieldType.IsGenericType)
            {
                Type tGeneric = fieldType.GetGenericTypeDefinition();  //tGeneric will be either an IList<> or an IDictionary<>.

                //Create an IList<> or an IDictionary<> to compare against tGeneric.
                Type iDictionaryType = typeof(IDictionary<int, int>).GetGenericTypeDefinition();
                Type iListType = typeof(IList<int>).GetGenericTypeDefinition();
                
                genericParams = fieldType.GetGenericArguments();  //Get the actual data type(s) stored in the field's IList<> or IDictionary<>
                if (tGeneric == iDictionaryType)
                   //Pass the key and value type of our dictionary type to AddMapField.
                   currentFieldBuilder = m_SchemaBuilder.AddMapField(currentFieldData.Name, genericParams[0], genericParams[1]);
                else if (tGeneric == iListType)
                   //Pass the value type of our list type to AddArrayField.
                   currentFieldBuilder = m_SchemaBuilder.AddArrayField(currentFieldData.Name, genericParams[0]);
                else
                   throw new Exception("Generic type is neither IList<> nor IDictionary<>, cannot process.");
            }
            else
               //The simpler case -- just add field using a name and a System.Type.
               currentFieldBuilder = m_SchemaBuilder.AddSimpleField(currentFieldData.Name, fieldType);

            if (  //if we're dealing with an Entity as a simple field, map field, or list field and need to do recursion...
                (fieldType == (typeof(Entity)))
                ||
               (fieldType.IsGenericType && ((genericParams[0] == (typeof(Entity))) || ((genericParams.Length > 1) && (genericParams[1] == typeof(Entity)))))
                )
            {
                currentFieldBuilder.SetSubSchemaGUID(subSchemaId);  //Set the SubSchemaID if our field
                currentFieldData.SubSchema.FinishSchema();  //Recursively create the schema for the subSchema.
            }

            if (currentFieldData.Unit != UnitType.UT_Undefined)
               currentFieldBuilder.SetUnitType(currentFieldData.Unit);
         }

         //Set all the top level data in the schema we are generating.
         m_SchemaBuilder.SetReadAccessLevel(this.Data.ReadAccess);
         m_SchemaBuilder.SetWriteAccessLevel(this.Data.WriteAccess);
         m_SchemaBuilder.SetVendorId(this.Data.VendorId);
         m_SchemaBuilder.SetApplicationGUID(new Guid(this.Data.ApplicationId));
         m_SchemaBuilder.SetDocumentation(this.Data.Documentation);
         m_SchemaBuilder.SetSchemaName(this.Data.Name);


         //Actually finish creating the Autodesk.Revit.DB.ExtensibleStorage.Schema.
         m_Schema = m_SchemaBuilder.Finish();
      }

      /// <summary>
      /// Serializes a SchemaWrapper to an Xml file.
      /// </summary>
      /// <param name="xmlDataPath">The path to save schema data</param>
      public void ToXml(string xmlDataPath)
      {
   
         XmlSerializer sampleSchemaOutXml = new XmlSerializer(typeof(SchemaWrapper));
         Stream streamXmlOut = new FileStream(xmlDataPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
         sampleSchemaOutXml.Serialize(streamXmlOut, this);
         streamXmlOut.Close();
         return;
      }

      public override string ToString()
      {
          StringBuilder strBuilder = new StringBuilder();
          strBuilder.AppendLine("--Start Schema--  " + " Name: " + this.Data.Name + ", Description: " + this.Data.Documentation + ", Id: " + this.Data.SchemaId + ", ReadAccess: " + this.Data.ReadAccess.ToString() + ", WriteAccess: " + this.Data.WriteAccess.ToString());
          foreach (FieldData fd in this.Data.DataList)
          {
              strBuilder.AppendLine(fd.ToString());
          }
          strBuilder.AppendLine("--End Schema--");
          return strBuilder.ToString();
      }

      #endregion

      #region Helper Methods

      /// <summary>
      /// Returns a string representation of all data in an Entity
      /// </summary>
      /// <param name="entity">The entity to query</param>
      /// <returns>All entity data as a string</returns>
      public string GetSchemaEntityData(Entity entity)
      {
         StringBuilder swBuilder = new StringBuilder();
         DumpAllSchemaEntityData<Entity>(entity, entity.Schema, swBuilder);
         return swBuilder.ToString();
      }

      /// <summary>
      /// Recursively gets all data from an Entity and appends it in string format to a StringBuilder.
      /// </summary>
      /// <typeparam name="EntityType">A type parameter that will always be set to type "Entity" -- used to get around some type reflection limitations in .NET</typeparam>
      /// <param name="storageEntity">The entity to query</param>
      /// <param name="schema">The schema of the Entity</param>
      /// <param name="strBuilder">The StringBuilder to append entity data to</param>
      private void DumpAllSchemaEntityData<EntityType>(EntityType storageEntity,  Schema schema, StringBuilder strBuilder)
      {
          strBuilder.AppendLine("Schema/Entity Name: " + "" + ", Description: " +  schema.Documentation + ", Id: " + schema.GUID.ToString() + ", Read Access: " + schema.ReadAccessLevel.ToString() + ", Write Access: " + schema.WriteAccessLevel.ToString());
          foreach (Field currentField in schema.ListFields())
          {

             //Here, we call GetFieldDataAsString on this class, the SchemaWrapper class.  However, we must
             //call it with specific generic parameters that are only known at runtime, so we must use reflection to 
             //dynamically create a method with parameters from the current field we want to extract data from


             ParameterModifier[] pmodifiers = new ParameterModifier[0]; //a Dummy parameter needed for GetMethod() call, empty

             //Get the method.
             MethodInfo getFieldDataAsStringMethod = typeof(SchemaWrapper).GetMethod("GetFieldDataAsString", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, System.Type.DefaultBinder, new Type[] { typeof(Field), typeof(Entity), typeof(StringBuilder) }, pmodifiers);
             
             //Determine if our field type is a simple field, an array field, or a map field.  Then, create an array
             //of type parameters corresponding to the key and value types of the field.

             //Note that GetFieldDatAsString takes two generic parameters, a key type and field(value) type.
             //The 'key' type is only used for dictionary/map types, so pass an int type as a placeholder when
             //processing simple and array fields.
             Type[] methodGenericParameters = null;
             if (currentField.ContainerType == ContainerType.Simple)
                 methodGenericParameters = new Type[] { typeof(int), currentField.ValueType };   //dummy int types for non dictionary type
             else if (currentField.ContainerType == ContainerType.Array)
                 methodGenericParameters = new Type[] { typeof(int), currentField.ValueType };  //dummy int types for non dictionary type
              else
                 methodGenericParameters = new Type[] { currentField.KeyType, currentField.ValueType };

             //Instantiate a generic version of "GetFieldDataAsString" with the type parameters we just got from our field.
             MethodInfo genericGetFieldDataAsStringmethodInstantiated = getFieldDataAsStringMethod.MakeGenericMethod(methodGenericParameters);
             //Call that method to get the data out of that field.
             genericGetFieldDataAsStringmethodInstantiated.Invoke(this, new object[] { currentField, storageEntity, strBuilder });
        
          }
          strBuilder.AppendLine("---------------------------------------------------------");


      }

     /// <summary>
     /// Recursively gets all data from a field and appends it in string format to a StringBuilder.
     /// </summary>
     /// <typeparam name="KeyType">The key type of the field -- used only for maps</typeparam>
     /// <typeparam name="FieldType">The data type of the field for simple types and arrays</typeparam>
     /// <param name="field">The field to query</param>
     /// <param name="entity">The entity to query</param>
     /// <param name="strBuilder">The StringBuilder to append entity data to</param>
      private void GetFieldDataAsString<KeyType, FieldType>(Field field, Entity entity, StringBuilder strBuilder)
      {
          string fieldName = field.FieldName;
          System.Type fieldType = field.ValueType;
          UnitType fieldUnit = field.UnitType;
          ContainerType fieldContainerType = field.ContainerType;
          Type[] methodGenericParameters = null;
          object[] invokeParams = null;
          Type[] methodOverloadSelectionParams = null;
          if (field.ContainerType == ContainerType.Simple)
              methodGenericParameters = new Type[] { field.ValueType };
          else if (field.ContainerType == ContainerType.Array)
              methodGenericParameters = new Type[] { typeof(IList<int>).GetGenericTypeDefinition().MakeGenericType(new Type[] { field.ValueType }) };
          else //map
              methodGenericParameters = new Type[] { typeof(IDictionary<int, int>).GetGenericTypeDefinition().MakeGenericType(new Type[] { field.KeyType, field.ValueType }) };

          if (fieldUnit == UnitType.UT_Undefined)
          {
              methodOverloadSelectionParams = new Type[] { typeof(Field) };
              invokeParams = new object[] { field };
          }
          else
          {
              methodOverloadSelectionParams = new Type[] { typeof(Field), typeof(DisplayUnitType) };
              invokeParams = new object[] { field, DisplayUnitType.DUT_METERS };
          }

          MethodInfo instantiatedGenericGetMethod = entity.GetType().GetMethod("Get", methodOverloadSelectionParams).MakeGenericMethod(methodGenericParameters);
          if (field.ContainerType == ContainerType.Simple)
          {
              FieldType retval = (FieldType) instantiatedGenericGetMethod.Invoke(entity, invokeParams);
              if (fieldType == typeof(Entity))
              {
                  Schema subSchema = Schema.Lookup(field.SubSchemaGUID);
                  strBuilder.AppendLine("Field: " + field.FieldName + ", Type: " + field.ValueType.ToString() + ", Value: " + " {SubEntity} " + ", Unit: " + field.UnitType.ToString() + ", ContainerType: " + field.ContainerType.ToString());
                  DumpAllSchemaEntityData<FieldType>(retval, subSchema, strBuilder);
              }
              else
              {
                  string sRetval = retval.ToString();
                  strBuilder.AppendLine("Field: " + field.FieldName + ", Type: " + field.ValueType.ToString() + ", Value: " + retval + ", Unit: " + field.UnitType.ToString() + ", ContainerType: " + field.ContainerType.ToString());
              }
          }
          else if (field.ContainerType == ContainerType.Array)
          {
              IList<FieldType> listRetval = (IList<FieldType>)instantiatedGenericGetMethod.Invoke(entity, invokeParams);
              if (fieldType == (typeof(Entity)))
              {
                  strBuilder.AppendLine("Field: " + field.FieldName + ", Type: " + field.ValueType.ToString() + ", Value: " + " {SubEntity} " + ", Unit: " + field.UnitType.ToString() + ", ContainerType: " + field.ContainerType.ToString());

                  foreach (FieldType fa in listRetval)
                  {
                      strBuilder.Append("  Array Value: ");
                      DumpAllSchemaEntityData<FieldType>(fa, Schema.Lookup(field.SubSchemaGUID), strBuilder);
                  }
              }
              else
              {
                  strBuilder.AppendLine("Field: " + field.FieldName + ", Type: " + field.ValueType.ToString() + ", Value: " + " {Array} " + ", Unit: " + field.UnitType.ToString() + ", ContainerType: " + field.ContainerType.ToString());
                  foreach (FieldType fa in listRetval)
                  {
                      strBuilder.AppendLine("  Array value: " + fa.ToString());
                  }
              }
          }
          else //Map
          {
              strBuilder.AppendLine("Field: " + field.FieldName + ", Type: " + field.ValueType.ToString() + ", Value: " + " {Map} " + ", Unit: " + field.UnitType.ToString() + ", ContainerType: " + field.ContainerType.ToString());
              IDictionary<KeyType, FieldType> mapRetval = (IDictionary<KeyType, FieldType>)instantiatedGenericGetMethod.Invoke(entity, invokeParams);
              if (fieldType == (typeof(Entity)))
              {
                  strBuilder.AppendLine("Field: " + field.FieldName + ", Type: " + field.ValueType.ToString() + ", Value: " + " {SubEntity} " + ", Unit: " + field.UnitType.ToString() + ", ContainerType: " + field.ContainerType.ToString());
                  foreach (FieldType fa in mapRetval.Values)
                  {
                      strBuilder.Append("  Map Value: ");
                      DumpAllSchemaEntityData<FieldType>(fa, Schema.Lookup(field.SubSchemaGUID), strBuilder);
                  }
              }
              else
              {
                  strBuilder.AppendLine("Field: " + field.FieldName + ", Type: " + field.ValueType.ToString() + ", Value: " + " {Map} " + ", Unit: " + field.UnitType.ToString() + ", ContainerType: " + field.ContainerType.ToString());
                  foreach (FieldType fa in mapRetval.Values)
                  {
                      strBuilder.AppendLine("  Map value: " + fa.ToString());
                  }
              }
          }

      }

      /// <summary>
      /// Creates an instance of the RevitAPI.DLL assembly so we can query type information
      /// from it
      /// </summary>
      private void SetRevitAssembly()
      {
         m_Assembly = System.Reflection.Assembly.GetAssembly(typeof(XYZ));
      }

      #endregion

      #region Properties

      /// <summary>
      /// Gets the Autodesk.Revit.DB.ExtensibleStorage schema that the wrapper owns.
      /// </summary>
      /// <returns>An Autodesk.Revit.DB.ExtensibleStorage.Schema</returns>
      public Schema GetSchema() { return m_Schema; }

      /// <summary>
      /// Sets the Autodesk.Revit.DB.ExtensibleStorage schema that the wrapper owns.
      /// </summary>
      /// <param name="schema">An Autodesk.Revit.DB.ExtensibleStorage.Schema</param>
      public void SetSchema(Schema schema) { m_Schema = schema; }


      /// <summary>
      /// Gets and set the SchemaDataWrapper of the SchemaWrapper.  "Set" is for serialization use only.
      /// </summary>
      public SchemaDataWrapper Data
      {
          get { return m_SchemaDataWrapper; }
          set { m_SchemaDataWrapper = value; }
      }

      /// <summary>
      /// Get the path of the xml file this Schema was generated from
      /// </summary>
      public string GetXmlPath()
      {
         return m_xmlPath;
      }

      /// <summary>
      /// Set the path of the xml file this Schema was generated from
      /// </summary>
      public void SetXmlPath(string path)
      {
         m_xmlPath = path;
      }
 
      #endregion

      #region Data
      private SchemaDataWrapper m_SchemaDataWrapper;
  
      [NonSerialized]
      private Schema m_Schema;

      [NonSerialized]
      private SchemaBuilder m_SchemaBuilder;

      [NonSerialized]
      private System.Reflection.Assembly m_Assembly;

      [NonSerialized]
      private string m_xmlPath;
      #endregion

   }
}

SchemaDataWrapper.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc. All rights reserved.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.

//
// AUTODESK PROVIDES THIS PROGRAM 'AS IS' AND WITH ALL ITS FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.ExtensibleStorage;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.IO;
using System.ComponentModel;

namespace SchemaWrapperTools
{
   /// <summary>
   /// A class to store a list of FieldData objects as well as the top level data (name, access levels, SchemaId, etc..)
   /// of an Autodesk.Revit.DB.ExtensibleStorage.Schema
   /// </summary>
   [Serializable]
   public class SchemaDataWrapper
   {
      #region Constructors
      /// <summary>
      /// For serialization only -- Do not use.
      /// </summary>
      internal SchemaDataWrapper() { }

      /// <summary>
      /// Create a new SchemaDataWrapper
      /// </summary>
      /// <param name="schemaId">The Guid of the Schema</param>
      /// <param name="readAccess">The access level for read permission</param>
      /// <param name="writeAccess">The access level for write permission</param>
      /// <param name="vendorId">The user-registered vendor ID string</param>
      /// <param name="applicationId">The application ID from the application manifest</param>
      /// <param name="name">The name of the schema</param>
      /// <param name="documentation">Descriptive details on the schema</param>
      public SchemaDataWrapper(Guid schemaId, AccessLevel  readAccess, AccessLevel writeAccess, string vendorId, string applicationId, string name, string documentation)
      {
         DataList = new System.Collections.Generic.List<FieldData >();
         SchemaId = schemaId.ToString();
         ReadAccess = readAccess;
         WriteAccess = writeAccess;
         VendorId  = vendorId;
         ApplicationId = applicationId;
         Name = name;
         Documentation = documentation;
      }
       #endregion

      #region Data addition
      /// <summary>
      /// Adds a new field to the wrapper's list of fields.
      /// </summary>
      /// <param name="name">the name of the field</param>
      /// <param name="typeIn">the data type of the field</param>
      /// <param name="unit">The unit type of the Field (set to UT_Undefined for non-floating point types</param>
      /// <param name="subSchema">The SchemaWrapper of the field's subSchema, if the field is of type "Entity"</param>
       public void AddData(string name, System.Type typeIn, UnitType unit, SchemaWrapper subSchema)
      {
         m_DataList.Add(new FieldData(name, typeIn.FullName, unit, subSchema));
      }

      #endregion

      #region Properties
      /// <summary>
      /// The list of FieldData objects in the wrapper
      /// </summary>
      public List<FieldData> DataList
      {
         get { return m_DataList; }
         set { m_DataList = value; }
      }


      /// <summary>
      /// The schemaId Guid of the Schema
      /// </summary>
      public string SchemaId
      {
         get { return m_schemaId; }
         set { m_schemaId = value; }
      }

 
       /// <summary>
       /// The read access of the Schema
       /// </summary>
      public AccessLevel ReadAccess
      {
          get { return m_ReadAccess; }
          set { m_ReadAccess = value; }
      }

      /// <summary>
      /// The write access of the Schema
      /// </summary>
      public AccessLevel WriteAccess
      {
          get { return m_WriteAccess; }
          set { m_WriteAccess = value; }
      }

      /// <summary>
      /// Vendor Id
      /// </summary>
      public string VendorId
      {
         get { return m_vendorId; }
         set 
         {
            m_vendorId = value;
         }
      }


      /// <summary>
      /// Application Id
      /// </summary>
      public string ApplicationId
      {
         get { return m_applicationId; }
         set { m_applicationId = value; }
      }

      /// <summary>
      /// The documentation string for the schema
      /// </summary>
      public string Documentation
      {
         get { return m_Documentation; }
         set
         {
            m_Documentation = value;
         }

      }

      /// <summary>
      /// The name of the schema
      /// </summary>
      public string Name
      {
         get { return m_Name; }
         set
         {
            m_Name = value;
         }
      }

      #endregion

      #region Data
      private AccessLevel m_ReadAccess;
      private AccessLevel m_WriteAccess;
      private System.Collections.Generic.List<FieldData> m_DataList;
      private string m_applicationId;
      private string m_schemaId;
      private string m_vendorId;
      private string m_Name;
      private string m_Documentation;
      #endregion

   }
}

FieldData.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc. All rights reserved.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.

//
// AUTODESK PROVIDES THIS PROGRAM 'AS IS' AND WITH ALL ITS FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.ExtensibleStorage;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.IO;

namespace SchemaWrapperTools
{
   /// <summary>
   /// A class to store schema field information
   /// </summary>
   [Serializable]
   public class FieldData
   {

      #region Constructors

      /// <summary>
      /// For serialization only -- Do not use.
      /// </summary>
      internal FieldData() { }

      /// <summary>
      /// Create a new FieldData object
      /// </summary>
      /// <param name="name">The name of the field</param>
      /// <param name="typeIn">The AssemblyQualifiedName of the Field's data type</param>
      /// <param name="unit">The unit type of the Field (set to UT_Undefined for non-floating point types</param>
      public FieldData(string name, string typeIn, UnitType unit) : this(name, typeIn, unit, null)
      {
          
      }

      /// <summary>
      /// Create a new FieldData object
      /// </summary>
      /// <param name="name">The name of the field</param>
      /// <param name="typeIn">The AssemblyQualifiedName of the Field's data type</param>
      /// <param name="unit">The unit type of the Field (set to UT_Undefined for non-floating point types</param>
      /// <param name="subSchema">The SchemaWrapper of the field's subSchema, if the field is of type "Entity"</param>
      public FieldData(string name, string typeIn, UnitType unit, SchemaWrapper subSchema) 
      { 
         m_Name = name; 
         m_Type = typeIn;
         m_Unit = unit;
         m_SubSchema = subSchema;
      }
      #endregion

      #region Other helper functions
      public override string ToString()
      {
          StringBuilder strBuilder = new StringBuilder();
          strBuilder.Append("   Field: ");
          strBuilder.Append(Name);
          strBuilder.Append(", ");
          strBuilder.Append(Type);
          strBuilder.Append(", ");
          strBuilder.Append(Unit.ToString());


          if (SubSchema != null)
          {
              strBuilder.Append(Environment.NewLine + "   " + SubSchema.ToString());
          }
          return strBuilder.ToString();
      }
      #endregion

      #region Properties
      /// <summary>
      /// The name of a schema field
      /// </summary>
      public string Name
      {
         get { return m_Name; }
         set { m_Name = value; }
      }

      /// <summary>
      /// The string representation of a schema field type (e.g. System.Int32)
      /// </summary>
      public string Type
      {
         get { return m_Type; }
         set { m_Type = value; }
      }

       /// <summary>
       /// The Unit type of the field
       /// </summary>
      public UnitType Unit
      {
         get { return m_Unit; }
         set { m_Unit = value; }
      }

       /// <summary>
       /// The SchemaWrapper of the field's sub-Schema, if is of type "Entity"
       /// </summary>
      public SchemaWrapper SubSchema
      {
          get { return m_SubSchema; }
          set { m_SubSchema = value; }
      }
      #endregion

      #region Data
      private SchemaWrapper m_SubSchema;
      private string m_Name;
      private string m_Type;
      private UnitType m_Unit;
      #endregion

   }
}

Application.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//

using System;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace ExtensibleStorageManager
{

    public class Application : IExternalApplication
    {


       /// <summary>
       /// There is no cleanup needed in this application  -- default implementation
       /// </summary>
        public Result OnShutdown(UIControlledApplication application)
        {
           
            return Result.Succeeded;
        }

       /// <summary>
       /// Add a button to the Ribbon and attach it to the IExternalCommand defined in Command.cs
       /// </summary>
        public Result OnStartup(UIControlledApplication application)
        {

            RibbonPanel rp = application.CreateRibbonPanel("Extensible Storage Manager");
            string currentAssembly = System.Reflection.Assembly.GetAssembly(this.GetType()).Location;
        
            PushButton pb = rp.AddItem(new PushButtonData("Extensible Storage Manager", "Extensible Storage Manager", currentAssembly, "ExtensibleStorageManager.Command")) as PushButton;
          
           return Result.Succeeded;
           
        }


       /// <summary>
       /// The Last Schema Guid value used in the UICommand dialog is stored here for future retrieval
       /// after the dialog is closed.
       /// </summary>
        public static string LastGuid
        {
           get { return m_lastGuid; }
           set { m_lastGuid = value; }
        }
        private static string m_lastGuid;

    }
}