在ASP.NET MVC2中,我们经常这样来写表单模板:
<% Html.BeginForm("Post"); %>
<%:Html.EditorFor(m=>m.Name) %>
<%:Html.EditorFor(m=>m.DisplayName) %>
<%:Html.EditorFor(m=>m.Culture) %>
<%:Html.EditorFor(m=>m.Theme) %>
<%:Html.EditorFor(m=>m.HostName) %>
<%:Html.EditorFor(m=>m.VirtualPath) %>
<%:Html.EditorFor(m=>m.Version) %>
<%:Html.EditorFor(m=>m.Mode) %>
<% Html.EndForm(); %>
由于DataAnnotations的存在,我们完全可以把字段的视图元数据全部都在Model中设计好,那么开发编辑视图就变得非常的简单,可能只是对应Model的字段,一行一行的去写Html.EditorFor ,这是一种重复而枯燥的工作。因此我想到,写一个VS扩展,在HTML编辑器的右键菜单中添加一个命令,点一下就可以根据当前的Model的类型来生成上面的代码。这种扩展,对应的VSX是add-in,如何来开发这个add-in并不是我这里要记录的重点。网上有好多介绍如何开发add-in,不过我是参考这篇文章。
这篇文章要介绍的重点是,如何在add-in中,动态去获得Model的Type实例。我们知道,我们在程序运行时,可以很容易的去得到Type实例。但是在这里,我们是要在VS中,去得到项目的Type实例,这是一个比较麻烦的事情。不过,从我们所知道的VS IDE的强大的智能提示功能就可以知道,要做到这个并不是不可能的事情。通过查找资料,整理了如下的代码,可以得到项目的所有的类型实例,还有其它对象是我们做很多扩展所必须的用到的:
public static class VsHelper
{
public static IVsHierarchy GetCurrentHierarchy(IServiceProvider provider)
{
DTE vs = (DTE)provider.GetService(typeof(DTE));
if (vs == null) throw new InvalidOperationException("DTE not found.");
return ToHierarchy(vs.SelectedItems.Item(1).ProjectItem.ContainingProject);
}
public static IVsHierarchy ToHierarchy(EnvDTE.Project project)
{
if (project == null) throw new ArgumentNullException("project");
string projectGuid = null;
// DTE does not expose the project GUID that exists at in the msbuild project file.
// Cannot use MSBuild object model because it uses a static instance of the Engine,
// and using the Project will cause it to be unloaded from the engine when the
// GC collects the variable that we declare.
using (XmlReader projectReader = XmlReader.Create(project.FileName))
{
projectReader.MoveToContent();
object nodeName = projectReader.NameTable.Add("ProjectGuid");
while (projectReader.Read())
{
if (Object.Equals(projectReader.LocalName, nodeName))
{
projectGuid = projectReader.ReadInnerXml();
break;
}
}
}
Debug.Assert(!String.IsNullOrEmpty(projectGuid));
IServiceProvider serviceProvider = new ServiceProvider(project.DTE as
Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
return VsShellUtilities.GetHierarchy(serviceProvider, new Guid(projectGuid));
}
public static IVsProject3 ToVsProject(EnvDTE.Project project)
{
if (project == null) throw new ArgumentNullException("project");
IVsProject3 vsProject = ToHierarchy(project) as IVsProject3;
if (vsProject == null)
{
throw new ArgumentException("Project is not a VS project.");
}
return vsProject;
}
public static EnvDTE.Project ToDteProject(IVsHierarchy hierarchy)
{
if (hierarchy == null) throw new ArgumentNullException("hierarchy");
object prjObject = null;
if (hierarchy.GetProperty(0xfffffffe, -2027, out prjObject) >= 0)
{
return (EnvDTE.Project)prjObject;
}
else
{
throw new ArgumentException("Hierarchy is not a project.");
}
}
public static EnvDTE.Project ToDteProject(IVsProject project)
{
if (project == null) throw new ArgumentNullException("project");
return ToDteProject(project as IVsHierarchy);
}
public static IServiceProvider GetServiceProvider(EnvDTE.Project project)
{
IServiceProvider serviceProvider = new ServiceProvider(project.DTE as
Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
//DynamicTypeService typeService = (DynamicTypeService)
// serviceProvider.GetService(typeof(DynamicTypeService));
return serviceProvider;
}
private static ITypeResolutionService GetResolutionService(EnvDTE.Project project)
{
DynamicTypeService typeService = GetService<DynamicTypeService>(project);
Debug.Assert(typeService != null, "No dynamic type service registered.");
IVsSolution sln = GetService<IVsSolution>(project);
IVsHierarchy hier;
sln.GetProjectOfUniqueName(project.UniqueName, out hier);
Debug.Assert(hier != null, "No active hierarchy is selected.");
return typeService.GetTypeResolutionService(hier);
}
public static T GetService<T>(EnvDTE.Project project)
{
return (T)GetServiceProvider(project).GetService(typeof(T));
}
public static IDictionary<string, Type> GetAvailableTypes(IServiceProvider provider, bool includeReferences)
{
DynamicTypeService typeService = (DynamicTypeService)provider.GetService(typeof(DynamicTypeService));
Debug.Assert(typeService != null, "No dynamic type service registered.");
IVsHierarchy hier = VsHelper.GetCurrentHierarchy(provider);
Debug.Assert(hier != null, "No active hierarchy is selected.");
ITypeDiscoveryService discovery = typeService.GetTypeDiscoveryService(hier);
Project dteProject = VsHelper.ToDteProject(hier);
IDictionary<string, Type> availableTypes = new Dictionary<string, Type>();
foreach (Type type in discovery.GetTypes(typeof(object), includeReferences))
{
// We will never allow non-public types selection, as it's terrible practice.
if (type.IsPublic)
{
if (!availableTypes.ContainsKey(type.FullName))
{
availableTypes.Add(type.FullName, type);
}
}
}
return availableTypes;
}
public static Type GetType(EnvDTE.Project project, string typeName)
{
var types = GetAvailableTypes(GetServiceProvider(project), true);
if (types.ContainsKey(typeName))
{
return types[typeName];
}
return null;
}
}
在Add-in的Connect实现中,我们可以通过如下的代码就可以根据类型名称得到它的类型实例:
private Project CurrentProject
{
get
{
return _applicationObject.DTE.Solution.Projects.Item(_applicationObject.DTE.Solution.Projects.Count);
}
}
public Type GetType(string typeName)
{
Type modelType = VsHelper.GetType(CurrentProject, typeName);
return modelType;
}
这里都是对COM 对象的操作,由于缺少资料文档的支持,开发起来相当的不方便。如有兴趣,参考以下几篇文章:
http://www.clariusconsulting.net/blogs/kzu/archive/2006/01/06/GetTypesFromProject.aspx
http://www.clariusconsulting.net/blogs/kzu/archive/2007/10/04/34019.aspx
http://www.clariusconsulting.net/blogs/kzu/archive/2006/01/06/GetTypesFromProject.aspx
http://msmvps.com/blogs/carlosq/archive/2008/03/06/how-do-i-get-a-system-type-from-a-type-name.aspx
http://weblogs.asp.net/cazzu/archive/2006/01/07/GetServiceFromDTE.aspx
最后也共享下我写的这个add-in:下载