Provided a way to add data to ActionDescriptor from ApplicationModel.

- Added Properties to Action, Controller and Application model
 - Added relevant tests
This commit is contained in:
Ajay Bhargav Baaskaran 2015-01-19 18:06:54 -08:00
parent 02f656667f
commit 8e85d53c88
16 changed files with 346 additions and 1 deletions

View File

@ -21,6 +21,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
HttpMethods = new List<string>();
Parameters = new List<ParameterModel>();
RouteConstraints = new List<IRouteConstraintProvider>();
Properties = new Dictionary<object, object>();
}
public ActionModel([NotNull] ActionModel other)
@ -36,6 +37,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
Attributes = new List<object>(other.Attributes);
Filters = new List<IFilter>(other.Filters);
HttpMethods = new List<string>(other.HttpMethods);
Properties = new Dictionary<object, object>(other.Properties);
// Make a deep copy of other 'model' types.
ApiExplorer = new ApiExplorerModel(other.ApiExplorer);
@ -79,5 +81,15 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public IList<ParameterModel> Parameters { get; private set; }
public IList<IRouteConstraintProvider> RouteConstraints { get; private set; }
/// <summary>
/// Gets a set of properties associated with the action.
/// These properties will be copied to <see cref="ActionDescriptor.Properties"/>.
/// </summary>
/// <remarks>
/// Entries will take precedence over entries with the same key in
/// <see cref="ApplicationModel.Properties"/> and <see cref="ControllerModel.Properties"/>.
/// </remarks>
public IDictionary<object, object> Properties { get; }
}
}

View File

@ -12,6 +12,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
ApiExplorer = new ApiExplorerModel();
Controllers = new List<ControllerModel>();
Filters = new List<IFilter>();
Properties = new Dictionary<object, object>();
}
/// <summary>
@ -30,5 +31,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public IList<ControllerModel> Controllers { get; private set; }
public IList<IFilter> Filters { get; private set; }
/// <summary>
/// Gets a set of properties associated with all actions.
/// These properties will be copied to <see cref="ActionDescriptor.Properties"/>.
/// </summary>
public IDictionary<object, object> Properties { get; }
}
}

View File

@ -21,6 +21,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
ActionConstraints = new List<IActionConstraintMetadata>();
Filters = new List<IFilter>();
RouteConstraints = new List<IRouteConstraintProvider>();
Properties = new Dictionary<object, object>();
}
public ControllerModel([NotNull] ControllerModel other)
@ -36,6 +37,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
Attributes = new List<object>(other.Attributes);
Filters = new List<IFilter>(other.Filters);
RouteConstraints = new List<IRouteConstraintProvider>(other.RouteConstraints);
Properties = new Dictionary<object, object>(other.Properties);
// Make a deep copy of other 'model' types.
Actions = new List<ActionModel>(other.Actions.Select(a => new ActionModel(a)));
@ -73,5 +75,15 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public IList<IFilter> Filters { get; private set; }
public IList<IRouteConstraintProvider> RouteConstraints { get; private set; }
/// <summary>
/// Gets a set of properties associated with the controller.
/// These properties will be copied to <see cref="ActionDescriptor.Properties"/>.
/// </summary>
/// <remarks>
/// Entries will take precedence over entries with the same key
/// in <see cref="ApplicationModel.Properties"/>.
/// </remarks>
public IDictionary<object, object> Properties { get; }
}
}

View File

