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


70

我找到了一些文章,介绍如何在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();
    }
}

假定能够使用以下命令从Controller调用:

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

当我尝试将以上内容运行到.NET Core中时,出现很多编译错误。

我试图将其转换为与.NET的核心工作,但失败了,谁能帮助与提要求using ..和所需"dependencies": { "Microsoft.AspNetCore.Mvc": "1.1.0", ... },的使用project.json

其他一些示例代码在此处此处此处

注意 我需要一种解决方案来将视图转换为string.NET Core,无论转换了相同的代码还是可以使用其他方法转换。

Answers:


111

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

因此,我制作了上述版本,但有以下区别:

  • 模型强类型化
  • 查找视图时进行错误检查
  • 能够将视图呈现为局部或页面
  • 异步
  • 实现为控制器扩展
  • 无需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);

10
这应该是更多的方式。它看起来更优雅,对我来说就像一种魅力。
DGaspar '18

2
谢谢,优秀的解决方案!我唯一更改的是为局部视图添加了其他包装的扩展方法。使用先前注入的ViewRenderService时,我实际上遇到了一个问题,因为它可以从其他控制器的视图树访问部分视图。这些视图确实可以正确渲染,但是在调试期间永远不会自动重新编译,只需将它们移至“共享”视图即可解决此问题!
LentoMan

1
如果无法找到视图,我宁愿抛出异常,而不是得到令人惊讶的结果:return $"A view with the name {viewName} could not be found";
Janis Veinbergs

1
如果您的视图位于不寻常的位置,则此方法不起作用。我在自己的答案中为此发布了修复程序。
Pharylon

1
以前,我曾使用不同的技术进行编码,但该技术可在开发中使用,而在IIS中则无法使用,最后您的代码可同时在开发人员和现场工作。
法拉兹·艾哈迈德

58

感谢Paris Polyzos及其文章

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

Service在文件中创建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();
                }
            }
        }
}

2.将服务添加到Startup.cs文件中,如下所示:

using WebApplication.Services;

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

3.添加"preserveCompilationContext": truebuildOptions中的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"
    }
    }
}

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

一种。首先定义以下内容:

private readonly IViewRenderService _viewRenderService;

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

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

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

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

13
这对于Core 2.0几乎适用,但有两个例外:(1)_razorViewEngine.FindView在绝对路径上不起作用,我至少需要这些,因为标准模板应用程序不使用它假定的Views文件夹。Tto使用他的代码在Core 2.0 GitHub网站上记录为“按设计”,解决方案是使用_razorViewEngine.GetView,它支持绝对路径。(2)没有解释preserveCompilationContext(不在原始文章中)-为什么需要它?目前尚不清楚将其与COre 2.0一起放置在何处,并且似乎没有它也可以工作。
菲尔

2
使用此代码,ViewData [“ Message”] =“您的应用程序描述页面。” 在视图中将为null。为什么?任何人都可以发布包含正确处理ViewData的固定版本,而不仅仅是视图模型。
martonx

1
很好,尽管我不得不将接口从接收object model到改为接收ViewDataDictionary ViewData,然后不像var viewDictionary您一样初始化接口,而是使用ViewData。这样,我就可以得到模型,ViewBag,ViewData以及其他所有内容,并且控制器上下文具有该View()
属性

1
我收到此错误,从Web API控制器在.NET Core 2.2中进行了尝试:“尝试激活'xxx.Api.ViewRenderService'时,无法解析类型为'Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine'的服务。” 我想我已经遵循了Hasan的回答。有什么事吗
haugan

1
谢谢,哈桑。这也使我获得了98%的收益。我不得不使用@philw的调整为“ GetView”,然后再出现1个问题。我无法使用DefaultHttpContext,所以我使用了此答案中的信息来获取当前的HttpContext。
mohrtan

18

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


1
可悲的是,这句话对我来说只是事实的一半。尽管此代码完成了将剃须刀页面转换为html字符串的实际工作,但必须适合周围的设置,否则编译器会抱怨。确保将services.AddControllersWithViews();和添加services.AddRazorPages();到Startup.cs中。确保正确添加以下DI services.AddTransient<IRazorViewToStringRenderer, RazorViewToStringRenderer>();:并且确保项目位于.Net Core 3.1上。我感觉这在.net core 3上工作有问题。如果我错了,请纠正我。希望这对
某人有

@beggarboy我也已经在asp.net core 2.2上尝试过了,您刚才所说的是非常标准的内容,只需要添加 ControllersWithViewsRazorPages(根据您的喜好)即可。
HMZ

您没看错,这是相当标准的内容,但是它试图从Web上的多个来源中修补此类特定内容的工作真是令人筋疲力尽,他们正拼命地试图使其在.net core的多次迭代以及它们之间的重大更改下都能正常工作。 。我在您之前尝试了4种方法,并且总是遇到缺少的,无法解决的依赖项或实现问题,所以我认为最好还是花一两个小时的时间来帮助其他人找出他们可能会错过的非常简单的事情:)
beggarboy

@beggarboy我同意,有时候会很痛苦,并且由于该引擎已经存在,因此可以在框架中轻松实现此功能。
HMZ

17

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

3
这个答案有一个问题,因为它改变了控制器自己的模型controller.ViewData.Model = model;,我们必须撤消任何更改,否则将破坏后续视图的呈现。我将这种突变包裹起来,并进行了最后的渲染以进行修复。
gtrak

我用这个解决方案,工作正常。。但是本地化不起作用。任何想法?我想念什么?
Kalpesh Rajai

可行,但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();
        }
    }
}

2
注:在Azure上,我需要将以下内容添加到启动 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); 古怪精细工作在本地没有这个...
戴夫Glassborow

这是正确的答案@Dave Glassborow,因为(至少在Core 3.0中)如果不注入HttpContext并创建默认值,则至少要Invalid URI: The format of the URI could not be determined执行RenderAsync()自定义View/Pages路径,才能继续执行。谢谢。
rvnlord

3

我可能参加聚会很晚,但是我设法找到了一个解决方案,该解决方案无需实例化新的Viewengine(RazorViewEngine),而是实际重用了每个Controller中已经可用的View Engine。同样,通过这种方法,当键入视图名称时,我们也会从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();
}

Layout = null;使用此方法渲染PartialViews(例如,用于Ajax调用)时,进行设置很重要。然后,它运作良好!
狮子”

2

我在Dotnet Core 2.1中尝试了@Hasan A Yousef回答的解决方案,但csthml对我来说效果不佳。它总是抛出NullReferenceException,请参见屏幕截图。 在此处输入图片说明

为了解决这个问题,我将Html.ViewData.Model分配给一个新对象。这是我的代码。

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

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

我尝试了您的方法,现在知道了-[ERR]中的已执行操作Controllers.PortfolioController.PrintStatement执行请求System.NullReferenceException时发生未处理的异常:对象引用未设置为对象的实例。在AspNetCore._Views_Portfolio_PrintStatement_cshtml。在PrintStatement.cshtml:第248行中。--------------在此之前,我在cshtml文件的第0行中得到了nullreference错误。
gbade_

确实可以,但是问题是@page指令将其标记为Razor Page。剃刀页面的工作方式不同于剃刀视图。有关将模型添加到剃刀的方法,请参见此其他SO解决方案:stackoverflow.com/a/49275145/943435
Roberto

2

我已经编写了一个干净的Razor.Templating.Core库,可在Web和控制台应用程序上与.NET Core 3.0,3.1一起使用。它可以作为NuGet包使用。安装完成后,您可以像

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

注意:以上代码段不会立即生效。请参考以下工作指南,了解如何应用。

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

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


1

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

MVC 6 Controller中的ControllerContext和ViewEngines属性在哪里?

在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的更改!
Piotr Kula

1
不客气@ppumkin,那时候我花了很长时间弄清楚这个问题,我非常需要这个。
理查德·曼尼

0

这是另一个更适合我的版本,但仍然与上述其他版本非常相似。这是Core MVC3.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>();

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.