用过
VS.NET
的朋友应该会发现
,
在编辑一些文件时
VS
会在文件下面自动创建它的附属文件
.
而这些附属文件往往是根据设计文件生成的代码文件来的
.
对于我们想实现这样的功能怎办呢
?
其实
MS
早就为我们想好了
,
只要简单地实现
IVsSingleFileGenerator;
说是简单不过还是要做些功夫的
,
就是把编写后
VsSingleFileGenerator
注册到共公程序集中
,
然后在注册表里添加一些东西才行
.
下面介绍自己实现
NClay
实体设计的
SingleFileGenerator,
有需要的朋友可以参考代码实现自己的
SingleFileGenerator:)
实现目的编写XML模型描述后自动生成附属C#代码文件.
XML设计文件:
<?xml version="1.0" encoding="utf-8" ?>
<nclay_models xmlns="http://nclay.cn/model.xsd" namespace="Blogs.Entities">
<class name="User" table="TUser" comment="">
<id name="UserID" type="System.String"/>
<property name="UserName" type="System.String"/>
<property name="UserPWD" type="System.String"/>
<property name="EMail" type="System.String"/>
<property name="Enabled" type="System.String"/>
<property name ="Remark" type="System.String"/>
</class>
</nclay_models>
生成代码模型文件内容:
using
System;
using
System.Data;
using
NClay.Data;
using
NClay.Data.Mappings;
[TableMapper(Name
=
"
User
"
)]

public
partial
class
User
{
private void mUserID;
[PrimaryKey(Name="UserID")]

public virtual void UserID
{

get
{
return this.mUserID;
}

set
{
this.mUserID = value;
}
}

public partial class Mapper : Table
{
private ObjectField mAll;
private ObjectField mUserID;
public Mapper() :

base("User")
{
this.mAll = new ObjectField("*", this);
this.mUserID = new ObjectField("UserID", this);
}

public virtual ObjectField All
{

get
{
return this.mAll;
}
}

public virtual ObjectField UserID
{

get
{
return this.mUserID;
}
}
}
}

public
partial
class
ModelContext
{
static User.Mapper mUser = new User.Mapper();

public static User.Mapper User
{

get
{
return mUser;
}
}
}
对于SingleFileGenerator的编写我直接就贴代码,其实也没什么好讲就一个类.
using
System;
using
System.Collections.Generic;
using
System.Text;
using
System.Runtime.InteropServices;
using
Microsoft.VisualStudio.Shell.Interop;
using
System.ComponentModel;
using
System.CodeDom.Compiler;
using
Microsoft.VisualStudio.Shell;
using
VSOLE
=
Microsoft.VisualStudio.OLE.Interop;
using
System.CodeDom;
using
System.IO;
using
System.Xml;

namespace
NClay.Generators

