Вернуться в виде строки в.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();
}
}
}
}
-
Добавьте службу в файл 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"
}
}
}
-
Определите model
, например:
public class InviteViewModel {
public string UserId {get; set;}
public string UserName {get; set;}
public string ReferralCode {get; set;}
public int Credits {get; set;}
}
-
Создайте свой Invite.cshtml
например:
@{
ViewData["Title"] = "Contact";
}
@ViewData["Title"].
user id: @Model.UserId
-
В 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, см. Скриншот.
Чтобы решить эту проблему, я присвою 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