如何生成简单的契约类
本文关键字:契约 简单 何生成 | 更新日期: 2023-09-27 18:04:57
我有一个c#应用程序,它为合约公开了一个API(通过WebAPI或WCF)和一个单独的dll。契约dll由NuGet公开,以便调用者在调用应用程序时可以使用契约对象。
然而,契约类是用自定义验证属性装饰的,其中许多属性依赖于存储库dll等,我不想将其包含在契约NuGet中。我基本上想发布一个简化形式的合约,我可以接收它,并将其反序列化到原始合约对象中(如果需要,使用valueinjector)。
我无法找到一个开箱即用的解决方案,所以我开始编写一个t4转换文件来运行契约程序集,并创建新的、简化的类,而不需要所有的验证属性。这变得相当乏味,我希望有人有一个更好的解决方案。我很难相信我是第一个遇到这种问题的人。
Edit:感谢您的反馈。目前,我编写的T4工作得很好,只生成我需要的简单类。好处是,虽然调用方发送的是简化的类,但它被反序列化为原始类。
我已经在下面包含了基本.ttinclude文件的代码:
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Runtime.Serialization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" #>
<#@ assembly name="System.Xml" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Text"#>
<#@ include file="EF.Utility.CS.ttinclude"#>
<#+
private class DtoGenerator
{
private static readonly Lazy<System.Resources.ResourceManager> ResourceManager =
new Lazy<System.Resources.ResourceManager>(
() => new System.Resources.ResourceManager("System.Data.Entity.Design", typeof(MetadataItemCollectionFactory).Assembly), isThreadSafe: true);
private readonly TextTransformation textTransform;
private readonly EntityFrameworkTemplateFileManager fileManager;
private readonly ITextTemplatingEngineHost Host;
public DtoGenerator(object textTransform, ITextTemplatingEngineHost host)
{
this.textTransform = textTransform as TextTransformation;
this.fileManager = EntityFrameworkTemplateFileManager.Create(textTransform);
this.Host = host;
}
public void Write(string text)
{
this.textTransform.Write(text);
}
public void Generate(string folderName, string assemblyName)
{
var inputAssembly = GetAssembly(folderName, assemblyName);
if (inputAssembly == null)
{
Write("// No assembly found");
return;
}
var classes = new Dictionary<Type, string>();
var types = inputAssembly.GetTypes()
.Where(type => type.GetCustomAttributes(false).Any(o => o.GetType().Name == "GenerateDtoAttribute")) //This is a custom attribute that marks classes to be generated
.ToList();
if (!types.Any())
{
Write("// No types found");
return;
}
foreach (var type in types)
{
var baseExtension = "";
var baseClass = type.BaseType;
if (baseClass != null && baseClass != typeof(object))
{
baseExtension = @" : " + baseClass;
}
var currentClass = string.Concat(@"
namespace ", type.Namespace, @"
{
public class ", type.Name, baseExtension, @"
{
", WritePublicProperties(type), @"
}
}");
classes.Add(type, currentClass);
}
foreach (var @class in classes)
{
fileManager.StartNewFile(@class.Key.Name + ".cs");
Write(GetHeader());
Write(@class.Value);
}
fileManager.Process();
}
private Assembly GetAssembly(string folderName, string assemblyName)
{
var relativePathTemplate = string.Format(@"..'{0}'bin'{{0}}'{1}.dll", folderName, assemblyName);
string debugPath,
releasePath,
debugRelativePath = string.Format(relativePathTemplate, "debug"),
releaseRelativePath = string.Format(relativePathTemplate, "release");
try
{
debugPath = Path.GetFullPath(Host.ResolvePath(debugRelativePath));
}
catch (FileNotFoundException)
{
debugPath = null;
}
try
{
releasePath = Path.GetFullPath(Host.ResolvePath(releaseRelativePath));
}
catch (FileNotFoundException)
{
releasePath = null;
}
if (debugPath == null && releasePath == null)
{
return null;
}
var debugDllDate = debugPath == null ? DateTime.MinValue : File.GetLastWriteTime(debugPath);
var releaseDllDate = releasePath == null ? DateTime.MinValue : File.GetLastWriteTime(releasePath);
var assemblyDllPath = debugDllDate > releaseDllDate ? debugPath : releasePath;
if (assemblyDllPath == null) return null;
var assembly = Assembly.LoadFrom(assemblyDllPath);
return assembly;
}
private string GetHeader()
{
var header = string.Format(
@"//------------------------------------------------------------------------------
// <auto-generated>
// {0}
//
// {1}
// {2}
// </auto-generated>
//------------------------------------------------------------------------------",
ResourceManager.Value.GetString("Template_GeneratedCodeCommentLine1", null),
ResourceManager.Value.GetString("Template_GeneratedCodeCommentLine2", null),
ResourceManager.Value.GetString("Template_GeneratedCodeCommentLine3", null));
return header;
}
private string WritePublicProperties(Type type)
{
var properties =
type.GetProperties()
.Where(
info => info.DeclaringType == type
&& info.GetMethod != null && info.GetMethod.IsPublic
&& info.SetMethod != null && info.SetMethod.IsPublic)
.ToList();
var propertyStrings = (from propertyInfo in properties
let typeString = GetTypeName(propertyInfo.PropertyType)
select string.Concat(@" public ", typeString, " ", propertyInfo.Name, @" { get; set; }")).ToList();
return string.Join("'r'n", propertyStrings);
}
private string GetTypeName(Type type)
{
if (type.IsGenericType)
return type.ToString().Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(GetTypeName).ToArray()) + ">";
return type.ToString();
}
}
#>
我有一个类似的情况,我有模型,我把模型从后台通过服务总线发送到前台和网站。我所做的是,在3个不同的命名空间中,有3个几乎相同的POCO类。
现在,前端办公室中的模型具有来自JSON.NET
名称空间的属性,这些属性决定了它需要如何(反)序列化。servicebus名称空间中的模型可能具有一些附加属性,如iscached
或其他属性。类似地,后端模型具有用于验证持久性[Required]
和其他东西的属性。
显然,我不希望我在数据库或服务总线中保存的模型上的JSON.NET
属性。我也不希望在前端或服务总线中使用System.ComponentModel.DataAnnotation
属性。因此,我将每一层的模型映射到下一层的名称空间(取决于数据流的方向)。
映射是用AutoMapper在每一层上完成的——如果模型非常相似,这可以是一行代码。因此,来自JSON的传入消息被反序列化为frontooffice模型,然后转换为ServiceBus模型并放置在总线上。BackOffice对ServiceBus模型进行了反序列化,并将其映射到BackOffice模型。不管怎样——你懂的。
你也许可以做同样的设置——你的NuGet库是非常干净的POCO库,几乎没有依赖关系。在它后面有一个非常相似的模型层,您可以将传入的消息映射到该模型层。然后对它执行您想要执行的富验证。
这似乎是重复,但对我来说,它工作得很好,因为我们有解耦的模型。这将——例如——还将后台办公室的ALM与前台办公室分离(只要映射仍然有效)。如果您使用T4方法-您的API模型与其他模型非常紧密地耦合,这可能在将来成为一个问题。