Visual Studio 的單一檔案產生器(Single File Generator)可在儲存特定副檔名檔案時,自動產生附屬代碼檔(如 .designer.cs)。本文示範實作 IVsSingleFileGenerator,將自訂 XML 模型轉換為 C# 實體類別與映射代碼。
<?xml version="1.0" encoding="utf-8"?>
<nclay_models xmlns="https://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; } }
}
實作 IVsSingleFileGenerator 與 IObjectWithSite:
[Guid("2F6150C6-BC48-4733-96FE-91F2A90AADCF")]
public class ModelGenerator : IVsSingleFileGenerator, VSOLE::IObjectWithSite {
private CodeDomProvider codeProvider;
private string codeFileNameSpace, codeFilePath;
private object site;
private IVsGeneratorProgress codeGeneratorProgress;
public CodeDomProvider CodeProvider {
get { return codeProvider ?? (codeProvider = CodeDomProvider.CreateProvider("C#")); }
set { codeProvider = value ?? throw new ArgumentNullException(); }
}
#region IVsSingleFileGenerator
public int DefaultExtension(out string ext) {
ext = string.Empty;
var defExt = CodeProvider.FileExtension;
if (!string.IsNullOrEmpty(defExt) && defExt[0] != '.') defExt = "." + defExt;
if (!string.IsNullOrEmpty(defExt)) ext = ".NClay" + 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(nameof(bstrInputFileContents));
codeFilePath = wszInputFilePath;
codeFileNameSpace = wszDefaultNamespace;
codeGeneratorProgress = pGenerateProgress;
byte[] generatedStuff = 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; // S_OK
}
#endregion
#region IObjectWithSite
public void GetSite(ref Guid riid, out IntPtr ppvSite) { /* COM 站點查詢 */ }
public void SetSite(object pUnkSite) { site = pUnkSite; codeProvider = null; }
#endregion
#region 私有方法
protected byte[] GenerateCode(string inputFileName, string inputFileContent) {
var compileUnit = CodeGenerator.GeneratorByCodeDom(
new XmlDocument { InnerXml = inputFileContent }, CodeProvider);
codeGeneratorProgress?.Progress(0x4b, 100);
using (var ms = new MemoryStream())
using (var writer = new StreamWriter(ms, Encoding.UTF8)) {
CodeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, null);
writer.Flush();
codeGeneratorProgress?.Progress(100, 100);
return StreamToBytes(ms);
}
}
protected byte[] StreamToBytes(Stream stream) { /* 流轉位元組陣列 */ }
private void ThrowOnFailure(int hr) { if (hr < 0) Marshal.ThrowExceptionForHR(hr); }
#endregion
}
CodeGenerator.GeneratorByCodeDom 解析 XML,建構 CodeCompileUnit:
<class> 產生:
型別對應邏輯(Mapper 欄位型別):
switch(type) {
case "System.Int16": case "System.Int32": ... 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;
}
gacutil /i NClay.Generators.dll)[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
// 同樣註冊至 {E6FDF8B0-F3D1-11D4-8576-0002A516ECE8} (VB) 與 {FAE04EC1-301F-11d3-BF4B-00C04F79EFBC} (C#)
NClayGenerator.NClay.cs 檔編寫 .xsd 結構描述並關聯 XML,即可獲得驗證與智慧提示:
