November Happy Hour will be moved to Thursday December 5th.
November Happy Hour will be moved to Thursday December 5th.
Maybe this will work did not test it out.
private readonly ContentApiOptions _options;
private readonly IHtmlHelper _htmlHelper;
private readonly ITempDataProvider _tempDataProvider;
private readonly ICompositeViewEngine _viewEngine;
private readonly IModelMetadataProvider _modelMetadataProvider;
public virtual async Task<string> RenderContentAsString(HttpContext context, IContent content)
{
var model = _templateResolver.Resolve(context, content.GetOriginalType(),
TemplateTypeCategories.MvcPartial | TemplateTypeCategories.MvcPartialController |
TemplateTypeCategories.MvcController, Array.Empty<string>());
using (var writer = new StringWriter())
{
// we need to set the ViewContext.View to a not null value for creating ViewComponentContext (in CMS Core),
// otherwise it will throw exception. This view result similar to rendering the content in view mode.
var viewResult = _viewEngine.GetView(null, model.Path, false);
var viewContext = new ViewContext
{
HttpContext = context,
Writer = writer,
RouteData = new RouteData(),
TempData = new TempDataDictionary(context, _tempDataProvider),
ActionDescriptor = new ActionDescriptor(),
View = viewResult.View,
ViewData = new ViewDataDictionary(_modelMetadataProvider, new ModelStateDictionary()),
FormContext = new FormContext()
};
(_htmlHelper as IViewContextAware)?.Contextualize(viewContext);
await _htmlHelper.RenderContentDataAsync(content, viewContext, false, model, null);
writer.Flush();
return writer.ToString();
}
}
Thanks, Mark! I have edited the code a little bit, but am running into an issue on the `viewResult` line (see comment):
public string Render(HttpContext context, ContentReference contentReference, IEnumerable<string> tags = null)
{
var content = _contentLoader.Get<IContent>(contentReference);
var model = _templateResolver.Resolve(content, content.GetOriginalType(),
TemplateTypeCategories.MvcPartial
| TemplateTypeCategories.MvcPartialComponent
| TemplateTypeCategories.MvcPartialController
| TemplateTypeCategories.MvcController, Array.Empty<string>());
using var writer = new StringWriter();
// Errors out here -- "'Value cannot be null or empty. Arg_ParamName_Name'"
var viewResult = _viewEngine.GetView(null, model.Path, false);
var viewContext = new ViewContext
{
HttpContext = context,
Writer = writer,
RouteData = new RouteData(),
TempData = new TempDataDictionary(context, _tempDataProvider),
ActionDescriptor = new ActionDescriptor(),
View = viewResult.View,
ViewData = new ViewDataDictionary(_modelMetadataProvider, new ModelStateDictionary()),
FormContext = new FormContext()
};
(_htmlHelper as IViewContextAware)?.Contextualize(viewContext);
_htmlHelper.RenderContentData(content, false, tags);
writer.Flush();
return writer.ToString();
}
It appears that model.Path is always null, and model resolves to our DefaultBlockController (see below) since this particular block does not have it's own dedicated controller. I'm not sure if it matters, but the ContentReference I'm trying to render to a string using this example is a block, not a page.
I feel like this is close but I'm not sure how to get it over the finish line from here.
[TemplateDescriptor(Inherited = true)]
public class DefaultBlockController : AsyncBlockComponent<BlockData>
{
protected override async Task<IViewComponentResult> InvokeComponentAsync(BlockData currentBlock)
{
var model = CreateModel(currentBlock);
var blockName = currentBlock.GetOriginalType().Name;
var viewPath = $"~/Features/Blocks/{blockName}/{blockName}.cshtml";
return await Task.FromResult(View(viewPath, model));
}
private static IBlockViewModel<BlockData> CreateModel(BlockData currentBlock)
{
var type = typeof(BlockViewModel<>).MakeGenericType(currentBlock.GetOriginalType());
return Activator.CreateInstance(type, currentBlock) as IBlockViewModel<BlockData>;
}
}
try
var contentName = content.GetOriginalType().Name;
var viewResult = _viewEngine.GetView(null, !string.IsNullOrEmpty(model.Path) ? model.Path : $"~/Features/Blocks/{contentName}/{contentName}.cshtml";, false);
Hello,
Are you looking for something like this?
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;
namespace A.B.C
{
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.GetView(viewName, 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();
}
}
}
}
Can be called like this:
var myString = await _viewRenderService.RenderToStringAsync("~/Views/Shared/View.cshtml", viewModel);
@Mark - thanks, that does the trick! I was hoping to be able to dynamically resolve the view paths, but this should be fine for now. Greatly appreciate everyone's help! Here's the final method:
public string Render(HttpContext context, ContentReference contentReference, IEnumerable<string> tags = null)
{
var content = _contentLoader.Get<IContent>(contentReference);
var model = _templateResolver.Resolve(content, content.GetOriginalType(),
TemplateTypeCategories.MvcPartial
| TemplateTypeCategories.MvcPartialComponent
| TemplateTypeCategories.MvcPartialController
| TemplateTypeCategories.MvcController, Array.Empty<string>());
using var writer = new StringWriter();
var contentName = content.GetOriginalType().Name;
var viewResult = _viewEngine.GetView(null,
!string.IsNullOrEmpty(model.Path) ? model.Path : $"~/Features/Blocks/{contentName}/{contentName}.cshtml",
false);
var viewContext = new ViewContext
{
HttpContext = context,
Writer = writer,
RouteData = new RouteData(), //routeData,
TempData = new TempDataDictionary(context, _tempDataProvider),
ActionDescriptor = new ActionDescriptor(),
View = viewResult.View,
ViewData = new ViewDataDictionary(_modelMetadataProvider, new ModelStateDictionary()),
FormContext = new FormContext()
};
(_htmlHelper as IViewContextAware)?.Contextualize(viewContext);
_htmlHelper.RenderContentData(content, false, tags);
writer.Flush();
return writer.ToString();
}
Are there any built-in helper methods or approaches that would render an existing ContentReference to a string? Essentially, I'm looking to return a string of the rendered block in a web API response.
The following code works in .NET MVC, but not .NET Core:
https://docs.developers.optimizely.com/content-cloud/v1.5.0-content-delivery-api/docs
https://docs.developers.optimizely.com/content-cloud/v1.5.0-content-delivery-api/docs/contentapi