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 HttpContext Context { get; set; }
public HttpContext Context
{
get
{
return ActionContext.HttpContext;
}
}
public ActionContext ActionContext { get; set; }
public string Index()
{

View File

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

View File

@ -6,15 +6,30 @@ namespace Microsoft.AspNet.Mvc
{
public class Controller
{
public void Initialize(IActionResultHelper actionResultHelper, IModelMetadataProvider metadataProvider)
public void Initialize(IActionResultHelper 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; }

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc
_activator = activator;
}
public object CreateController(ActionContext actionContext, ModelStateDictionary modelState)
public object CreateController(ActionContext actionContext)
{
var actionDescriptor = actionContext.ActionDescriptor as ReflectedActionDescriptor;
if (actionDescriptor == null)
@ -32,7 +32,7 @@ namespace Microsoft.AspNet.Mvc
var controller = _activator.CreateInstance(actionDescriptor.ControllerDescriptor.ControllerTypeInfo.AsType());
// TODO: How do we feed the controller with context (need DI improvements)
InitializeController(controller, actionContext, modelState);
InitializeController(controller, actionContext);
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();
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(
actionContext.HttpContext,

View File

@ -1,11 +1,10 @@

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

View File

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

View File

@ -13,16 +13,32 @@ namespace Microsoft.AspNet.Mvc.Rendering
private 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>();
_metadataProvider = metadataProvider;
}
public ViewData([NotNull] ViewData source)
: this(source.MetadataProvider)
{
_data = source._data;
_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);
}
@ -32,19 +48,15 @@ namespace Microsoft.AspNet.Mvc.Rendering
set { SetModel(value); }
}
public ModelStateDictionary ModelState { get; private set; }
public dynamic this[string index]
{
get
{
dynamic result;
if (_data.TryGetValue(index, out result))
{
result = _data[index];
}
else
{
result = null;
}
_data.TryGetValue(index, out result);
return result;
}
@ -73,6 +85,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
{
get { return _metadataProvider; }
}
public Dictionary<object, dynamic>.Enumerator GetEnumerator()
{
return _data.GetEnumerator();
}
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));
}
public ViewData([NotNull] IModelMetadataProvider metadataProvider, [NotNull] ModelStateDictionary modelState)
: base(metadataProvider, modelState)
{
}
public ViewData(ViewData source)
: base(source)

View File

@ -10,6 +10,7 @@
"net45": {},
"k10": {
"dependencies": {
"Microsoft.CSharp": "4.0.0.0",
"System.Collections": "4.0.0.0",
"System.ComponentModel": "4.0.0.0",
"System.Diagnostics.Contracts": "4.0.0.0",
@ -19,6 +20,7 @@
"System.Globalization": "4.0.10.0",
"System.IO": "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.Extensions": "4.0.0.0",
"System.Resources.ResourceManager": "4.0.0.0",