Вернуться в виде строки в.NET Core

Я нашел статью о том, как вернуть представление в строку в ASP.NET, но не мог скрывать ее, чтобы иметь возможность запускать ее с помощью.NET Core

public static string RenderViewToString(this Controller controller, string viewName, object model)
{
    var context = controller.ControllerContext;
    if (string.IsNullOrEmpty(viewName))
        viewName = context.RouteData.GetRequiredString("action");

    var viewData = new ViewDataDictionary(model);

    using (var sw = new StringWriter())
    {
        var viewResult = ViewEngines.Engines.FindPartialView(context, viewName);
        var viewContext = new ViewContext(context, viewResult.View, viewData, new TempDataDictionary(), sw);
        viewResult.View.Render(viewContext, sw);

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

который предположил, что можно вызвать из контроллера, используя:

var strView = this.RenderViewToString("YourViewName", yourModel);

Когда я пытаюсь запустить выше в.NET Core, я получаю много ошибок компиляции.

Я попытался преобразовать его для работы с.NET Core, но не смог, может кто-нибудь помочь с упоминанием требуемого using.. и необходимых "dependencies": { "Microsoft.AspNetCore.Mvc": "1.1.0",... }, который будет использоваться в project.json.

некоторые другие примеры кода здесь и здесь и здесь

ПРИМЕЧАНИЕ. Мне нужно решение, чтобы преобразовать представление в string в.NET Core, независимо от того, какой код был преобразован, или другим способом, который может это сделать.

Ответы

Ответ 1

Благодаря Парижскому Полизосу и его статье.

Я переписываю его код здесь, на всякий случай, если исходная почта была удалена по какой-либо причине.

Создайте Service в файле viewToString.cs как viewToString.cs ниже:

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();
            }
        }
    }
}
  1. Добавьте службу в файл Startup.cs, как:

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

Добавьте "preserveCompilationContext": true для buildOptions в project.json, поэтому файл выглядит так:

{
  "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"
    }
  }
}
  1. Определите model, например:

    public class InviteViewModel {
        public string   UserId {get; set;}
        public string   UserName {get; set;}
        public string   ReferralCode {get; set;}
        public int  Credits {get; set;}
    }
    
  2. Создайте свой Invite.cshtml например:

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

а. Определите ниже в начале:

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

б. Вызовите и верните представление с помощью модели, как показано ниже:

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

с. Пример FULL-контроллера может выглядеть так:

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

Ответ 2

Если мне нравится, у вас есть несколько контроллеров, которым это необходимо, например, на сайте для отчетности, это не идеальный вариант для повторения этого кода, и даже инъекция или вызов другой службы действительно не кажутся правильными.

Поэтому я сделал свою собственную версию выше со следующими отличиями:

  • модель сильной типизации
  • проверка ошибок при поиске
  • возможность визуализации представлений как частичных или страниц
  • asynchronus
  • реализован как расширение контроллера
  • нет необходимости в DI

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.AspNetCore.Mvc.ViewEngines;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using System.IO;
    using System.Threading.Tasks;
    
    namespace CC.Web.Helpers
    {
        public static class ControllerExtensions
        {
            public static async Task<string> RenderViewAsync<TModel>(this Controller controller, string viewName, TModel model, bool partial = false)
            {
                if (string.IsNullOrEmpty(viewName))
                {
                    viewName = controller.ControllerContext.ActionDescriptor.ActionName;
                }
    
                controller.ViewData.Model = model;
    
                using (var writer = new StringWriter())
                {
                    IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
                    ViewEngineResult viewResult = viewEngine.FindView(controller.ControllerContext, viewName, !partial);
    
                    if (viewResult.Success == false)
                    {
                        return $"A view with the name {viewName} could not be found";
                    }
    
                    ViewContext viewContext = new ViewContext(
                        controller.ControllerContext,
                        viewResult.View,
                        controller.ViewData,
                        controller.TempData,
                        writer,
                        new HtmlHelperOptions()
                    );
    
                    await viewResult.View.RenderAsync(viewContext);
    
                    return writer.GetStringBuilder().ToString();
                }
            }
        }
    }
    

Затем просто выполните с:

viewHtml = await this.RenderViewAsync("Report", model);

Или это для PartialView:

partialViewHtml = await this.RenderViewAsync("Report", model, true);

Ответ 3

Красный ответ дал мне 99% пути, но он не работает, если ваши взгляды находятся в неожиданном месте. Вот мое решение для этого.

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System.IO;
using System.Threading.Tasks;

