RazorEngine icon indicating copy to clipboard operation
RazorEngine copied to clipboard

Making IntelliSense work again for Visual Studio 2015

Open cyrilgandon opened this issue 8 years ago • 9 comments

From what I understand from this post : http://stackoverflow.com/a/27671174/128662 And the documentation that reflect it : https://antaris.github.io/RazorEngine/IntellisenseAndResharper.html

The documentation was target to Visual Studio 2013.

It appears that Visual Studio 2015 have changed thing, there is problems arise here and there about Razor not functionning anymore (with MVC), and I guess Razor Engine take his parts too.

I've made small example of dysfunctionning IntelliSense on this repo : https://github.com/cyrilgandon/RazorEngineGenerator/branches

One time it is Linq not availaible, the other, it is C#3+ functionnalities not available, and another : 'The name Context does not exist in the current context'.

Could we have guidelines to start a fresh new project with IntelliSense fully functional for Visual Studio 2015 and RazorEngine? I tried so many things that I give up for now. Web.config, App.config, no config, deleting user, temp files, reinstalling VS..... It seems that error pop up and down in a completely random way.

The best of best should be to have access to a blank solution, or a Demo folder, on GitHub.

Thank you!

cyrilgandon avatar Mar 02 '16 08:03 cyrilgandon

That would be great, the only way I got rid of "... does not exists in the current context" problem is @inherit/ing from a base class (e.g. TemplateBase) but this implies other problems.

senj avatar Mar 10 '16 11:03 senj

Anyone who could do some research on how to do it and publish it here (or via PR for the docs) would be a real hero! Intellisense for RazorEngine always was kind of a hack (sadly).

matthid avatar Oct 28 '16 18:10 matthid

I am not sure about ASP.NET projects, but have workaround for my Console project:

  1. Install-Package Microsoft.CodeDom.Providers.DotNetCompilerPlatform
  2. In app.config (for web.config this will work too, I think):
<system.codedom>
	<compilers>
		<compiler language="c#;cs;csharp" extension=".cs"
		  type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
		  warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701"/>
	</compilers>
</system.codedom>

With these steps features like implicit local variables processed (var ...) and IntelliSense are OK. Unfortunately, string interpolation does not work at run time ("Errors while compiling a Template"). Any suggestions?

P.S. I have ReSharper installed.

UPD: VS show errors in cshtml, but IntelliSense works.

OlegAxenow avatar Dec 06 '16 13:12 OlegAxenow

@OlegAxenow For C# 6 features like string interpolation, you'll have to use RazorEngine.Roslyn (currently published as a Prerelease) and:

config.CompilerServiceFactory = new RazorEngine.Roslyn.RoslynCompilerServiceFactory();

(more details here)


As for the IntelliSense, I am currently using a class deriving from TemplateBase<T> re-exposing some of the members I need (as described in the current documentation).

It seems to work a bit weird for me too, VS with ReSharper underlines some parts of the line with @inherits and var keywords complaining about "RazorEngine" not being referenced. but Intellisense works.

tompazourek avatar Dec 21 '16 19:12 tompazourek

I've spent some time figuring out the steps needed to have intellisense working and using the C# 6 syntax.

VS Projects

I created three projects to roughly emulate how I want a production setup to look:

  1. Models project. Class library.
  2. Templates project. Class library.
  3. Console project. Console application.

Model Project

Name: RazorEngineRoslyn.Models Project References: none. NuGet References: none.

PersonModel.cs

    public class PersonModel
    {
        public string Name { get; set; }
        public string Job { get; set; }
    }

Template Project

Name: RazorEngineRoslyn.Templates Project References: RazorEngineRoslyn.Models NuGet References: RazorEngine, Microsoft.CodeDom.Providers.DotNetCompilerPlatform

  1. Create project. (Class Library)
  2. Add RazorEngine nuget package. (Pre-release V4.4.3-rc1)
  3. Update nuget packages.
  4. Add Post Build event commands:
	copy "$(TargetDir)RazorEngine.dll" "$(TargetDir)..\RazorEngine.dll" /Y
	copy "$(TargetDir)RazorEngine.xml" "$(TargetDir)..\RazorEngine.xml" /Y

