Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Adding support for model binding specifically marked controller prope…
Browse files Browse the repository at this point in the history
…rties.
  • Loading branch information
harshgMSFT committed Mar 21, 2015
1 parent c082d4a commit adeb1ba
Show file tree
Hide file tree
Showing 42 changed files with 958 additions and 280 deletions.
5 changes: 5 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/ActionDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public ActionDescriptor()

public IList<ParameterDescriptor> Parameters { get; set; }

/// <summary>
/// The set of properties which are model bound.
/// </summary>
public IList<ParameterDescriptor> BoundProperties { get; set; }

public IList<FilterDescriptor> FilterDescriptors { get; set; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public ControllerModel([NotNull] TypeInfo controllerType,
Filters = new List<IFilter>();
RouteConstraints = new List<IRouteConstraintProvider>();
Properties = new Dictionary<object, object>();
ControllerProperties = new List<PropertyModel>();
}

public ControllerModel([NotNull] ControllerModel other)
Expand All @@ -48,6 +49,8 @@ public ControllerModel([NotNull] ControllerModel other)
ApiExplorer = new ApiExplorerModel(other.ApiExplorer);
AttributeRoutes = new List<AttributeRouteModel>(
other.AttributeRoutes.Select(a => new AttributeRouteModel(a)));
ControllerProperties =
new List<PropertyModel>(other.ControllerProperties.Select(p => new PropertyModel(p)));
}

public IList<IActionConstraintMetadata> ActionConstraints { get; private set; }
Expand Down Expand Up @@ -76,6 +79,8 @@ public ControllerModel([NotNull] ControllerModel other)

public TypeInfo ControllerType { get; private set; }

public IList<PropertyModel> ControllerProperties { get; }

public IList<IFilter> Filters { get; private set; }

