在 .NET Core 中以字符串形式返回视图

Posted

技术标签:

【中文标题】在 .NET Core 中以字符串形式返回视图【英文标题】:Return View as String in .NET Core 【发布时间】:2017-04-16 04:38:40 【问题描述】:

我找到了一些文章如何在 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 一起使用,但失败了,任何人都可以帮助提及在 project.json 中使用所需的 using .. 和所需的 "dependencies": "Microsoft.AspNetCore.Mvc": "1.1.0", ... ,

其他一些示例代码是here 和here 和here

注意 我需要在 .NET Core 中将视图转换为 string 的解决方案,无论是否转换了相同的代码或其他方式。

【问题讨论】:

【参考方案1】:

如果像我一样,您有许多需要此功能的控制器,例如在报告站点中,重复此代码并不理想,甚至注入或调用另一个服务似乎也不正确。

所以我制作了我自己的上述版本,但有以下不同:

模型强类型 查找视图时的错误检查 能够将视图呈现为部分或页面 异步 作为控制器扩展实现

不需要 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);

【讨论】:

这个应该更上一层楼。它看起来更优雅,对我来说就像一种魅力。 谢谢,很好的解决方案!我唯一改变的是为局部视图添加了一个额外的包装扩展方法。在使用之前注入的 ViewRenderService 时,我实际上遇到了一些问题,因为它可以从其他控制器的视图树中访问部分视图。这些视图确实渲染正确,但在调试期间永远不会自动重新编译,将它们移动到共享视图解决了这个问题! 如果找不到视图,我宁愿抛出异常而不是得到惊喜结果:return $"A view with the name viewName could not be found"; 如果您的视图位于不寻常的位置,这将不起作用。我在自己的答案中发布了一个修复程序。 以前我使用不同的技术进行编码,它在开发中有效,但在 IIS 中无效,最后您的代码在开发和实时中都有效。【参考方案2】:

感谢Paris Polyzos 和他的article。

我在这里重新发布他的代码,以防原帖因任何原因被删除。

在文件viewToString.cs中创建Service,代码如下:

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.将服务添加到Startup.cs文件中,如下:

using WebApplication.Services;

public void ConfigureServices(IServiceCollection services)

    ...
    services.AddScoped<IViewRenderService, ViewRenderService>();

3."preserveCompilationContext": true 添加到project.json 中的buildOptions,文件如下所示:


    "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.定义你model,例如:

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

5. 例如,创建您的Invite.cshtml

@
    ViewData["Title"] = "Contact";

@ViewData["Title"].
user id: @Model.UserId

6.Controller

a.在开头定义如下:

private readonly IViewRenderService _viewRenderService;

public RenderController(IViewRenderService viewRenderService)

    _viewRenderService = viewRenderService;

b. 调用并返回带有模型的视图,如下所示:

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

c. 完整的控制器示例,可能如下:

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;
     

【讨论】:

这几乎适用于 Core 2.0,但有两个例外:(1) _razorViewEngine.FindView 不适用于绝对路径,我至少需要这些,因为标准模板应用程序不使用视图它假定的文件夹。使用他的文档在 Core 2.0 GitHub 站点上被记录为“按设计”,解决方案是使用支持绝对路径的 _razorViewEngine.GetView。 (2)没有解释preserveCompilationContext(不在原始文章中)-为什么需要它?目前尚不清楚将它与 COre 2.0 放在哪里,没有它似乎也可以工作。 使用此代码 ViewData["Message"] = "您的应用程序描述页面。";将在视图中为空。为什么?任何人都可以发布一个包含正确处理 ViewData 的固定版本,而不仅仅是视图模型。 这很好,虽然我不得不将接口从接收object model 改为接收ViewDataDictionary ViewData,然后不像你那样初始化var viewDictionary,我只是使用查看数据。通过这种方式,我得到了模型、ViewBag、ViewData 和其他所有内容以及控制器上下文,就像我在调用 View() 我收到此错误,在 .NET Core 2.2 中从 Web API 控制器尝试此操作:“尝试激活 xxx 时无法解析类型 'Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine' 的服务” .Api.ViewRenderService'。”我想我已经按照哈桑的回答中的说明进行了操作。有什么问题? 谢谢,哈桑。这也让我成功了 98%。我不得不使用@philw 对“GetView”的调整,然后又遇到了 1 个问题。我无法使用 DefaultHttpContext,所以我使用来自 this answer 的信息来获取当前的 HttpContext。【参考方案3】:

ASP.NET Core 3.1

我知道这里有很多很好的答案,我想我也分享一下我的:

这是从 GitHub 上的 asp.net core 源代码中提取的,我通常使用它来使用 Razor 呈现 HTML 电子邮件,以及通过 Ajax 或 SignalR 返回部分视图的 HTML。

作为临时服务添加并在控制器中注入 DI

    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.ViewEngines;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using Microsoft.AspNetCore.Routing;
    using System;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;

    public sealed class RazorViewToStringRenderer : IRazorViewToStringRenderer
    
        private readonly IRazorViewEngine _viewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;

        public RazorViewToStringRenderer(
            IRazorViewEngine viewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider)
        
            _viewEngine = viewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
        

        public async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
        
           //If you wish to use the route data in the generated view (e.g. use 
           //the Url helper to construct dynamic links)
           //inject the IHttpContextAccessor then use: var actionContext = new ActionContext(_contextAccessor.HttpContext, _contextAccessor.HttpContext.GetRouteData(), new ActionDescriptor());
          //instead of the line below

            var actionContext = GetActionContext();
            var view = FindView(actionContext, viewName);

            using (var output = new StringWriter())
            
                var viewContext = new ViewContext(
                    actionContext,
                    view,
                    new ViewDataDictionary<TModel>(
                        metadataProvider: new EmptyModelMetadataProvider(),
                        modelState: new ModelStateDictionary())
                    
                        Model = model
                    ,
                    new TempDataDictionary(
                        actionContext.HttpContext,
                        _tempDataProvider),
                    output,
                    new HtmlHelperOptions());

                await view.RenderAsync(viewContext);

                return output.ToString();
            
        

        private IView FindView(ActionContext actionContext, string viewName)
        
            var getViewResult = _viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true);
            if (getViewResult.Success)
            
                return getViewResult.View;
            

            var findViewResult = _viewEngine.FindView(actionContext, viewName, isMainPage: true);
            if (findViewResult.Success)
            
                return findViewResult.View;
            

            var searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations);
            var errorMessage = string.Join(
                Environment.NewLine,
                new[]  $"Unable to find view 'viewName'. The following locations were searched:" .Concat(searchedLocations)); ;

            throw new InvalidOperationException(errorMessage);
        

        private ActionContext GetActionContext()
        
            var httpContext = new DefaultHttpContext();
            httpContext.RequestServices = _serviceProvider;
            return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
        
    

    public interface IRazorViewToStringRenderer
    
        Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model);
    

【讨论】:

遗憾的是,这条评论对我来说只是事实的一半。虽然这段代码完成了将 razor 页面转换为 html 字符串的实际工作,但周围的设置必须适合,否则编译器会报错。确保将 services.AddControllersWithViews();services.AddRazorPages(); 添加到 Startup.cs。确保您在下面也正确添加了 DI:services.AddTransient&lt;IRazorViewToStringRenderer, RazorViewToStringRenderer&gt;(); 并确保您的项目在 .Net Core 3.1 上。我觉得这在 .net core 3 上工作有问题。如果我错了,请纠正我。希望这会有所帮助1 @beggarboy 我在 asp.net core 2.2 上也试过了,你刚才说的是很标准的东西,除了你只需要添加 ControllersWithViewsRazorPages (根据你的偏好)。 你没看错,它是非常标准的东西,但是试图通过网络从多个来源拼凑特定的东西,拼命试图让它在多次迭代中工作,这已经足够耗费精力了.net 核心和其间的重大变化。我在您之前尝试了 4 种方法,但总是遇到缺失的、无法解决的依赖关系或实现问题,所以我认为最好为其他人节省一两个小时试图找出他们可能错过的非常简单的事情 :) @beggarboy 我同意,有时这很痛苦,而且这个功能可以很容易地在框架中实现,因为引擎已经在这里了。 您的代码对我有用,但部分视图除外。您的回答说“以及通过 Ajax 返回部分视图的 HTML”,但您的代码缺少一个可以在完整视图和部分视图之间切换的布尔值。您在方法private IView FindView 中硬编码isMainPage: true。在@Red 提供的答案中,可以在RenderViewToStringAsync 方法中添加一个可选参数bool partial = false。然后将partial 参数传递给private IView FindView 方法。在该方法中,将isMainPage: true 替换为isMainPage: !partial【参考方案4】:

Red 的回答让我成功了 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);

【讨论】:

这个答案有一个问题,因为它改变了控制器自己的模型controller.ViewData.Model = model;,我们必须撤消任何更改,否则它会破坏后续视图渲染。我包装了这个突变并在 try-finally 中进行渲染以修复它。 我使用了这个解决方案,工作正常。但本地化不起作用。任何想法?我错过了什么? 有效,但 IHostingEnvironment 已过时。只需替换为IWebHostEnvironment【参考方案5】:

上面的答案很好,但需要调整以使任何标签助手工作(我们需要使用实际的 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();
        
    

【讨论】:

注意:在 azure 上,我需要将以下内容添加到 Startup services.AddSingleton&lt;IHttpContextAccessor, HttpContextAccessor&gt;(); 奇怪地在没有这个的情况下在本地工作得很好...... 这是正确的答案@Dave Glassborow,因为(至少在Core 3.0中)如果你不注入HttpContext并创建默认的,你会在执行@时得到Invalid URI: The format of the URI could not be determined 987654326@,至少如果你有自定义的View/Pages 路径。谢谢。【参考方案6】:

我可能迟到了,但我设法找到了一个解决方案,该解决方案无需实例化新的视图引擎 (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();

【讨论】:

在使用此方法渲染 PartialViews(例如用于 Ajax 调用)时设置 Layout = null; 很重要。然后效果很好!【参考方案7】:

我编写了一个干净的库 Razor.Templating.Core,它可在 Web 和控制台应用程序上与 .NET Core 3.0、3.1 一起使用。 它的地址为NuGet package。 安装后可以调用like

var htmlString = await RazorTemplateEngine
                      .RenderAsync("/Views/ExampleView.cshtml", model, viewData);

注意:上面的 sn-p 不会立即起作用。有关如何应用它,请参阅以下工作指南。

完整的工作指南:https://medium.com/@soundaranbu/render-razor-view-cshtml-to-string-in-net-core-7d125f32c79

示例项目:https://github.com/soundaranbu/RazorTemplating/tree/master/examples

【讨论】:

好项目。值得注意的是,截至目前(.NET Core 5.0.400),对于托管 Razor 模板的类库,需要使用“Razor 类库”模板创建项目并在向导中勾选“支持页面和视图” ,否则无法创建 Razor 文件。【参考方案8】:

我尝试了 @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>

【讨论】:

我试过你的方法,现在我得到了这个 - 执行操作 Controllers.PortfolioController.PrintStatement in [ERR] 执行请求时发生未处理的异常 System.NullReferenceException: Object reference not set to an对象的实例。在 AspNetCore._Views_Portfolio_PrintStatement_cshtml。在 PrintStatement.cshtml:line 248 中。 --------------在此之前,我在 cshtml 文件的第 0 行遇到了 nullreference 错误。 这确实有效,但问题在于将其标记为 Razor 页面的 @page 指令。 Razor 页面的工作方式与 Razor 视图不同。有关将模型添加到 Razor 页面的方法,请参见其他 SO 解决方案:***.com/a/49275145/943435【参考方案9】:

ASP .NET 5

这里已经发布了很多很棒的答案。基本上我有类似的问题,我想从剃刀(.cshtml)页面读取电子邮件模板作为字符串,我遇到了这个问题并尝试了所有答案。我在 .NET 5 中遇到了一些问题,所以我在这里发布了我稍微调整过的解决方案。谢谢。

Startup.cs文件

public void ConfigureServices(IServiceCollection services)

    ...
    services.AddScoped<CustomViewRendererService>();
    ...

CustomViewRendererService.cs文件

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;
using System.Threading.Tasks;

namespace Services

    public class CustomViewRendererService
    
        private readonly IRazorViewEngine _razorViewEngine;
        private readonly ITempDataProvider _tempDataProvider;

        public CustomViewRendererService(
            IRazorViewEngine razorViewEngine,
            ITempDataProvider tempDataProvider)
        
            _razorViewEngine = razorViewEngine;
            _tempDataProvider = tempDataProvider;
        
        
        public async Task<string> RenderViewToStringAsync(ControllerContext actionContext, string viewPath, object model)
        
            var viewEngineResult = _razorViewEngine.GetView(viewPath, viewPath, false);

            if (viewEngineResult.View == null || (!viewEngineResult.Success))
            
                throw new ArgumentNullException($"Unable to find view 'viewPath'");
            

            var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), actionContext.ModelState);
            viewDictionary.Model = model;

            var view = viewEngineResult.View;
            var tempData = new TempDataDictionary(actionContext.HttpContext, _tempDataProvider);

            using var sw = new StringWriter();
            var viewContext = new ViewContext(actionContext, view, viewDictionary, tempData, sw, new HtmlHelperOptions());
            await view.RenderAsync(viewContext);
            return sw.ToString();
        
    

