【问题标题】:Render Partial View to HTML string in ASP.NET Core 2.2在 ASP.NET Core 2.2 中将部分视图呈现为 HTML 字符串
【发布时间】:2019-02-11 21:02:09
【问题描述】:

我正在尝试使用 AJAX 调用我的控制器并返回一个带有模型作为字符串的部分视图,以便我可以将它注入到我的 HTML 中。我之前在 MVC5 中使用控制器接口完成了此操作,但我似乎无法找到有关如何在 Asp.Net Core 2.2 中为局部视图执行此操作的任何内容。我找到了如何将视图呈现为字符串的示例,但我无法修改它们以适用于局部视图。

控制器动作:

    public JsonResult GetUpsertPartialView(MessageBoard messageBoard)
    {
        Dictionary<string, string> result = new Dictionary<string, string>();
        string errorState = "0";
        string errorMessage = "";

        try
        {
            result["view"] = ""; // My call to render the Partial View would go here.
        }
        catch (Exception)
        {
            errorState = "1";
            errorMessage = "An error was encountered while constructing the View.";
        }
        result["errorState"] = errorState;
        result["errorMessage"] = errorMessage;
        return Json(result);
    }

AJAX 调用:

   $.ajax({
      type: "GET",
      url: "../Home/GetUpsertPartialView/",
      data: messageBoardData,
      contentType: 'application/json; charset=utf-8',
      success: function (data) {
         console.log(data);
         $("#messageBoardModalBody").val(data.view);
         $("#messageBoardModal").modal("show");
      }
   });

我已经确认我的 Controller 被击中,参数被正确传递,并且我从 Action 接收到正确的数据返回给我的 AJAX 调用。我唯一缺少的是将部分视图直接呈现为字符串的能力。

如果在 Asp.net Core 中有其他方法可以做到这一点,我愿意接受其他选择。

注意:我这样做只是为了学习 Asp.Net Core。如果需要更多信息,请告诉我。

