I need to be able to return partial view as string in my MVC app via signalR. I'm using hubs.
I'm using the following method to return a partial view string (from here):
public static string RenderPartialView(string controllerName, string partialView, object model)
{
var context = httpContextBase as HttpContextBase;
var routes = new RouteData();
routes.Values.Add("controller", controllerName);
var requestContext = new RequestContext(context, routes);
string requiredString = requestContext.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var controller = controllerFactory.CreateController(requestContext, requiredString) as ControllerBase;
controller.ControllerContext = new ControllerContext(context, routes, controller);
var ViewData = new ViewDataDictionary();
var TempData = new TempDataDictionary();
ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, partialView);
var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
In order for this method to work I need HttpContext.Current therefore in my OnConnected (I noticed that this always exists) I set it like so:
public class TaskActionStatus : Hub
{
private static HttpContextBase httpContextBase;
...
public override Task OnConnected()
{
httpContextBase = new HttpContextWrapper(HttpContext.Current) as HttpContextBase;
...
and then I use it in my RenderPartialView method:
var context = httpContextBase as HttpContextBase;
This way I always have access to current HttpContext. However, I noticed that sometimes my static copy of HttpContext is null. Why is that?.
What's the best approach here?
Is there a way to render partial view without HttpContext?
I used fake http context to generate view:
public static string GetRazorViewAsString(object model, string filePath)
{
HttpContext httpContext = MockHelper.FakeHttpContext();
var st = new StringWriter();
var context = new HttpContextWrapper(httpContext);
var routeData = new RouteData();
var controllerContext = new ControllerContext(new RequestContext(context, routeData), new FakeController());
var razor = new RazorView(controllerContext, filePath, null, false, null);
razor.Render(
new ViewContext(controllerContext, razor, new ViewDataDictionary(model), new TempDataDictionary(), st),
st);
return st.ToString();
}
#endregion
}
public class FakeController : Controller
{
}
public class MockHelper
{
#region Public Methods and Operators
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest(string.Empty, "http://novomatic/", string.Empty);
var stringWriter = new StringWriter();
var httpResponce = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponce);
var sessionContainer = new HttpSessionStateContainer(
"id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10,
true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,
false);
httpContext.Items["AspSession"] =
typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null).Invoke(new object[] { sessionContainer });
return httpContext;
}
#endregion
}
Related
I am trying to test the following filter:
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Filters;
namespace Hello
{
public class ValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.ModelState.IsValid) {
filterContext.Result = new BadRequestObjectResult(filterContext.ModelState);
}
}
}
}
I am trying to mock the ActionFilterAttribute using Moq.
I am using Mvc 6.0.0-rc1
My first try:
var context = new ActionExecutingContext(
new ActionContext(Mock.Of<HttpContext>(), new RouteData(), new ActionDescriptor()),
new IFilterMetadata[] { filter, },
new Dictionary<string, object>(),
controller: new object());
But I could not override the ModelState. It looks to be readonly: https://github.com/aspnet/Mvc/blob/6.0.0-rc1/src/Microsoft.AspNet.Mvc.Abstractions/ActionContext.cs#L25
Then I tried:
var contextMock = new Mock<ActionExecutingContext>(
new ActionContext(Mock.Of<HttpContext>(), new RouteData(), new ActionDescriptor()),
new IFilterMetadata[] { filter, },
new Dictionary<string, object>());
But when I call filter.OnActionExecuting(contextMock.Object) I get the following error:
Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: Microsoft.AspNet.Mvc.Filters.ActionExecutingContext.
Could not find a constructor that would match given arguments:
Microsoft.AspNet.Mvc.ActionContext
Microsoft.AspNet.Mvc.Filters.IFilterMetadata[]
System.Collections.Generic.Dictionary`2[System.String,System.Object]
How to test a filter that uses ModelState?
I just stumbled on the same problem and solved it in this way.
[Fact]
public void ValidateModelAttributes_SetsResultToBadRequest_IfModelIsInvalid()
{
var validationFilter = new ValidationFilter();
var modelState = new ModelStateDictionary();
modelState.AddModelError("name", "invalid");
var actionContext = new ActionContext(
Mock.Of<HttpContext>(),
Mock.Of<RouteData>(),
Mock.Of<ActionDescriptor>(),
modelState
);
var actionExecutingContext = new ActionExecutingContext(
actionContext,
new List<IFilterMetadata>(),
new Dictionary<string, object>(),
Mock.Of<Controller>()
);
validationFilter.OnActionExecuting(actionExecutingContext);
Assert.IsType<BadRequestObjectResult>(actionExecutingContext.Result);
}
If someone was wondering how to do this when you inherit from IAsyncActionFilter
[Fact]
public async Task MyTest()
{
var modelState = new ModelStateDictionary();
var httpContextMock = new DefaultHttpContext();
httpContextMock.Request.Query = new QueryCollection(new Dictionary<string, StringValues> {}); // if you are reading any properties from the query parameters
var actionContext = new ActionContext(
httpContextMock,
Mock.Of<RouteData>(),
Mock.Of<ActionDescriptor>(),
modelState
);
var actionExecutingContext = new ActionExecutingContext(
actionContext,
new List<IFilterMetadata>(),
new Dictionary<string, object>(),
Mock.Of<Controller>()
)
{
Result = new OkResult() // It will return ok unless during code execution you change this when by condition
};
Mymock1.SetupGet(x => x.SomeProperty).Returns("MySomething");
Mymock2.Setup(x => x.GetSomething(It.IsAny<string>(), It.IsAny<string>())).ReturnsAsync(true);
var context = new ActionExecutedContext(actionContext, new List<IFilterMetadata>(), Mock.Of<Controller>());
await _classUnderTest.OnActionExecutionAsync(actionExecutingContext, async () => { return context; });
actionExecutingContext.Result.Should().BeOfType<OkResult>();
}
ASP.NET MVC4 Web API controller should return Razor view as html in json result property.
I tried controller
public class TestController : ApiController
{
public HttpResponseMessage Get()
{
var body = RenderViewToString<object>("~/Views/Broneeri/Schedule.cshtml", new object() );
return Request.CreateResponse(HttpStatusCode.OK, new { content = body } );
}
string RenderViewToString<T>(string viewName, T model)
{
ViewDataDictionary ViewData = new ViewDataDictionary(model);
TempDataDictionary TempData = new TempDataDictionary();
ControllerContext controllerContext = new System.Web.Mvc.ControllerContext();
controllerContext.RouteData = new RouteData();
controllerContext.RouteData.Values.Add("controller", "Schedule");
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(controllerContext, 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();
}
}
}
but I got a strange exception
System.NotImplementedException was unhandled by user code
HResult=-2147467263
Message=The method or operation is not implemented.
Source=System.Web
StackTrace:
at System.Web.HttpContextBase.get_Items()
at System.Web.Mvc.VirtualPathProviderViewEngine.GetPath(ControllerContext controllerContext, String[] locations, String[] areaLocations, String locationsPropertyName, String name, String controllerName, String cacheKeyPrefix, Boolean useCache, String[]& searchedLocations)
at System.Web.Mvc.VirtualPathProviderViewEngine.FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache)
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 lookup, Boolean trackSearchedPaths)
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 cacheLocator, Func`2 locator)
...
at line
var viewResult = ViewEngines.Engines.FindPartialView(controllerContext, viewName);
How to return HTML content from view in ASP.NET 4 Web API controller?
You need to Install-Package RazorEngine using your NuGet console. Here is code that worked for me:
using RazorEngine;
using RazorEngine.Templating;
string razorTemplate = "Hello #Model!";
string html = Engine.Razor.RunCompile(
razorTemplate,
"uniqueTemplateKey", // Guid.NewGuid().ToString() for example
modelType: typeof(string),
model: "Lorem Ipsum");
Hope it helps!
This method works great if I pass null to the last param masterName, My views setup in my class derivved from RazorViewEngine work and all is good. Out of curiosity what is the masterName parameter used for? I first thought maybe it was for a layout.cshtml, however; when I pass it a layout it throws an exception.... Any ideas on how this is supposed to be used, what is it looking for?
Custom View Engine (Hardly LOL)
public class CustomRazorViewEngine : RazorViewEngine
{
private readonly string[] NewViewFormats = new[]
{
"~/Views/Messaging/{0}.cshtml"
};
public CustomRazorViewEngine()
{
base.ViewLocationFormats = base.ViewLocationFormats.Union(NewViewFormats).ToArray();
}
}
public string RenderViewToString(string viewName, object model, ControllerContext controllerContext,
string masterName)
{
if (string.IsNullOrEmpty(viewName))
viewName = controllerContext.RouteData.GetRequiredString("action");
controllerContext.Controller.ViewData.Model = model;
using (var stringWriter = new StringWriter())
{
ViewEngineResult viewEngineResult = ViewEngines.Engines.FindView(controllerContext, viewName, masterName);
var viewContext = new ViewContext(controllerContext, viewEngineResult.View,
controllerContext.Controller.ViewData,
controllerContext.Controller.TempData,
stringWriter);
viewEngineResult.View.Render(viewContext, stringWriter);
return stringWriter.GetStringBuilder().ToString();
}
}
So after some more debugging I have found what appears to be the correct answer. First let me state that the masterName parameter is the name of the "Layout" so to say that the view being rendered will use. The catch here is that layout must be able to be located. So instead of the code for the ViewEngine in my original post the following code works as desired.
public string RenderViewToString(string viewName, object model, ControllerContext controllerContext,
string masterName)
{
if (string.IsNullOrEmpty(viewName))
viewName = controllerContext.RouteData.GetRequiredString("action");
controllerContext.Controller.ViewData.Model = model;
using (var stringWriter = new StringWriter())
{
ViewEngineResult viewEngineResult = ViewEngines.Engines.FindView(controllerContext, viewName, masterName);
var viewContext = new ViewContext(controllerContext, viewEngineResult.View,
controllerContext.Controller.ViewData,
controllerContext.Controller.TempData,
stringWriter);
viewEngineResult.View.Render(viewContext, stringWriter);
return stringWriter.GetStringBuilder().ToString();
}
}
public class CustomRazorViewEngine : RazorViewEngine
{
private readonly string[] NewMasterViewFormats = new[]
{
"~/Views/Messaging/Layouts/{0}.cshtml"
};
private readonly string[] NewViewFormats = new[]
{
"~/Views/Messaging/{0}.cshtml"
};
public CustomRazorViewEngine()
{
base.ViewLocationFormats = base.ViewLocationFormats.Union(NewViewFormats).ToArray();
base.MasterLocationFormats = base.MasterLocationFormats.Union(NewMasterViewFormats).ToArray();
}
}
Now when calling
string returnViewToString = _viewUtils.RenderViewToString("RegistrationEmail", new RegistrationEmailModel
{ UserName = userName
},
this.ControllerContext,"_RegistrationEmailLayout");
Everything is happy and my layout for the passed in view, if it exists in the folder gets used. This was the highlight of my day... LOL
I have the following code:
public ActionResult SomeAction()
{
return new JsonpResult
{
Data = new { Widget = "some partial html for the widget" }
};
}
I'd like to modify it so that I could have
public ActionResult SomeAction()
{
// will render HTML that I can pass to the JSONP result to return.
var partial = RenderPartial(viewModel);
return new JsonpResult
{
Data = new { Widget = partial }
};
}
is this possible? Could somebody explain how?
note, I edited the question before posting the solution.
I opted for an extension method like the following for an ASP.NET MVC 4 app. I think it's simpler than some of the suggestions I've seen:
public static class ViewExtensions
{
public static string RenderToString(this PartialViewResult partialView)
{
var httpContext = HttpContext.Current;
if (httpContext == null)
{
throw new NotSupportedException("An HTTP context is required to render the partial view to a string");
}
var controllerName = httpContext.Request.RequestContext.RouteData.Values["controller"].ToString();
var controller = (ControllerBase)ControllerBuilder.Current.GetControllerFactory().CreateController(httpContext.Request.RequestContext, controllerName);
var controllerContext = new ControllerContext(httpContext.Request.RequestContext, controller);
var view = ViewEngines.Engines.FindPartialView(controllerContext, partialView.ViewName).View;
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
using (var tw = new HtmlTextWriter(sw))
{
view.Render(new ViewContext(controllerContext, view, partialView.ViewData, partialView.TempData, tw), tw);
}
}
return sb.ToString();
}
}
It allows me to do the following:
var html = PartialView("SomeView").RenderToString();
Also, this approach persists any Model, ViewBag and other view data for the view.
This is a slightly modified version of an answer that works:
public static string RenderPartialToString(string controlName, object viewData)
{
ViewPage viewPage = new ViewPage() { ViewContext = new ViewContext() };
viewPage.ViewData = new ViewDataDictionary(viewData);
viewPage.Controls.Add(viewPage.LoadControl(controlName));
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter tw = new HtmlTextWriter(sw))
{
viewPage.RenderControl(tw);
}
}
return sb.ToString();
}
Usage:
string ret = RenderPartialToString("~/Views/MyController/MyPartial.ascx", model);
DaveDev's answer worked well for me, however when the partial view calls another partial I get "Value cannot be null. Parameter name: view"
Searching around I have made a variant of the following that seems to work well.
public static string RenderPartialToString(string viewName, object model, ControllerContext ControllerContext)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.RouteData.GetRequiredString("action");
ViewDataDictionary ViewData = new ViewDataDictionary();
TempDataDictionary TempData = new TempDataDictionary();
ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
Usage:
String result = MVCHelpers.RenderPartialToString("PartialViewHere", Model, ControllerContext)
You can create extension that render view into string.
public static class RenderPartialToStringExtensions
{
/// <summary>
/// render PartialView and return string
/// </summary>
/// <param name="context"></param>
/// <param name="partialViewName"></param>
/// <param name="model"></param>
/// <returns></returns>
public static string RenderPartialToString(this ControllerContext context, string partialViewName, object model)
{
return RenderPartialToStringMethod(context, partialViewName, model);
}
/// <summary>
/// render PartialView and return string
/// </summary>
/// <param name="context"></param>
/// <param name="partialViewName"></param>
/// <param name="viewData"></param>
/// <param name="tempData"></param>
/// <returns></returns>
public static string RenderPartialToString(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)
{
return RenderPartialToStringMethod(context, partialViewName, viewData, tempData);
}
public static string RenderPartialToStringMethod(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)
{
ViewEngineResult result = ViewEngines.Engines.FindPartialView(context, partialViewName);
if (result.View != null)
{
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter output = new HtmlTextWriter(sw))
{
ViewContext viewContext = new ViewContext(context, result.View, viewData, tempData, output);
result.View.Render(viewContext, output);
}
}
return sb.ToString();
}
return String.Empty;
}
public static string RenderPartialToStringMethod(ControllerContext context, string partialViewName, object model)
{
ViewDataDictionary viewData = new ViewDataDictionary(model);
TempDataDictionary tempData = new TempDataDictionary();
return RenderPartialToStringMethod(context, partialViewName, viewData, tempData);
}
}
And then use it in action
[HttpPost]
public ActionResult GetTreeUnit(string id)
{
int _id = id.ExtractID();
string render = ControllerContext.RenderPartialToString("SomeView");
return Json(new { data = render });
}
Works perfect (Only view name required)
* for parameters you can use a model
* can call this from a view also
View side or Calling Side
BuyOnlineCartMaster ToInvoice1 = new BuyOnlineCartMaster(); // for passing parameters
ToInvoice1.CartID = 1;
string HtmlString = RenderPartialViewToString("PartialInvoiceCustomer", ToInvoice1);
Function Generating HTML
public class BuyOnlineCartMaster
{
public int CartID { get; set; }
}
public static string RenderPartialViewToString(string viewName, object model)
{
using (var sw = new StringWriter())
{
BuyOnlineController controller = new BuyOnlineController(); // instance of the required controller (you can pass this as a argument if needed)
// Create an MVC Controller Context
var wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", controller.GetType().Name
.ToLower()
.Replace("controller", ""));
controller.ControllerContext = new ControllerContext(wrapper, routeData, controller);
controller.ViewData.Model = model;
var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.ToString();
}
}
Partial View page
#{
var ModelContents = (Common.BuyOnlineCartMaster)ViewData.Model;
}
Your cart id : #(ModelContents.CartID)
Dave,
a variation on the same theme (mvc v1.0):
protected static string RenderPartialToString(Controller controller, string partialName, object model)
{
var vd = new ViewDataDictionary(controller.ViewData);
var vp = new ViewPage
{
ViewData = vd,
ViewContext = new ViewContext(),
Url = new UrlHelper(controller.ControllerContext.RequestContext)
};
ViewEngineResult result = ViewEngines
.Engines
.FindPartialView(controller.ControllerContext, partialName);
if (result.View == null)
{
throw new InvalidOperationException(
string.Format("The partial view '{0}' could not be found", partialName));
}
var partialPath = ((WebFormView)result.View).ViewPath;
vp.ViewData.Model = model;
Control control = vp.LoadControl(partialPath);
vp.Controls.Add(control);
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
using (var tw = new HtmlTextWriter(sw))
{
vp.RenderControl(tw);
}
}
return sb.ToString();
}
usage within controller:
public string GetLocationHighlites()
{
IBlockData model = WebPagesMapper.GetLocationHighlites();
// **this** being the controoler instance
// LocationPartial.ascx can be defined in shared or in view folder
return RenderPartialToString(**this**,"LocationPartial", model);
}
You can do that out of the box with:
var partial = new HtmlString(Html.Partial("_myPartial", Model).ToString());
public virtual string RenderPartialViewToString(string viewName, object viewmodel)
{
if (string.IsNullOrEmpty(viewName))
{
viewName = this.ControllerContext.RouteData.GetRequiredString("action");
}
ViewData.Model = viewmodel;
using (var sw = new StringWriter())
{
ViewEngineResult viewResult = System.Web.Mvc.ViewEngines.Engines.FindPartialView(this.ControllerContext, viewName);
var viewContext = new ViewContext(this.ControllerContext, viewResult.View, this.ViewData, this.TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
Old post, but I think you are making this too difficult.
Why not return PartialView("<my partial>", model); will return the string you desire.
Just make the Get/Post method an IActionResult as JsonResult, PartialResultand other inherit from ActionResult you can send back anything including a JsonResult
[HttpPost]
public IActionResult CheckForContent(string id, string type){
try{
if(type = "something"){
return Json(new {
success = true,
myProp = "this prop"
});
} else {
MyViewModel model = new MyViewModel();
model.Content = _service.GetContent(new GetContentRequest(){ id = id }
return PartialView("<my partial>", model);
} catch(Exception ex){
_logger.LogError(ex.Message);
return Json(new { success = false }
}
}
Ajax:
$.ajax({
url: '/CheckForContent',
type: 'POST',
cache: false,
contentType: false,
processData: false,
success: function (result) {
if(!result && !result.success){
console.log("Failed to load");
} else if(result && result.success){
$('#MyProp').val(result.myProp);
} else {
$('#MyContainer').html(result);
}
},
});
How do I mock a page request for a .net MVC page?
Using RhinoMocks:
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
var httpRequest = MockRepository.GenerateMock<HttpRequestBase>();
httpContext.Expect( c => c.Request ).Return( httpRequest ).Repeat.Any();
... set up expectations on request...
var controller = new MyController();
controller.ControllerContext = new ControllerContext( httpContext,
new RouteData(),
controller );
...invoke action, check assertions...
httpContext.VerifyAllExpectations();
httpRequest.VerifyAllExpectations();
Using Moq:
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
request.Setup(x => x.ApplicationPath).Returns("/");
request.Setup(x => x.Url).Returns(new Uri("http://localhost/home"));
request.Setup(x => x.ServerVariables).Returns(new System.Collections.Specialized.NameValueCollection());
var context = new Mock<HttpContextBase>(MockBehavior.Strict);
context.SetupGet(x => x.Request).Returns(request.Object);
var controller = new YourController();
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
Manually (because I hate Mocking frameworks that require 8 lines of setup per test)
// in some common location, only once
public class MockHttpContext : HttpContextBase
{
public MockHttpRequest m_request = new MockHttpRequest();
public MockHttpResponse m_response = new MockHttpResponse();
public override HttpRequestBase Request
{ get { return m_request; } }
public override HttpResponseBase Response
{ get { return m_response; } }
}
public class MockHttpRequest : HttpRequestBase
{
// override whatever bits you want (eg cookies)
}
public class MockHttpResponse : HttpResponseBase
{
// override whatever bits you want (eg cookies)
}
// in your specific test
controller = new YourController {
ControllerContext = new ControllerContext { HttpContext = new MockHttpContext() }
};