namespace Example
{
    public static class ControllerExtensions
    {
        public static async Task<string> RenderViewAsync<TModel>(this Controller controller, string viewName, TModel model, bool isPartial = false)
        {
            if (string.IsNullOrEmpty(viewName))
            {
                viewName = controller.ControllerContext.ActionDescriptor.ActionName;
            }

            controller.ViewData.Model = model;

            using (var writer = new StringWriter())
            {
                IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
                ViewEngineResult viewResult = GetViewEngineResult(controller, viewName, isPartial, viewEngine);

                if (viewResult.Success == false)
                {
                    throw new System.Exception($"A view with the name {viewName} could not be found");
                }

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

                await viewResult.View.RenderAsync(viewContext);

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

        private static ViewEngineResult GetViewEngineResult(Controller controller, string viewName, bool isPartial, IViewEngine viewEngine)
        {
            if (viewName.StartsWith("~/"))
            {
                var hostingEnv = controller.HttpContext.RequestServices.GetService(typeof(IHostingEnvironment)) as IHostingEnvironment;
                return viewEngine.GetView(hostingEnv.WebRootPath, viewName, !isPartial);
            }
            else
            {
                return viewEngine.FindView(controller.ControllerContext, viewName, !isPartial);

            }
        }
    }
}

Это позволяет использовать его, как показано ниже:

var emailBody = await this.RenderViewAsync("~/My/Different/View.cshtml", myModel);

Ответ 4

Ответы выше хорошо, но нужно настроить, чтобы заставить работать теги (мы должны использовать контекст http). Также вам нужно будет явно задать макет в представлении, чтобы получить отображаемый макет.

public class ViewRenderService : IViewRenderService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;
    private readonly IHostingEnvironment _env;
    private readonly HttpContext _http;

    public ViewRenderService(IRazorViewEngine razorViewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider, IHostingEnvironment env, IHttpContextAccessor ctx)
    {
        _razorViewEngine = razorViewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; _env = env; _http = ctx.HttpContext;
    }

    public async Task<string> RenderToStringAsync(string viewName, object model)
    {
        var actionContext = new ActionContext(_http, new RouteData(), new ActionDescriptor());

        using (var sw = new StringWriter())
        {
            var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
            //var viewResult = _razorViewEngine.GetView(_env.WebRootPath, viewName, false); // For views outside the usual Views folder
            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(_http, _tempDataProvider), sw, new HtmlHelperOptions());
            viewContext.RouteData = _http.GetRouteData();
            await viewResult.View.RenderAsync(viewContext);
            return sw.ToString();
        }
    }
}

Ответ 5

Я попробовал решение, которое ответил @Hasan A Yousef в Dotnet Core 2.1, но csthml не работает хорошо для меня. Он всегда генерирует исключение NullReferenceException, см. Скриншот. enter image description here

Чтобы решить эту проблему, я присвою Html.ViewData.Model новый объект. Вот мой код.

@page
@model InviteViewModel 
@{
    var inviteViewModel = Html.ViewData.Model;
}

<p>
    <strong>User Id:</strong> <code>@inviteViewModel.UserId </code>
</p>

Ответ 6

В приведенной ниже ссылке рассматривается практически та же проблема:

Где находятся свойства ControllerContext и ViewEngines в контроллере MVC 6?

В ответе Hasan A Yousef мне пришлось сделать то же самое, что и в ссылке выше, чтобы заставить меня работать:

using Microsoft.AspNetCore.Hosting;
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;
using System;
using System.IO;
using System.Threading.Tasks;

public class ViewRenderService : IViewRenderService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;
    private readonly IHostingEnvironment _env;

    public ViewRenderService(IRazorViewEngine razorViewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider, IHostingEnvironment env)
    {
        _razorViewEngine = razorViewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; _env = env;
    }

    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);
            var viewResult = _razorViewEngine.GetView(_env.WebRootPath, 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();
        }
    }

Ответ 7

Я, вероятно, опаздываю на вечеринку, но мне удалось найти решение, которое работает без создания экземпляра нового viewengine (RazorViewEngine), но фактически использует механизм представления, уже доступный в каждом из контроллеров. Кроме того, при таком подходе мы также получаем помощь от IntelliSense при вводе имени представления, что очень полезно при попытке определить точный путь к представлению.

Итак, при таком подходе ваш код будет выглядеть следующим образом:

public override async Task<IActionResult> SignUp()
{
    ...
    // send an email notification
    var emailView = View("Emails/SignupNotification"); // simply get the email view
    var emailBody = await RenderToStringAsync(emailView, _serviceProvider); // render it as a string

    SendEmail(emailBody);
    ...

    return View();
}

Метод RenderToStringAsync, используемый в этом примере, выглядит следующим образом:

private static async Task<string> RenderToStringAsync(ViewResult viewResult, IServiceProvider serviceProvider)
{
    if (viewResult == null) throw new ArgumentNullException(nameof(viewResult));

    var httpContext = new DefaultHttpContext
    {
        RequestServices = serviceProvider
    };

    var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

    using (var stream = new MemoryStream())
    {
        httpContext.Response.Body = stream; // inject a convenient memory stream
        await viewResult.ExecuteResultAsync(actionContext); // execute view result on that stream

        httpContext.Response.Body.Position = 0;
        return new StreamReader(httpContext.Response.Body).ReadToEnd(); // collect the content of the stream
    }
}

Если вы реализуете метод как метод расширения, его использование может стать следующим:

public override async Task<IActionResult> SignUp()
{
    ...
    var emailBody = View("Emails/SignupNotification")
        .RenderToStringAsync(_serviceProvider);
    ...

    return View();
}

Ответ 8

Microsoft имеет отличную статью о тестировании контроллеров на https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/testing

После того, как вы вернете ViewResult, вы можете получить содержимое строки

var strResult = ViewResult.Content