How to include a partial view inside a webform - asp.net

Some site I'm programming is using both ASP.NET MVC and WebForms.
I have a partial view and I want to include this inside a webform. The partial view has some code that has to be processed in the server, so using Response.WriteFile don't work.
It should work with javascript disabled.
How can I do this?

I had a look at the MVC source to see if I could figure out how to do this. There seems to be very close coupling between controller context, views, view data, routing data and the html render methods.
Basically in order to make this happen you need to create all of these extra elements. Some of them are relatively simple (such as the view data) but some are a bit more complex - for instance the routing data will consider the current WebForms page to be ignored.
The big problem appears to be the HttpContext - MVC pages rely on a HttpContextBase (rather than HttpContext like WebForms do) and while both implement IServiceProvider they're not related. The designers of MVC made a deliberate decision not to change the legacy WebForms to use the new context base, however they did provide a wrapper.
This works and lets you add a partial view to a WebForm:
public class WebFormController : Controller { }
public static class WebFormMVCUtil
{
public static void RenderPartial( string partialName, object model )
{
//get a wrapper for the legacy WebForm context
var httpCtx = new HttpContextWrapper( System.Web.HttpContext.Current );
//create a mock route that points to the empty controller
var rt = new RouteData();
rt.Values.Add( "controller", "WebFormController" );
//create a controller context for the route and http context
var ctx = new ControllerContext(
new RequestContext( httpCtx, rt ), new WebFormController() );
//find the partial view using the viewengine
var view = ViewEngines.Engines.FindPartialView( ctx, partialName ).View;
//create a view context and assign the model
var vctx = new ViewContext( ctx, view,
new ViewDataDictionary { Model = model },
new TempDataDictionary() );
//render the partial view
view.Render( vctx, System.Web.HttpContext.Current.Response.Output );
}
}
Then in your WebForm you can do this:
<% WebFormMVCUtil.RenderPartial( "ViewName", this.GetModel() ); %>

It took a while, but I've found a great solution. Keith's solution works for a lot of people, but in certain situations it's not the best, because sometimes you want your application to go through the process of the controller for rendering the view, and Keith's solution just renders the view with a given model I'm presenting here a new solution that will run the normal process.
General Steps:
Create a Utility class
Create a Dummy Controller with a dummy view
In your aspx or master page, call the utility method to render partial passing the Controller, view and if you need, the model to render (as an object),
Let's check it closely in this example
1) Create a Class called MVCUtility and create the following methods:
//Render a partial view, like Keith's solution
private static void RenderPartial(string partialViewName, object model)
{
HttpContextBase httpContextBase = new HttpContextWrapper(HttpContext.Current);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Dummy");
ControllerContext controllerContext = new ControllerContext(new RequestContext(httpContextBase, routeData), new DummyController());
IView view = FindPartialView(controllerContext, partialViewName);
ViewContext viewContext = new ViewContext(controllerContext, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), httpContextBase.Response.Output);
view.Render(viewContext, httpContextBase.Response.Output);
}
//Find the view, if not throw an exception
private static IView FindPartialView(ControllerContext controllerContext, string partialViewName)
{
ViewEngineResult result = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName);
if (result.View != null)
{
return result.View;
}
StringBuilder locationsText = new StringBuilder();
foreach (string location in result.SearchedLocations)
{
locationsText.AppendLine();
locationsText.Append(location);
}
throw new InvalidOperationException(String.Format("Partial view {0} not found. Locations Searched: {1}", partialViewName, locationsText));
}
//Here the method that will be called from MasterPage or Aspx
public static void RenderAction(string controllerName, string actionName, object routeValues)
{
RenderPartial("PartialRender", new RenderActionViewModel() { ControllerName = controllerName, ActionName = actionName, RouteValues = routeValues });
}
Create a class for passing the parameters, I will call here RendeActionViewModel (you can create in the same file of the MvcUtility Class)
public class RenderActionViewModel
{
public string ControllerName { get; set; }
public string ActionName { get; set; }
public object RouteValues { get; set; }
}
2) Now create a Controller named DummyController
//Here the Dummy controller with Dummy view
public class DummyController : Controller
{
public ActionResult PartialRender()
{
return PartialView();
}
}
Create a Dummy view called PartialRender.cshtml (razor view) for the DummyController with the following content, note that it will perform another Render Action using the Html helper.
#model Portal.MVC.MvcUtility.RenderActionViewModel
#{Html.RenderAction(Model.ActionName, Model.ControllerName, Model.RouteValues);}
3) Now just put this in your MasterPage or aspx file, to partial render a view that you want. Note that this is a great answer when you have multiple razor's views that you want to mix with your MasterPage or aspx pages. (supposing we have a PartialView called Login for the Controller Home).
<% MyApplication.MvcUtility.RenderAction("Home", "Login", new { }); %>
or if you have a model for passing into the Action
<% MyApplication.MvcUtility.RenderAction("Home", "Login", new { Name="Daniel", Age = 30 }); %>
This solution is great, doesn't use ajax call, which will not cause a delayed render for the nested views, it doesn't make a new WebRequest so it will not bring you a new session, and it will process the method for retrieving the ActionResult for the view you want, it works without passing any model
Thanks to Using MVC RenderAction within a Webform

