【问题标题】:How to generate simple contract classes如何生成简单的合约类
【发布时间】:2015-07-28 07:23:48
【问题描述】:

我有一个 c# 应用程序,它公开了一个 API(通过 WebAPI 或 WCF)和一个用于合同的单独 dll。合约 dll 由 NuGet 公开,以便调用者在调用应用程序时可以使用合约对象。

但是,合同类使用自定义验证属性进行修饰,其中许多都依赖于存储库 dll 等,我不想将其包含在合同 NuGet 中。我基本上想发布合同的简化形式,我可以接收它并将其反序列化为原始合同对象(如果需要,使用 ValueInjecter)。

我无法找到开箱即用的解决方案,因此我开始编写一个 t4 转换文件来运行合同程序集并创建新的、简化的类,而无需所有验证属性。这变得非常乏味,我希望有人有更好的解决方案。我很难相信我是第一个遇到这个问题的人。

编辑:感谢您的反馈。目前,我编写的 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();
        }
    }

    #>

【问题讨论】:

    标签: c# t4 datacontract


    【解决方案1】:

    我有一种类似的情况,我有模型,我从后台通过服务总线将模型发送到前台和网站。我所做的是在 3 个不同的命名空间中拥有 3 个几乎相同的 POCO 类。

    现在,前台中的模型具有来自JSON.NET 命名空间的属性,这些属性决定了它需要如何进行(反)序列化。 servicebus 命名空间中的模型可能有一些额外的属性,如iscached 或其他东西。同样,后端模型具有用于验证持久性[Required] 和事物的属性。

    显然我不希望我保存在数据库或服务总线中的模型具有JSON.NET 属性。我也不希望前端或服务总线中的 System.ComponentModel.DataAnnotation 属性。所以我将每一层的模型映射到下一层的命名空间(取决于数据流的方向)。

    在每一层上使用 AutoMapper 完成映射 - 如果模型非常相似,这可以是单行的。因此,来自 JSON 的传入消息被反序列化为 FrontOffice 模型,然后转换为放置在总线上的 ServiceBus 模型。 BackOffice 反序列化 ServiceBus 模型并将其映射到 BackOffice 模型...无论如何 - 你明白了。

    你也许可以做同样的设置——你的 NuGet 库是非常干净的 POCO 库,几乎没有依赖关系。您在其后面有一个高度相似的模型层,您可以将传入消息映射到该模型层。然后您对其执行您想要执行的丰富验证。

    这似乎是重复,但对我来说它工作得非常好,因为我们已经解耦了模型。例如,这也会将 BackOffice 的 ALM 与 FrontOffice 分离(只要映射仍然有效)。如果您使用 T4 方法 - 您的 API 模型与您的其他模型非常紧密耦合,这可能会成为未来的问题。

    【讨论】:

      猜你喜欢
      • 2017-07-10
      • 2018-11-07
      • 2021-08-19
      • 1970-01-01
      • 2019-01-27
      • 1970-01-01
      • 2011-11-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多