@ -58,6 +58,7 @@ namespace Microsoft.AspNet.Mvc
AddApiExplorerInfo(actionDescriptor, application, controller, action);
AddRouteConstraints(removalConstraints, actionDescriptor, controller, action);
AddProperties(actionDescriptor, action, controller, application);
if (IsAttributeRoutedAction(actionDescriptor))
{
@ -329,6 +330,28 @@ namespace Microsoft.AspNet.Mvc
}
}
private static void AddProperties(
ControllerActionDescriptor actionDescriptor,
ActionModel action,
ControllerModel controller,
ApplicationModel application)
{
foreach (var item in application.Properties)
{
actionDescriptor.Properties[item.Key] = item.Value;
}
foreach (var item in controller.Properties)
{
actionDescriptor.Properties[item.Key] = item.Value;
}
foreach (var item in action.Properties)
{
actionDescriptor.Properties[item.Key] = item.Value;
}
}
private static void AddActionFilters(
ControllerActionDescriptor actionDescriptor,
IEnumerable<IFilter> actionFilters,

View File

@ -33,6 +33,7 @@ namespace Microsoft.AspNet.Mvc.Logging
}
HttpMethods = inner.HttpMethods;
ActionConstraints = inner.ActionConstraints?.Select(a => new ActionConstraintValues(a))?.ToList();
Properties = new Dictionary<object, object>(inner.Properties);
}
}
@ -86,6 +87,11 @@ namespace Microsoft.AspNet.Mvc.Logging
/// </summary>
public IList<ActionConstraintValues> ActionConstraints { get; }
/// <summary>
/// Gets the set of properties associated with the action <see cref="ActionModel.Properties"/>.
/// </summary>
public IDictionary<object, object> Properties { get; }
public override string Format()
{
return LogFormatter.FormatStructure(this);

View File

@ -29,6 +29,7 @@ namespace Microsoft.AspNet.Mvc.Logging
r => new RouteConstraintProviderValues(r)).ToList();
AttributeRoutes = inner.AttributeRoutes.Select(
a => new AttributeRouteModelValues(a)).ToList();
Properties = new Dictionary<object, object>(inner.Properties);
}
}
@ -83,6 +84,11 @@ namespace Microsoft.AspNet.Mvc.Logging
/// </summary>
public List<AttributeRouteModelValues> AttributeRoutes { get; set; }
/// <summary>
/// Gets the set of properties associated with the controller <see cref="ControllerModel.Properties"/>.
/// </summary>
public IDictionary<object, object> Properties { get; }
public override string Format()
{
return LogFormatter.FormatStructure(this);

View File

@ -53,6 +53,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
action.Filters.Add(new AuthorizeAttribute());
action.HttpMethods.Add("GET");
action.RouteConstraints.Add(new AreaAttribute("Admin"));
action.Properties.Add(new KeyValuePair<object, object>("test key", "test value"));
// Act
var action2 = new ActionModel(action);
@ -60,6 +61,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
// Assert
foreach (var property in typeof(ActionModel).GetProperties())
{
// Reflection is used to make sure the test fails when a new property is added.
if (property.Name.Equals("ApiExplorer") ||
property.Name.Equals("AttributeRouteModel") ||
property.Name.Equals("Parameters"))
@ -78,6 +80,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
// Ensure non-default value
Assert.NotEmpty((IEnumerable<object>)value1);
}
else if (typeof(IDictionary<object, object>).IsAssignableFrom(property.PropertyType))
{
Assert.Equal(value1, value2);
// Ensure non-default value
Assert.NotEmpty((IDictionary<object, object>)value1);
}
else if (property.PropertyType.IsValueType ||
Nullable.GetUnderlyingType(property.PropertyType) != null)
{

View File

@ -56,6 +56,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
controller.ControllerName = "cool";
controller.Filters.Add(new AuthorizeAttribute());
controller.RouteConstraints.Add(new AreaAttribute("Admin"));
controller.Properties.Add(new KeyValuePair<object, object>("test key", "test value"));
// Act
var controller2 = new ControllerModel(controller);
@ -81,6 +82,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
// Ensure non-default value
Assert.NotEmpty((IEnumerable<object>)value1);
}
else if (typeof(IDictionary<object, object>).IsAssignableFrom(property.PropertyType))
{
Assert.Equal(value1, value2);
// Ensure non-default value
Assert.NotEmpty((IDictionary<object, object>)value1);
}
else if (property.PropertyType.IsValueType ||
Nullable.GetUnderlyingType(property.PropertyType) != null)
{

View File

@ -0,0 +1,94 @@
// 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.Linq;
using System.Reflection;
using Microsoft.AspNet.Mvc.ApplicationModels;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class ControllerActionDescriptorBuilderTest
{
[Fact]
public void Build_WithPropertiesSet_FromApplicationModel()
{
// Arrange
var applicationModel = new ApplicationModel();
applicationModel.Properties["test"] = "application";
var controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
new List<object>() { });
controller.Application = applicationModel;
applicationModel.Controllers.Add(controller);
var methodInfo = typeof(TestController).GetMethod("SomeAction");
var actionModel = new ActionModel(methodInfo, new List<object>() { });
actionModel.Controller = controller;
controller.Actions.Add(actionModel);
// Act
var descriptors = ControllerActionDescriptorBuilder.Build(applicationModel);
// Assert
Assert.Equal("application", descriptors.Single().Properties["test"]);
}
[Fact]
public void Build_WithPropertiesSet_ControllerOverwritesApplicationModel()
{
// Arrange
var applicationModel = new ApplicationModel();
applicationModel.Properties["test"] = "application";
var controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
new List<object>() { });
controller.Application = applicationModel;
controller.Properties["test"] = "controller";
applicationModel.Controllers.Add(controller);
var methodInfo = typeof(TestController).GetMethod("SomeAction");
var actionModel = new ActionModel(methodInfo, new List<object>() { });
actionModel.Controller = controller;
controller.Actions.Add(actionModel);
// Act
var descriptors = ControllerActionDescriptorBuilder.Build(applicationModel);
// Assert
Assert.Equal("controller", descriptors.Single().Properties["test"]);
}
[Fact]
public void Build_WithPropertiesSet_ActionOverwritesApplicationAndControllerModel()
{
// Arrange
var applicationModel = new ApplicationModel();
applicationModel.Properties["test"] = "application";
var controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
new List<object>() { });
controller.Application = applicationModel;
controller.Properties["test"] = "controller";
applicationModel.Controllers.Add(controller);
var methodInfo = typeof(TestController).GetMethod("SomeAction");
var actionModel = new ActionModel(methodInfo, new List<object>() { });
actionModel.Controller = controller;
actionModel.Properties["test"] = "action";
controller.Actions.Add(actionModel);
// Act
var descriptors = ControllerActionDescriptorBuilder.Build(applicationModel);
// Assert
Assert.Equal("action", descriptors.Single().Properties["test"]);
}
private class TestController
{
public void SomeAction() { }
}
}
}

