web-dev-qa-db-fra.com

Rendre Razor View en chaîne dans ASP.NET Core

J'utilise RazorEngine pour l'analyse de modèles dans mon projet MVC 6 comme ceci:

Engine.Razor.RunCompile(File.ReadAllText(fullTemplateFilePath), templateName, null, model);

Cela fonctionne très bien pour la version 6. Cela ne fonctionne pas après la mise à niveau vers la version 7 avec l’erreur suivante:

MissingMethodException: Méthode introuvable: "Annuler Microsoft.AspNet.Razor.CodeGenerators.GeneratedClassContext.set_ResolveUrlMethodName (System.String)" . dans RazorEngine.Compilation.CompilerServiceBase.CreateHost (Type templateType, Type modelType, String className)

C'est global.json:

{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-beta7",
    "runtime": "clr",
    "architecture": "x64"
  }
}

C'est project.json:

...
"dependencies": {
    "EntityFramework.SqlServer": "7.0.0-beta7",
    "EntityFramework.Commands": "7.0.0-beta7",
    "Microsoft.AspNet.Mvc": "6.0.0-beta7",
    "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta7",
    "Microsoft.AspNet.Authentication.Cookies": "1.0.0-beta7",
    "Microsoft.AspNet.Authentication.Facebook": "1.0.0-beta7",
    "Microsoft.AspNet.Authentication.Google": "1.0.0-beta7",
    "Microsoft.AspNet.Authentication.MicrosoftAccount": "1.0.0-beta7",
    "Microsoft.AspNet.Authentication.Twitter": "1.0.0-beta7",
    "Microsoft.AspNet.Diagnostics": "1.0.0-beta7",
    "Microsoft.AspNet.Diagnostics.Entity": "7.0.0-beta7",
    "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-beta7",
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta7",
    "Microsoft.AspNet.Server.WebListener": "1.0.0-beta7",
    "Microsoft.AspNet.StaticFiles": "1.0.0-beta7",
    "Microsoft.AspNet.Tooling.Razor": "1.0.0-beta7",
    "Microsoft.Framework.Configuration.Abstractions": "1.0.0-beta7",
    "Microsoft.Framework.Configuration.Json": "1.0.0-beta7",
    "Microsoft.Framework.Configuration.UserSecrets": "1.0.0-beta7",
    "Microsoft.Framework.Logging": "1.0.0-beta7",
    "Microsoft.Framework.Logging.Console": "1.0.0-beta7",
    "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-beta7",
    "RazorEngine": "4.2.2-beta1"
  },
...
  "frameworks": {
    "dnx451": { }
  },
...

Mon modèle est:

@model dynamic
@{
    Layout = null;
}

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Registration</title>
</head>
<body>
<p>
    Hello, @Model
</p>
</body>
</html>

Quelqu'un a-t-il des problèmes similaires? Il existe un autre moyen d'analyser les modèles dans MVC 6?

23
hcp

MISE À JOUR Juillet 2016

Fonctionne bien sur les versions suivantes 1.0.0, RC2


À qui s'adresse aspnetcore RC2, cet extrait de code pourrait vous aider:

  • Créez un service distinct, afin que vous puissiez l'utiliser soit si vous ne vous trouvez pas dans un contexte de contrôleur, par exemple. depuis une ligne de commande ou sur un coureur de file d'attente, etc ...
  • Enregistrez ce service dans votre conteneur IoC dans la classe Startup 

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);

Remarques

Les liens dans Razor sont affichés sous la forme d'URL relative. Par conséquent, cela ne fonctionnera pas avec les vues externes (telles que les courriels, etc.).

Pour l'instant, je crée le lien sur le contrôleur et le passe à la vue via le ViewModel.

Crédit

La source est extraite de (Merci à @pholly): https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs )

20
amd

J'ai trouvé ce fil qui en parle: https://github.com/aspnet/Mvc/issues/3091

Une personne du fil a créé un exemple de service ici: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs

Après des essais et des erreurs, j'ai été en mesure de réduire le service. Il n'a donc besoin que d'une HttpContext valide et d'une ViewEngine et j'ai ajouté une surcharge ne nécessitant pas de modèle. Les vues sont relatives à la racine de votre application (elles ne doivent pas nécessairement résider dans un dossier Views). 

Vous devrez enregistrer le service dans Startup.cs et également enregistrer HttpContextAccessor:

//Startup.cs ConfigureServices()
services.AddTransient<ViewRenderService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.IO;

namespace LibraryApi.Services
{
    public class ViewRenderService
    {
        IRazorViewEngine _viewEngine;
        IHttpContextAccessor _httpContextAccessor;

        public ViewRenderService(IRazorViewEngine viewEngine, IHttpContextAccessor httpContextAccessor)
        {
            _viewEngine = viewEngine;
            _httpContextAccessor = httpContextAccessor;
        }

        public string Render(string viewPath)
        {
            return Render(viewPath, string.Empty);
        }

        public string Render<TModel>(string viewPath, TModel model)
        {
            var viewEngineResult = _viewEngine.GetView("~/", viewPath, false);

            if (!viewEngineResult.Success)
            {
                throw new InvalidOperationException($"Couldn't find view {viewPath}");
            }

            var view = viewEngineResult.View;

            using (var output = new StringWriter())
            {
                var viewContext = new ViewContext();
                viewContext.HttpContext = _httpContextAccessor.HttpContext;
                viewContext.ViewData = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                { Model = model };
                viewContext.Writer = output;

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

                return output.ToString();
            }
        }
    }
}

Exemple d'utilisation:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using LibraryApi.Services;
using System.Dynamic;

namespace LibraryApi.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        ILogger<ValuesController> _logger;
        ViewRenderService _viewRender;
        public ValuesController(ILogger<ValuesController> logger, ViewRenderService viewRender)
        {
            _logger = logger;
            _viewRender = viewRender;
        }

        // GET api/values
        [HttpGet]
        public string Get()
        {
            //ViewModel is of type dynamic - just for testing
            dynamic x = new ExpandoObject();
            x.Test = "Yes";
            var viewWithViewModel = _viewRender.Render("eNotify/Confirm.cshtml", x);
            var viewWithoutViewModel = _viewRender.Render("MyFeature/Test.cshtml");
            return viewWithViewModel + viewWithoutViewModel;
        }
    }
}
17
pholly

Auparavant, j’utilisais la RazorEngine dans une bibliothèque de classes car mon objectif était de restituer des modèles à partir de cette bibliothèque de classes. 

Si je comprends bien, vous semblez être à l’intérieur d’un projet MVC 6.0, pourquoi ne pas utiliser une méthode RenderPartialViewToString() sans devoir ajouter la dépendance à la RazorEngine

Gardez à l'esprit, je demande seulement parce que je suis curieux.

Par exemple, à partir de VS2015, j'ai créé une nouvelle application Web ASP.NET et sélectionné le modèle d'application Web dans les modèles de prévisualisation ASP.NET 5.

Dans le dossier ViewModels, j'ai créé une PersonViewModel:

public class PersonViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName
    {
        get
        {
            return string.Format("{0} {1}", this.FirstName, this.LastName);
        }
    } 
}

J'ai ensuite créé une nouvelle BaseController et ajouté une méthode RenderPartialViewToString():

