4

我正在寻找一种方法,让我使用 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-Methods 必须以“Get”字开头。我相信这将有助于在与多个同事一起工作时保持服务的可维护性。

背景生成:

我将在其他几个项目中使用这个 REST Web 服务,因为我需要编写一个客户端来访问这个服务。但是我不想为每一个都写一个客户端,总是在服务发生变化时进行调整。我希望根据 Web 服务代码文件自动生成客户端。

以前的方法:

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

欢迎任何建议。:)

4

4 回答 4

2

这可能是一个长镜头,但仍然有资格作为“任何建议”:)

您可以编译代码,然后运行构建后命令,这将是您必须编写的工具,它使用反射将解析的 UriTemplate 文本与方法参数名称进行比较,捕获错误并以 MSBuild 的方式输出它们会接。查看此链接以获取有关如何输出的信息,以便 MSBuild 将错误放入 Visual Studio 错误列表中。如果发现错误,则构建后工具可以删除已编译的程序集,从而“模拟”失败的构建。

这是引导我访问 MSBuild 博客的SO 链接,仅供参考。

高温高压

于 2012-01-26T01:13:23.620 回答
2

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

对于客户端代码生成,有很多可能性。如果您喜欢 T4 方法,则可能有一种方法可以使其与 MSBuild 一起使用(但您肯定需要提供更多关于现在不起作用的细节)。如果您仍然想要一个替代方案,基于反射的后期构建工具是另一种方法......

于 2012-01-26T01:41:05.803 回答
2

除了使用 DTE 或其他方法来解析 C# 代码,您还可以使用反射(带有仅反射上下文)在编译后检查程序集。使用反射是一种更强大的解决方案,并且可能也更快(特别是如果您使用 Mono.Cecil 进行反射)。

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

于 2012-01-26T01:51:53.500 回答
2

这是一个简短的、极其丑陋的程序,您可以在一个程序集或一组程序集上运行(只需将 dll 作为参数传递)来执行 WebGet UriTemplate 检查。如果你没有通过任何东西,它会自行运行(并且适当地失败,因为它是它自己的单元测试)。

程序会将缺少参数的方法的名称和缺少的参数的名称打印到标准输出,如果找到,将返回非零返回码(程序失败的标准),使其适合作为构建后的事件。如果您的眼睛流血,我概不负责:

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) { }
    }
}
于 2012-01-26T02:59:24.163 回答