{

[Guid("2F6150C6-BC48-4733-96FE-91F2A90AADCF")]
public class ModelGenerator : IVsSingleFileGenerator, VSOLE::IObjectWithSite

{
private CodeDomProvider codeProvider;

private string codeFileNameSpace;
private string codeFilePath;

private object site;

private IVsGeneratorProgress codeGeneratorProgress;

public CodeDomProvider CodeProvider

{
get

{
if (this.codeProvider == null)

{
codeProvider = CodeDomProvider.CreateProvider("C#");
}

return this.codeProvider;
}

set

{
if (value == null)

{
throw new ArgumentNullException();
}

this.codeProvider = value;
}
}


IVsSingleFileGenerator Members#region IVsSingleFileGenerator Members

public int DefaultExtension(out string ext)

{
string defExt;
ext = string.Empty;

defExt = this.CodeProvider.FileExtension;

if (((defExt != null) && (defExt.Length > 0)) && (defExt[0] != '.'))

{
defExt = "." + defExt;
}

if (!string.IsNullOrEmpty(defExt))

{
ext = ".NCaly" + defExt;
}

return 0;
}

public int Generate(string wszInputFilePath, string bstrInputFileContents, string wszDefaultNamespace, IntPtr[] pbstrOutputFileContents, out uint pbstrOutputFileContentSize, IVsGeneratorProgress pGenerateProgress)

{
if (bstrInputFileContents == null)

{
throw new ArgumentNullException(bstrInputFileContents);
}

this.codeFilePath = wszInputFilePath;
this.codeFileNameSpace = wszDefaultNamespace;
this.codeGeneratorProgress = pGenerateProgress;

byte[] generatedStuff = this.GenerateCode(wszInputFilePath, bstrInputFileContents);

if (generatedStuff == null)

{
pbstrOutputFileContents[0] = IntPtr.Zero;
pbstrOutputFileContentSize = 0;
}
else

{
pbstrOutputFileContents[0] = Marshal.AllocCoTaskMem(generatedStuff.Length);
Marshal.Copy(generatedStuff, 0, pbstrOutputFileContents[0], generatedStuff.Length);
pbstrOutputFileContentSize = (uint)generatedStuff.Length;
}
return 0;
}
#endregion


IObjectWithSite Members#region IObjectWithSite Members

public void GetSite(ref Guid riid, out IntPtr ppvSite)

{
if (this.site == null)

{
throw new Win32Exception(-2147467259);
}

IntPtr objectPointer = Marshal.GetIUnknownForObject(this.site);

try

{
Marshal.QueryInterface(objectPointer, ref riid, out ppvSite);
if (ppvSite == IntPtr.Zero)

{
throw new Win32Exception(-2147467262);
}
}
finally

{
if (objectPointer != IntPtr.Zero)

{
Marshal.Release(objectPointer);
objectPointer = IntPtr.Zero;
}
}
}

public void SetSite(object pUnkSite)

{
this.site = pUnkSite;
this.codeProvider = null;
}

#endregion


Private Methods#region Private Methods
protected byte[] GenerateCode(string inputFileName, string inputFileContent)

{
CodeCompileUnit compileUnit;

StreamWriter writer = new StreamWriter(new MemoryStream(), Encoding.UTF8);

XmlDocument doc = new XmlDocument();
doc.LoadXml(inputFileContent);

//compileUnit = ClassGenerator.Create(doc, this.codeProvider);
//补上代码
compileUnit = CodeGenerator.GeneratorByCodeDom(doc, this.CodeProvider);

if (this.codeGeneratorProgress != null)

{
this.codeGeneratorProgress.Progress(0x4b, 100);
}

this.CodeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, null);

if (this.codeGeneratorProgress != null)

{
this.ThrowOnFailure(this.codeGeneratorProgress.Progress(100, 100));
}
writer.Flush();

return this.StreamToBytes(writer.BaseStream);
}

protected byte[] StreamToBytes(Stream stream)

{
if (stream.Length == 0)

{
return new byte[0];
}

long pos = stream.Position;

stream.Position = 0;

byte[] buffer = new byte[(int)stream.Length];
stream.Read(buffer, 0, buffer.Length);

stream.Position = pos;
return buffer;
}

private void ThrowOnFailure(int hr)

{
if ((hr < 0))

{
Marshal.ThrowExceptionForHR(hr);
}
}
#endregion
}

class CodeGenerator

{
public static CodeCompileUnit GeneratorByCodeDom(XmlDocument doc, CodeDomProvider codeprovider)

{
CodeCompileUnit codeunit = new CodeCompileUnit();
CodeNamespace nspace = null;
try

{
nspace = new CodeNamespace(
doc.ChildNodes[1].Attributes["namespace"].Value);
codeunit.Namespaces.Add(nspace);
nspace.Imports.Add(new CodeNamespaceImport("System"));
nspace.Imports.Add(new CodeNamespaceImport("System.Data"));
nspace.Imports.Add(new CodeNamespaceImport("NClay.Data"));
nspace.Imports.Add(new CodeNamespaceImport("NClay.Data.Mappings"));
foreach (XmlNode cls in doc.ChildNodes[1].ChildNodes)

{
if (cls.Name.ToLower() == "class")
CreateClass(nspace, cls);
}


}
catch (Exception e_)

{
nspace.Comments.Add(new CodeCommentStatement(e_.Message));
}
return codeunit;
}
private static void CreateClass(CodeNamespace nspace, XmlNode cls)

{
CodeMemberField mfield;
CodeMemberProperty mproperty;
string type, name, column, table, sequence, cast, comment;
comment = null;
sequence = null;
cast = null;
name = cls.Attributes["name"].Value;
if (cls.Attributes["table"] == null)
table = name;
else
table = cls.Attributes["table"].Value;
if (cls.Attributes["comment"] != null)
comment = cls.Attributes["comment"].Value;
name = FirstToUpper(name);
table = FirstToUpper(table);
CodeTypeDeclaration entity = new CodeTypeDeclaration(
name);
entity.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("TableMapper"),
new CodeAttributeArgument("Name", new CodePrimitiveExpression(table))));
entity.IsPartial = true;
if (comment != null)