most obvious way would be via AJAX
something like this (using jQuery)
<div id="mvcpartial"></div>
<script type="text/javascript">
$(document).load(function () {
$.ajax(
{
type: "GET",
url : "urltoyourmvcaction",
success : function (msg) { $("#mvcpartial").html(msg); }
});
});
</script>

This is great, thanks!
I'm using MVC 2 on .NET 4, which requires a TextWriter gets passed into the ViewContext, so you have to pass in httpContextWrapper.Response.Output as shown below.
public static void RenderPartial(String partialName, Object model)
{
// get a wrapper for the legacy WebForm context
var httpContextWrapper = new HttpContextWrapper(HttpContext.Current);
// create a mock route that points to the empty controller
var routeData = new RouteData();
routeData.Values.Add(_controller, _webFormController);
// create a controller context for the route and http context
var controllerContext = new ControllerContext(new RequestContext(httpContextWrapper, routeData), new WebFormController());
// find the partial view using the viewengine
var view = ViewEngines.Engines.FindPartialView(controllerContext, partialName).View as WebFormView;
// create a view context and assign the model
var viewContext = new ViewContext(controllerContext, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), httpContextWrapper.Response.Output);
// render the partial view
view.Render(viewContext, httpContextWrapper.Response.Output);
}

Here's a similar approach that has been working for me. The strategy is to render the partial view to a string, then output that in the WebForm page.
public class TemplateHelper
{
/// <summary>
/// Render a Partial View (MVC User Control, .ascx) to a string using the given ViewData.
/// http://www.joeyb.org/blog/2010/01/23/aspnet-mvc-2-render-template-to-string
/// </summary>
/// <param name="controlName"></param>
/// <param name="viewData"></param>
/// <returns></returns>
public static string RenderPartialToString(string controlName, object viewData)
{
ViewDataDictionary vd = new ViewDataDictionary(viewData);
ViewPage vp = new ViewPage { ViewData = vd};
Control control = vp.LoadControl(controlName);
vp.Controls.Add(control);
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter tw = new HtmlTextWriter(sw))
{
vp.RenderControl(tw);
}
}
return sb.ToString();
}
}
In the page codebehind, you can do
public partial class TestPartial : System.Web.UI.Page
{
public string NavigationBarContent
{
get;
set;
}
protected void Page_Load(object sender, EventArgs e)
{
NavigationVM oVM = new NavigationVM();
NavigationBarContent = TemplateHelper.RenderPartialToString("~/Views/Shared/NavigationBar.ascx", oVM);
}
}
and in the page you'll have access to the rendered content
<%= NavigationBarContent %>
Hope that helps!

