.NET Core 反射 - 如何在 ASP.NET Core 3 Razor Views 中查找 属性 的引用?

.NET Core Reflection - How to find references of a property inside ASP.NET Core 3 Razor Views?

我有 Razor Views localizated resources 使用 IViewLocalizer 像这样:

@inject  Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer Localizer
...
@Localizer.GetString("Click here to Log in")

我正在尝试从我的预编译视图中提取所有这些字符串(以构建 PO 文件字典),所以我想我必须 找到注入到我的视图中的所有 IViewLocalizer 引用,如下所示:


var findAll = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic |
                                         BindingFlags.Instance | BindingFlags.Static;
var asm = Assembly.LoadFile("MySite.Web.Views.dll"));
var view = asm.GetType("AspNetCore.Views_Home_Index");
var props = view.GetProperties(findAll).Where(p=>p.PropertyType.FullName == "Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer").ToList();

// I want to find all references that use this this IViewLocalizer Localizer.get()
var getter = props.First().GetGetMethod().MetadataToken; 

// How can I do it?
var allMethods = view.GetMethods(findAll);
foreach(var method in allMethods)
{
    // How can I check my methods for usages of that property getter, and in case get the strings?
}

我尝试使用 中使用 MethodBase.GetMethodBody().GetILAsByteArray() 的代码,但它没有用 - 看起来方法 public override Task ExecuteAsync() 很短,所以看起来我的观点是在其他地方呈现的。

此外,我不确定是否应该寻找 getter 的 MetadataToken(注入到我的视图中的 IViewLocalizer),或者我是否应该寻找实现 [=15 的任何具体方法的用法=] 和 IViewLocalizer.GetString(string, params object[]) .

PS:我知道我可以在我的 cshtml 视图上使用 Regex,只要注入的本地化程序遵循一些命名标准。如果我想不出反射解决方案,那就是 B 计划。

在 ILSpy 的帮助下(后来被 and 确认)我发现 异步方法(以及迭代器 类)是跨嵌套帮助程序生成的 类代表状态机。这些助手 类 可以在主类型的 NestedTypes 属性 中找到。

当我将所有内部类型的方法添加到搜索中时,我可以找到对我正在寻找的方法(getter 的 IViewLocalizer)的多次调用。 这有效:

var allMethods = view.GetMethods(findAll).ToList();
allMethods.AddRange( // also search in methods of nested types
  view.GetNestedTypes(findAll).SelectMany(x => x.GetMethods(findAll)));

foreach(var method in allMethods)
{
    var usages = ((MethodBase)method).GetMethodUsageOffsets(get);
    if (usages.Count() > 0)
        Debug.WriteLine($"{method.ReflectedType.FullName}.{method.Name}");
}

我期望在 AspNetCore 中找到的所有调用。Views_Home_Index3.ExecuteAsync() 实际上分布在这些内部类型的方法中:

AspNetCore.Views_Home_Index3+<ExecuteAsync>d__22.MoveNext
AspNetCore.Views_Home_Index3+<<ExecuteAsync>b__22_0>d.MoveNext
AspNetCore.Views_Home_Index3+<<ExecuteAsync>b__22_1>d.MoveNext
AspNetCore.Views_Home_Index3+<<ExecuteAsync>b__22_2>d.MoveNext
AspNetCore.Views_Home_Index3+<<ExecuteAsync>b__22_3>d.MoveNext
AspNetCore.Views_Home_Index3+<<ExecuteAsync>b__22_4>d.MoveNext
AspNetCore.Views_Home_Index3+<<ExecuteAsync>b__22_5>d.MoveNext
AspNetCore.Views_Home_Index3+<<ExecuteAsync>b__22_6>d.MoveNext
... etc... all states from the state machine

使用 Mono.Cecil 它变得更加容易,因为我什至不需要这个 GetMethodUsageOffsets ,因为 Cecil 可以轻松解释字节码:

var module = ModuleDefinition.ReadModule(System.IO.Path.Combine(appFolder, "MyApp.Web.Views.dll"));
var type = module.Types.First(x => x.Name.EndsWith("Index3"));
var allInstructions = typeDefinition.Methods
    .Union(typeDefinition.NestedTypes.SelectMany(x => x.Methods))
    .Where(m => m.HasBody)
    .SelectMany(x => x.Body.Instructions).ToList();

var calls = allInstructions.Where(i => i.ToString().Contains(
                "callvirt Microsoft.Extensions.Localization.LocalizedString Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer::GetString(System.String)")).ToList();

foreach (var call in calls) // previous is where string parameter is loaded
   System.Diagnostics.Debug.WriteLine(i.Previous.ToString());

结果:

IL_020a: ldstr "More Options"
IL_0241: ldstr "Add Text"
IL_15c8: ldstr "Reset"
IL_15ff: ldstr "Save Text Box Settings"
IL_0019: ldstr "None"
...etc

2020-09-23 编辑: 我刚刚发现有些方法可以拆分为多个级别,因此仅搜索第一级 NestedTypes 是行不通的。由于我目前正在使用 Cecil(而不是纯反射),这是我当前的代码:

// All assemblies that I want to scan
var modules = new List<ModuleDefinition>();
modules.Add(ModuleDefinition.ReadModule(System.IO.Path.Combine(appFolder, "MyProject.Web.Views.dll")));
modules.Add(ModuleDefinition.ReadModule(System.IO.Path.Combine(appFolder, "MyProject.Web.dll")));

// All types in my Assemblies
var allTypes = modules.SelectMany(m => m.Types).ToList();

// Then for each of those types I get all Nested Types:

// Get all nested types
for(int i = 0; I < allTypes.Count(); i++) 
    allTypes.AddRange(allTypes[i].NestedTypes); 

// Get all instructions using Cecil
var allInstructions = allTypes.SelectMany(x => x.Methods)
    .Where(m => m.HasBody)
    .SelectMany(x => x.Body.Instructions).ToList();

var i18nInstructions = allInstructions.Where(i => 
    i.ToString().Contains("GetString(System.String)") 
    || i.ToString().Contains("Microsoft.AspNetCore.Mvc.Localization.HtmlLocalizerExtensions::GetHtml(Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer,System.String)")
    ).ToList();

// This prints all values of "ldstr 'value'" 
// which happen before GetString() or GetHtml()
foreach (var i in i18nInstructions)
    System.Diagnostics.Debug.WriteLine(i.Previous.Operand.ToString());