{
entity.Comments.Add(new CodeCommentStatement("<summary>", true));
entity.Comments.Add(new CodeCommentStatement(comment, true));
entity.Comments.Add(new CodeCommentStatement("</summary>", true));
}
CodeTypeDeclaration mapper = new CodeTypeDeclaration("Mapper");
mapper.IsPartial = true;
mapper.BaseTypes.Add(new CodeTypeReference("Table"));
entity.Members.Add(mapper);
if (comment != null)

{
mapper.Comments.Add(new CodeCommentStatement("<summary>", true));
mapper.Comments.Add(new CodeCommentStatement(comment, true));
mapper.Comments.Add(new CodeCommentStatement("</summary>", true));
}
CodeConstructor constructor = new CodeConstructor();
constructor.Attributes = MemberAttributes.Public;
constructor.BaseConstructorArgs.Add(new CodePrimitiveExpression(table));
mapper.Members.Add(constructor);
mfield = new CodeMemberField(new CodeTypeReference("ObjectField"), "mAll");
mapper.Members.Add(mfield);
mproperty = new CodeMemberProperty();
mproperty.Type = new CodeTypeReference("ObjectField");
mproperty.Name = "All";
mproperty.Attributes = MemberAttributes.Public;
mproperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "mAll")));
mapper.Members.Add(mproperty);
CodeAssignStatement assingexp;
assingexp = new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "mAll"),
new CodeObjectCreateExpression("ObjectField",
new CodePrimitiveExpression("*"),
new CodeThisReferenceExpression()));
constructor.Statements.Add(assingexp);

comment = null;
foreach (XmlNode property in cls.ChildNodes)

{

entity#region entity

type = property.Attributes["type"].Value;
name = property.Attributes["name"].Value;
name = FirstToUpper(name);
if (property.Attributes["sequence"] != null)
sequence = property.Attributes["sequence"].Value;
if (property.Attributes["cast"] != null)
cast = property.Attributes["cast"].Value;
if (property.Attributes["column"] == null)
column = name;
else
column = property.Attributes["column"].Value;
if (property.Attributes["comment"] != null)
comment = property.Attributes["comment"].Value;
column = FirstToUpper(column);
mfield = new CodeMemberField(type, "m" + name);
entity.Members.Add(mfield);
mproperty = new CodeMemberProperty();
mproperty.Type = new CodeTypeReference(type);
mproperty.Name = name;
mproperty.Attributes = MemberAttributes.Public;
if (comment != null)

{
mproperty.Comments.Add(new CodeCommentStatement("<summary>", true));
mproperty.Comments.Add(new CodeCommentStatement(comment, true));
mproperty.Comments.Add(new CodeCommentStatement("</summary>", true));
}
mproperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m" + name)));
mproperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m" + name), new CodePropertySetValueReferenceExpression()));
entity.Members.Add(mproperty);
if (property.Name == "id")

{
if (sequence == null)

{

mproperty.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("PrimaryKey"),
new CodeAttributeArgument("Name", new CodePrimitiveExpression(column))));
}
else

{
mproperty.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("PrimaryKey"),
new CodeAttributeArgument("Name", new CodePrimitiveExpression(column)),
new CodeAttributeArgument("Sequence", new CodeTypeOfExpression(sequence))));
}
}
else

{
if (cast == null)

{
mproperty.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("Column"),
new CodeAttributeArgument("Name", new CodePrimitiveExpression(column))));
}
else

{
mproperty.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("Column"),
new CodeAttributeArgument("Name", new CodePrimitiveExpression(column)),
new CodeAttributeArgument("Cast", new CodeTypeOfExpression(cast))));
}

}
#endregion
string mapptype;

