Getting the semantic model of a cshtml file?

I would like to use Roslyn to analyze semantic information in the context of a C # code block inside Razor View.

Is there a way (in Visual Studio 2015 or even in unit test) to get the SemanticModel that represents this code?

+6
source share
4 answers

Razor files contain a C # design buffer with generated C # code (including those parts that you don’t write yourself). This buffer has full Roslyn services and exactly what you are looking for.

You need to go through the TextView BufferGraph and find the CSharp buffer; you can get its Document and semantic model.

If you start with the cursor location, you just need to map this location to the CSharp buffer.

Note that for a TextView, it is acceptable to contain multiple CSharp buffers. (although the Razor editor will never do this)


If you are not working in TextView, you need to do it all yourself; you need to run the Razor source through the Razor compiler to get the generated C # source, and then compile it with Roslyn to get a semantic model.

+5
source

Extract the code representing the view from the Razor view file using RazorTemplateEngine.GenerateCode and CSharpCodeProvider.GenerateCodeFromCompileUnit (or VBCodeProvider if you want the intermediate source to be VB.NET). Then you can use Roslyn to analyze the code.

Here is an example of using Roslyn with Razor files here .

Please note that GenerateCode contains a disclaimer:

This type / member supports the .NET Framework and is not intended to be used directly from your code.

+6
source

Just in case someone is stuck with this, I have a mini-example application that can help.

I had a CMS class:

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

... and I wanted to know which ones were used in my report solution ... Enter Roslyn!

The following application will print a counter for used and unused links:

 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 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; } } } 
+2
source

Roslyn only simulates cshtml files while they are open, but during this time they look like any other source file in the Workspace model.

Is there anything specific you tried that doesn't work?

+1
source

Source: https://habr.com/ru/post/982161/


All Articles