This solution takes a different approach. It defines a System.Web.UI.UserControl which can be place on any Web Form and be configured to display the content from any URL…including an MVC partial view.
This approach is similar to an AJAX call for HTML in that parameters (if any) are given via the URL query string.
First, define a user control in 2 files:
/controls/PartialViewControl.ascx file
<%# Control Language="C#"
AutoEventWireup="true"
CodeFile="PartialViewControl.ascx.cs"
Inherits="PartialViewControl" %>
/controls/PartialViewControl.ascx.cs:
public partial class PartialViewControl : System.Web.UI.UserControl {
[Browsable(true),
Category("Configutation"),
Description("Specifies an absolute or relative path to the content to display.")]
public string contentUrl { get; set; }
protected override void Render(HtmlTextWriter writer) {
string requestPath = (contentUrl.StartsWith("http") ? contentUrl : "http://" + Request.Url.DnsSafeHost + Page.ResolveUrl(contentUrl));
WebRequest request = WebRequest.Create(requestPath);
WebResponse response = request.GetResponse();
Stream responseStream = response.GetResponseStream();
var responseStreamReader = new StreamReader(responseStream);
var buffer = new char[32768];
int read;
while ((read = responseStreamReader.Read(buffer, 0, buffer.Length)) > 0) {
writer.Write(buffer, 0, read);
}
}
}
Then add the user control to your web form page:
<%# Page Language="C#" %>
<%# Register Src="~/controls/PartialViewControl.ascx" TagPrefix="mcs" TagName="PartialViewControl" %>
<h1>My MVC Partial View</h1>
<p>Below is the content from by MVC partial view (or any other URL).</p>
<mcs:PartialViewControl runat="server" contentUrl="/MyMVCView/" />