public string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = ActionContext.ActionDescriptor.Name;

    ViewData.Model = model;

    using (StringWriter sw = new StringWriter())
    {
        var engine = Resolver.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
        ViewEngineResult viewResult = engine.FindPartialView(ActionContext, viewName);

        ViewContext viewContext = new ViewContext(ActionContext, viewResult.View, ViewData, TempData, sw,new HtmlHelperOptions());

        var t = viewResult.View.RenderAsync(viewContext);
        t.Wait();

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

Le mérite revient à @DavidG pour sa méthode

Dans le dossier Views-->Shared, j’ai créé un nouveau dossier Modèles dans lequel j’ai ajouté une simple vue RegistrationTemplate.cshtml View fortement typée à ma PersonViewModel, comme ceci:

@model MyWebProject.ViewModels.PersonViewModel
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Registration</title>
</head>
<body>
    <p>
        Hello, @Model.FullName
    </p>
</body>
</html>

La dernière étape consiste à faire hériter ma Controller de ma BaseController

public class MyController : BaseController

Et créer quelque chose comme:

public IActionResult Index()
{
    var model = new PersonViewModel();
    model.FirstName = "Frank";
    model.LastName = "Underwood";
    var emailbody = base.RenderPartialViewToString("Templates/RegistrationTemplate", model);

    return View();
}

Bien sûr, l’exemple ci-dessus est inutile car je ne fais rien avec la variable emailbody mais l’idée est de montrer comment elle est utilisée. 

À ce stade, j'aurais pu (par exemple) invoquer une EmailService et transmettre la emailbody:

_emailService.SendEmailAsync("[email protected]", "registration", emailbody);

Je ne suis pas sûr que ce soit une alternative appropriée à votre tâche actuelle.

14
Vlince

Aujourd'hui, j'ai fini avec ma bibliothèque qui peut résoudre votre problème. Vous pouvez l'utiliser en dehors d'ASP.NET car il n'y a pas de dépendances

Exemple:

string content = "Hello @Model.Name. Welcome to @Model.Title repository";

var model = new
{
  Name = "John Doe",
  Title = "RazorLight"
};

var engine = new RazorLightEngine();
string result = engine.ParseString(content, model);

//Output: Hello John Doe, Welcome to RazorLight repository

Plus: https://github.com/toddams/RazorLight

13
Toddams

Pour améliorer la réponse de @vlince (cela ne fonctionnait pas pour moi), voici ce que j'ai fait:

1- Créez un contrôleur de base que votre autre contrôleur héritera

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

namespace YourNameSpace
{
    public class BaseController : Controller
    {
        protected ICompositeViewEngine viewEngine;

        public BaseController(ICompositeViewEngine viewEngine)
        {
            this.viewEngine = viewEngine;
        }

        protected string RenderViewAsString(object model, string viewName = null)
        {
            viewName = viewName ?? ControllerContext.ActionDescriptor.ActionName;
            ViewData.Model = model;

            using (StringWriter sw = new StringWriter())
            {
                IView view = viewEngine.FindView(ControllerContext, viewName, true).View;
                ViewContext viewContext = new ViewContext(ControllerContext, view, ViewData, TempData, sw, new HtmlHelperOptions());

                view.RenderAsync(viewContext).Wait();

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

2- Hériter du contrôleur de base et appeler la méthode

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;

namespace YourNameSpace
{
    public class YourController : BaseController
    {
        public YourController(ICompositeViewEngine viewEngine) : base(viewEngine) { }

        public string Index(int? id)
        {
            var model = new MyModel { Name = "My Name" };

            return RenderViewAsString(model);
        }
    }
}
3
Gudradain

ResolveUrlMethodName a été supprimé. Par conséquent, dans votre CreateHostici vous essayez de définir une propriété qui n'existe pas :).

Nous avons décidé de déplacer le traitement ~/ du noyau Razor vers un TagHelper implémenté dans l’assemblage Microsoft.AspNet.Mvc.Razor. Voici le commit aux bits qui ont supprimé la méthode.

Espérons que cela aide.

3
N. Taylor Mullen

Méthode d'extension pour convertir des vues partielles en réponse en chaîne.

public static class PartialViewToString
{
    public static async Task<string> ToString(this PartialViewResult partialView, ActionContext actionContext)
    {
        using(var writer = new StringWriter())
        {
            var services = actionContext.HttpContext.RequestServices;
            var executor = services.GetRequiredService<PartialViewResultExecutor>();
            var view = executor.FindView(actionContext, partialView).View;
            var viewContext = new ViewContext(actionContext, view, partialView.ViewData, partialView.TempData, writer, new HtmlHelperOptions());
            await view.RenderAsync(viewContext);
            return writer.ToString();
        }
    }
}

Utilisation dans vos actions de contrôleur.

public async Task<IActionResult> Index()
{
    return await PartialView().ToString(ControllerContext)
}
1
Prashanth Babu T

Une solution alternative utilisant uniquement ASP.NET Core, aucune bibliothèque externe et aucune réflexion ne peut être trouvée ici: https://weblogs.asp.net/ricardoperes/getting-html-for-a-viewresult-in-asp- net-core . Il nécessite juste un ViewResult et un HttpContext.

0
Ricardo Peres