2015-02-08 1 views
6

Я хотел бы использовать Roslyn для анализа семантической информации в контексте блока кода C# внутри Razor View.Получение SemanticModel файла cshtml?

Есть ли способ (в рамках Visual Studio 2015 или даже в модульном тесте) получить SemanticModel, который представляет этот код?

+0

Что цель анализа SemanticModel? –

+0

@ErikPhilips Я создаю [расширение Visual Studio] (http: //www.oz-code.com), который должен иметь возможность задавать семантические вопросы (вызов 'GetSymbolInfo',' GetTypeInfo' и т. д.), учитывая контекст следующего оператора («желтая строка») в отладчике. В настоящее время для Razor Views я не смог этого сделать. –

+0

@OmerRaviv Возможно, ваш вопрос ближе к * Как мне получить «SemanticModel» текущего документа? * Кроме того, вам нужно расширение Visual Studio? Не можете ли вы добавить свой код в качестве анализатора Roslyn? Например. если вы заботитесь о некоторых типах нарушений стиля, вы можете эффективно применять его через анализатор вместо расширения. И в этот момент, наличие расширения не нужно. –

ответ

5

Файлы Razor содержат буфер проектирования C# с сгенерированным кодом C# (включая те части, которые вы не пишете сами). Этот буфер имеет полные сервисы Roslyn и именно то, что вы ищете.

Вам нужно пройти через BufferGraph TextView и найти буфер CSharp; вы можете получить его Document и семантическую модель.

Если вы начинаете с местоположения курсора, вам просто нужно сопоставить это местоположение с буфером CSharp.

Обратите внимание, что для TextView вполне достаточно содержать несколькоCSharp буферов. (Хотя редактор Razor никогда не будет делать)


Если вы не работаете в TextView, что вам нужно сделать все это самостоятельно; вам нужно запустить источник Razor через компилятор Razor, чтобы получить сгенерированный источник C#, а затем скомпилировать его с помощью Roslyn для получения семантической модели.

6

Извлеките код, представляющий представление из файла вида Razor, используя RazorTemplateEngine.GenerateCode и CSharpCodeProvider.GenerateCodeFromCompileUnit (или VBCodeProvider, если вы хотите, чтобы промежуточный источник использовался как VB.NET). Затем вы можете использовать Roslyn для анализа кода.

Существует пример использования файлов Roslyn с Razor view here.

Примите к сведению, что GenerateCode несет предостережение:

Этот тип/член поддерживает инфраструктуру .NET Framework и не предназначен для использования непосредственно из вашего кода.

+0

Есть еще несколько сведений о Roslyn и ASP.NET [здесь] (http://blogs.msdn.com/b/webdev/archive/2014/05/12/enabling-the-net-compiler-platform-roslyn- in-asp-net-applications.aspx) –

+0

Хорошая информация - хорошо сработала для меня! –

1

Roslyn только моделирует файлы cshtml, пока они открыты, но за это время они похожи на все остальные исходные файлы в модели Workspace.

Есть ли что-то конкретное, что вы пробовали, что не работает?

2

На всякий случай кто-то застрял на этом, у меня есть приложение для мини-примера, которое может помочь.

У меня был класс CMS как это:

public partial class CMS 
{ 
    public static string SomeKey 
    { 
     get { return (string) ResourceProvider.GetResource("some_key"); } 
    } 

    // ... and many more ... 
} 

... и я хотел бы узнать, какие из них были использованы на протяжении всего моего решения для отчета ... Введите Рослин!

Следующая приложение распечатает подсчета для использованных и неиспользованных ссылок:

using Microsoft.CodeAnalysis; 
using Microsoft.CodeAnalysis.FindSymbols; 
using Microsoft.CodeAnalysis.MSBuild; 
using Microsoft.CSharp; 
using System; 
using System.CodeDom.Compiler; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Web.Razor; 

namespace TranslationSniffer 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      new Program().Go().Wait(); 
     }   

     public async Task Go() 
     { 
      // Roslyn! 
      var ws = MSBuildWorkspace.Create(); 

      // Store the translation keys... 
      List<string> used = new List<string>(); 
      List<string> delete = new List<string>(); 

      string solutionRoot = @"C:\_Code\PathToProject\"; 
      string sln = solutionRoot + "MySolution.sln"; 

      // Load the solution, and find all the cshtml Razor views... 
      var solution = await ws.OpenSolutionAsync(sln); 
      var mainProj = solution.Projects.Where(x => x.Name == "ConsumerWeb").Single(); 
      FileInfo[] cshtmls = new DirectoryInfo(solutionRoot).GetFiles("*.cshtml", SearchOption.AllDirectories); 

      // Go through each Razor View - generate the equivalent CS and add to the project for compilation. 
      var host = new RazorEngineHost(RazorCodeLanguage.Languages["cshtml"]); 
      var razor = new RazorTemplateEngine(host); 
      var cs = new CSharpCodeProvider(); 
      var csOptions = new CodeGeneratorOptions(); 
      foreach (var cshtml in cshtmls) 
      { 
       using (StreamReader re = new StreamReader(cshtml.FullName)) 
       { 
        try 
        { 
         // Let Razor do it's thang... 
         var compileUnit = razor.GenerateCode(re).GeneratedCode; 

         // Pull the code into a stringbuilder, and append to the main project: 
         StringBuilder sb = new StringBuilder(); 
         using (StringWriter rw = new StringWriter(sb)) 
         { 
          cs.GenerateCodeFromCompileUnit(compileUnit, rw, csOptions); 
         } 

         // Get the new immutable project 
         var doc = mainProj.AddDocument(cshtml.Name + ".cs", sb.ToString()); 
         mainProj = doc.Project; 
        } 
        catch(Exception ex) 
        { 
         Console.WriteLine("Compile fail for: {0}", cshtml.Name); 
         // throw; 
        } 

        continue; 
       } 
      } 

      // We now have a new immutable solution, as we have changed the project instance... 
      solution = mainProj.Solution; 

      // Pull out our application translation list (its in a static class called 'CMS'): 
      var mainCompile = await mainProj.GetCompilationAsync(); 
      var mainModel = mainCompile.GetTypeByMetadataName("Resources.CMS"); 
      var translations = mainModel.GetMembers().Where(x => x.Kind == SymbolKind.Property).ToList(); 

      foreach (var translation in translations) 
      { 
       var references = await SymbolFinder.FindReferencesAsync(translation, solution)     ; 

       if (!references.First().Locations.Any()) 
       { 
        Console.WriteLine("{0} translation is not used!", translation.Name); 
        delete.Add(translation.Name); 
       } 
       else 
       { 
        Console.WriteLine("{0} :in: {1}", translation.Name, references.First().Locations.First().Document.Name); 
        used.Add(translation.Name); 
       } 
      } 

      Console.WriteLine(); 
      Console.WriteLine("Used references {0}. Unused references: {1}", used.Count, delete.Count); 

      return; 
     } 
    } 
}