我正在寻找一种方法,让我们在构建过程中使用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) { }
}
}