using System; using System.IO; using System.Threading; using System.Web.Mvc; using System.Web.Routing; namespace Umbraco.Web.Mvc { internal static class ControllerExtensions { /// /// Return the controller name from the controller type /// /// /// internal static string GetControllerName(Type controllerType) { if (!controllerType.Name.EndsWith("Controller")) { throw new InvalidOperationException("The controller type " + controllerType + " does not follow conventions, MVC Controller class names must be suffixed with the term 'Controller'"); } return controllerType.Name.Substring(0, controllerType.Name.LastIndexOf("Controller")); } /// /// Return the controller name from the controller instance /// /// /// internal static string GetControllerName(this IController controllerInstance) { return GetControllerName(controllerInstance.GetType()); } /// /// Return the controller name from the controller type /// /// /// /// internal static string GetControllerName() { return GetControllerName(typeof(T)); } /// /// This is generally used for proxying to a ChildAction which requires a ViewContext to be setup /// but since the View isn't actually rendered the IView object is null, however the rest of the /// properties are filled in. /// /// /// internal static ViewContext CreateEmptyViewContext(this ControllerBase controller) { return new ViewContext { Controller = controller, HttpContext = controller.ControllerContext.HttpContext, RequestContext = controller.ControllerContext.RequestContext, RouteData = controller.ControllerContext.RouteData, TempData = controller.TempData, ViewData = controller.ViewData }; } /// /// Returns the string output from a ViewResultBase object /// /// /// /// internal static string RenderViewResultAsString(this ControllerBase controller, ViewResultBase viewResult) { using (var sw = new StringWriter()) { controller.EnsureViewObjectDataOnResult(viewResult); var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, viewResult.ViewData, viewResult.TempData, sw); viewResult.View.Render(viewContext, sw); foreach (var v in viewResult.ViewEngineCollection) { v.ReleaseView(controller.ControllerContext, viewResult.View); } return sw.ToString().Trim(); } } /// /// Renders the partial view to string. /// /// The controller context. /// Name of the view. /// The model. /// true if it is a Partial view, otherwise false for a normal view /// internal static string RenderViewToString(this ControllerBase controller, string viewName, object model, bool isPartial = false) { if (controller.ControllerContext == null) throw new ArgumentException("The controller must have an assigned ControllerContext to execute this method."); controller.ViewData.Model = model; using (var sw = new StringWriter()) { var viewResult = isPartial == false ? ViewEngines.Engines.FindView(controller.ControllerContext, viewName, null) : ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName); if (viewResult.View == null) throw new InvalidOperationException("No view could be found by name " + viewName); var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw); viewResult.View.Render(viewContext, sw); viewResult.ViewEngine.ReleaseView(controller.ControllerContext, viewResult.View); return sw.GetStringBuilder().ToString(); } } /// /// Renders the partial view to string. /// /// The request context. /// /// /// Name of the view. /// The model. /// true if it is a Partial view, otherwise false for a normal view /// internal static string RenderViewToString( this RequestContext requestContext, ViewDataDictionary viewData, TempDataDictionary tempData, string viewName, object model, bool isPartial = false) { if (requestContext == null) throw new ArgumentNullException("requestContext"); if (viewData == null) throw new ArgumentNullException("viewData"); if (tempData == null) throw new ArgumentNullException("tempData"); var routeData = requestContext.RouteData; if (routeData.Values.ContainsKey("controller") == false) routeData.Values.Add("controller", "Fake"); viewData.Model = model; var controllerContext = new ControllerContext( requestContext.HttpContext, routeData, new FakeController { ViewData = viewData }); using (var sw = new StringWriter()) { var viewResult = isPartial == false ? ViewEngines.Engines.FindView(controllerContext, viewName, null) : ViewEngines.Engines.FindPartialView(controllerContext, viewName); if (viewResult.View == null) throw new InvalidOperationException("No view could be found by name " + viewName); var viewContext = new ViewContext(controllerContext, viewResult.View, viewData, tempData, sw); viewResult.View.Render(viewContext, sw); viewResult.ViewEngine.ReleaseView(controllerContext, viewResult.View); return sw.GetStringBuilder().ToString(); } } private class FakeController : ControllerBase { protected override void ExecuteCore() { } } /// /// Normally in MVC the way that the View object gets assigned to the result is to Execute the ViewResult, this however /// will write to the Response output stream which isn't what we want. Instead, this method will use the same logic inside /// of MVC to assign the View object to the result but without executing it. /// This is only relevant for view results of PartialViewResult or ViewResult. /// /// /// private static void EnsureViewObjectDataOnResult(this ControllerBase controller, ViewResultBase result) { if (result.View != null) return; if (string.IsNullOrEmpty(result.ViewName)) result.ViewName = controller.ControllerContext.RouteData.GetRequiredString("action"); if (result.View != null) return; if (result is PartialViewResult) { var viewEngineResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, result.ViewName); if (viewEngineResult.View == null) { throw new InvalidOperationException("Could not find the view " + result.ViewName + ", the following locations were searched: " + Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations)); } result.View = viewEngineResult.View; } else if (result is ViewResult) { var vr = (ViewResult)result; var viewEngineResult = ViewEngines.Engines.FindView(controller.ControllerContext, vr.ViewName, vr.MasterName); if (viewEngineResult.View == null) { throw new InvalidOperationException("Could not find the view " + vr.ViewName + ", the following locations were searched: " + Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations)); } result.View = viewEngineResult.View; } } } }