Where Are the Controllercontext and Viewengines Properties in MVC 6 Controller

Where are the ControllerContext and ViewEngines properties in MVC 6 Controller?

Update: I'm updating this to work with .Net Core 2.x as the APIs have changed since 2015!

First of all we can leverage the built in dependency injection that comes with ASP.Net MVC Core which will give us the ICompositeViewEngine object we need to render our views manually. So for example, a controller would look like this:

public class MyController : Controller
{
private ICompositeViewEngine _viewEngine;

public MyController(ICompositeViewEngine viewEngine)
{
_viewEngine = viewEngine;
}

//Rest of the controller code here
}

Next, the code we actually need to render a view. Note that is is now an async method as we will be making asynchronous calls internally:

private async Task<string> RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.ActionDescriptor.ActionName;

ViewData.Model = model;

using (var writer = new StringWriter())
{
ViewEngineResult viewResult =
_viewEngine.FindView(ControllerContext, viewName, false);

ViewContext viewContext = new ViewContext(
ControllerContext,
viewResult.View,
ViewData,
TempData,
writer,
new HtmlHelperOptions()
);

await viewResult.View.RenderAsync(viewContext);

return writer.GetStringBuilder().ToString();
}
}

And to call the method, it's as simple as this:

public async Task<IActionResult> Index()
{
var model = new TestModel
{
SomeProperty = "whatever"
}

var renderedView = await RenderPartialViewToString("NameOfView", model);

//Do what you want with the renderedView here

return View();
}

Required dependencies for Resolver or ServiceProvider for using ICompositeViewEngine

You're mostly on the right track. ASP.NET Core got rid of many static objects so you can't do things like Resolver.GetService. Resolver doesn't exist. Instead, use the dependency injection system.

If you just need to access ICompositeViewEngine from a controller, inject it in the constructor:

public MyController(ICompositeViewEngine viewEngine)
{
// save a reference to viewEngine
}

If you want to have a discrete service that handles Razor-to-string rendering, you'll need to register it at startup:

public void ConfigureServices(IServiceCollection services)
{
// (Other code...)

services.AddTransient<IViewRenderingService, ViewRenderingService>();

services.AddMvc();
}

The service itself would look like this:

using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

public interface IViewRenderingService
{
string RenderPartialView(ActionContext context, string name, object model = null);
}

public class ViewRenderingService : IViewRenderingService
{
private readonly ICompositeViewEngine _viewEngine;
private readonly ITempDataProvider _tempDataProvider;

public ViewRenderingService(ICompositeViewEngine viewEngine, ITempDataProvider tempDataProvider)
{
_viewEngine = viewEngine;
_tempDataProvider = tempDataProvider;
}

public string RenderPartialView(ActionContext context, string name, object model)
{
var viewEngineResult = _viewEngine.FindView(context, name, false);

if (!viewEngineResult.Success)
{
throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", name));
}

var view = viewEngineResult.View;

using (var output = new StringWriter())
{
var viewContext = new ViewContext(
context,
view,
new ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary())
{
Model = model
},
new TempDataDictionary(
context.HttpContext,
_tempDataProvider),
output,
new HtmlHelperOptions());

view.RenderAsync(viewContext).GetAwaiter().GetResult();

return output.ToString();
}
}
}

To use it from a controller, inject and call it:

public class HomeController : Controller
{
private readonly IViewRenderingService _viewRenderingService;

public HomeController(IViewRenderingService viewRenderingService)
{
_viewRenderingService = viewRenderingService;
}

public IActionResult Index()
{
var result = _viewRenderingService.RenderPartialView(ControllerContext, "PartialViewName", model: null);
// do something with the string

return View();
}
}

If you want to use Razor outside of MVC entirely, see this answer.

Return View as String in .NET Core

Thanks to Paris Polyzos and his article.

I'm re-posting his code here, just in case the original post got removed for any reason.

Create Service in file viewToString.cs as below code:

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
 
namespace WebApplication.Services
{
    public interface IViewRenderService
    {
        Task<string> RenderToStringAsync(string viewName, object model);
    }
 
    public class ViewRenderService : IViewRenderService
    {
        private readonly IRazorViewEngine _razorViewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;
 
        public ViewRenderService(IRazorViewEngine razorViewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider)
        {
            _razorViewEngine = razorViewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
        }
 
        public async Task<string> RenderToStringAsync(string viewName, object model)
        {
            var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
 
            using (var sw = new StringWriter())
            {
                var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
 
                if (viewResult.View == null)
                {
                    throw new ArgumentNullException($"{viewName} does not match any available view");
                }
 
                var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                };
 
                var viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    sw,
                    new HtmlHelperOptions()
                );
 
                await viewResult.View.RenderAsync(viewContext);
                return sw.ToString();
            }
        }
    }
}

2. Add the service to the Startup.cs file, as:

using WebApplication.Services;

public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IViewRenderService, ViewRenderService>();
}

3. Add "preserveCompilationContext": true to the buildOptions in the project.json, so the file looks like:

{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"dependencies": {
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.AspNetCore.Mvc": "1.0.1"
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
}
},
"imports": "dnxcore50"
}
}
}

4. Define you model, for example:

public class InviteViewModel {
public string UserId {get; set;}
public string UserName {get; set;}
public string ReferralCode {get; set;}
public int Credits {get; set;}
}

5. Create your Invite.cshtml for example:

@{
ViewData["Title"] = "Contact";
}
@ViewData["Title"].
user id: @Model.UserId

6. In the Controller:

a. Define the below at the beginning:

private readonly IViewRenderService _viewRenderService;

public RenderController(IViewRenderService viewRenderService)
{
_viewRenderService = viewRenderService;
}

b. Call and return the view with model as below:

var result = await _viewRenderService.RenderToStringAsync("Email/Invite", viewModel);
return Content(result);

c. The FULL controller example, could be like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using WebApplication.Services;

namespace WebApplication.Controllers
{
[Route("render")]
public class RenderController : Controller
{
private readonly IViewRenderService _viewRenderService;

public RenderController(IViewRenderService viewRenderService)
{
_viewRenderService = viewRenderService;
}

[Route("invite")]
public async Task<IActionResult> RenderInviteView()
{
ViewData["Message"] = "Your application description page.";
var viewModel = new InviteViewModel
{
UserId = "cdb86aea-e3d6-4fdd-9b7f-55e12b710f78",
UserName = "Hasan",
ReferralCode = "55e12b710f78",
Credits = 10
};
 
var result = await _viewRenderService.RenderToStringAsync("Email/Invite", viewModel);
return Content(result);
}

public class InviteViewModel {
public string UserId {get; set;}
public string UserName {get; set;}
public string ReferralCode {get; set;}
public int Credits {get; set;}
}
}

Is is possible to specify searchable location formats for an MVC Razor Layout?

Recently I was struggling on a similar issue where, in a themed application that uses a custom ViewEngine to search theme's location first for views, I was trying to override some master files. One way to force the location of the layout to go through the ViewEngine's FindView is to specify the name of the master view in a controller action when returning:

return View("myView", "myViewMaster");

However, if "myViewMaster" also has a layout (nested layouts), this method doesn't work for the nested layout. My best solution so far is to call the view engine directly in the view:

@{
Layout = (ViewEngines.Engines.FindView(this.ViewContext.Controller.ControllerContext, "myViewMaster", "").View as RazorView).ViewPath;
}

This works but could certainly be encapsulated in a extension method to avoid repetition and abstract the code.

Hope it helps!

Render a Razor view to string = ControllerContext is null when called from recurrent task

Instead of trying to simulate a web hit, you can actually trigger a web hit to allow you to render the view with proper context. Take the result and store it into the body of your email. I had to do something similar for insurance quotes. Here is my code, followed by an adaptation for your needs.

    public ActionResult StartInsuranceQuote()
{

using (var client = new WebClient())
{
var values = new NameValueCollection
{
{ "sid", DataSession.Id.ExtractSid() }
};
client.UploadValuesAsync(new Uri(Url.AbsoluteAction("QuoteCallback", "Quote")), values);
}
return PartialView();
}

The key to this would be populating the values collection from your model. Since you didn't provide that I'll assume some of the properties for illustration:

    public void SendEmail(YourViewModel model)
{
using (var client = new WebClient())
{
var values = new NameValueCollection
{
{ "Name", model.Name },
{ "Product", model.Product },
{ "Color", model.Color },
{ "Comment", model.Comment }
};
string body = client.UploadValues(new Uri(Url.AbsoluteAction("GenerateBody", "RenderEmail")), values);

// send email here
}
}

RenderEmailController:

    public ActionResult GenerateBody()
{
return View();
}

GenerateBody.cshtml:

@foreach (string key in Request.Form.AllKeys)
{
Response.Write(key + "=" + Request[key] + "<br />");
}

UPDATED: AbsoluteAction is an extension method, included below

public static string AbsoluteAction(this UrlHelper url, string actionName, string controllerName, object routeValues = null)
{
if (url.RequestContext.HttpContext.Request.Url != null)
{
string scheme = url.RequestContext.HttpContext.Request.Url.Scheme;
return url.Action(actionName, controllerName, routeValues, scheme);
}
throw new Exception("Absolute Action: Url is null");
}

Render Razor View to string in ASP.NET Core

UPDATE July, 2016

Working fine on the following versions 1.0.0, RC2


Who's targeting aspnetcore RC2, this snippet might help you:

  • Create a separate Service, so you can use it either if you are not in a controller context, e.g. from a command line or on a queue runner, etc ...
  • Register this service in your IoC container in the Startup class

https://gist.github.com/ahmad-moussawi/1643d703c11699a6a4046e57247b4d09

Usage

// using a Model
string html = view.Render("Emails/Test", new Product("Apple"));

// using a Dictionary<string, object>
var viewData = new Dictionary<string, object>();
viewData["Name"] = "123456";

string html = view.Render("Emails/Test", viewData);

Notes

Links in Razor are rendered as relative URL, so this will not work on external views (like emails, etc ...).

As for now am generating the link on the controller and pass it to the view through the ViewModel.

Credit

The source is extracted from (Thanks To @pholly): https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs)

ControllerContext and ViewData Outside Scope of Controller - MVC3 C#

I think you are on the right track, but the problem is your eagerness to complete separation, it is rather too eager.

You are using Razor view engine to render your rich text HTML email. A very noble approach. However, this means that you will be very close to your presentation layer and running this from outside a controller - in my view - does not make a lot of sense.

I believe you need to make (if not already made):

  • Your email Razor view as strongly typed
  • Let the rendering called in the controller as usual
  • Rendering would be as simple as passing the model to the Render method
  • Take out the building of your email model out to the helper you wish. This would not require any presentation layer logic and as such oblivious to it.

So the point is, calling of the rendering does not need to go out of the controller, building of the email model should.

Now if you are doing all of that it means I have not understood your question and requires more explanation.



Related Topics



Leave a reply



Submit