MVC Render View to String - c#

I have found a few other questions like this, however every single one has not been able to fix this problem I am having. I continuously get the same problem.
I have checked out this and that and multiple others! Every single thing I get the same problem.
I get the following exception:
The view at '~/Views/Email/Ticket.cshtml' must derive from WebViewPage, or WebViewPage<TModel>.
The code I am using is:
public static string RenderViewToString(string controllerName, string viewName, object viewModel)
{
HttpContextWrapper context = new HttpContextWrapper(HttpContext.Current);
RouteData routeData = new System.Web.Routing.RouteData();
routeData.Values.Add("controller", controllerName);
ControllerContext controllerContext = new ControllerContext(context, routeData, new RenderController());
RazorViewEngine razorViewEngine = new RazorViewEngine();
ViewEngineResult razorViewResult = razorViewEngine.FindView(controllerContext, viewName, string.Empty, false);
using (StringWriter writer = new StringWriter())
{
ViewDataDictionary viewData = new ViewDataDictionary(viewModel);
var viewContext = new ViewContext(controllerContext, razorViewResult.View, viewData, new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
writer.Flush();
return writer.GetStringBuilder().ToString();
}
}
The RenderController is just:
private class RenderController : ControllerBase
{
protected override void ExecuteCore()
{
}
}
No matter what I have done, or different methods I have tried nothing seems to make this error go away, I have tried making the view inherit the objects but you can't have #model and #inherit in the same thing. Getting quite aggravated over this as I have followed pretty much everyone's instructions on other questions/posts but hasn't been able to help me.

Thanks to #JustinRusso he helped figure this out!
What seemed to happen was, my refernce to MVC 5.0.0.0 in my actual web project was different to my MVC version that I was referencing in my Common library which was at 4.0.0.1.
After I updated the version it worked. I feel stupid now looking back on it but that is what learning is all about!
Thanks to all for the help and suggestions!
I don't think you can mark comments as answers (at least from what I can tell) so just making it a bigger item so future people can find it.

Related

RenderViewToString conflict

In MVC Project I dynamically render a view to string using the following code.
public string RenderViewToString(string viewName, object model, RequestContext requestContext){
ViewData.Model = model;
var cContext = ControllerContext;
if (cContext == null)
{
var ctrlFactory = ControllerBuilder.Current.GetControllerFactory();
var ctrl = ctrlFactory.CreateController(requestContext, "Documents") as Controller;
var newCContext = new ControllerContext(requestContext, ctrl);
cContext = newCContext;
var ctrlDesc = new ReflectedControllerDescriptor(ctrl.GetType());
}
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(cContext, viewName);
var viewContext = new ViewContext(cContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(cContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
This works fine until multiple users run the code at the same time. It then gets each users' view data mixed up.
Any suggestions why? Thanks
I had the same problem with similar code. I spend a couple of days figuring this one out.
After reading loads around this, containing suggestions like 'write your own viewRenderer' and so on, it all seemed to complex to solve this problem.
After diving into the .net code itself, I found a dirty trick that that fixes this
Add a couple of lines to your ConfigureServices (startup):
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddTransient<IViewRenderer, ViewRenderer>();
// reason for this registration https://github.com/aspnet/Mvc/issues/5106 . A scoped buffer causes overflow.
var memoryPoolViewBufferScopeType = Type.GetType("Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers.MemoryPoolViewBufferScope, Microsoft.AspNetCore.Mvc.ViewFeatures");
if (memoryPoolViewBufferScopeType != null)
{
services.AddTransient(typeof(IViewBufferScope), memoryPoolViewBufferScopeType);
}
It makes the IViewRenderer and IViewBufferScope transient, which solves the problem.
The reason the IViewBufferScope is added this way, is because it's an internal, so some trickery is needed to get to it.

How do I instantiate CachedDataAnnotationsModelMetadata?

I'm trying to test a custom model binder. As part of that process I am creating a new ModelBindingContext to pass into the BindModel method of the custom binder. The problem is that I need to set the MetaData property of the ModelBindingContext as a CachedDataAnnotationsModelMetadata object, but honestly am not sure how to instantiate the object.
The signature for the CachedDataAnnotationsModelMetadata object is:
public CachedDataAnnotationsModelMetadata(
CachedDataAnnotationsModelMetadata prototype,
Func<object> modelAccessor
)
Does anyone have an example of what the prototype and modelAccessor parameters are supposed to be?
Here's a snippet of the non-functional code.
// Assemble
var formCollection = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string> ("SomeRequiredProperty", "SomeValue")
};
var valueProvider = new System.Web.Http.ValueProviders.Providers.NameValuePairsValueProvider(formCollection, null);
var metadata = new System.Web.Http.Metadata.Providers.CachedDataAnnotationsModelMetadata(????, ????)
var bindingContext = new ModelBindingContext {
ModelName = "ClaimsModelBinderInputModel",
ValueProvider = valueProvider,
ModelMetadata = metadata
};
var actionContext = new HttpActionContext();
var httpControllerContext = new HttpControllerContext();
httpControllerContext.Request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/someUri");
actionContext.ControllerContext = httpControllerContext;
var cmb = new ClaimsModelBinder();
// Act
cmb.BindModel(actionContext, bindingContext);
// Assert...
I've found a few examples around the net and SO of people interacting with this class, but no concrete example of implementing it.
Thanks in advance!
Update
Figured out ModelAccessor, it's just used to delay accessing the actual model until the model property is accessed. https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http/Metadata/ModelMetadata.cs#L103
I'm still working on supplying a prototype object if anyone can help with that.
So I ended up sidestepping this issue entirely.
After inspecting the inheritance model, I realized that in this case I can get away with using the DataAnnotationsModelMetadataProvider rather than the CachedDataAnnotationsModelMetadata. The latter of these doesn't require the parameters necessary to setup caching, and since I don't need caching to test what I am testing, the standard DataAnnotationsModelMetadataProvider will suffice.
I was able to identify that I could take a different approach after reading through the source code for the metadata namespace.
https://github.com/ASP-NET-MVC/aspnetwebstack/tree/master/src/System.Web.Http/Metadata
That said, if anyone else can provide some clues to my original question, I would prefer to use the cached model object if possible.

Custom WebViewPage inject code when razor template is rendering

I'm trying to create a custom Razor view base class (inheriting WebViewPage) that will inject a bit of HTML for each view template being rendered (including Layouts and Partial Views) so that I have a reference on the client of where each Razor template starts (not interested in where it ends).
What I have tried so far is
overriding the Write method (as described in a comment here) . This injects code at every razor section, not just once per template (for example every time that you use the HTML.TextBoxFor)
overriding the ExecutePageHierarchy method (as described in the post of the link above). This throws an error every time it hits the first PopContext call: The "RenderBody" method has not been called for layout page "~/Views/Shared/_Layout.cshtml".
after trying your solution, I had some problems with the rendered HTML of complex pages with partial views.
my issue was that everything was reversed. (order of partial views)
to correct - I ended up replacing the Output stream in the OutputStack
public override void ExecutePageHierarchy()
{
// Replace output stream with a fake local stream
StringWriter fakeOutput = new StringWriter();
// Save output stack top level stream, and replace with fake local stream
TextWriter outputStackTopOutput = OutputStack.Pop();
OutputStack.Push(fakeOutput);
// Run Razor view engine
base.ExecutePageHierarchy();
string content = fakeOutput.ToString();
// Set back real outputs, and write to the real output
OutputStack.Pop();
OutputStack.Push(outputStackTopOutput);
outputStackTopOutput.Write(content);
}
Think that I have an answer to this now:
public abstract class CustomWebViewPage: WebViewPage
{
public override void ExecutePageHierarchy()
{
var layoutReferenceMarkup = #"<script type=""text/html"" data-layout-id=""" + TemplateInfo.VirtualPath + #"""></script>";
base.ExecutePageHierarchy();
string output = Output.ToString();
//if the body tag is present the script tag should be injected into it, otherwise simply append
if (output.Contains("</body>"))
{
Response.Clear();
Response.Write(output.Replace("</body>", layoutReferenceMarkup+"</body>"));
Response.End();
}
else
{
Output.Write(layoutReferenceMarkup);
}
}
}
public abstract class CustomWebViewPage<TModel>: CustomWebViewPage
{
}
Seems to work, but if anyone has a better solution, please share.

how do I test views using razor syntax in mvc3?

I am writing code to test an C# MVC3 application. I can test the controllers but how do I test the code in the views? This includes javascript and razor styled code.
Are there any tools available which can mock out views or test views and javascript in C# in them?
The following is about testing the rendered output of the view. This textual output can for instance be loaded into a DOM for further analysis with XPath (using XmlReader for XHTML or HtmlAgilityPack for SGML-style HTML). With some nice helper methods this allows easy check for checking specific parts of the view, such as testing //a[#href='#'] or anything else which you want to test. This helps making the unit tests more stable.
One would expect that this is easy when using Razor instead of the "blown-up" WebForms engine, but it turned out to be quite the contrary, due to many inner workings of the Razor view engine and views using parts (HtmlHelper especially) of the HTTP request lifecycle. In fact, properly testing the generated output requires a lot of running code to get reliable and appropriate results, even more so if you use exotic stuff such as portable areas (from the MVCContrib project) and the like in the mix.
The HTML helpers for actions and URLs require that the routing is properly initialized, the route dictionary is properly set up, the controller must also exist, and there are other "gotchas" related to loading data for the view, such as setting up the view data dictionary...
We ended up creating a ViewRenderer class which will actually instantiate an application host on the physical path of your web to be tested (can be statically cached, re-initialization for each single test is impractical due to the initialization lag):
host = (ApplicationHost)System.Web.Hosting.ApplicationHost.CreateApplicationHost(typeof(ApplicationHost), "/", physicalDir.FullName);
The ApplicationHost class in turn inherits from MarshalByRefObject since the host will be loaded in a separate application domain. The host performs all sorts of unholy initialization stuff in order to properly initialize the HttpApplication (the code in global.asax.cs which registers routes etc.) while disabling some aspects (such as authentication and authorization). Be warned, serious hacking ahead. Use at your own risk.
public ApplicationHost() {
ApplicationMode.UnitTesting = true; // set a flag on a global helper class to indicate what mode we're running in; this flag can be evaluated in the global.asax.cs code to skip code which shall not run when unit testing
// first we need to tweak the configuration to successfully perform requests and initialization later
AuthenticationSection authenticationSection = (AuthenticationSection)WebConfigurationManager.GetSection("system.web/authentication");
ClearReadOnly(authenticationSection);
authenticationSection.Mode = AuthenticationMode.None;
AuthorizationSection authorizationSection = (AuthorizationSection)WebConfigurationManager.GetSection("system.web/authorization");
ClearReadOnly(authorizationSection);
AuthorizationRuleCollection authorizationRules = authorizationSection.Rules;
ClearReadOnly(authorizationRules);
authorizationRules.Clear();
AuthorizationRule rule = new AuthorizationRule(AuthorizationRuleAction.Allow);
rule.Users.Add("*");
authorizationRules.Add(rule);
// now we execute a bogus request to fully initialize the application
ApplicationCatcher catcher = new ApplicationCatcher();
HttpRuntime.ProcessRequest(new SimpleWorkerRequest("/404.axd", "", catcher));
if (catcher.ApplicationInstance == null) {
throw new InvalidOperationException("Initialization failed, could not get application type");
}
applicationType = catcher.ApplicationInstance.GetType().BaseType;
}
The ClearReadOnly method uses reflection to make the in-memory web configuration mutable:
private static void ClearReadOnly(ConfigurationElement element) {
for (Type type = element.GetType(); type != null; type = type.BaseType) {
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.DeclaredOnly).Where(f => typeof(bool).IsAssignableFrom(f.FieldType) && f.Name.EndsWith("ReadOnly", StringComparison.OrdinalIgnoreCase))) {
field.SetValue(element, false);
}
}
}
The ApplicationCatcher is a "null" TextWriter which stores the application instance. I couldn't find another way to initialize the application instance and get it. The core of it is pretty simple.
public override void Close() {
Flush();
}
public override void Flush() {
if ((applicationInstance == null) && (HttpContext.Current != null)) {
applicationInstance = HttpContext.Current.ApplicationInstance;
}
}
This now enables us to render out almost any (Razor) view as if it were hosted in a real web server, creating almost a full HTTP lifecycle for rendering it:
private static readonly Regex rxControllerParser = new Regex(#"^(?<areans>.+?)\.Controllers\.(?<controller>[^\.]+)Controller$", RegexOptions.CultureInvariant|RegexOptions.IgnorePatternWhitespace|RegexOptions.ExplicitCapture);
public string RenderViewToString<TController, TModel>(string viewName, bool partial, Dictionary<string, object> viewData, TModel model) where TController: ControllerBase {
if (viewName == null) {
throw new ArgumentNullException("viewName");
}
using (StringWriter sw = new StringWriter()) {
SimpleWorkerRequest workerRequest = new SimpleWorkerRequest("/", "", sw);
HttpContextBase httpContext = new HttpContextWrapper(HttpContext.Current = new HttpContext(workerRequest));
RouteData routeData = new RouteData();
Match match = rxControllerParser.Match(typeof(TController).FullName);
if (!match.Success) {
throw new InvalidOperationException(string.Format("The controller {0} doesn't follow the common name pattern", typeof(TController).FullName));
}
string areaName;
if (TryResolveAreaNameByNamespace<TController>(match.Groups["areans"].Value, out areaName)) {
routeData.DataTokens.Add("area", areaName);
}
routeData.Values.Add("controller", match.Groups["controller"].Value);
ControllerContext controllerContext = new ControllerContext(httpContext, routeData, (ControllerBase)FormatterServices.GetUninitializedObject(typeof(TController)));
ViewEngineResult engineResult = partial ? ViewEngines.Engines.FindPartialView(controllerContext, viewName) : ViewEngines.Engines.FindView(controllerContext, viewName, null);
if (engineResult.View == null) {
throw new FileNotFoundException(string.Format("The view '{0}' was not found", viewName));
}
ViewDataDictionary<TModel> viewDataDictionary = new ViewDataDictionary<TModel>(model);
if (viewData != null) {
foreach (KeyValuePair<string, object> pair in viewData) {
viewDataDictionary.Add(pair.Key, pair.Value);
}
}
ViewContext viewContext = new ViewContext(controllerContext, engineResult.View, viewDataDictionary, new TempDataDictionary(), sw);
engineResult.View.Render(viewContext, sw);
return sw.ToString();
}
}
Maybe this helps you to get to some results. In general many people say that the hassle of testing views is not worth the effort. I'll let you be the judge of that.
Check out vantheshark's article, it describes how to mock the ASP.NET MVC View engine using NSubstitute.

Spark Globalization with ASP.NET MVC

I am using the spark viewengine, asp.net mvc, and .resx files.
I want to set a language through my custom SessionModel (Session) which is registered through Castle.Windsor and has a string property of Culture which can be set by the user...
I need the current language to persist on every view, without having to constantly set the current UICulture.
Not having to do this everytime in each Controller Action:
public SessionModel SessionModel { get; set; }
public ActionResult Index()
{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(SessionModel.Culture);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}
The problem with doing it this way, is if I go onto another page the current culture will flip back to the default language.
On the spark view I simply call, to obtain the current Culture:
${SR.Home}
SR.resx contains a public entry for Home.
Does anyone have a good idea of how to do this, should I do this with an ActionFilter?
Action filter seems like a good idea:
public class SetCultureActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CultureInfo culture = FetchCultureFromContext(filterContext);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
base.OnActionExecuting(filterContext);
}
private CultureInfo FetchCultureFromContext(ActionExecutingContext filterContext)
{
throw new NotImplementedException();
}
}
and then decorate your base controller with this attribute.
I guess I am missing something. This is what I am trying to avoid (Application.cs) inside of the spark samples, I already have a custom session, it don't need to manage another, just for "culture":
the sample code:
public static string GetSessionCulture(ControllerContext controllerContext)
{
return Convert.ToString(controllerContext.HttpContext.Session["culture"]);
}
public static void SetSessionCulture(ControllerContext controllerContext, string culture)
{
controllerContext.HttpContext.Session["culture"] = culture;
}
Also after its done, how do I load the current culture for every page I have, I would have to make the call inside of HomeController Index to pull the current culture out of the session:
public object Index()
{
var user = new UserInfo
{
Name = "Frankie",
Culture = Application.GetSessionCulture(ControllerContext)
};
try
{
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(user.Culture);
}
catch
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB");
}
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
ViewData["user"] = user;
return View();
}
Which the only way I can come up with right now is by either creating a custom ActionFilter or creating a base Controller.
There is a much better and scalable (from a flexibility point of view) solution to this problem which has been solved if you're using the Spark View Engine.
Have a look at the sample solution here in the code base for an excellent example of how to do Internationalization or Globalization with Spark. There is absolutely no need to start doing fancy things with your ActionFilters - and this method works in MVC2 and MVC3 to allow for an easy migration if that's your future plan.
Update
In response to your other question regarding the sample in the Spark code base I think there are a few ways of skinning that cat. One way would be to keep the culture as part of the session, but in past projects where we didn't want that, we did what many website already do - we included the culture as a parameter in the route data by making sure it is included in the URL:
routes.Add(new Route("{culture}/{controller}/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(
new { culture="en-us" action="Index", id="" }),
});
This kind of thing in combination with the Spark module I mentioned above gives you the freedom to just spend time focussing on your .resx files instead of all your time figuring out the culture and routing manually.
The fact that you have an Index.spark and an Index.fr.spark in the same views folder means the rest gets taken care of for you.
Hope that helps!
All the best,
Rob

Categories

Resources