At this point you can:

  • Create using statements for the local project: @using RazorEngineRoslyn.Models
  • Create using statements for RazorEngine: @using RazorEngine.Templating
  • Create inherits statements : @inherits TemplateBase<PersonModel> NOTE: There's no need to create a custom TemplateBase class, intellisense correctly resolves it's properties and methods.
  • Use 'dynamic'.

You can't:

  • Use interpolated strings, and other C# 6 features. "CS8025 Feature 'interpolated strings' is not available in C# 4. Please use language version 6 or greater."
  1. Add DotNetCompilerPlatform nuget package. (Microsoft.CodeDom.Providers.DotNetCompilerPlatform)
  2. Update nuget packages. (Restart VS when it asks.) NOTE: The DotNetCompilerPlatform DLL is copied to the right directory, but this is done upon install of the nuget package using a powershell script. If the DLL is deleted after install it'll never be copied to the right place again.
  3. Add Post Build event commands:
	copy "$(TargetDir)Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll" "$(TargetDir)..\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll" /Y
	copy "$(TargetDir)Microsoft.CodeDom.Providers.DotNetCompilerPlatform.xml" "$(TargetDir)..\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.xml" /Y
  1. Add to app.config:
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701"/>
    </compilers>
  </system.codedom>

At this point you can:

  • Use interpolated strings, and other C# 6 features.

NOTE: If your cshtml is showing errors, try closing that file in VS, rebuilding the project, and opening the file again. Steps such as setting the app.config don't seem to affect already open files.

Templates/PersonTemplate.cshtml

@using RazorEngineRoslyn.Models
@using RazorEngine.Templating
@inherits TemplateBase<PersonModel>
@{ 
    var title = $"{Model.Name}, {Model.Job}";
}
@Raw(title)

NOTE: For this example we'll be loading up the cshtml file directly, so right click the Templates/PersonTemplate.cshtml file in the project, set it's Build Action to Content, and Copy to Output Directory to Copy always. This will make sure that the template ends up in a directory below the console apps output.

Console Project

Name: RazorEngineRoslyn.Cli Project References: RazorEngineRoslyn.Models, RazorEngineRoslyn.Templates NuGet References: RazorEngine, Microsoft.AspNetCore.Razor, RazorEngine.Roslyn

  1. Create project. (Cli)
  2. Add RazorEngine nuget package. (Pre-release V4.4.3-rc1)
  3. Update Microsoft.ASpNetCore.Razor nuget package.
  4. Add RazorEngine.Roslyn nuget package. (Pre-release V4.1.0-rc1)
  5. Update nuget packages. (Restart VS when it asks.) NOTE: Currently fails ... Error Could not install package 'Microsoft.CodeAnalysis.Common 2.0.0-rc2'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.5.2', but the package does not contain any assembly references or content files that are compatible with that framework.

Program.cs

using System;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
using RazorEngine.Configuration;
using RazorEngine.Roslyn;
using RazorEngine.Templating;
using RazorEngineRoslyn.Models;

namespace RazorEngineRoslyn.Cli
{
    class Program
    {
        static int Main(string[] args)
        {
            var exitCode = 0;

            if (AppDomain.CurrentDomain.IsDefaultAppDomain())
            {
                var domain = AppDomain.CreateDomain
                (
                    "RazorEngineDomain",
                    null,
                    AppDomain.CurrentDomain.SetupInformation,
                    new PermissionSet(PermissionState.Unrestricted),
                    new StrongName[0]
                );

                exitCode = domain.ExecuteAssembly(Assembly.GetExecutingAssembly().Location);

                AppDomain.Unload(domain);
            }
            else
            {
                var service = RazorEngineService.Create(new TemplateServiceConfiguration()
                {
                    CompilerServiceFactory = new RoslynCompilerServiceFactory()
                });

                var template = File.ReadAllText(@"Templates\PersonTemplate.cshtml");
                var templateKey = "Person";
                var model = new PersonModel() { Name = "Bob", Job = "C# Developer" };
                var output = service.RunCompile(template, templateKey, model: model);

                File.WriteAllText("Output.txt", output);
            }

            return exitCode;
        }
    }
}

