I am trying to use AJAX to call my Controller and return a Partial View with a model as a string so that I can inject it into my HTML. I have done this before in MVC5 using a controller interface, but I can't seem to find anything on how to do this for a Partial View in Asp.Net Core 2.2. I have found examples of how to render a View to a string, but I could not modify them to work for a partial view.
Controller Action:
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 Call:
$.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");
}
});
I've confirmed that my Controller is being hit, the parameters are being passed correctly, and I'm receiving the correct data back from the Action to my AJAX call. The only thing I'm missing is the ability to render a Partial View directly to a string.
If there is another way to do this in Asp.net Core, I'm open to other options.
Note: I'm just doing this to learn Asp.Net Core. Please let me know if any more information is needed.
Declare partial views In ASP.NET Core MVC, a controller's ViewResult is capable of returning either a view or a partial view. In Razor Pages, a PageModel can return a partial view represented as a PartialViewResult object. Referencing and rendering partial views is described in the Reference a partial view section.
Listing 1: Rendering a Razor View to String from within a Controller. The RenderViewToString() method works by receiving a controller context and virtual view path (i.e., ~/views/item/page. cshtml ) and optional model data that are passed to the view.
Partial function which renders the Partial View. The name of the View and the object of the CustomerModel class are passed to the @Html. Partial function. In order to add Partial View, you will need to Right Click inside the Controller class and click on the Add View option in order to create a View for the Controller.
It is possible to get a partial view's (or several) from any where (a controller, page model etc) using a service:
The Origin of this answer, he deserves the credit (I'm adding it here because I needed something like that and it took me quite a while to find it)
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
function:services.AddScoped<IViewRenderService, ViewRenderService>();
string html = await m_RenderService.RenderToStringAsync("<NameOfPartial>", new Model());
The ControllerExtensions implementation
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;
}
}
Usage in your controller actions:
var html = await this.RenderViewToStringAsync("actionName" , model);
Why Json result with html string? You can return a partial view directly to return html.
public IActionResult GetUpsertPartialView(MessageBoard messageBoard)
{
return PartialView("someviewname", messageBoard);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With