[c#] Render partial from different folder (not shared)

How can I have a view render a partial (user control) from a different folder? With preview 3 I used to call RenderUserControl with the complete path, but whith upgrading to preview 5 this is not possible anymore. Instead we got the RenderPartial method, but it's not offering me the functionality I'm looking for.

This question is related to c# asp.net-mvc renderpartial

The answer is


If you are using this other path a lot of the time you can fix this permanently without having to specify the path all of the time. By default, it is checking for partial views in the View folder and in the Shared folder. But say you want to add one.

Add a class to your Models folder:

public class NewViewEngine : RazorViewEngine {

   private static readonly string[] NEW_PARTIAL_VIEW_FORMATS = new[] {
      "~/Views/Foo/{0}.cshtml",
      "~/Views/Shared/Bar/{0}.cshtml"
   };

   public NewViewEngine() {
      // Keep existing locations in sync
      base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(NEW_PARTIAL_VIEW_FORMATS).ToArray();
   }
}

Then in your Global.asax.cs file, add the following line:

ViewEngines.Engines.Add(new NewViewEngine());

In my case I was using MvcMailer (https://github.com/smsohan/MvcMailer) and wanted to access a partial view from another folder, that wasn't in "Shared." The above solutions didn't work, but using a relative path did.

@Html.Partial("../MyViewFolder/Partials/_PartialView", Model.MyObject)

Try using RenderAction("myPartial","Account");


For readers using ASP.NET Core 2.1 or later and wanting to use Partial Tag Helper syntax, try this:

<partial name="~/Views/Folder/_PartialName.cshtml" />

The tilde (~) is optional.

The information at https://docs.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-3.1#partial-tag-helper is helpful too.


The VirtualPathProviderViewEngine, on which the WebFormsViewEngine is based, is supposed to support the "~" and "/" characters at the front of the path so your examples above should work.

I noticed your examples use the path "~/Account/myPartial.ascx", but you mentioned that your user control is in the Views/Account folder. Have you tried

<%Html.RenderPartial("~/Views/Account/myPartial.ascx");%>

or is that just a typo in your question?


In my case I was using MvcMailer (https://github.com/smsohan/MvcMailer) and wanted to access a partial view from another folder, that wasn't in "Shared." The above solutions didn't work, but using a relative path did.

@Html.Partial("../MyViewFolder/Partials/_PartialView", Model.MyObject)

For a user control named myPartial.ascx located at Views/Account folder write like this:

<%Html.RenderPartial("~/Views/Account/myPartial.ascx");%>

For readers using ASP.NET Core 2.1 or later and wanting to use Partial Tag Helper syntax, try this:

<partial name="~/Views/Folder/_PartialName.cshtml" />

The tilde (~) is optional.

The information at https://docs.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-3.1#partial-tag-helper is helpful too.


you should try this

~/Views/Shared/parts/UMFview.ascx

place the ~/Views/ before your code


Try using RenderAction("myPartial","Account");


you should try this

~/Views/Shared/parts/UMFview.ascx

place the ~/Views/ before your code


I've created a workaround that seems to be working pretty well. I found the need to switch to the context of a different controller for action name lookup, view lookup, etc. To implement this, I created a new extension method for HtmlHelper:

public static IDisposable ControllerContextRegion(
    this HtmlHelper html, 
    string controllerName)
{
    return new ControllerContextRegion(html.ViewContext.RouteData, controllerName);
}

ControllerContextRegion is defined as:

internal class ControllerContextRegion : IDisposable
{
    private readonly RouteData routeData;
    private readonly string previousControllerName;

    public ControllerContextRegion(RouteData routeData, string controllerName)
    {
        this.routeData = routeData;
        this.previousControllerName = routeData.GetRequiredString("controller");
        this.SetControllerName(controllerName);
    }

    public void Dispose()
    {
        this.SetControllerName(this.previousControllerName);
    }

    private void SetControllerName(string controllerName)
    {
        this.routeData.Values["controller"] = controllerName;
    }
}

The way this is used within a view is as follows:

@using (Html.ControllerContextRegion("Foo")) {
    // Html.Action, Html.Partial, etc. now looks things up as though
    // FooController was our controller.
}

There may be unwanted side effects for this if your code requires the controller route component to not change, but in our code so far, there doesn't seem to be any negatives to this approach.


The VirtualPathProviderViewEngine, on which the WebFormsViewEngine is based, is supposed to support the "~" and "/" characters at the front of the path so your examples above should work.

I noticed your examples use the path "~/Account/myPartial.ascx", but you mentioned that your user control is in the Views/Account folder. Have you tried

<%Html.RenderPartial("~/Views/Account/myPartial.ascx");%>

or is that just a typo in your question?


For a user control named myPartial.ascx located at Views/Account folder write like this:

<%Html.RenderPartial("~/Views/Account/myPartial.ascx");%>

The VirtualPathProviderViewEngine, on which the WebFormsViewEngine is based, is supposed to support the "~" and "/" characters at the front of the path so your examples above should work.

I noticed your examples use the path "~/Account/myPartial.ascx", but you mentioned that your user control is in the Views/Account folder. Have you tried

<%Html.RenderPartial("~/Views/Account/myPartial.ascx");%>

or is that just a typo in your question?


If you are using this other path a lot of the time you can fix this permanently without having to specify the path all of the time. By default, it is checking for partial views in the View folder and in the Shared folder. But say you want to add one.

Add a class to your Models folder:

public class NewViewEngine : RazorViewEngine {

   private static readonly string[] NEW_PARTIAL_VIEW_FORMATS = new[] {
      "~/Views/Foo/{0}.cshtml",
      "~/Views/Shared/Bar/{0}.cshtml"
   };

   public NewViewEngine() {
      // Keep existing locations in sync
      base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(NEW_PARTIAL_VIEW_FORMATS).ToArray();
   }
}

Then in your Global.asax.cs file, add the following line:

ViewEngines.Engines.Add(new NewViewEngine());

Create a Custom View Engine and have a method that returns a ViewEngineResult In this example you just overwrite the _options.ViewLocationFormats and add your folder directory :

public ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage)
        {
            var controllerName = context.GetNormalizedRouteValue(CONTROLLER_KEY);
            var areaName = context.GetNormalizedRouteValue(AREA_KEY);

            var checkedLocations = new List<string>();
            foreach (var location in _options.ViewLocationFormats)
            {
                var view = string.Format(location, viewName, controllerName);
                if (File.Exists(view))
                {
                    return ViewEngineResult.Found("Default", new View(view, _ViewRendering));
                }
                checkedLocations.Add(view);
            }

            return ViewEngineResult.NotFound(viewName, checkedLocations);
        }

Example: https://github.com/AspNetMonsters/pugzor


Create a Custom View Engine and have a method that returns a ViewEngineResult In this example you just overwrite the _options.ViewLocationFormats and add your folder directory :

public ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage)
        {
            var controllerName = context.GetNormalizedRouteValue(CONTROLLER_KEY);
            var areaName = context.GetNormalizedRouteValue(AREA_KEY);

            var checkedLocations = new List<string>();
            foreach (var location in _options.ViewLocationFormats)
            {
                var view = string.Format(location, viewName, controllerName);
                if (File.Exists(view))
                {
                    return ViewEngineResult.Found("Default", new View(view, _ViewRendering));
                }
                checkedLocations.Add(view);
            }

            return ViewEngineResult.NotFound(viewName, checkedLocations);
        }

Example: https://github.com/AspNetMonsters/pugzor


I've created a workaround that seems to be working pretty well. I found the need to switch to the context of a different controller for action name lookup, view lookup, etc. To implement this, I created a new extension method for HtmlHelper:

public static IDisposable ControllerContextRegion(
    this HtmlHelper html, 
    string controllerName)
{
    return new ControllerContextRegion(html.ViewContext.RouteData, controllerName);
}

ControllerContextRegion is defined as:

internal class ControllerContextRegion : IDisposable
{
    private readonly RouteData routeData;
    private readonly string previousControllerName;

    public ControllerContextRegion(RouteData routeData, string controllerName)
    {
        this.routeData = routeData;
        this.previousControllerName = routeData.GetRequiredString("controller");
        this.SetControllerName(controllerName);
    }

    public void Dispose()
    {
        this.SetControllerName(this.previousControllerName);
    }

    private void SetControllerName(string controllerName)
    {
        this.routeData.Values["controller"] = controllerName;
    }
}

The way this is used within a view is as follows:

@using (Html.ControllerContextRegion("Foo")) {
    // Html.Action, Html.Partial, etc. now looks things up as though
    // FooController was our controller.
}

There may be unwanted side effects for this if your code requires the controller route component to not change, but in our code so far, there doesn't seem to be any negatives to this approach.


The VirtualPathProviderViewEngine, on which the WebFormsViewEngine is based, is supposed to support the "~" and "/" characters at the front of the path so your examples above should work.

I noticed your examples use the path "~/Account/myPartial.ascx", but you mentioned that your user control is in the Views/Account folder. Have you tried

<%Html.RenderPartial("~/Views/Account/myPartial.ascx");%>

or is that just a typo in your question?