FWIW, I needed to be able to render a partial view dynamically from existing webforms code, and insert it at the top of a given control. I found that Keith's answer can cause the partial view to be rendered outside the <html /> tag.
Using the answers from Keith and Hilarius for inspiration, rather than render direct to HttpContext.Current.Response.Output, I rendered the html string and added it as a LiteralControl to the relevant control.
In static helper class:
public static string RenderPartial(string partialName, object model)
{
//get a wrapper for the legacy WebForm context
var httpCtx = new HttpContextWrapper(HttpContext.Current);
//create a mock route that points to the empty controller
var rt = new RouteData();
rt.Values.Add("controller", "WebFormController");
//create a controller context for the route and http context
var ctx = new ControllerContext(new RequestContext(httpCtx, rt), new WebFormController());
//find the partial view using the viewengine
var view = ViewEngines.Engines.FindPartialView(ctx, partialName).View;
//create a view context and assign the model
var vctx = new ViewContext(ctx, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), new StringWriter());
// This will render the partial view direct to the output, but be careful as it may end up outside of the <html /> tag
//view.Render(vctx, HttpContext.Current.Response.Output);
// Better to render like this and create a literal control to add to the parent
var html = new StringWriter();
view.Render(vctx, html);
return html.GetStringBuilder().ToString();
}
In calling class:
internal void AddPartialViewToControl(HtmlGenericControl ctrl, int? insertAt = null, object model)
{
var lit = new LiteralControl { Text = MvcHelper.RenderPartial("~/Views/Shared/_MySharedView.cshtml", model};
if (insertAt == null)
{
ctrl.Controls.Add(lit);
return;
}
ctrl.Controls.AddAt(insertAt.Value, lit);
}

Related

ASP.NET Core ControllerContext vs ActionContext in UrlHelper

I am trying to implement pagination in my Asp.net core 2 API. To create pagination links, I am using UrlHelper. The constructor for UrlHelper requires the context in which the action runs.
The examples I've seen have been using below configuration in startup and then injecting IUrlHelper into the controller where it is needed.
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper>(x => {
var actionContext = x.GetRequiredService<IActionContextAccessor>().ActionContext;
var factory = x.GetRequiredService<IUrlHelperFactory>();
return factory.GetUrlHelper(actionContext);
});
But controllers also have ControllerContext which derives from ActionContext (https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllercontext?view=aspnetcore-2.1).
I am able to do the following:
public Object GetAll() //ignore object return, for test purposes
{
var urlHelper = new UrlHelper(ControllerContext);
var nextLink = urlHelper.Link("GetPosts", new { page = 1, pageSize = 3 });
//return _context.Posts;
return new
{
NextPageLink = nextLink,
Results = _context.Posts,
test = ControllerContext.RouteData.Values
};
}
The code above is able to create the links correctly. I don't have a firm grasp on the nuances of the framework so I am wondering if above is a correct way to initialize UrlHelper. Will this lead to problems? If you can point me in the direction of some documentation around this or explain the reason behind if the approach is good/bad, that would be very helpful.
What you have can work.
It does however tightly couple the controller to an implementation concern.
If you have need for the helper you can follow a similar format to what was configured at startup by injecting the IUrlHelperFactory into the controller and getting the helper using the controller's ControllerContext, which as you have already discovered, derives from ActionContext
public class MyController : Controller {
private readonly IUrlHelperFactory factory;
//...other dependencies
public MyController(IUrlHelperFactory factory) {
this.factory = factory;
//...other dependencies
}
public IActionResult GetAll() {
var urlHelper = factory.GetUrlHelper(ControllerContext);
var nextLink = urlHelper.Link("GetPosts", new { page = 1, pageSize = 3 });
return Ok(new {
NextPageLink = nextLink,
Results = _context.Posts,
test = ControllerContext.RouteData.Values
});
}
//...other actions
}

RazorViewEngine.FindView can't find the precompiled view

App.Web and App.Views are my projects in one solution, I put my views in App.Views and precompiled with RazorGenerator. It's working well if I used App.Web like,
~/Views/Index.cshtml is virtual path of my view in App.View
It can successfully render this view in App.Web
public ActionResult Index() {
return View("~/Views/Index.cshtml");
}
But when I try to RenderViewToString, it returns null.
class FakeController : ControllerBase
{
protected override void ExecuteCore() { }
public static string RenderViewToString(string controllerName, string viewName, object viewData)
{
using (var writer = new StringWriter())
{
var routeData = new RouteData();
routeData.Values.Add("controller", controllerName);
var fakeControllerContext = new ControllerContext(new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null), new HttpResponse(null))), routeData, new FakeController());
var razorViewEngine = new RazorViewEngine();
var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);
var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
}
And this is how can all it,
FakeController.RenderViewToString("FakeName", "~/Views/Index.csthml", MessageModel);
This is discussed and probably solved in asp.net core, but I'm working with asp.net mvc 5.
Could you please help me to figure out, why it's not working?
You are trying to use Razor View Engine for getting precompiled views. This is a mistake. Razor engine produces views based on cshtml files. However in case of precompiled views, cshtml files have been already compiled by RazorGenerator to a set of classes derivied from System.Web.Mvc.WebViewPage. These classes override method Execute() (autogenerated by RazorGenerator based on input cshtml) that write html to output TextWriter.
Original view files (cshtml) are not required anymore and thus are not deployed with the application. When you call Razor that tries to locate cshtml and build view based on it, it expectedly returns null view.
ASP.NET MVC supports multiple view engines (the classes that implement System.Web.Mvc.IViewEngine). RazorViewEngine is one of them. RazorGenerator.Mvc NuGet package adds its own view engine (PrecompiledMvcEngine) that works based on precompiled views. Registered view engines are stored in ViewEngines.Engines collection. When you install RazorGenerator.Mvc NuGet package, it adds RazorGeneratorMvcStart class that registers instance of PrecompiledMvcEngine:
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(App.Views.RazorGeneratorMvcStart), "Start")]
namespace App.Views {
public static class RazorGeneratorMvcStart {
public static void Start() {
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly) {
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Insert(0, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
}
You should use this instance of PrecompiledMvcEngine instead of RazorViewEngine for accessing precompiled views. Here is adjusted code of RenderViewToString method:
public static string RenderViewToString(string controllerName, string viewName, object viewData)
{
using (var writer = new StringWriter())
{
var routeData = new RouteData();
routeData.Values.Add("controller", controllerName);
var fakeControllerContext = new ControllerContext(new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null), new HttpResponse(null))), routeData, new FakeController());
var viewEngine = ViewEngines.Engines.OfType<PrecompiledMvcEngine>().FirstOrDefault();
if (viewEngine == null)
{
throw new InvalidOperationException("PrecompiledMvcEngine is not registered");
}
var viewResult = viewEngine.FindView(fakeControllerContext, viewName, "", false);
var viewContext = new ViewContext(fakeControllerContext, viewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
viewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
One important note: you should install RazorGenerator.Mvc NuGet package into project with your views (App.Views), not into Web application project, because PrecompiledMvcEngine takes current assembly as the source for the precompiled views. Aslo make sure that RazorGeneratorMvcStart was not added to App.Web project. It happened for me when I have added reference to RazorGenerator.Mvc.dll assembly.
Sample Project on GitHub

Switching between ViewEngines with partial views doesn't find the view to display

I have a legacy application that was made with Web-Forms, and trying to use asp.net mvc to render a content inside a partial view. Following #ScottHanselman post of MixingRazorViewsAndWebFormsMasterPagesWithASPNETMVC3 and finally Matt Honeycutt approach http://trycatchfail.com/blog/post/ASPNET-MVC-3-Razor-C-and-VBNET-WebForms-Using-Razor-Views-With-WebForms-Master-Pages
I ended with a view inside my web-form page
<%# Page Title="Home Page" Language="C#" MasterPageFile="~/MyMasterSite.Master" AutoEventWireup="false"
CodeBehind="InvoiceProfileReport.aspx.cs" Inherits="MyWebFormsNameSpace" %>
<%# Import namespace="MyMvc.MvcReports.Controllers" %>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
<%= this.BlackMagicMVC().Partial("HomepageWidget") %>
</asp:Content>
as you can see I am trying to insert HomepageWidget that is inside of my View folder
Solution Explorer
My controller creates a wrapper around the request, Partial is an extension method
public class BlackMagicMvcHelper
{
private static HtmlHelper GetHtmlHelper()
{
var controllerContext = new ControllerContext
{
HttpContext = new HttpContextWrapper(HttpContext.Current),
RouteData = new RouteData()
};
controllerContext.RouteData.Values["controller"] = "does-not-exist";
var context = new ViewContext(controllerContext,
new WebFormView(controllerContext, "does-not-exist", string.Empty),
new ViewDataDictionary(),
new TempDataDictionary(),
TextWriter.Null);
var helper = new HtmlHelper(context, new ViewPage());
return helper;
}
public MvcHtmlString ActionLink<TController>(Expression<Action<TController>> action, string linkText) where TController : Controller
{
var helper = GetHtmlHelper();
return helper.ActionLink(action, linkText);
}
public MvcHtmlString Partial(string partialName)
{
var helper = GetHtmlHelper();
return helper.Partial(partialName);
}
public MvcHtmlString RenderAction<TController>(Expression<Action<TController>> action) where TController : Controller
{
var helper = GetHtmlHelper();
var routeValues = ExpressionHelper.GetRouteValuesFromExpression(action);
return helper.Action((string)routeValues["action"], (string)routeValues["controller"], routeValues);
}
}
After loading the page I am getting the error:
The partial view 'HomepageWidget' was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/does-not-exist/HomepageWidget.aspx
~/Views/does-not-exist/HomepageWidget.ascx
~/Views/Shared/HomepageWidget.aspx
~/Views/Shared/HomepageWidget.ascx
~/Views/does-not-exist/HomepageWidget.cshtml
~/Views/does-not-exist/HomepageWidget.vbhtml
~/Views/Shared/HomepageWidget.cshtml
~/Views/Shared/HomepageWidget.vbhtml
Any suggestion of what I am doing wrong?

Retaining RouteData in Html.BeginForm()

I have been using a variant of the Html.BeginForm() method to attach an html attribute to my form, like this :
#using (Html.BeginForm("actionname", "controllername", FormMethod.Post, new { id = "myform" }))
Unfortunately this causes the form target to loose all route data.
Say my url was controller/action?abc=123, then using Html.BeginForm() generates the form post target as controller/action?abc=123 but the overloaded version (which I am using to add the html id attribute to the form), generates the target as controller/action (which actually is understandable, since I am specifying the route myself, but it doesn't solve my purpose).
Is there a variant of the Html.BeginForm() which would allow me retain the old route values and let me add html attributes to the form at the same time?
As far as I can see, only the parameterless version of BeginForm uses the current full URL.
public static MvcForm BeginForm(this HtmlHelper htmlHelper) {
// generates <form action="{current url}" method="post">...</form>
string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return FormHelper(htmlHelper, formAction, FormMethod.Post, new RouteValueDictionary());
}
I'm not sure if this is the best way, but you could write a custom form helper to include the QueryString values:
public static class MyFormExtensions
{
public static MvcForm MyBeginForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
var rvd = new RouteValueDictionary(htmlHelper.ViewContext.RouteData.Values);
var queryString = htmlHelper.ViewContext.HttpContext.Request.QueryString;
foreach (string key in queryString.AllKeys) rvd.Add(key, queryString[key]);
return htmlHelper.BeginForm(null, null, rvd, FormMethod.Post, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
}
#using (Html.MyBeginForm(new { id = "myform" }))
{
//...
}

Change the Views location

I am developing a website in MVC 2.0. I want to change the View folder location in my website. I wanted to keep the views folder inside other folders, When I try to do so i am getting following errors
The view 'Index' or its master was not found. The following locations were searched:
~/Views/Search/Index.aspx
~/Views/Search/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
My Views folder will be in ~/XYZ/ABC/Views instead of ~/Views. Please solve my problem. Will I get any problems If I change the default Views folder location. Do I need to change anything in HTML Helper classes because I don't know anything in MVC as this is my starting project i dont want to risk..Please help me out...
You'll need to create a custom view engine and use that instead. Fortunately you can just inherit from the default one and change the locations on the constructor. Here's a guide to creating your own view engine: http://www.singingeels.com/Articles/Creating_a_Custom_View_Engine_in_ASPNET_MVC.aspx
From the article:
protected void Application_Start()
{
//... other things up here.
// I want to REMOVE the ASP.NET ViewEngine...
ViewEngines.Engines.Clear();
// and then add my own :)
ViewEngines.Engines.Add(new HoTMeaTViewEngine());
}
public class HoTMeaTViewEngine : VirtualPathProviderViewEngine
{
public HoTMeaTViewEngine()
{
// This is where we tell MVC where to look for our files. This says
// to look for a file at "Views/Controller/Action.html"
base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.html" };
base.PartialViewLocationFormats = base.ViewLocationFormats;
}
}
Check this place out. How to change default view location scheme in ASP.NET MVC?
base.ViewLocationFormats = new string[] {
"~/Views/{1}/{2}/{0}.aspx",
"~/Views/{1}/{2}/{0}.ascx",
"~/Views/Shared/{2}/{0}.aspx",
"~/Views/Shared/{2}/{0}.ascx" ,
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
Even easier is this one Can I specify a custom location to “search for views” in ASP.NET MVC?
As an alternative, you can override the view engine locations for a specific controller without affecting the view engines for the other controllers.
These are some snippets from a product I am developing, but it shows the constructor for one of my controllers, and a view engine I made specificially for controllers that inherit from KBRenderMvcController.
So any controller based off KBRenderMvcController will also have my view engine.
However at no point did I clear the view engine collection, which is relevant. Because I wanted the views my product is using to fall back to default locations.
In short, if you delete \App_plugins\Product\Views\MyView And instead create a \Views\MyView it will still render from \Views\MyView instead.
Also in the ViewEngine I demonstrate code that determines the type of controller being used and if it's not a target controller I return empty view locations so they don't get used for other controllers.
#region Constructor
public KBRenderMvcController()
: base()
{
viewEngine = new KBFrontEndViewEngine();
if (!this.ViewEngineCollection.Contains(viewEngine))
this.ViewEngineCollection.Insert(0, viewEngine);
}
#endregion
public class KBFrontEndViewEngine : RazorViewEngine
{
#region Fields
private static bool _Initialized = false;
private static string[] viewLocationFormats = null;
private static string[] partialViewLocationFormats = null;
private static string[] viewEngineFileExtensions = new string[] { "cshtml" };
#endregion
#region Constructor
public KBFrontEndViewEngine()
{
if (!_Initialized)
{
viewLocationFormats = new string[]
{
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/{1}/{0}.cshtml"),
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/Partials/{0}.cshtml")
};
partialViewLocationFormats = new string[]
{
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/{1}/Partials/_partial{0}.cshtml"),
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/Partials/_partial{0}.cshtml"),
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/{1}/Dialogs/_dialog{1}.cshtml"),
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/Dialogs/_dialog{1}.cshtml"),
};
_Initialized = true;
}
base.ViewLocationFormats = viewLocationFormats;
base.PartialViewLocationFormats = partialViewLocationFormats;
base.MasterLocationFormats = viewLocationFormats;
base.FileExtensions = viewEngineFileExtensions;
}
#endregion
#region Methods
//Don't run on requests that are not for our hijacked controllers
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
Type controllerType = controllerContext.Controller.GetType();
Type baseType = controllerType.BaseType;
if ((baseType != null) && (baseType.Name == "KBRenderMvcController`1") || (baseType.Name == "KBFrontEndBaseSurfaceController"))
return base.FindPartialView(controllerContext, partialViewName, useCache);
else
return new ViewEngineResult(new List<string>());
}
#endregion
}

Resources