Output.txt

Bob, C# Developer

Resharper

I don't use it ... so ... let me know how it does ;)

ghost avatar Jan 17 '17 10:01 ghost

Well it looked good for my small initial test, but as things got more complicated it started falling apart.

I'm currently struggling with intellisense not being able to find ExpandoObject.

ghost avatar Jan 18 '17 17:01 ghost

Things seem to work significantly better if I use a .Net Core Class Library project.

All I had to do to get it working better than the classic project was:

  • Create .net core class library project.
  • Change the framework in project.json to "net46".
    • This targets DNX and allows other DNX project to consume/to be consumed by this project.
  • Add two nuget packages:
    • RazorEngine
    • Microsoft.AspNetCore.Mvc

After than handful of steps I had intellisense working better than in the original DNX templates project. e.g. ExpandoObject appears, which is one of the things I was struggling with.


Only a single error remained: CS0115 '_Page_FloodEmail_Body_cshtml.ExecuteAsync()': no suitable method found to override

I checked out the source code for TemplateBase<T> and found that it did have an Execute method which returned a Task, but not ExecuteAsync.

https://github.com/Antaris/RazorEngine/blob/master/src/source/RazorEngine.Core/Templating/TemplateBase.cs#L173

So I made myself a new template base class that has the ExecuteAsync method simply call the Execute method.

    public class TemplateBaseAsync<T> : TemplateBase<T>
    {
        public virtual Task ExecuteAsync()
        {
            return base.Execute();
        }
    }

And that's it, no more errors. No warnings either, other than legit warnings coming from the bad content in one of my test templates.


One thing to note is that the .Net Core Class Library project is targeting .net 4.6.

By default DNX projects are created targeting .net 4.5, meaning your DNX projects can't reference the .Net Core Class Library project.

One option would be to drop the .Net Core Class Library from "dnxcore46" to "dnxcore45", but the "Microsoft.AspNetCore.Mvc" nuget package doesn't support 4.5, so that won't work.

Another option is bumping your DNX projects to 4.6(.1), which will then allow the DNX projects to reference the .Net Core Class Library just fine.

Finally, you could use linked files: https://msdn.microsoft.com/en-us/library/windows/apps/jj714082(v=vs.105).aspx (The article is aimed at WinPhone 8 devs, but linked files work almost anywhere in VS.)


Another issue is the copying of the template from the template project to the Cli apps binary output.

DNX projects have the option to mark a file as copy always, and that includes when it's being referenced.

Unfortunately the project.json build system only has support for copying files to the output of the containing project. i.e. they'll get copied to templateproject/bin/debug/templates, but not cliproject/bin/debug/templates.

https://github.com/dotnet/cli/issues/2902

It appears this issue won't be fixed in project.json files as the build system is being moved back to MSBuild.

It's good that MSBuild will fix the problem, but support for that won't be included until VS2017.

As with the previous issue, linked files are one option to handle this.

Obviously this isn't an issue if you manually copy around your templates, or otherwise move them around outside of the build system.

ghost avatar Jan 19 '17 10:01 ghost

The errors appear to be intermittant.

While my earlier work banished them, they're now back, but slightly different.

The classic "_Page_Templates_FloodEmail_Body_cshtml.Execute(): return type must be 'Task' to match overridden member TemplateBase.Execute()" has returned which confuses me as earlier it was complaining about a missing ExecuteAsync method which I fixed.

I "feels" like it's switching between cshtml editor versions. Earlier I got the newer one which produced less errors, and now I'm getting the old one again.

ghost avatar Jan 19 '17 16:01 ghost

I know this is pretty old, and most of you have probably moved on from VS 2015 issues. After much searching, I found that it's also important to have:

  <system.web>
    <compilation debug="true" targetFramework="4.7.2"></compilation>
  </system.web>

In your App.config in order to prevent the following message:

Feature 'implicitly typed local variable' is not available in C# 2. Please use language version 3 or greater.

Hope this saves someone time. I had to reboot Visual Studio after doing this.

crush83 avatar Jan 21 '20 23:01 crush83