View File

@ -64,7 +64,57 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("CoolMetadata", body);
}
[Fact]
public async Task ApplicationModel_AddPropertyToActionDescriptor_FromApplicationModel()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/Home/GetCommonDescription");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Common Application Description", body);
}
[Fact]
public async Task ApplicationModel_AddPropertyToActionDescriptor_ControllerModelOverwritesCommonApplicationProperty()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/ApplicationModel/GetControllerDescription");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Common Controller Description", body);
}
[Fact]
public async Task ApplicationModel_ProvidesMetadataToActionDescriptor_ActionModelOverwritesControllerModelProperty()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/ApplicationModel/GetActionSpecificDescription");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Specific Action Description", body);
}
}
}

View File

@ -0,0 +1,25 @@
// 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 Microsoft.AspNet.Mvc;
namespace ApplicationModelWebSite
{
// This controller uses an reflected model attribute to add arbitrary data to controller and action model.
[ControllerDescription("Common Controller Description")]
public class ApplicationModelController : Controller
{
public string GetControllerDescription()
{
var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.Properties["description"].ToString();
}
[ActionDescription("Specific Action Description")]
public string GetActionSpecificDescription()
{
var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.Properties["description"].ToString();
}
}
}

View File

@ -0,0 +1,16 @@
// 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 Microsoft.AspNet.Mvc;
namespace ApplicationModelWebSite
{
public class HomeController : Controller
{
public string GetCommonDescription()
{
var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.Properties["description"].ToString();
}
}
}

View File

@ -0,0 +1,24 @@
// 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;
using Microsoft.AspNet.Mvc.ApplicationModels;
namespace ApplicationModelWebSite
{
public class ActionDescriptionAttribute : Attribute, IActionModelConvention
{
private object _value;
public ActionDescriptionAttribute(object value)
{
_value = value;
}
public void Apply(ActionModel model)
{
model.Properties["description"] = _value;
}
}
}

View File

@ -0,0 +1,23 @@
// 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 Microsoft.AspNet.Mvc.ApplicationModels;
namespace ApplicationModelWebSite
{
public class ApplicationDescription : IApplicationModelConvention
{
private string _description;
public ApplicationDescription(string description)
{
_description = description;
}
public void Apply(ApplicationModel application)
{
application.Properties["description"] = _description;
}
}
}

View File

@ -0,0 +1,24 @@
// 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;
using Microsoft.AspNet.Mvc.ApplicationModels;
namespace ApplicationModelWebSite
{
public class ControllerDescriptionAttribute : Attribute, IControllerModelConvention
{
private object _value;
public ControllerDescriptionAttribute(object value)
{
_value = value;
}
public void Apply(ControllerModel model)
{
model.Properties["description"] = _value;
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.DependencyInjection;
namespace ApplicationModelWebSite
@ -15,6 +16,11 @@ namespace ApplicationModelWebSite
app.UseServices(services =>
{
services.AddMvc(configuration);
services.Configure<MvcOptions>(options =>
{
options.ApplicationModelConventions.Add(new ApplicationDescription("Common Application Description"));
});
});
app.UseMvc(routes =>