Render ViewComponent as string

Rendering a View to a string is covered here. Turns out there’s a very similar approach to rendering a ViewComponent as a string. Here’s the code, thanks to this comment on the ASP.NET forums:

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

string result = await _viewRenderService.RenderToStringAsync("Shared/Components/Foo/Default", model);

// Don't forget to register the service so you can use constructor injection in your controller:

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

Versioned content in MVC

When you add a script or stylesheet to your HTML page, those requests can be cached by the browser, potentially providing outdated content to the browser.

If you’re not using a bundler or anything fancy like that, then the only way to prevent this problem is to create a brand new URL whenever your script file or stylesheet changes. This is easily accomplished with a querystring, so if your app was previously using app.js?v=1 and you change that to app.js?v=2, the browser will definitely not use the cached version, and will instead make a new request to the server, and pull the latest one.

Using the following technique, you can guarantee that you’ll always pull the latest script and stylesheet when working in dev, and when in production, you can define a “version key”, possibly in a config file or database, and if you ever update just the static content, you would only need to update that key, and it would force all browsers to pull new content.

public static class HtmlExtensions {
    private static string GetUniqueVersionKey() {
        #if DEBUG
        return Guid.NewGuid().ToString("N");
        #else
        // This needs to be somewhere you can get to it, like a web.config file or database
        return MvcApplication.GetUniqueVersionKey();
        #endif
    }

    public static MvcHtmlString Script(this HtmlHelper htmlHelper, string scriptFileName) {
        var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
        return new MvcHtmlString(string.Format("<script type=\"text/javascript\" src=\"{0}?uv={1}\"></script>",
            urlHelper.Content(scriptFileName), GetUniqueVersionKey()));
    }

    public static MvcHtmlString Style(this HtmlHelper htmlHelper, string styleFileName) {
        var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
        return new MvcHtmlString(string.Format("<link rel=\"stylesheet\" href=\"{0}?uv={1}\">",
            urlHelper.Content(styleFileName), GetUniqueVersionKey()));
    }
}

These will produce the appropriate link or script tag.

@Html.Style("app.css")
@Html.Script("app.js")

Render view from controller in ASP.NET Core

In ASP.NET Core, if you want to render a view to a string, this Stack Overflow answer makes it simple. I’ve used this to return HTML in a JSON request that also includes other data, to build an email body, and to get raw HTML to pass off to wkhtmltopdf to build PDF from HTML.

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

JSON infinite loops in ASP.NET

If you accidentally (or purposely) have an infinite loop in an object, where it has a reference that points back to itself, when you try to return that object as JSON in ASP.NET, you get an error:

JsonSerializationException: Self referencing loop detected for property …

To avoid that, you can add a line to your Startup ConfigureServices method:

// dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson

services.AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.ReferenceLoopHandling
            = ReferenceLoopHandling.Ignore;
    });

JSON casing in ASP.NET Core

Some mallethead decided that they wanted to take UpperCamelCase names of properties and change them to lowerCamelCase when sending JSON to the client. Sure, maybe this fits standard naming conventions, but it means that you’ll end up with different property names.

public JsonResult GetFoo()
{
    return Json(new Foo { 
        Name = "Jane Doe", 
        Age = 32 
    });
}

This returns the following content:

{"name":"Jane Doe","age":32}

Personally, I want the properties to match. Here’s the change to make that happen:

// dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson

// Startup.cs: in ConfigureServices
services.AddNewtonsoftJson(options =>
    {
        options.UseMemberCasing();
    });

Now the same code produces:

{"Name":"Jane Doe","Age":32}

Force IE in Edge Mode

If you’re seeing Internet Exploder loading your site in an old compatibility mode, try adding the following to your web.config file:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="X-UA-Compatible" value="IE=Edge" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

It may or may not work – I haven’t had the time or patience to learn all the various scenarios that screw up Internet Exploder, but this works in at least some scenarios.

ASP.NET Embedded Resource

Description

You can compile binary or text files into the application assembly – here’s the basic idea.

Add the file to the project in Visual Studio, and in the Properties, mark it as “Embedded Resource”. The name of the resource is the default namespace for the project + relative namespace (folder structure) + filename. This is not the same as a resx file.

In the code, you can grab the file contents as follows:

var stream = Assembly.GetExecutingAssembly()
    .GetManifestResourceStream(
        "WebApplication2.Resources.SomeExcelFile.xlsx"
    )
);
// Do whatever you want to with this stream
// copy to byte[], write to output, etc.