【问题讨论】:

    标签: c# asp.net asp.net-core asp.net-core-mvc


    【解决方案1】:

    可以使用服务从任何地方(控制器、页面模型等)获取部分视图(或多个):

    The Origin of this answer,他值得称赞(我在这里添加它是因为我需要类似的东西,我花了很长时间才找到它)

    定义依赖注入的接口

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

    ConfigureServices函数中添加类:

    services.AddScoped<IViewRenderService, ViewRenderService>();
    

    最后,使用服务

    string html = await m_RenderService.RenderToStringAsync("<NameOfPartial>", new Model());
    

    【讨论】:

    • 我非常担心这个(或变体)真的是唯一的解决方案。获取单个 Partial 的字符串表示非常麻烦。
    【解决方案2】:

    ControllerExtensions 实现

        public static class ControllerExtensions
    {
        /// <summary>
        /// Render a partial view to string.
        /// </summary>
        public static async Task<string> RenderViewToStringAsync(this Controller controller, string viewNamePath, object model = null)
        {
            if (string.IsNullOrEmpty(viewNamePath))
                viewNamePath = controller.ControllerContext.ActionDescriptor.ActionName;
    
            controller.ViewData.Model = model;
    
            using (StringWriter writer = new StringWriter())
            {
                try
                {
                    var view = FindView(controller, viewNamePath);
    
                    ViewContext viewContext = new ViewContext(
                        controller.ControllerContext,
                        view,
                        controller.ViewData,
                        controller.TempData,
                        writer,
                        new HtmlHelperOptions()
                    );
    
                    await view.RenderAsync(viewContext);
    
                    return writer.GetStringBuilder().ToString();
                }
                catch (Exception exc)
                {
                    return $"Failed - {exc.Message}";
                }
            }
        }
    
        private static IView FindView(Controller controller, string viewNamePath)
        {
            IViewEngine viewEngine = controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
    
            ViewEngineResult viewResult = null;
    
            if (viewNamePath.EndsWith(".cshtml"))
                viewResult = viewEngine.GetView(viewNamePath, viewNamePath, false);
            else
                viewResult = viewEngine.FindView(controller.ControllerContext, viewNamePath, false);
    
            if (!viewResult.Success)
            {
                var endPointDisplay = controller.HttpContext.GetEndpoint().DisplayName;
    
                if (endPointDisplay.Contains(".Areas."))
                {
                    //search in Areas
                    var areaName = endPointDisplay.Substring(endPointDisplay.IndexOf(".Areas.") + ".Areas.".Length);
                    areaName = areaName.Substring(0, areaName.IndexOf(".Controllers."));
    
                    viewNamePath = $"~/Areas/{areaName}/views/{controller.HttpContext.Request.RouteValues["controller"]}/{controller.HttpContext.Request.RouteValues["action"]}.cshtml";
    
                    viewResult = viewEngine.GetView(viewNamePath, viewNamePath, false);
                }
    
                if (!viewResult.Success)
                    throw new Exception($"A view with the name '{viewNamePath}' could not be found");
    
            }
    
            return viewResult.View;
        }
    
    }
    

    在您的控制器操作中使用:

    var html = await this.RenderViewToStringAsync("actionName" , model);
    

    【讨论】:

      【解决方案3】:

      为什么 Json 结果带有 html 字符串?您可以直接返回部分视图以返回 html。

      public IActionResult GetUpsertPartialView(MessageBoard messageBoard)
      {
      
          return PartialView("someviewname", messageBoard);
      }
      

      【讨论】:

      • 大多数时候我需要返回部分视图之外的其他数据。具体来说,我需要能够在大多数情况下返回错误数据。这就是为什么我想将它作为 Json 结果的一部分返回。
      • 我要么返回 json 并使用 json 构建 html,要么只返回部分视图,如果发生错误,我将只返回 NotFound() 而不是部分视图和 ajax可以通过状态码知道这是一个错误。
      • 这是个好主意。我想我真的不需要返回特定的错误消息,因为它已记录在我的应用程序中。我可以根据需要返回不同的状态码。这符合我的所有要求,所以我将其标记为答案。谢谢!
      • @JoeAudette 我真的am返回了额外的数据。我当然可以用 javascript 构建 HTML,但为什么我要获取 HTML 部分,将其转换为 JSON,然后从 JSON 构建 HTML?
      【解决方案4】:

      扩展 Aharon 的答案,它解决了 OP 的“纯”标题问题,即如何将部分呈现为字符串,我在下面添加了我自己的解决方案。我的做法是直接通过控制器操作来执行此操作。

      当然,OP 不应该在他们的用例中使用它,因为例外的答案是正确的。

      CreateViewModelAsync 方法是你自己的。

      // SomeController.cs
      
      public SomeController(
          IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine,
          IModelMetadataProvider metadataProvider, IViewBufferScope bufferScope,
          HtmlEncoder htmlEncoder, UrlEncoder urlEncoder,
          ModelExpressionProvider modelExpressionProvider)            
      {
          this.viewEngine = viewEngine;
          this.cardHtmlHelper = new HtmlHelper<TimesheetCardModel>(
              htmlGenerator, viewEngine, metadataProvider, bufferScope,
              htmlEncoder, urlEncoder, modelExpressionProvider);
      }
      
      [HttpGet]
      [Route("test", Name = "TestRoute")]
      public async Task<IActionResult> GetTest()
      {
          // Example renders a partial view as HTML using MotherModel.Children.First() as
          // the model for the partial.
      
          MotherModel viewModel = await this.CreateViewModelAsync();
      
          const string viewName = "_MyPartial";
      
          var foundView = this.viewEngine.FindView(this.ControllerContext, viewName, false);
          var viewData = new ViewDataDictionary<FirstChildModel>(this.ViewData);
          
          var textWriter = new StringWriter();
          var viewContext = new ViewContext(this.ControllerContext, foundView.View, viewData, this.TempData, textWriter, new HtmlHelperOptions());
          
          this.cardHtmlHelper.Contextualize(viewContext);
      
          var htmlContent = await this.cardHtmlHelper.PartialAsync(viewName, pageModel.Children.First(), viewData);
      
          htmlContent.WriteTo(textWriter, HtmlEncoder.Default);
      
          var html = textWriter.ToString();
      
          ...
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-16
        • 2021-07-10
        • 2015-08-02
        • 2011-01-28
        • 2013-03-13
        • 1970-01-01
        相关资源
        最近更新 更多