Type-safe JSON result in ASP.NET MVC

public abstract class BaseController : Controller {
    protected internal JsonResult<T> Json<T>(T data) {
        return Json(data, null /* contentType */, null /* contentEncoding */, JsonRequestBehavior.DenyGet);
    }
    protected internal JsonResult<T> Json<T>(T data, string contentType) {
        return Json(data, contentType, null /* contentEncoding */, JsonRequestBehavior.DenyGet);
    }
    protected internal virtual JsonResult<T> Json<T>(T data, string contentType, Encoding contentEncoding) {
        return Json(data, contentType, contentEncoding, JsonRequestBehavior.DenyGet);
    }
    protected internal JsonResult<T> Json<T>(T data, JsonRequestBehavior behavior) {
        return Json(data, null /* contentType */, null /* contentEncoding */, behavior);
    }
    protected internal JsonResult<T> Json<T>(T data, string contentType, JsonRequestBehavior behavior) {
        return Json(data, contentType, null /* contentEncoding */, behavior);
    }
    protected internal virtual JsonResult<T> Json<T>(T data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior) {
        return new JsonResult<T> {
            Data = data,
            ContentType = contentType,
            ContentEncoding = contentEncoding,
            JsonRequestBehavior = behavior
        };
    }
}

public class JsonResult<T> : JsonResult { }
public class Foo { public int FooId { get; set; } }

// Type-safe result, so you can't accidentally return the wrong type.
[HttpPost]
public JsonResult<Foo> GetSomeFoo() {
    return Json(new Foo { FooId = 1 });
}

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

MVC Disable Converting Empty String to Null

Some joker decided that empty strings passed into an MVC controller should be converted to null by default. One way to avoid this is to decorate the string properties of the model class:

using System.ComponentModel.DataAnnotations;

public class SomeModel {
    [DisplayFormat(ConvertEmptyStringToNull = false)]
    public string SomeProperty { get; set; }
}

[HttpPost]
public JsonResult DoSomething(SomeModel myModel) {
    bool isEmpty = (myModel.SomeProperty == string.Empty);
    return Json(new { IsEmpty = isEmpty });
}

Then when you pass in an empty string, it will remain an empty string in the .NET code:

jQuery.ajax({
    type: "post",
    url: "@Url.Action("DoSomething")",
    data: JSON.stringify({ SomeProperty: "" }),
    dataType: "json",
    contentType: "application/json;charset=utf-8;"
}).done(function (result) {
    console.log(result);
});

Javascript controller actions

In your ASP.NET MVC application, you may need to reference action URLs, for things like AJAX calls. You can do this inline in CSHTML with something like:

var url = "@Url.Action("SavePerson", "People")";

But it’s a little harder to do it in standalone javascript files.

You can define your app root and then concatenate, like:

<script>
window._rootUrl = "@Url.Content("~")";
</script>
// In standalone .js file:
var url = _rootUrl + "/People/SavePerson";

But that only works if you’re using standard URL routing. And if you try to do it with absolute references, like /People/SavePerson, you might run into issues if your development, test, and production environments are at different levels in IIS.

One solution is to define all your routes in javascript, so you can use them globally. You can use something like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Mvc;
using Newtonsoft.Json;

namespace Acme.Web {
    /// <summary>
    /// You must initialize this in order to use it. 
    /// Generally you'll want to initialize in your Global.asax.cs
    /// Application_Start method, by calling:    
    /// ActionListController.Initialize(typeof(HomeController).Assembly);
    /// If you have controllers in multiple assemblies, 
    /// pass in an array of assemblies to the Initialize method.
    /// </summary>
    public class ActionListController : Controller {
        private static Assembly[] _controllerAssemblies = new Assembly[0];

        public static void Initialize(params Assembly[] controllerAssemblies) {
            if (controllerAssemblies != null) {
                _controllerAssemblies = controllerAssemblies;
            }
        }

        [HttpGet]
        [OutputCache(Duration = 6000, VaryByParam = "none")]
        public ActionResult ControllerActionList() {
            var rootObjects = new Dictionary<string, object>();

            // Area -> Controller -> Action -> Url
            var areas = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();

            // Controller -> Action -> Url
            var rootMethods = new Dictionary<string, Dictionary<string, string>>();

            foreach (var assembly in _controllerAssemblies) {
                foreach (var controller in assembly.GetTypes().Where(t => typeof (Controller).IsAssignableFrom(t))) {
                    string controllerName = Regex.Replace(controller.Name, "Controller$", string.Empty);
                    var areaNameMatch = Regex.Match(controller.Namespace, @"(?<=\.Areas\.)[a-zA-Z0-9_]+");
                    if (areaNameMatch.Success) {
                        if (!areas.ContainsKey(areaNameMatch.Value)) {
                            areas[areaNameMatch.Value] = new Dictionary<string, Dictionary<string, string>>();
                        }
                        areas[areaNameMatch.Value].Add(controllerName, GetActionsFromController(controller, new {Area = areaNameMatch.Value}));
                    } else {
                        rootMethods.Add(controllerName, GetActionsFromController(controller));
                    }
                }
            }
            foreach (var kvp in areas) {
                rootObjects[kvp.Key] = kvp.Value;
            }
            foreach (var kvp in rootMethods) {
                rootObjects[kvp.Key] = kvp.Value;
            }
            string script = string.Format("window._controllerActions={0};", JsonConvert.SerializeObject(rootObjects).Replace(@"/", @"\/"));
            return Content(script, "text/javascript");
        }

        private Dictionary<string, string> GetActionsFromController(Type controller, object routeValue = null) {
            var controllerActions = new Dictionary<string, string>();
            string controllerName = Regex.Replace(controller.Name, "Controller$", string.Empty);

            var controllerMethods = controller.GetMethods(BindingFlags.Public | BindingFlags.Instance);
            var syncActionMethods = controllerMethods.Where(m => typeof (ActionResult).IsAssignableFrom(m.ReturnType));
            var asyncActionMethods = controllerMethods
                .Where(m => m.ReturnType.IsGenericType
                    && m.ReturnType.GetGenericTypeDefinition() == typeof (Task<>)
                    && typeof (ActionResult).IsAssignableFrom(m.ReturnType.GetGenericArguments()[0]));

            foreach (var method in syncActionMethods.Union(asyncActionMethods)) {
                controllerActions[method.Name] = Url.Action(method.Name, controllerName, routeValue);
            }
            return controllerActions;
        }
    }
}

In your layout page, make sure you put that script tag above all others.

<script src="@Url.Action("ControllerActionList", "ActionList", new {v = "1.0"})"></script>

When you’re ready to use it, simply call:

var url = _controllerActions.People.SavePerson;