public IList<IRouteConstraintProvider> RouteConstraints { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNet.Cors.Core;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
Expand Down Expand Up @@ -44,8 +45,9 @@ public DefaultControllerModelBuilder(
public ControllerModel BuildControllerModel([NotNull] TypeInfo typeInfo)
{
var controllerModel = CreateControllerModel(typeInfo);
var controllerType = typeInfo.AsType();

foreach (var methodInfo in typeInfo.AsType().GetMethods())
foreach (var methodInfo in controllerType.GetMethods())
{
var actionModels = _actionModelBuilder.BuildActionModels(typeInfo, methodInfo);
if (actionModels != null)
Expand All @@ -58,6 +60,17 @@ public ControllerModel BuildControllerModel([NotNull] TypeInfo typeInfo)
}
}

foreach (var propertyHelper in PropertyHelper.GetProperties(controllerType))
{
var propertyInfo = propertyHelper.Property;
var propertyModel = CreatePropertyModel(propertyInfo);
if (propertyModel != null)
{
propertyModel.Controller = controllerModel;
controllerModel.ControllerProperties.Add(propertyModel);
}
}

return controllerModel;
}

Expand Down Expand Up @@ -134,6 +147,25 @@ protected virtual ControllerModel CreateControllerModel([NotNull] TypeInfo typeI
return controllerModel;
}

/// <summary>
/// Creates a <see cref="PropertyModel"/> for the given <see cref="PropertyInfo"/>.
/// </summary>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/>.</param>
/// <returns>A <see cref="PropertyModel"/> for the given <see cref="PropertyInfo"/>.</returns>
protected virtual PropertyModel CreatePropertyModel([NotNull] PropertyInfo propertyInfo)
{
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
// is needed to so that the result of ToArray() is object
var attributes = propertyInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
var propertyModel = new PropertyModel(propertyInfo, attributes);
var bindingInfo = BindingInfo.GetBindingInfo(attributes);

propertyModel.BindingInfo = bindingInfo;
propertyModel.PropertyName = propertyInfo.Name;

return propertyModel;
}

private static void AddRange<T>(IList<T> list, IEnumerable<T> items)
{
foreach (var item in items)
Expand Down
69 changes: 69 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/ApplicationModels/PropertyModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.Internal;

namespace Microsoft.AspNet.Mvc.ApplicationModels
{
/// <summary>
/// A type which is used to represent a property in a <see cref="ControllerModel"/>.
/// </summary>
[DebuggerDisplay("PropertyModel: Name={PropertyName}")]
public class PropertyModel
{
/// <summary>
/// Creates a new instance of <see cref="PropertyModel"/>.
/// </summary>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> for the underlying property.</param>
/// <param name="attributes">Any attributes which are annotated on the property.</param>
public PropertyModel([NotNull] PropertyInfo propertyInfo,
[NotNull] IReadOnlyList<object> attributes)
{
PropertyInfo = propertyInfo;

Attributes = new List<object>(attributes);
}

/// <summary>
/// Creats a new instance of <see cref="PropertyModel"/> from a given <see cref="PropertyModel"/>.
/// </summary>
/// <param name="other">The <see cref="PropertyModel"/> which needs to be copied.</param>
public PropertyModel([NotNull] PropertyModel other)
{
Controller = other.Controller;
Attributes = new List<object>(other.Attributes);
BindingInfo = other.BindingInfo;
PropertyInfo = other.PropertyInfo;
PropertyName = other.PropertyName;
}

/// <summary>
/// Gets or sets the <see cref="ControllerModel"/> this <see cref="PropertyModel"/> is associated with.
/// </summary>
public ControllerModel Controller { get; set; }

/// <summary>
/// Gets any attributes which are annotated on the property.
/// </summary>
public IReadOnlyList<object> Attributes { get; }

/// <summary>
/// Gets or sets the <see cref="BindingInfo"/> associated with this model.
/// </summary>
public BindingInfo BindingInfo { get; set; }

/// <summary>
/// Gets the underlying <see cref="PropertyInfo"/>.
/// </summary>
public PropertyInfo PropertyInfo { get; }

/// <summary>
/// Gets or sets the name of the property represented by this model.
/// </summary>
public string PropertyName { get; set; }
}
}
7 changes: 4 additions & 3 deletions src/Microsoft.AspNet.Mvc.Core/Controller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1293,9 +1293,10 @@ public virtual bool TryValidateModel([NotNull] object model, string prefix)

var validationContext = new ModelValidationContext(
modelName,
BindingContext.ValidatorProvider,
ModelState,
modelExplorer);
bindingSource: null,
validatorProvider: BindingContext.ValidatorProvider,
modelState: ModelState,
modelExplorer: modelExplorer);

ObjectValidator.Validate(validationContext);
return ModelState.IsValid;
Expand Down
28 changes: 24 additions & 4 deletions src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.ModelBinding;

namespace Microsoft.AspNet.Mvc
{
Expand Down Expand Up @@ -42,6 +43,12 @@ public static IList<ControllerActionDescriptor> Build(ApplicationModel applicati

foreach (var controller in application.Controllers)
{
// Only add properties which are explictly marked to bind.
// The attribute check is required for ModelBinder attribute.
var controllerPropertyDescriptors = controller.ControllerProperties
.Where(p => p.BindingInfo != null)
.Select(CreateParameterDescriptor)
.ToList();
foreach (var action in controller.Actions)
{
// Controllers with multiple [Route] attributes (or user defined implementation of
Expand All @@ -60,6 +67,7 @@ public static IList<ControllerActionDescriptor> Build(ApplicationModel applicati
AddRouteConstraints(removalConstraints, actionDescriptor, controller, action);
AddProperties(actionDescriptor, action, controller, application);

actionDescriptor.BoundProperties = controllerPropertyDescriptors;
if (IsAttributeRoutedAction(actionDescriptor))
{
hasAttributeRoutes = true;
Expand Down Expand Up @@ -272,13 +280,25 @@ private static ControllerActionDescriptor CreateActionDescriptor(
return actionDescriptor;
}

private static ParameterDescriptor CreateParameterDescriptor(ParameterModel parameter)
private static ParameterDescriptor CreateParameterDescriptor(ParameterModel parameterModel)
{
var parameterDescriptor = new ParameterDescriptor()
{
Name = parameter.ParameterName,
ParameterType = parameter.ParameterInfo.ParameterType,
BindingInfo = parameter.BindingInfo
Name = parameterModel.ParameterName,
ParameterType = parameterModel.ParameterInfo.ParameterType,
BindingInfo = parameterModel.BindingInfo
};

return parameterDescriptor;
}

private static ParameterDescriptor CreateParameterDescriptor(PropertyModel propertyModel)
{
var parameterDescriptor = new ParameterDescriptor()
{
BindingInfo = propertyModel.BindingInfo,
Name = propertyModel.PropertyName,
ParameterType = propertyModel.PropertyInfo.PropertyType,
};

return parameterDescriptor;
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ protected override async Task<IActionResult> InvokeActionAsync(ActionExecutingCo
return actionResult;
}

protected override Task<IDictionary<string, object>> GetActionArgumentsAsync(
protected override Task<IDictionary<string, object>> BindActionArgumentsAsync(
ActionContext context,
ActionBindingContext bindingContext)
{
return _argumentBinder.GetActionArgumentsAsync(context, bindingContext);
return _argumentBinder.BindActionArgumentsAsync(context, bindingContext, Instance);
}

// Marking as internal for Unit Testing purposes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal;
using Microsoft.Framework.OptionsModel;

namespace Microsoft.AspNet.Mvc
Expand All @@ -34,9 +35,10 @@ public DefaultControllerActionArgumentBinder(
_validator = validator;
}

public async Task<IDictionary<string, object>> GetActionArgumentsAsync(
public async Task<IDictionary<string, object>> BindActionArgumentsAsync(
ActionContext actionContext,
ActionBindingContext actionBindingContext)
ActionBindingContext actionBindingContext,
object controller)
{
var actionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor;
if (actionDescriptor == null)
Expand All @@ -47,31 +49,48 @@ public async Task<IDictionary<string, object>> GetActionArgumentsAsync(
nameof(actionContext));
}

var operationBindingContext = GetOperationBindingContext(actionContext, actionBindingContext);
var controllerProperties = new Dictionary<string, object>(StringComparer.Ordinal);
await PopulateArgumentsAsync(
operationBindingContext,
actionContext.ModelState,
controllerProperties,
actionDescriptor.BoundProperties);
var controllerType = actionDescriptor.ControllerTypeInfo.AsType();
ActivateProperties(controller, controllerType, controllerProperties);

var actionArguments = new Dictionary<string, object>(StringComparer.Ordinal);
await PopulateArgumentsAsync(
actionContext,
actionBindingContext,
operationBindingContext,
actionContext.ModelState,
actionArguments,
actionDescriptor.Parameters);
return actionArguments;
}

private void ActivateProperties(object controller, Type containerType, Dictionary<string, object> properties)
{
var propertyHelpers = PropertyHelper.GetProperties(controller);
foreach (var property in properties)
{
var propertyHelper = propertyHelpers.First(helper => helper.Name == property.Key);
if (propertyHelper.Property == null || !propertyHelper.Property.CanWrite)
{
// nothing to do
return;
}

var setter = PropertyHelper.MakeFastPropertySetter(propertyHelper.Property);
setter(controller, property.Value);
}
}

private async Task PopulateArgumentsAsync(
ActionContext actionContext,
ActionBindingContext bindingContext,
OperationBindingContext operationContext,
ModelStateDictionary modelState,
IDictionary<string, object> arguments,
IEnumerable<ParameterDescriptor> parameterMetadata)
{
var operationBindingContext = new OperationBindingContext
{
ModelBinder = bindingContext.ModelBinder,
ValidatorProvider = bindingContext.ValidatorProvider,
MetadataProvider = _modelMetadataProvider,
HttpContext = actionContext.HttpContext,
ValueProvider = bindingContext.ValueProvider,
};

var modelState = actionContext.ModelState;
modelState.MaxAllowedErrors = _options.MaxModelValidationErrors;
foreach (var parameter in parameterMetadata)
{
Expand All @@ -82,9 +101,9 @@ private async Task PopulateArgumentsAsync(
metadata,
parameter.BindingInfo,
modelState,
operationBindingContext);
operationContext);

var modelBindingResult = await bindingContext.ModelBinder.BindModelAsync(modelBindingContext);
var modelBindingResult = await operationContext.ModelBinder.BindModelAsync(modelBindingContext);
if (modelBindingResult != null && modelBindingResult.IsModelSet)
{
var modelExplorer = new ModelExplorer(
Expand All @@ -95,8 +114,9 @@ private async Task PopulateArgumentsAsync(
arguments[parameter.Name] = modelBindingResult.Model;
var validationContext = new ModelValidationContext(
modelBindingResult.Key,
bindingContext.ValidatorProvider,
actionContext.ModelState,
modelBindingContext.BindingSource,
operationContext.ValidatorProvider,
modelState,
modelExplorer);
_validator.Validate(validationContext);
}
Expand All @@ -121,5 +141,19 @@ private static ModelBindingContext GetModelBindingContext(

return modelBindingContext;
}

private OperationBindingContext GetOperationBindingContext(
ActionContext actionContext,
ActionBindingContext bindingContext)
{
return new OperationBindingContext
{
ModelBinder = bindingContext.ModelBinder,
ValidatorProvider = bindingContext.ValidatorProvider,
MetadataProvider = _modelMetadataProvider,
HttpContext = actionContext.HttpContext,
ValueProvider = bindingContext.ValueProvider,
};
}
}
}
Loading

0 comments on commit adeb1ba

Please sign in to comment.