When I posted my Razor Hosting article a couple of weeks ago I got a number of questions on how to get IntelliSense to work inside of Visual Studio while editing your templates. The answer to this question is mainly dependent on how Visual Studio recognizes assemblies, so a little background is required.
If you open a template just on its own as a standalone file by clicking on it say in Explorer, Visual Studio will open up with the template in the editor, but you won’t get any IntelliSense on any of your related assemblies that you might be using by default. It’ll give Intellisense on base System namespace, but not on your imported assembly types. This makes sense: Visual Studio has no idea what the assembly associations for the single file are.
There are two options available to you to make IntelliSense work for templates:
- Add the templates as included files to your non-Web project
- Add a BIN folder to your template’s folder and add all assemblies required there
Including Templates in your Host Project
By including templates into your Razor hosting project, Visual Studio will pick up the project’s assembly references and make IntelliSense available for any of the custom types in your project and on your templates. To see this work I moved the \Templates folder from the samples from the Debug\Bin folder into the project root and included the templates in the WinForm sample project. Here’s what this looks like in Visual Studio after the templates have been included:
Notice that I take my original example and type cast the Context object to the specific type that it actually represents – namely CustomContext – by using a simple code block:
CustomContext Model = Context as CustomContext;
After that assignment my Model local variable is in scope and IntelliSense works as expected. Note that you also will need to add any namespaces with the using command in this case:
which has to be defined at the very top of a Razor document.
BTW, while you can only pass in a single Context 'parameter’ to the template with the default template I’ve provided realize that you can also assign a complex object to Context. For example you could have a container object that references a variety of other objects which you can then cast to the appropriate types as needed:
ContextContainer container = Context as ContextContainer;
CustomContext Model = container.Model;
CustomDAO DAO = container.DAO;
and so forth.
IntelliSense for your Custom Template
Notice also that you can get IntelliSense for the top level template by specifying an inherits tag at the top of the document:
By specifying the above you can then get IntelliSense on your base template’s properties. For example, in my base template there are Request and Response objects. This is very useful especially if you end up creating custom templates that include your custom business objects as you can get effectively see full IntelliSense from the ‘page’ level down. For Html Help Builder for example, I’d have a Help object on the page and assuming I have the references available I can see all the way into that Help object without even having to do anything fancy.
Note that the @inherits key is a GREAT and easy way to override the base template you normally specify as the default template. It allows you to create a custom template and as long as it inherits from the base template it’ll work properly. Since the last post I’ve also made some changes in the base template that allow hooking up some simple initialization logic so it gets much more easy to create custom templates and hook up custom objects with an IntializeTemplate() hook function that gets called with the Context and a Configuration object. These objects are objects you can pass in at runtime from your host application and then assign to custom properties on your template.
For example the default implementation for RazorTemplateFolderHost does this:
public override void InitializeTemplate(object context, object configurationData)
// Pick up configuration data and stuff into Request object
RazorFolderHostTemplateConfiguration config = configurationData as RazorFolderHostTemplateConfiguration;
this.Request.TemplatePath = config.TemplatePath;
this.Request.TemplateRelativePath = config.TemplateRelativePath;
// Just use the entire ConfigData as the model, but in theory
// configData could contain many objects or values to set on
// template properties
this.Model = config.ConfigData as TModel;
to set up a strongly typed Model and the Request object. You can do much more complex hookups here of course and create complex base template pages that contain all the objects that you need in your code with strong typing.
Adding a Bin folder to your Template’s Root Path
Including templates in your host project works if you own the project and you’re the only one modifying the templates. However, if you are distributing the Razor engine as a templating/scripting solution as part of your application or development tool the original project is likely not available and so that approach is not practical.
Another option you have is to add a Bin folder and add all the related assemblies into it. You can also add a Web.Config file with assembly references for any GAC’d assembly references that need to be associated with the templates. Between the web.config and bin folder Visual Studio can figure out how to provide IntelliSense.
The Bin folder should contain:
- The RazorHosting.dll
- Your host project’s EXE or DLL – renamed to .dll if it’s an .exe
- Any external (bin folder) dependent assemblies
Note that you most likely also want a reference to the host project if it contains references that are going to be used in templates. Visual Studio doesn’t recognize an EXE reference so you have to rename the EXE to DLL to make it work. Apparently the binary signature of EXE and DLL files are identical and it just works – learn something new everyday…
For GAC assembly references you can add a web.config file to your template root. The Web.config file then should contain any full assembly references to GAC components:
<add assembly="System.Web.Mvc, Version=220.127.116.11, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add assembly="System.Web, Version=18.104.22.168, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<add assembly="System.Web.Extensions, Version=22.214.171.124, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
And with that you should get full IntelliSense.
Note that if you add a BIN folder and you also have the templates in your Visual Studio project Visual Studio will complain about reference conflicts as it’s effectively seeing both the project references and the ones in the bin folder. So it’s probably a good idea to use one or the other but not both at the same time :-)
Seeing IntelliSense in your Razor templates is a big help for users of your templates. If you’re shipping an application level scripting solution especially it’ll be real useful for your template consumers/users to be able to get some quick help on creating customized templates – after all that’s what templates are all about – easy customization. Making sure that everything is referenced in your bin folder and web.config is a good idea and it’s great to see that Visual Studio (and presumably WebMatrix/Visual Web Developer as well) will be able to pick up your custom IntelliSense in Razor templates.