ReadOnly attributes for Action, Controller, and Parameter

This commit is contained in:
ianhong 2014-11-13 14:11:25 -08:00
parent 38b3b61485
commit 7b58d569eb
9 changed files with 71 additions and 35 deletions

View File

@ -9,12 +9,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
{
public class ActionModel
{
public ActionModel([NotNull] MethodInfo actionMethod)
public ActionModel([NotNull] MethodInfo actionMethod,
[NotNull] IReadOnlyList<object> attributes)
{
ActionMethod = actionMethod;
ApiExplorer = new ApiExplorerModel();
Attributes = new List<object>();
Attributes = new List<object>(attributes);
ActionConstraints = new List<IActionConstraintMetadata>();
Filters = new List<IFilter>();
HttpMethods = new List<string>();
@ -45,9 +46,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
AttributeRouteModel = new AttributeRouteModel(other.AttributeRouteModel);
}
}
public List<IActionConstraintMetadata> ActionConstraints { get; private set; }
public MethodInfo ActionMethod { get; private set; }
public MethodInfo ActionMethod { get; }
public string ActionName { get; set; }
@ -60,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// </remarks>
public ApiExplorerModel ApiExplorer { get; set; }
public List<object> Attributes { get; private set; }
public IReadOnlyList<object> Attributes { get; }
public ControllerModel Controller { get; set; }

View File

@ -9,13 +9,14 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
{
public class ControllerModel
{
public ControllerModel([NotNull] TypeInfo controllerType)
public ControllerModel([NotNull] TypeInfo controllerType,
[NotNull] IReadOnlyList<object> attributes)
{
ControllerType = controllerType;
Actions = new List<ActionModel>();
ApiExplorer = new ApiExplorerModel();
Attributes = new List<object>();
Attributes = new List<object>(attributes);
AttributeRoutes = new List<AttributeRouteModel>();
ActionConstraints = new List<IActionConstraintMetadata>();
Filters = new List<IFilter>();
@ -56,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public List<AttributeRouteModel> AttributeRoutes { get; private set; }
public List<object> Attributes { get; private set; }
public IReadOnlyList<object> Attributes { get; }
public string ControllerName { get; set; }

View File

@ -213,13 +213,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
[NotNull] MethodInfo methodInfo,
[NotNull] IReadOnlyList<object> attributes)
{
var actionModel = new ActionModel(methodInfo)
var actionModel = new ActionModel(methodInfo, attributes)
{
IsActionNameMatchRequired = true,
};
actionModel.Attributes.AddRange(attributes);
actionModel.ActionConstraints.AddRange(attributes.OfType<IActionConstraintMetadata>());
actionModel.Filters.AddRange(attributes.OfType<IFilter>());
@ -268,12 +266,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// <returns>A <see cref="ParameterModel"/> for the given <see cref="ParameterInfo"/>.</returns>
protected virtual ParameterModel CreateParameterModel([NotNull] ParameterInfo parameterInfo)
{
var parameterModel = new ParameterModel(parameterInfo);
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
// is needed to so that the result of ToArray() is object
var attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
parameterModel.Attributes.AddRange(attributes);
var parameterModel = new ParameterModel(parameterInfo, attributes);
parameterModel.BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();

View File

@ -88,12 +88,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// <returns>A <see cref="ControllerModel"/> for the given <see cref="TypeInfo"/>.</returns>
protected virtual ControllerModel CreateControllerModel([NotNull] TypeInfo typeInfo)
{
var controllerModel = new ControllerModel(typeInfo);
// CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object>
// is needed to so that the result of ToArray() is object
var attributes = typeInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
controllerModel.Attributes.AddRange(attributes);
var controllerModel = new ControllerModel(typeInfo, attributes);
controllerModel.ControllerName =
typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ?

View File

@ -9,11 +9,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
{
public class ParameterModel
{
public ParameterModel(ParameterInfo parameterInfo)
public ParameterModel(ParameterInfo parameterInfo,
IReadOnlyList<object> attributes)
{
ParameterInfo = parameterInfo;
Attributes = new List<object>();
Attributes = new List<object>(attributes);
}
public ParameterModel([NotNull] ParameterModel other)
@ -28,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public ActionModel Action { get; set; }
public List<object> Attributes { get; private set; }
public IReadOnlyList<object> Attributes { get; }
public IBinderMetadata BinderMetadata { get; set; }

View File

@ -14,9 +14,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public void CopyConstructor_DoesDeepCopyOfOtherModels()
{
// Arrange
var action = new ActionModel(typeof(TestController).GetMethod("Edit"));
var action = new ActionModel(typeof(TestController).GetMethod("Edit"),
new List<object>());
var parameter = new ParameterModel(action.ActionMethod.GetParameters()[0]);
var parameter = new ParameterModel(action.ActionMethod.GetParameters()[0],
new List<object>());
parameter.Action = action;
action.Parameters.Add(parameter);
@ -40,13 +42,14 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public void CopyConstructor_CopiesAllProperties()
{
// Arrange
var action = new ActionModel(typeof(TestController).GetMethod("Edit"));
var action = new ActionModel(typeof(TestController).GetMethod("Edit"),
new List<object>() { new HttpGetAttribute() });
action.ActionConstraints.Add(new HttpMethodConstraint(new string[] { "GET" }));
action.ActionName = "Edit";
action.Attributes.Add(new HttpGetAttribute());
action.Controller = new ControllerModel(typeof(TestController).GetTypeInfo());
action.Controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
new List<object>());
action.Filters.Add(new AuthorizeAttribute());
action.HttpMethods.Add("GET");
action.IsActionNameMatchRequired = true;

View File

@ -14,9 +14,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public void CopyConstructor_DoesDeepCopyOfOtherModels()
{
// Arrange
var controller = new ControllerModel(typeof(TestController).GetTypeInfo());
var controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
new List<object>());
var action = new ActionModel(typeof(TestController).GetMethod("Edit"));
var action = new ActionModel(typeof(TestController).GetMethod("Edit"),
new List<object>());
controller.Actions.Add(action);
action.Controller = controller;
@ -46,11 +48,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public void CopyConstructor_CopiesAllProperties()
{
// Arrange
var controller = new ControllerModel(typeof(TestController).GetTypeInfo());
var controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
new List<object>() { new HttpGetAttribute() });
controller.ActionConstraints.Add(new HttpMethodConstraint(new string[] { "GET" }));
controller.Application = new ApplicationModel();
controller.Attributes.Add(new HttpGetAttribute());
controller.ControllerName = "cool";
controller.Filters.Add(new AuthorizeAttribute());
controller.RouteConstraints.Add(new AreaAttribute("Admin"));

View File

@ -14,10 +14,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public void CopyConstructor_CopiesAllProperties()
{
// Arrange
var parameter = new ParameterModel(typeof(TestController).GetMethod("Edit").GetParameters()[0]);
var parameter = new ParameterModel(typeof(TestController).GetMethod("Edit").GetParameters()[0],
new List<object>() { new FromBodyAttribute() });
parameter.Action = new ActionModel(typeof(TestController).GetMethod("Edit"));
parameter.Attributes.Add(new FromBodyAttribute());
parameter.Action = new ActionModel(typeof(TestController).GetMethod("Edit"), new List<object>());
parameter.BinderMetadata = (IBinderMetadata)parameter.Attributes[0];
parameter.IsOptional = true;
parameter.ParameterName = "id";

View File

@ -1136,19 +1136,25 @@ namespace Microsoft.AspNet.Mvc.Test
var options = new MockMvcOptionsAccessor();
options.Options.ApplicationModelConventions.Add(applicationConvention.Object);
var provider = GetProvider(typeof(ConventionsController).GetTypeInfo(), options);
var model = provider.BuildModel();
var controller = model.Controllers.Single();
controller.Attributes.Add(controllerConvention.Object);
model.Controllers.Remove(controller);
controller = BuildControllerModel(controller, controllerConvention.Object);
model.Controllers.Add(controller);
var action = controller.Actions.Single();
action.Attributes.Add(actionConvention.Object);
controller.Actions.Remove(action);
action = BuildActionModel(action, actionConvention.Object);
controller.Actions.Add(action);
var parameter = action.Parameters.Single();
parameter.Attributes.Add(parameterConvention.Object);
action.Parameters.Remove(parameter);
parameter = BuildParameterModel(parameter, parameterConvention.Object);
action.Parameters.Add(parameter);
// Act
ApplicationModelConventions.ApplyConventions(model, options.Options.ApplicationModelConventions);
@ -1300,6 +1306,33 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Single(action.ActionConstraints, a => a is ConstraintAttribute);
}
private ActionModel BuildActionModel(ActionModel action, IActionModelConvention actionConvention)
{
var t = new ActionModel(action.ActionMethod,
new List<object>(action.Attributes) { actionConvention });
t.Parameters.AddRange(action.Parameters);
return t;
}
private ParameterModel BuildParameterModel(ParameterModel parameter, IParameterModelConvention parameterConvention)
{
var t = new ParameterModel(parameter.ParameterInfo,
new List<object>(parameter.Attributes) { parameterConvention });
t.Action = parameter.Action;
return t;
}
private ControllerModel BuildControllerModel(ControllerModel controller, IControllerModelConvention controllerConvention)
{
var t = new ControllerModel(controller.ControllerType,
new List<object>(controller.Attributes) { controllerConvention });
t.Actions.AddRange(controller.Actions);
return t;
}
private ControllerActionDescriptorProvider GetProvider(
TypeInfo controllerTypeInfo,
IEnumerable<IFilter> filters = null)