mapper#region mapper
switch(type)

{
case "System.Int16":
case "System.Int32":
case "System.Int64":
case"System.Double":
case "System.Single":
case "System.Decimal":
case "System.Byte":
mapptype = "NumberField";
break;
case"System.Byte[]":
mapptype = "BytesField";
break;
case "System.Boolean":
mapptype = "BooleanField";
break;
case"System.DateTime":
mapptype = "DateTimeField";
break;
case "System.Guid":
mapptype = "GuidField";
break;
case"System.String":
mapptype = "StringField";
break;
default:
mapptype = "ObjectField";
break;
}


mfield = new CodeMemberField(new CodeTypeReference(mapptype), "m" + name);
mapper.Members.Add(mfield);
mproperty = new CodeMemberProperty();
mproperty.Type = new CodeTypeReference(mapptype);
mproperty.Name = name;
mproperty.Attributes = MemberAttributes.Public;
if (comment != null)

{
mproperty.Comments.Add(new CodeCommentStatement("<summary>", true));
mproperty.Comments.Add(new CodeCommentStatement(comment, true));
mproperty.Comments.Add(new CodeCommentStatement("</summary>", true));
}
mproperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m" + name)));
mapper.Members.Add(mproperty);

assingexp = new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m" + name),
new CodeObjectCreateExpression(mapptype,
new CodePrimitiveExpression(column),
new CodeThisReferenceExpression()));
constructor.Statements.Add(assingexp);
#endregion


}


nspace.Types.Add(entity);
name = cls.Attributes["name"].Value;
name = FirstToUpper(name);
CodeTypeDeclaration modelcontext = new CodeTypeDeclaration("ModelContext");
modelcontext.IsPartial = true;
mfield = new CodeMemberField(new CodeTypeReference(name + ".Mapper"), "m" + name);
mfield.Attributes = MemberAttributes.Static;

mfield.InitExpression = new CodeObjectCreateExpression(new CodeTypeReference(name + ".Mapper"), new CodeExpression[]
{ });
modelcontext.Members.Add(mfield);
mproperty = new CodeMemberProperty();
mproperty.Type = new CodeTypeReference(name + ".Mapper");
mproperty.Name = name;
mproperty.Attributes = MemberAttributes.Public | MemberAttributes.Static;
mproperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("m" + name)));
modelcontext.Members.Add(mproperty);

nspace.Types.Add(modelcontext);
}
private static string FirstToUpper(string value)

{
string first = value.Substring(0, 1).ToUpper();
return first + value.Substring(1, value.Length - 1);
}

}
}
接下来就是注册
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\CLSID\{2F6150C6-BC48-4733-96FE-91F2A90AADCF}]
@="NClay.Generators.ModelGenerator"
"InprocServer32"="d:\\windows\\system32\\mscoree.dll"
"Class"="NClay.Generators.ModelGenerator"
"Assembly"="NClay.Generators, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8c768ba656ce9125"
"ThreadingModel"="Both"
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\NClayGenerator]
@="NClay Model Generator"
"CLSID"="{2F6150C6-BC48-4733-96FE-91F2A90AADCF}"
"GeneratesDesignTimeSource"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}\NClayGenerator]
@="NClay Model Generator"
"CLSID"="{2F6150C6-BC48-4733-96FE-91F2A90AADCF}"
"GeneratesDesignTimeSource"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}\NClayGenerator]
@="NClay Model Generator"
"CLSID"="{2F6150C6-BC48-4733-96FE-91F2A90AADCF}"
"GeneratesDesignTimeSource"=dword:00000001
所有CLSID对应的值是实现IVsSingleFileGenerator的Guid描述.(记住要所DLL注册到全局程序集中)
这样SingleFileGenerator就完成了,在VS中使用这个SingleFileGenerator.在解决方案管理器右键文件属性,在自定义工具填写上: NclayGenerator。
在这个应用中也许有朋友想这样编写XML很麻烦,又没有编写提示和验证等功能.其实MS也为我们想好了,只需要写个XmlSchema就可以了.下载代码里也有考参文件.

下载代码
参考资料:
其实在google 搜一下IVsSingleFileGenerator就有一堆资料
多谢timiil告诉我IVsSingleFileGenerator的功能.