通过Roslyn检查Automapper的使用情况

时间:2016-11-15 16:50:06

标签: c# automapper roslyn

我有一个定义如下的泛型方法,它包含对AutoMapper的调用。

public TOut CreateRequest<TOut, TModelIn>(TModelIn data)
{
  ...
  return Mapper.Map<TModelIn, TOut>();
}

如果我打电话给Mapper.AssertConfigurationIsValid,那么我可以检查我的地图是否设置正确,但我无法检查是否有人添加了一行代码以在没有定义地图时尝试地图。

所以我希望能够扫描我的程序集并找到对上述方法的所有调用,提取正在使用的泛型类型,然后将这些类型连接到Mapper.Map<Type1, Type2>();调用中。然后我可以调用Mapper.AssertConfigurationIsValid方法,并确保我的代码中的所有地图都已映射并且有效。

我的想法是将它添加到UnitTest中,以便在我让用户去测试它以查看会发生什么之前我可以确定映射。

[更新] 我一直在寻找在我的单元测试中使用Roslyn来做到这一点。有谁知道如何通过Roslyn找到方法的调用,直接调用和通过参数列表?

1 个答案:

答案 0 :(得分:1)

我设法通过Rosyln实现了这一目标,但这并不容易。 我最终在我的解决方案中加载了所有文档,在其中搜索了MemberAccessExpressionSyntax类型,并提取了GenericNameSyntax标识为CreateRequest的文档。

然后我可以从TypeListArgument中获取每个参数,其中我知道可能有1,2或3.我只想要3的实例,因此可以将它们读作IdentifixNameSyntax对象,并使用Identifier为我提供AutoMapper地图所需的类名。

然后,我必须使用Reflection在程序集中查找类或枚举的名称,以便为Type提供我可以传递到AutoMapper的内容。

测试设置代码:

var slnPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "MySolution.sln"));
var workspace = MSBuildWorkspace.Create();
_solution = workspace.OpenSolutionAsync(slnPath).Result;
_project = _solution.Projects.First(p => p.Name == "MyProject");

foreach (var documentId in _project.DocumentIds) {
    var document = _solution.GetDocument(documentId);
    if (document.SupportsSyntaxTree) {
        _documents.Add(document);
    }
}

主要代码:

foreach (var document in _documents) {
    var methods = document.GetSyntaxRootAsync().Result.DescendantNodes().OfType<MemberAccessExpressionSyntax>();

    foreach (var m in methods.Where(x => x.Name is GenericNameSyntax)) {
        var genSyntax = m.Name as GenericNameSyntax;
        if (genSyntax?.Identifier.Text == "CreateRequest") {
            var args = genSyntax.TypeArgumentList.Arguments;

            if (args.Count == 3) {
                var item1 = args[0] as IdentifierNameSyntax;
                var item2 = args[1] as IdentifierNameSyntax;

                if (item1 != null && item2 != null) {

                    var c1 = ReflectionTestHelper.GetClassesWithKeyword(item1.Identifier.Text).SingleOrDefault(x => x.Name == item1.Identifier.Text)
                        ?? ReflectionTestHelper.GetEnumsWithKeyword(item1.Identifier.Text).SingleOrDefault(x => x.Name == item1.Identifier.Text);

                    var c2 = ReflectionTestHelper.GetClassesWithKeyword(item2.Identifier.Text).SingleOrDefault(x => x.Name == item2.Identifier.Text)
                        ?? ReflectionTestHelper.GetEnumsWithKeyword(item2.Identifier.Text).SingleOrDefault(x => x.Name == item2.Identifier.Text);

                    if (c1 == null)
                        errors.Add("Unable to find Class for mapping :: " + item1.Identifier.Text);

                    if (c2 == null)
                        errors.Add("Unable to find Class for mapping :: " + item2.Identifier.Text);

                    if (c1 != null && c2 != null) {

                        var map = Mapper.Configuration.FindTypeMapFor(c1, c2);

                        if (map == null) {

                            var location = genSyntax.GetLocation().GetMappedLineSpan();
                            var line = location.Span.Start.Line + 1;

                            var errormessage = new StringBuilder();
                            errormessage.AppendLine("No AutoMapper map found for :: " + item1.Identifier.Text + " -> " + item2.Identifier.Text);
                            errormessage.AppendLine("\tLocation: " + document.FilePath + "[Line:" + line + "]");
                            errormessage.AppendLine("\tMethod: " + genSyntax.Parent);
                            errors.Add(errormessage.ToString());
                        }
                    }
                }
            }
        }
    }
}

就像我说的那样,不是那么好,但它确实起到了作用。