在我的控制器中

public class TestController : ControllerBase

    private readonly CustomViewRendererService _viewService;

    public TestController(CustomViewRendererService viewService)
    
        _viewService = viewService;
    

    public async Task<IActionResult> SendTestEmail2Async()
    
        var templatePath = "~/Views/Email/Test.cshtml";
        var msg = await _viewService.RenderViewToStringAsync(ControllerContext, templatePath, ("Foo", "Bar"));
        
        return Ok(msg);
    

终于Test.cshtml文件

@model (string arg1, string arg2)

<h1>Param: @Model.arg1</h1>
<h1>Param @Model.arg2</h1>

【讨论】:

【参考方案10】:

下面的链接解决了几乎相同的问题:

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

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

【讨论】:

谢谢-我一直在寻找类似的东西来使用 Razor 在 HTML 中合并模型。不知道为什么 MS 不会做一个更简单的方法。虽然.. 它比 MVC 1 容易得多。很好的解决方案,感谢分享 GetView 更改! 欢迎您@ppumkin,那时我花了很长时间才弄清楚这一点,我非常需要这个。【参考方案11】:

这是另一个更适合我的版本,但与上面的其他版本仍然非常相似。这是核心 MVC 3.X。

控制器:

public IActionResult UserClientView(UserClientModel ucm)

        try
        
            
            PartialViewResult pvr = PartialView("_AddEditUserPartial", ucm);

            string s = _helper.ViewToString(this.ControllerContext, pvr, _viewEngine);

            return Ok(s);

        
        catch (Exception ex)
        
            _logger.LogError(ex, "UserClientView Error. userName: userName", new[]  ucm.UserLogonName );
            return new JsonResult(StatusCode(StatusCodes.Status500InternalServerError));
        
    

助手:

public interface IHelper

    string ViewToString(ControllerContext controllerContext, PartialViewResult pvr, ICompositeViewEngine _viewEngine);


public class Helper : IHelper


    public string ViewToString(ControllerContext controllerContext, PartialViewResult pvr, ICompositeViewEngine _viewEngine)
    

        using (var writer = new StringWriter())
        
            ViewEngineResult viewResult = _viewEngine.FindView(controllerContext, pvr.ViewName, false);

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

            viewResult.View.RenderAsync(viewContext);

            return writer.GetStringBuilder().ToString();
        
    


启动:

services.AddSingleton<IHelper, Helper>();

【讨论】:

很遗憾你没有说明_helper_viewEngine 是如何定义的。我假设 _helper 来自 IHelper 的注入,但我完全无法弄清楚你的 _viewEngine _viewEngine 作为“ICompositeViewEngine viewEngine”来自控制器 DI,然后我将其分配给构造函数中的 _viewEngine。 _helper 你是对的,它也在 DI 构造函数中。【参考方案12】:

Microsoft 在https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/testing 上有一篇关于控制器测试的优秀文章

一旦你返回了一个 ViewResult 然后你可以通过

var strResult = ViewResult.Content

【讨论】:

我收到了error CS0117: 'ViewResult' doesn't contain a definition for 'Content' 我已经看到带有 Content 的 ViewResults 但 Content 为空。我认为它不包含您认为的内容。

以上是关于在 .NET Core 中以字符串形式返回视图的主要内容,如果未能解决你的问题,请参考以下文章

PayPal 快速结帐在 MVC .net 中以 html 形式返回响应

在 ASP.NET Core 中将 Razor 视图渲染为字符串

[Asp.Net Core]MVC开发&解读Razor混编

[Asp.Net Core]MVC开发&解读Razor混编

在 ASP.NET CORE 表单中使用 AJAX 返回 JSON

如何在socks5代理中以2个字节的形式返回端口号到客户端?