2016-11-15 46 views
1

我有一個定義如下的泛型方法,它包裝了對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找到對方法的調用,既有直接調用,也有通過參數列表?

+0

你可以在visual studio中找到所有方法的正則表達式(或者是懶,只需找到'CreateRequest <') –

+0

我需要這個在單元測試中自動運行,而不是手動運行。 – Nick

+0

我不確定你可以更改哪部分代碼。你能修改通用方法嗎?如果可以的話,我會建議啓用自動測試功能,嵌入到方法中。如果你願意的話,我可以嘗試闡述一個答案。 –

回答

1

我設法通過Rosyln來實現這個目標,但這不是一件容易的事情。 我最終在我的解決方案中加載了所有文檔,在其中搜索了MemberAccessExpressionSyntax類型,並將其標識爲CreateRequestGenericNameSyntax拉出。

然後我可以抓住每一個從我知道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()); 
         } 
        } 
       } 
      } 
     } 
    } 
} 

就像我說的,不是很好,但它的工作。