Click here to Skip to main content
Click here to Skip to main content

SharePoint 2013 Razor View WebPart

, 6 Mar 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
A SharePoint WebPart that hosts Razor files (.cshtml, .vbhtml)

Introduction

Razor syntax is a very easy, powerful and less verbose format. This is a proof of concept to utilize this powerful format to be hosted in a SharePoint WebPart!

The idea is very simple; to have a SharePoint WebPart that can host a razor view (.cshtml, .vbhtml) and render its output.

Background

We can compile razor files (.cshtml, .vbhtml) during the runtime using System.Web.Compilation.BuildManager. This will require adding cshtml and vbhtml build providers to the application web.config file.

<add extension=".cshtml" type="System.Web.WebPages.Razor.RazorBuildProvider, 
        System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
 
<add extension=".vbhtml" type="System.Web.WebPages.Razor.RazorBuildProvider, 
System.Web.WebPages.Razor, Version=3.0.0.0,Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

So, now all we need is to use the BuildManager to compile a razor file and render it in the normal rendering method of the SharePoint WebPart.

The razor file can be saved in SharePoint content database, and can be edited from SharePoint designer using Visual Studio or any text editor.

Using the Code

The render method of the WebPart will look like the following:

 protected override void Render(HtmlTextWriter writer)
        {
            // Check that there is a file specified
            if (string.IsNullOrWhiteSpace(RazorViewVirtualPath))
            {
                writer.Write(string.Format("Please specify a razor file 
                    to render in the WebPart settings. 
                    <a href=\"javascript:ShowToolPane2Wrapper
                    ('Edit', this,'" + this.ID + "')\">Open tool pane</a>"));
                return;
            }
 
            Type razorViewType;
            try
            {
                // Compile the razor file into a type (This should be cached to improve the performance)
                razorViewType = BuildManager.GetCompiledType(RazorViewVirtualPath);
                if (null == razorViewType)
                {
                    throw new Exception("Unable to compile the razor view.");
                }
            }
            catch (Exception e)
            {
                writer.Write(string.Format("Something went wrong 
                while compiling the razor view at {0}: {1}", RazorViewVirtualPath, e.Message));
                return;
            }
            // Create an object of this type
            var page = Activator.CreateInstance(razorViewType) as WebPageBase;
            page.ExecutePageHierarchy(new WebPageContext
            (new HttpContextWrapper(this.Context), page, null), writer, page);
 
            base.Render(writer);
        } 

That's all! for the concept. The compilation of Razor is working now but it gives a compilation error!

It was required to add "System.Web.WebPages" to the assemblies in the application web.config to overcome these compilation errors during the razor file compilation.

<add assembly="System.Web.WebPages, Version=3.0.0.0, 
        Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 

But adding the "System.Web.WebPages" assembly automatically adds an HttpModule to the pipeline which in turn required me to implement a custom VirtualPathProvider to handle some special cases that was making the default SPVirtualPathProvider crashing!

So, I wrote this custom VirtualPathProvider to handle those special cases as follows:

internal sealed class SPRazorViewVirtualPathProvider : VirtualPathProvider
    {
        public override string CombineVirtualPaths(string basePath, string relativePath)
        {
            return Previous.CombineVirtualPaths(basePath, relativePath);
        }
        public override string GetCacheKey(string virtualPath)
        {
            return Previous.GetCacheKey(virtualPath);
        }
        public override VirtualDirectory GetDirectory(string virtualDir)
        {
            return Previous.GetDirectory(virtualDir);
        }
        public override VirtualFile GetFile(string virtualPath)
        {
            return Previous.GetFile(virtualPath);
        }
        public override string GetFileHash
        (string virtualPath, System.Collections.IEnumerable virtualPathDependencies)
        {
            return Previous.GetFileHash(virtualPath, virtualPathDependencies);
        }
        public override bool FileExists(string virtualPath)
        {
            // This is a workaround for System.Web.WebPages 
            // module initialization checking for precompiledapp which will 
            // not work with SPVirtualPathProvider
            if (virtualPath.ToLower().EndsWith("precompiledapp.config"))
                return false;
            return Previous.FileExists(virtualPath);
        }
        public override System.Web.Caching.CacheDependency GetCacheDependency
        (string virtualPath, System.Collections.IEnumerable virtualPathDependencies, System.DateTime utcStart)
        {
            if (virtualPath.ToLower().StartsWith("~/_appstart."))
                virtualPath = virtualPath.TrimStart('~');
            return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
        }
        public override bool DirectoryExists(string virtualDir)
        {
            try
            {
                return Previous.DirectoryExists(virtualDir);
            }
            catch (Exception e)
            {
                return false;
            }
        }
    }

I registered this SPRazorViewVirtualPathProvider to the HostingEnvironment via a custom HttpModule:

internal sealed class SPRazorViewHttpModule : IHttpModule
    {
        private static bool razorViewVirtualPathProviderInitialized = false;
        private static object _lock = new object();
        public void Init(HttpApplication context)
        {
            if (razorViewVirtualPathProviderInitialized)
                return;
            lock (_lock)
            {
                var razorViewVirtualPathProvider = new SPRazorViewVirtualPathProvider();
                HostingEnvironment.RegisterVirtualPathProvider(razorViewVirtualPathProvider);
                razorViewVirtualPathProviderInitialized = true;
            }
        }
        public void Dispose()
        {
        }
    }  

To add the SPRazorViewHttpModule to the application pipeline, I used the PreApplicationStartMethod assembly attribute to register the module via DynamicModuleUtility.RegisterModule during the application starting.

I added the solution assembly to the web.config assemblies:

<add assembly="SPRazorView, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=64bd6e273698a7b0">

I packaged the whole solution along with all the required web.config modification in a SharePoint Package (WSP) to make things easier.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Adel Refaat
LINK Development
Egypt Egypt
No Biography provided
Follow on   Twitter   Google+

Comments and Discussions

 
SuggestionServer error on first trial ? PinmemberPola Edward6-Aug-14 2:54 
QuestionVery Interesting PinmemberPola Edward19-Jul-14 22:09 
GeneralMy vote of 5 PinmemberMBSMBS10-Mar-14 22:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411022.1 | Last Updated 6 Mar 2014
Article Copyright 2014 by Adel Refaat
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid