Re-plumb ModelState.

Modified ModelState to only ever be created on the ActionContext and then plumbed/exposed it on ViewData and Controller.  This will enable: ActionFilterContext's will have access to ModelState via its ActionContext member, allow HTMLHelpers to access ModelState via ViewData, and unify the locations of "source" model state.  In the old world we used to copy/replace/instantiate new model state all over unnecessarily.
This commit is contained in:
N. Taylor Mullen 2014-03-19 16:38:29 -07:00
parent 32d031c6eb
commit 5b6eb307ae
9 changed files with 77 additions and 28 deletions

View File

@ -15,7 +15,15 @@ namespace MvcSample.Web.RandomNameSpace
public IActionResultHelper Result { get; private set; } public IActionResultHelper Result { get; private set; }
public HttpContext Context { get; set; } public HttpContext Context
{
get
{
return ActionContext.HttpContext;
}
}
public ActionContext ActionContext { get; set; }
public string Index() public string Index()
{ {

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -12,6 +13,7 @@ namespace Microsoft.AspNet.Mvc
Router = router; Router = router;
RouteValues = routeValues; RouteValues = routeValues;
ActionDescriptor = actionDescriptor; ActionDescriptor = actionDescriptor;
ModelState = new ModelStateDictionary();
} }
public HttpContext HttpContext { get; private set; } public HttpContext HttpContext { get; private set; }
@ -20,6 +22,8 @@ namespace Microsoft.AspNet.Mvc
public IDictionary<string, object> RouteValues { get; private set; } public IDictionary<string, object> RouteValues { get; private set; }
public ModelStateDictionary ModelState { get; private set; }
public ActionDescriptor ActionDescriptor { get; private set; } public ActionDescriptor ActionDescriptor { get; private set; }
} }
} }

View File

@ -6,15 +6,30 @@ namespace Microsoft.AspNet.Mvc
{ {
public class Controller public class Controller
{ {
public void Initialize(IActionResultHelper actionResultHelper, IModelMetadataProvider metadataProvider) public void Initialize(IActionResultHelper actionResultHelper)
{ {
Result = actionResultHelper; Result = actionResultHelper;
ViewData = new ViewData<object>(metadataProvider);
} }
public IActionResultHelper Result { get; private set; } public HttpContext Context
{
get
{
return ActionContext.HttpContext;
}
}
public HttpContext Context { get; set; } public ModelStateDictionary ModelState
{
get
{
return ViewData.ModelState;
}
}
public ActionContext ActionContext { get; set; }
public IActionResultHelper Result { get; private set; }
public IUrlHelper Url { get; set; } public IUrlHelper Url { get; set; }

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc
_activator = activator; _activator = activator;
} }
public object CreateController(ActionContext actionContext, ModelStateDictionary modelState) public object CreateController(ActionContext actionContext)
{ {
var actionDescriptor = actionContext.ActionDescriptor as ReflectedActionDescriptor; var actionDescriptor = actionContext.ActionDescriptor as ReflectedActionDescriptor;
if (actionDescriptor == null) if (actionDescriptor == null)
@ -32,7 +32,7 @@ namespace Microsoft.AspNet.Mvc
var controller = _activator.CreateInstance(actionDescriptor.ControllerDescriptor.ControllerTypeInfo.AsType()); var controller = _activator.CreateInstance(actionDescriptor.ControllerDescriptor.ControllerTypeInfo.AsType());
// TODO: How do we feed the controller with context (need DI improvements) // TODO: How do we feed the controller with context (need DI improvements)
InitializeController(controller, actionContext, modelState); InitializeController(controller, actionContext);
return controller; return controller;
} }
@ -47,21 +47,21 @@ namespace Microsoft.AspNet.Mvc
{ {
} }
private void InitializeController(object controller, ActionContext actionContext, ModelStateDictionary modelState) private void InitializeController(object controller, ActionContext actionContext)
{ {
var controllerType = controller.GetType(); var controllerType = controller.GetType();
foreach (var prop in controllerType.GetRuntimeProperties()) foreach (var prop in controllerType.GetRuntimeProperties())
{ {
if (prop.Name == "Context" && prop.PropertyType == typeof(HttpContext)) if(prop.Name == "ActionContext" && prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(ActionContext).GetTypeInfo()))
{ {
prop.SetValue(controller, actionContext.HttpContext); prop.SetValue(controller, actionContext);
} }
else if (prop.Name == "ModelState" && prop.PropertyType == typeof(ModelStateDictionary)) else if (prop.Name == "ViewData" && prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(ViewData<object>).GetTypeInfo()))
{ {
prop.SetValue(controller, modelState); prop.SetValue(controller, new ViewData<object>(_serviceProvider.GetService<IModelMetadataProvider>(), actionContext.ModelState));
} }
else if (prop.Name == "Url" && prop.PropertyType == typeof(IUrlHelper)) else if (prop.Name == "Url" && prop.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(IUrlHelper).GetTypeInfo()))
{ {
var urlHelper = new UrlHelper( var urlHelper = new UrlHelper(
actionContext.HttpContext, actionContext.HttpContext,

View File

@ -1,11 +1,10 @@
 
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
public interface IControllerFactory public interface IControllerFactory
{ {
object CreateController(ActionContext actionContext, ModelStateDictionary modelState); object CreateController(ActionContext actionContext);
void ReleaseController(object controller); void ReleaseController(object controller);
} }

View File

@ -50,8 +50,7 @@ namespace Microsoft.AspNet.Mvc
PreArrangeFiltersInPipeline(filterProviderContext); PreArrangeFiltersInPipeline(filterProviderContext);
var modelState = new ModelStateDictionary(); var controller = _controllerFactory.CreateController(_actionContext);
object controller = _controllerFactory.CreateController(_actionContext, modelState);
if (controller == null) if (controller == null)
{ {
@ -96,7 +95,7 @@ namespace Microsoft.AspNet.Mvc
if (actionResult == null) if (actionResult == null)
{ {
var parameterValues = await GetParameterValues(modelState); var parameterValues = await GetParameterValues(_actionContext.ModelState);
var actionFilterContext = new ActionFilterContext(_actionContext, var actionFilterContext = new ActionFilterContext(_actionContext,
filterMetaItems, filterMetaItems,

View File

@ -13,16 +13,32 @@ namespace Microsoft.AspNet.Mvc.Rendering
private IModelMetadataProvider _metadataProvider; private IModelMetadataProvider _metadataProvider;
public ViewData([NotNull] IModelMetadataProvider metadataProvider) public ViewData([NotNull] IModelMetadataProvider metadataProvider)
: this(metadataProvider, new ModelStateDictionary())
{ {
}
public ViewData([NotNull] IModelMetadataProvider metadataProvider, [NotNull] ModelStateDictionary modelState)
{
ModelState = modelState;
_data = new Dictionary<object, dynamic>(); _data = new Dictionary<object, dynamic>();
_metadataProvider = metadataProvider; _metadataProvider = metadataProvider;
} }
public ViewData([NotNull] ViewData source) public ViewData([NotNull] ViewData source)
: this(source.MetadataProvider)
{ {
_data = source._data;
_modelMetadata = source.ModelMetadata; _modelMetadata = source.ModelMetadata;
_metadataProvider = source.MetadataProvider;
foreach (var entry in source.ModelState)
{
ModelState.Add(entry.Key, entry.Value);
}
foreach (var entry in source)
{
_data.Add(entry.Key, entry.Value);
}
SetModel(source.Model); SetModel(source.Model);
} }
@ -32,19 +48,15 @@ namespace Microsoft.AspNet.Mvc.Rendering
set { SetModel(value); } set { SetModel(value); }
} }
public ModelStateDictionary ModelState { get; private set; }
public dynamic this[string index] public dynamic this[string index]
{ {
get get
{ {
dynamic result; dynamic result;
if (_data.TryGetValue(index, out result))
{ _data.TryGetValue(index, out result);
result = _data[index];
}
else
{
result = null;
}
return result; return result;
} }
@ -73,6 +85,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
{ {
get { return _metadataProvider; } get { return _metadataProvider; }
} }
public Dictionary<object, dynamic>.Enumerator GetEnumerator()
{
return _data.GetEnumerator();
}
public override bool TryGetMember(GetMemberBinder binder, out object result) public override bool TryGetMember(GetMemberBinder binder, out object result)
{ {

View File

@ -14,6 +14,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
{ {
_defaultModelMetadata = MetadataProvider.GetMetadataForType(null, typeof(TModel)); _defaultModelMetadata = MetadataProvider.GetMetadataForType(null, typeof(TModel));
} }
public ViewData([NotNull] IModelMetadataProvider metadataProvider, [NotNull] ModelStateDictionary modelState)
: base(metadataProvider, modelState)
{
}
public ViewData(ViewData source) public ViewData(ViewData source)
: base(source) : base(source)

View File

@ -10,6 +10,7 @@
"net45": {}, "net45": {},
"k10": { "k10": {
"dependencies": { "dependencies": {
"Microsoft.CSharp": "4.0.0.0",
"System.Collections": "4.0.0.0", "System.Collections": "4.0.0.0",
"System.ComponentModel": "4.0.0.0", "System.ComponentModel": "4.0.0.0",
"System.Diagnostics.Contracts": "4.0.0.0", "System.Diagnostics.Contracts": "4.0.0.0",
@ -19,6 +20,7 @@
"System.Globalization": "4.0.10.0", "System.Globalization": "4.0.10.0",
"System.IO": "4.0.0.0", "System.IO": "4.0.0.0",
"System.Linq": "4.0.0.0", "System.Linq": "4.0.0.0",
"System.Linq.Expressions": "4.0.0.0",
"System.Reflection": "4.0.10.0", "System.Reflection": "4.0.10.0",
"System.Reflection.Extensions": "4.0.0.0", "System.Reflection.Extensions": "4.0.0.0",
"System.Resources.ResourceManager": "4.0.0.0", "System.Resources.ResourceManager": "4.0.0.0",