在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:下载