在.NET Core中将视图返回为字符串

时间:2016-12-01 13:49:25

标签: c# asp.net razor asp.net-core .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();
    }
}

假设能够使用以下方式从Controller调用:

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

当我尝试将上述内容运行到.NET Core时,我遇到了很多编译错误。

我尝试将其转换为使用.NET Core,但失败了,任何人都可以帮助提及using ..中所需的"dependencies": { "Microsoft.AspNetCore.Mvc": "1.1.0", ... },和所需的project.json。< / p>

其他一些示例代码为herehere以及here

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

11 个答案:

答案 0 :(得分:48)

如果像我一样,你有许多需要它的控制器,比如在报告网站中,重复这段代码并不是很理想,甚至注入或调用其他服务似乎并不合适

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

  • 模型强类型
  • 查找视图时的错误检查
  • 将视图呈现为部分或页面的能力
  • 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);

答案 1 :(得分:35)

感谢Paris Polyzos和他的article

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

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

    using WebApplication.Services;
    
    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddScoped<IViewRenderService, ViewRenderService>();
     }
    
  2. "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

    4. 一个。在开头定义以下内容:

      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 :(得分:18)

ASP.NET Core 3.1

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

这是从GitHub上asp.net核心的源代码中提取的,我通常使用它来通过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);
    }

答案 3 :(得分:3)

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

答案 4 :(得分:2)

上面的答案很好,但需要调整以使任何标记助手工作(我们需要使用实际的http上下文)。您还需要明确设置 获取布局的视图中的布局。

{{1}}

答案 5 :(得分:2)

我尝试了在Dotnet Core 2.1中由@Hasan A Yousef回答的解决方案,但是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 :(得分:2)

我可能参加聚会的时间很晚,但是我设法找到了一种解决方案,该解决方案无需实例化新的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();
}

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

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

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

答案 9 :(得分:0)

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

答案 10 :(得分:-1)

Microsoft在{{3}}

上有一篇关于控制器测试的优秀文章

返回ViewResult后,您可以通过

获取字符串内容

var strResult = ViewResult.Content