Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert Server Side Include to Razor

I'm in the process of upgrading/modifying an ASP.NET webforms application to MVC5 - ideally, to the Razor engine (to allow for automatic Mobile layout page switching, and to take advantage of MVC features).

We have hundreds of server-side includes, all named *.inc. Absolute gobs of them. The application is so large that we will have to convert pages piecemeal - there is no way to do it all at once. (For an idea of how big this project is and how long it's been around, the entire source control history in VSS upon our migration to TFS was about 10GB. That includes lots of media files, but still. It's big, and it's not going away.)

Is there some way I can convert these #!include calls to a RenderPartial() or RenderAction() call without renaming every single file to .cshtml?

Duplicating the include file is a poor recommendation, because that breaks DRY.

Ideally, I'd like one partial view that handles this - I guess it would take the filename as an argument, and something that's a one-liner, so I can train everyone to convert these trivially.

I also want sort of file-level caching. Just loading the output from disk & dumping it into the output stream is nice for small sites...but this isn't a small site.

We have an ascx file that does the caching now for many of these includes - but I have no clue how to write the corresponding one in Razor.

Edit 1: These are for the most part non-code includes, but I'm sure a user control definition has snuck in a few. I'd have to rewrite those, so assume the answer is "these are pure html, no code"

Edit 2: We have situations where the include files are nested (It wasn't me I swear). An include file can include other files. I have a caching ASCX control that handles this recursively, so it's a matter of porting the code.

Edit 3: Any potential solution must also work in Layout cshtml files. We've got includes in the existing master pages.

like image 736
Broam Avatar asked Apr 09 '15 19:04

Broam


2 Answers

Off the top of my head I can think of about 4 ways to tackle this.

Note: I didn't bother too much with the caching mechanisms because that's design decision that is outside of this question.


Create a controller/action to render the include for you:

public class IncludeController : Controller
{
    // GET: Render
    public ActionResult Render(string path)
    {
        return new IncludeResult(path);
    }
}

public class IncludeResult : ActionResult
{
    private string _path { get; set; }

    public IncludeResult(string path)
    {
        _path = path;
    }
    public override void ExecuteResult(ControllerContext context)
    {
        // possibly check the cache here, or let MVC handle cache at the action level
        // get the contents of the include file output to the response
        context.HttpContext.Response.Write(File.OpenRead(_path));
    }
}

In the view

@Html.Action("Render", "Include", new {path = "~/Includes/test.inc"})

Note: tried using the FilePathResult for this and it seems to return odd characters, hence the custom IncludeResult


Create an HtmlHelper extension:

public static class HtmlExtension
{
    public static MvcHtmlString RenderInclude(this HtmlHelper helper, string path)
    {
        // check the cache
        string output = File.ReadAllText(path);
        return new MvcHtmlString(output);
    }
}

In the view

@Html.RenderInclude("~/includes/test.inc")

This option I think would fit what you are trying to do the best however from a purest approach, this might not fit with 'the way MVC does things'


Extend the RazorViewEngine to look for the include files as well:

I didn't explore this one to far, and will probably be way overkill for what you are trying to achieve but the following blog has some code samples that should help you: http://weblogs.asp.net/imranbaloch/view-engine-with-dynamic-view-location

Then you could use the standard Html.Partial code in your views

@Html.Patial("~/includes/test.inc");

Expose the include contents in a property of your view model:

This seems like the most cumbersome, but still it's an option.

public class ViewModel
{
    public MvcHtmlString IncludeContents { get; set; }
    ....

    private GetIncludeContent(string path)
    {
        IncludeContents = new MvcHtmlString(File.ReadAllText(path));
    }
}

Then in the view

@model.IncludeContents
like image 58
Brent Mannering Avatar answered Oct 05 '22 04:10

Brent Mannering


I would highly recommend:

Edit your web config and add (BuildProviders info):

<system.web>
  <compilation debug="true" targetFramework="4.5">
    // Start (Build Provider Information)
    <buildProviders>
      <add extension=".inc" 
           type="System.Web.WebPages.Razor.RazorBuildProvider, System.Web.WebPages.Razor"/>
    </buildProviders>
    // End (Build Provider Information)
  </compilation>
</system.web>

This provides the Runtime engine with what build provider to build your INC files. If they are pure HTML that's fine, we just need to register something so MVC will include them.

Create a New View Engine:

public class CustomViewEngine : RazorViewEngine
{
    public CustomViewEngine()
    {
        PartialViewLocationFormats = new[]
        {
            "~/Views/{1}/{0}.cshtml",
            "~/Views/{1}/{0}.vbhtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Shared/{0}.vbhtml",
            "~/Include/{0}.inc"  // Path to your include files
        };

        FileExtensions = new[]
        {
            "cshtml",
            "vbhtml",
            "inc",
        };
    }
}

Register your view engine:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        // Need to let the RazorCodeLanguage it can compile .inc files
        RazorCodeLanguage.Languages.Add("inc", new CSharpRazorCodeLanguage());
        // Need to register the extension with the WebPage Http Handler
        WebPageHttpHandler.RegisterExtension("inc");

        // Create the engine
        var viewEngine = new CustomViewEngine();
        // Clear unused Engines
        ViewEngines.Engines.Clear();
        // Add only the modified razor engine
        ViewEngines.Engines.Add(viewEngine);
    }
}

Copy \Views\Web.config to \Include\Web.Config to let the view engine know about what the views derive from.

With the file \Include\Test.inc containing:

<div>Some Data</div>

Now you can simply add:

@Html.Partial("Test")

or (more performant)

@{Html.RenderPartial("Test");}

Now your INC files are full MVC-ized. This means any MVC cache control etc etc work as normal as any .cshtml file.

Bonus

Because we're directly tied into the RazorViewEngine, as you migrate your \Include\*.inc to \Views\Shared\*.cshtml files, you don't have to modify any @Html.Partial("<include file>") because it will look in shared first then include. Eventually you can remove everything (i've recommended) once you have no more include files and the site continues to work.

like image 21
Erik Philips Avatar answered Oct 05 '22 03:10

Erik Philips