基于跨项目的代码文件进行构建时代码验证和生成

本文关键字:代码 验证 时代 构建 项目 于跨 文件 | 更新日期: 2023-09-27 18:21:05

我正在寻找一种方法,让我们在构建过程中使用Visual Studio 2010(非express)和MSBuild验证代码和生成器代码。

背景验证:

我正在使用WCF web Api编写一个RESTful web服务。在代表web服务的服务类中,我必须定义一个端点,并将其他参数声明为普通测试。当端点声明中的参数名称与C#方法的参数不同时,我会收到一个错误——不幸的是,在访问web服务时是在运行时,而不是在编译时。因此,我认为在编译步骤中分析web服务类是否存在这样的缺陷会很好,当出现错误时会返回错误。

示例:

[WebGet(UriTemplate = "Endpoint/{param1}/{param2}")]
public string MyMethod(string param1, string parameter2) {
    // Accessing the web service now will result in an error,
    // as there's no fitting method-parameter named "param2".
}

此外,我还想强制执行一些命名规则,例如GET方法必须以"GET"字开头。我相信这将有助于在与几位同事合作时保持服务的可维护性。

背景生成:

我将在其他一些项目中使用此RESTWeb服务,因为我需要编写一个客户端来访问此服务。但我不想为每一个都编写一个客户端,总是在服务更改时进行调整。我希望客户端是基于web服务代码文件自动生成的。

以前的方法:

到目前为止,我尝试使用T4模板,使用DTE接口来解析代码文件并对其进行验证,或者生成客户端。手动保存时,这在Visual Studio中运行良好,但在生成过程中集成它的效果并不好,因为Visual Studio主机无法使用MSBuild。

欢迎任何建议。:)

基于跨项目的代码文件进行构建时代码验证和生成

编译程序集后,可以使用反射(带"仅反射"上下文)来检查程序集,而不是使用DTE或其他方法来解析C#代码。使用反射是一个更健壮的解决方案,而且可能更快(尤其是如果您使用Mono.Cecil进行反射)。

对于MSBuild集成,我建议编写一个自定义MSBuild任务——这比编写由MSBuild执行的命令行实用程序更简单、更健壮/更优雅。

这可能是一个很长的机会,但仍然符合"任何建议":)

您可以编译代码,然后运行一个生成后命令,这将是您必须编写的一个工具,它使用反射将解析的UriTemplate文本与方法参数名称进行比较,捕捉错误并以MSBuild将拾取的方式输出。有关如何输出的信息,请参阅此链接,以便MSBuild将错误放入visual studio错误列表中。如果发现错误,生成后工具可以删除已编译的程序集,从而"模拟"失败的生成。

以下是SO链接,它也引导我进入MSBuild博客,仅供参考。

HTH

对于强制执行方面,自定义FxCop规则可能非常适合。

对于客户端代码生成,有相当多的可能性。如果你喜欢T4方法,可能有一种方法可以让它与MSBuild一起工作(但你肯定需要提供更多关于现在不起作用的细节)。如果你无论如何都想要一个替代方案,基于反射的后构建工具是另一种选择。。。

这里有一个非常丑陋的程序,您可以在一个程序集或一组程序集上运行该程序(只需将dll作为参数传递)来执行WebGet-UriTemplate检查。如果您没有通过任何测试,它会自行运行(并且会失败,因为这是它自己的单元测试)。

该程序将向stdout输出缺少参数的方法的名称和缺少参数的名称,如果找到,将返回非零返回代码(程序失败的标准返回代码),使其适合作为生成后事件。如果你的眼睛流血,我不负责:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.ServiceModel.Web;
namespace ConsoleApplication1
{
    class Program
    {
        static int Main(string[] args)
        {
            var failList = new ConcurrentDictionary<MethodInfo, ISet<String>>();
            var assembliesToRunOn = (args.Length == 0 ? new[] {Assembly.GetExecutingAssembly()} : args.Select(Assembly.LoadFrom)).ToList();
            assembliesToRunOn.AsParallel().ForAll(
                a => Array.ForEach(a.GetTypes(), t => Array.ForEach(t.GetMethods(BindingFlags.Public | BindingFlags.Instance),
                    mi =>
                        {
                            var miParams = mi.GetParameters();
                            var attribs = mi.GetCustomAttributes(typeof (WebGetAttribute), true);
                            if (attribs.Length <= 0) return;
                            var wga = (WebGetAttribute)attribs[0];
                            wga.UriTemplate
                                .Split('/')
                                .ToList()
                                .ForEach(tp =>
                                             {
                                                 if (tp.StartsWith("{") && tp.EndsWith("}"))
                                                 {
                                                     var tpName = tp.Substring(1, tp.Length - 2);
                                                     if (!miParams.Any(pi => pi.Name == tpName))
                                                     {
                                                         failList.AddOrUpdate(mi, new HashSet<string> {tpName}, (miv, l) =>
                                                                                                                    {
                                                                                                                        l.Add(tpName);
                                                                                                                        return l;
                                                                                                                    });
                                                     }
                                                 }
                                             });
                        })));
            if (failList.Count == 0) return 0;
            failList.ToList().ForEach(kvp => Console.Out.WriteLine("Method " + kvp.Key + " in type " + kvp.Key.DeclaringType + " is missing the following expected parameters: " + String.Join(", ", kvp.Value.ToArray())));
            return failList.Count;
        }
        [WebGet(UriTemplate = "Endpoint/{param1}/{param2}")]
        public void WillPass(String param1, String param2) { }
        [WebGet(UriTemplate = "Endpoint/{param1}/{param2}")]
        public void WillFail() { }
        [WebGet(UriTemplate = "Endpoint/{param1}/{param2}")]
        public void WillFail2(String param1) { }
    }
}