Last active
August 20, 2019 16:10
-
-
Save b9chris/6991b341e89bb0a4e6d801d02dfd7730 to your computer and use it in GitHub Desktop.
Asp.Net MVC BaseController that fixes a number of problems with the default MVC implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Security.Principal; | |
using System.Text; | |
using System.Web; | |
using System.Web.Mvc; | |
namespace Brass9.Web.Mvc.Controllers | |
{ | |
/// <summary> | |
/// Improvements on the standard MVC Controller class: | |
/// | |
/// 1) By default JSON output is done by MVC's JsonSerializer, not JSON.Net, resulting in non-standard outputs, most | |
/// notably for dates and nulls. Both break jQuery. JSON.Net's works fine. | |
/// | |
/// 2) MVC likes to emit Vary: * which should never happen. They have a suppression method just for that bad | |
/// implementation. Call it. | |
/// | |
/// 3) Optionally expose the entire Controller, even the public-seeming methods like View and Json, via .Expose() | |
/// which just passes back a wrapper inner-class, wrapping the protected methods in public ones. | |
/// </summary> | |
public class BaseController : Controller | |
{ | |
protected override JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior) | |
{ | |
return new JsonNetResult | |
{ | |
Data = data, | |
ContentType = contentType, | |
ContentEncoding = contentEncoding, | |
JsonRequestBehavior = behavior | |
}; | |
} | |
protected override JsonResult Json(object data, string contentType, Encoding contentEncoding) | |
{ | |
return new JsonNetResult | |
{ | |
Data = data, | |
ContentType = contentType, | |
ContentEncoding = contentEncoding | |
}; | |
} | |
protected override void OnActionExecuting(ActionExecutingContext filterContext) | |
{ | |
base.OnActionExecuting(filterContext); | |
// Fix an ultra-annoying bug in Asp.Net MVC where setting any OutputCache VaryByParam | |
// emits a Vary: *, which causes client caches to stop caching entirely, or rather ask for a new version | |
// for any sort of change, which is equivalent to blowing up the cache. | |
// http://stackoverflow.com/a/7612239/176877 | |
// TODO: Verify this change. We've shifted to ETags so ideally Vary: * is just never sent, ever. | |
Response.Cache.SetOmitVaryStar(true); | |
} | |
public ControllerPublic Expose() | |
{ | |
return new ControllerPublic(this); | |
} | |
/// <summary> | |
/// Presents a facade that exposes View, Json, etc as public methods in case a Controller needs to be passed | |
/// around to helpers | |
/// </summary> | |
public class ControllerPublic | |
{ | |
protected BaseController controller; | |
public ControllerPublic(BaseController controller) | |
{ | |
this.controller = controller; | |
} | |
public ViewResult View() | |
{ | |
return controller.View(); | |
} | |
public ViewResult View(string viewName) | |
{ | |
return controller.View(viewName); | |
} | |
public ViewResult View(object model) | |
{ | |
return controller.View(model); | |
} | |
public JsonResult Json(object model) | |
{ | |
return controller.Json(model); | |
} | |
public RedirectResult Redirect(string path) | |
{ | |
return controller.Redirect(path); | |
} | |
public HttpSessionStateBase Session { get { return controller.Session; } } | |
public IPrincipal User { get { return controller.User; } } | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.IO; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Brass9.IO.FileSystemInfoExtension; | |
namespace Brass9.IO | |
{ | |
public static class FileHelper | |
{ | |
public static string ReadFile(FileInfo file) | |
{ | |
using (var reader = file.OpenText()) | |
{ | |
return reader.ReadToEnd(); | |
} | |
} | |
public static IEnumerable<string> ReadLines(string path) | |
{ | |
using (var stream = File.OpenText(path)) | |
{ | |
// Holy fancy enumerators! http://msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx | |
string line = stream.ReadLine(); | |
while (line != null) | |
{ | |
yield return line; | |
line = stream.ReadLine(); | |
} | |
} | |
yield break; | |
} | |
/// <summary> | |
/// Processes the lines of | |
/// </summary> | |
/// <param name="path"></param> | |
/// <param name="processHeader"></param> | |
/// <returns></returns> | |
public static string ReadLinesUntil(string path, Func<string, bool> where) | |
{ | |
foreach (var line in ReadLines(path)) | |
{ | |
if (where(line)) | |
return line; | |
} | |
return null; | |
} | |
public static void WriteFile(FileInfo file, string s) | |
{ | |
WriteFile(file, s, Encoding.UTF8); | |
} | |
public static void WriteFile(FileInfo file, string s, Encoding enc) | |
{ | |
using (var stream = file.OpenWrite()) | |
{ | |
var bytes = enc.GetBytes(s); | |
stream.Write(bytes, 0, bytes.Length); | |
} | |
} | |
/// <summary> | |
/// Reads all text in a file, asynchronously. Doesn't throw for missing files - returns null. | |
/// </summary> | |
/// <param name="path">Full file path</param> | |
/// <returns>Contents of the file, or null if the file doesn't exist.</returns> | |
public static async Task<string> ReadAllTextAsync(string path) | |
{ | |
// If the path is enclosed in double quotes for DOS etc we'll screw up in the OpenText call below - strip if present. | |
if (path.StartsWith("\"") && path.EndsWith("\"")) | |
path = path.Substring(1, path.Length - 2); | |
// http://stackoverflow.com/a/13168006/176877 | |
string s; | |
// Avoid throwing if not exists, especially in async | |
if (!File.Exists(path)) | |
return null; | |
using(var reader = File.OpenText(path)) | |
{ | |
s = await reader.ReadToEndAsync(); | |
} | |
return s; | |
} | |
public static async Task WriteFileAsync(string path, string s) | |
{ | |
await WriteFileAsync(new FileInfo(path), s); | |
} | |
public static async Task WriteFileAsync(FileInfo file, string s) | |
{ | |
await WriteFileAsync(file, s, Encoding.UTF8); | |
} | |
public static async Task WriteFileAsync(FileInfo file, string s, Encoding enc) | |
{ | |
using (var stream = file.OpenWrite()) | |
{ | |
var bytes = enc.GetBytes(s); | |
await stream.WriteAsync(bytes, 0, bytes.Length); | |
} | |
} | |
/// <summary> | |
/// Checks for the directory and its parent directories to ensure they exist; creates it and as many parents as necessary | |
/// if they don't. | |
/// </summary> | |
/// <param name="path">Path for directory to ensure exists.</param> | |
/// <returns>True if the entire path already existed. False if any directories were created.</returns> | |
public static bool EnsureDirectory(string path) | |
{ | |
var dir = new DirectoryInfo(path); | |
bool existed = dir.EnsureDirectory(); | |
return existed; | |
} | |
/// <summary> | |
/// Exactly like File.Move(), but overwrites if the file exists. | |
/// </summary> | |
/// <param name="from"></param> | |
/// <param name="to"></param> | |
public static void Move(string from, string to) | |
{ | |
if (File.Exists(to)) | |
File.Delete(to); | |
File.Move(from, to); | |
} | |
public static void Copy(string from, string to) | |
{ | |
if (from == null) | |
throw new ArgumentNullException("from"); | |
if (from.Equals(to)) | |
return; | |
if (File.Exists(to)) | |
File.Delete(to); | |
File.Copy(from, to); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Converters; | |
using Newtonsoft.Json.Serialization; | |
using Brass9.IO; | |
namespace Brass9.Web.Script.JsonSerialization | |
{ | |
public class JsonNetHelper | |
{ | |
public const string IsoFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; | |
public static JsonNetHelper Create() | |
{ | |
return new JsonNetHelper(); | |
} | |
/// <summary> | |
/// Serialize to a TextWriter, using the fixed Iso DateTime converter, instead of Json.Net's built-in. | |
/// </summary> | |
/// <param name="writer"></param> | |
/// <param name="o"></param> | |
public void Serialize(TextWriter writer, object o) | |
{ | |
var jsonSerializer = getFixedSerializer(); | |
jsonSerializer.Serialize(writer, o); | |
} | |
/// <param name="contractResolver">Used to whitelist properties, or otherwise limit what's serialized</param> | |
public void Serialize(TextWriter writer, object o, IContractResolver contractResolver) | |
{ | |
var jsonSerializer = getFixedSerializer(); | |
jsonSerializer.Serialize(writer, o); | |
} | |
public string SerializeObject(object o) | |
{ | |
var sb = new StringBuilder(); | |
using(var writer = new StringWriter(sb)) | |
{ | |
Serialize(writer, o); | |
} | |
var s = sb.ToString(); | |
return s; | |
} | |
/// <param name="contractResolver">Used to whitelist properties, or otherwise limit what's serialized</param> | |
public string SerializeObject(object o, IContractResolver contractResolver) | |
{ | |
var sb = new StringBuilder(); | |
using(var writer = new StringWriter(sb)) | |
{ | |
Serialize(writer, o, contractResolver); | |
} | |
var s = sb.ToString(); | |
return s; | |
} | |
protected JsonConverter getFixedIsoDateTimeConverter() | |
{ | |
return new IsoDateTimeConverter | |
{ | |
DateTimeFormat = IsoFormat | |
}; | |
} | |
protected JsonSerializer getFixedSerializer() | |
{ | |
var settings = new JsonSerializerSettings | |
{ | |
Converters = new[] { getFixedIsoDateTimeConverter() }, | |
//#if MIN | |
Formatting = Formatting.None | |
//#endif | |
}; | |
var jsonSerializer = JsonSerializer.Create(settings); | |
return jsonSerializer; | |
} | |
/// <param name="contractResolver">Used to whitelist properties, or otherwise limit what's serialized</param> | |
protected JsonSerializer getFixedSerializer(IContractResolver contractResolver) | |
{ | |
var settings = new JsonSerializerSettings | |
{ | |
Converters = new[] { getFixedIsoDateTimeConverter() }, | |
ContractResolver = contractResolver, | |
//#if MIN | |
Formatting = Formatting.None | |
//#endif | |
}; | |
var jsonSerializer = JsonSerializer.Create(settings); | |
return jsonSerializer; | |
} | |
/// <summary> | |
/// Write any object out to a file as JSON, UTF-8 encoded | |
/// </summary> | |
/// <param name="obj"></param> | |
/// <param name="fullFilePath"></param> | |
public void WriteFile(object obj, string fullFilePath) | |
{ | |
using (var writer = new FileStreamWriter(fullFilePath)) | |
{ | |
var jsonEncoder = getFixedSerializer(); | |
jsonEncoder.Serialize(writer, obj); | |
} | |
} | |
public async Task WriteFileAsync(object obj, string fullFilePath) | |
{ | |
string json = JsonConvert.SerializeObject(obj); | |
await FileHelper.WriteFileAsync(fullFilePath, json); | |
} | |
/// <summary> | |
/// Read a UTF-8 encoded JSON file in as an object | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="fullFilePath"></param> | |
/// <returns></returns> | |
public T ReadFile<T>(string fullFilePath) | |
{ | |
using (var reader = File.OpenText(fullFilePath)) | |
{ | |
using (var jsonReader = new JsonTextReader(reader)) | |
{ | |
var jsonEncoder = new JsonSerializer(); | |
return jsonEncoder.Deserialize<T>(jsonReader); | |
} | |
} | |
} | |
public async Task<T> ReadFileAsync<T>(string fullFilePath) | |
{ | |
string json = await FileHelper.ReadAllTextAsync(fullFilePath); | |
T obj = JsonConvert.DeserializeObject<T>(json); | |
return obj; | |
} | |
public T ReadAndDisposeStream<T>(Stream stream) | |
{ | |
// http://stackoverflow.com/a/17788118/176877 | |
var jsonSerializer = new JsonSerializer(); | |
using(var sr = new StreamReader(stream)) | |
{ | |
using(var jsonReader = new JsonTextReader(sr)) | |
{ | |
var result = jsonSerializer.Deserialize<T>(jsonReader); | |
return result; | |
} | |
} | |
} | |
/// <summary> | |
/// Consumes a WebResponse's content as JSON, and returns the resulting object | |
/// </summary> | |
/// <param name="re">Any WebResponse, often the result of await request.GetResponseAsync()</param> | |
public T ReadResponse<T>(System.Net.WebResponse re) | |
{ | |
return ReadAndDisposeStream<T>(re.GetResponseStream()); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using System.Web.Mvc; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Converters; | |
using Brass9.Web.Script.JsonSerialization; | |
namespace Brass9.Web.Mvc.Controllers | |
{ | |
// http://stackoverflow.com/questions/7109967/using-json-net-as-default-json-serializer-in-asp-net-mvc-3-is-it-possible/7150912#7150912 | |
public class JsonNetResult : JsonResult | |
{ | |
// To use, add this to a base Controller | |
// protected override JsonResult Json(object data, string contentType, Encoding contentEncoding) | |
public override void ExecuteResult(ControllerContext context) | |
{ | |
if (context == null) | |
throw new ArgumentNullException("context"); | |
var response = context.HttpContext.Response; | |
response.ContentType = !String.IsNullOrEmpty(ContentType) ? ContentType : "application/json"; | |
if (ContentEncoding != null) | |
response.ContentEncoding = ContentEncoding; | |
JsonNetHelper.Create().Serialize(response.Output, Data); | |
} | |
public static JsonNetResult JsonNet(object o) | |
{ | |
return new JsonNetResult { Data = o }; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment