5

I am trying to use ICompositeViewEngine in ASP.NET Core MVC for substituting ViewEngine from System.Web.Mvc since it is no longer available in .NET Core. I am generally trying to migrate a webform from ASP.NET to ASP.NET Core in this project.

I have found the following solution: Where are the ControllerContext and ViewEngines properties in MVC 6 Controller? and I believe that this may resolve my issue. I have also found a similar engine creation with ServiceProvider in a github question: https://github.com/aspnet/Mvc/issues/3091

However, I am not sure about what dependencies or frameworks I may be missing as I am very new with .NET. I have the following namespaces:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;

That I believe might be related to my issue.

My original code is:

    public static string RenderPartialToString(Controller controller, string viewName, object model)
    {
        controller.ViewData.Model = model;

        using (StringWriter sw = new StringWriter())
        {
            ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
            ViewContext viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
            viewResult.View.Render(viewContext, sw);

            return "document.write('" + sw.GetStringBuilder().Replace('\n', ' ').Replace('\r', ' ').Replace("'","\\'").ToString() + "');";
        }
    }

And now I am trying to use either of the following:

 var engine = Resolver.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
 var engine2 = IServiceProvider.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;

Am I on the right track to fix this? Is there an easier way to replace System.Web.Mvc ViewEngines in .NET Core? What do I need to fix "does not exist in the current context" errors for Resolver and/or ServiceProvider?

Thanks. I hope I was able to follow the question guidelines.

Edit: Please let me know if I should include anything else from my code for this question. I am currently reading about Dependency Injection to understand the situation better.

Community
  • 1
  • 1
Kemal Tezer Dilsiz
  • 3,739
  • 5
  • 24
  • 43

1 Answers1

7

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.

Community
  • 1
  • 1
Nate Barbettini
  • 51,256
  • 26
  • 134
  • 147
  • I'm really grateful for your answer but what references/namespaces should I use for these classes/methods? – Kemal Tezer Dilsiz Aug 04 '16 at 20:36
  • @KemalTezerDilsiz Added the namespaces to the example service code. Does that help? – Nate Barbettini Aug 04 '16 at 20:50
  • The issue is that I am using ViewEngine in a public static class. It is called Utils and has two methods, RenderPartialToString being one the one that I am trying to modify. If it wasn't a static constructor, I am sure this would work. Any suggestions? Should I try to restructure my project? – Kemal Tezer Dilsiz Aug 05 '16 at 18:21
  • @KemalTezerDilsiz Yes, I think you should restructure that part of your project. Having a static class referenced by your web code is considered an antipattern for ASP.NET Core. Where do you call RenderPartialToString? From controllers? – Nate Barbettini Aug 05 '16 at 19:59
  • Yes, I call the RenderPartialToString method from the controllers, could I possibly implement the IViewEngineInterface to my static class for direct reference? (Example use: `return Utils.RenderPartialToString(this, "ContactUsForm", new ContactUs());`) (this being the controller and ContactUs is a Model that stores some values from the generated table) – Kemal Tezer Dilsiz Aug 08 '16 at 13:43
  • 1
    You might be able to, but it's a much better approach to get rid of the static class entirely if you can. You can achieve the same functionality using a service class (like the one I demonstrated above), where you inject `IViewRenderingService` into the controller and then call it like `return _viewRenderingService.RenderPartialToString(...)` – Nate Barbettini Aug 08 '16 at 17:54
  • I am getting an error, "An exception of type 'System.InvalidOperationException' occurred in System.Private.CoreLib.ni.dll but was not handled in user code // Additional information: The ViewData item that has the key 'InvestorTypeId' is of type 'System.Int32' but must be of type 'IEnumerable'." in the `view.RenderAsync(viewContext).GetAwaiter().GetResult();` part – Kemal Tezer Dilsiz Aug 17 '16 at 15:51
  • I found the part where it creates a problem: I'll make a new question for it and share the link here in case anybody has the same problem – Kemal Tezer Dilsiz Aug 17 '16 at 17:36
  • Strangely this does not work with Razor Pages - the Model is null in the View. I think the processing in Razor Pages is different than in standard MVC – Sven Jun 15 '18 at 12:38