Getting a SemanticModel of a cshtml file?

Deadly 提交于 2020-05-12 22:23:54

问题


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

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


回答1:


Razor files contain a C# projection buffer with the generated C# code (including the parts that you don't write yourself). This buffer has full Roslyn services and is exactly what you're looking for.

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

If you're starting from the cursor location, you need simply need to map that location to a CSharp buffer.

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


If you aren't working in a TextView, you need to do all of this yourself; you need to run the Razor source through the Razor compiler to get the generated C# source, then compile that with Roslyn to get a semantic model.




回答2:


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

There's an example of using Roslyn with Razor view files here.

Take note that GenerateCode carries a caveat:

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




回答3:


Just in case anyone else gets stuck on this, I have mini sample app which may help.

I had a CMS class like this:

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

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

... and I wanted to find out which of these were used throughout my solution for a report ... Enter Roslyn!

The following app will print out the count for the used and unused references:

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;
        }
    }
}



回答4:


Roslyn only models cshtml files while they are open, but during that time they are similar to every other source file in the Workspace model.

Is there something specific you have tried that isn't working?



来源:https://stackoverflow.com/questions/28392086/getting-a-semanticmodel-of-a-cshtml-file

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!