Fix for #1192 - Support customizing reflected model through attributes

This adds support for attributes which interact with reflected model.
These conventions are applied after all of our built-in constructs so that
you can see and modify the results.
This commit is contained in:
Ryan Nowak 2014-09-26 16:18:42 -07:00
parent 6c49d0b40e
commit d8995a7767
27 changed files with 549 additions and 52 deletions

13
Mvc.sln
View File

@ -82,6 +82,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UrlHelperWebSite", "test\We
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiExplorerWebSite", "test\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.kproj", "{61061528-071E-424E-965A-07BCC2F02672}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ReflectedModelWebSite", "test\WebSites\ReflectedModelWebSite\ReflectedModelWebSite.kproj", "{C2EF54F8-8886-4260-A322-44F76245F95D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -422,6 +424,16 @@ Global
{61061528-071E-424E-965A-07BCC2F02672}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{61061528-071E-424E-965A-07BCC2F02672}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{61061528-071E-424E-965A-07BCC2F02672}.Release|x86.ActiveCfg = Release|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Debug|x86.ActiveCfg = Debug|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Any CPU.Build.0 = Release|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C2EF54F8-8886-4260-A322-44F76245F95D}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -461,5 +473,6 @@ Global
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{A192E504-2881-41DC-90D1-B7F1DD1134E8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{61061528-071E-424E-965A-07BCC2F02672} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{C2EF54F8-8886-4260-A322-44F76245F95D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,31 @@
// 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.ReflectedModelBuilder;
namespace MvcSample.Web
{
// Adds an auto-generated route-name to each action in the controller
public class AutoGenerateRouteNamesAttribute : Attribute, IReflectedControllerModelConvention
{
public void Apply(ReflectedControllerModel model)
{
foreach (var action in model.Actions)
{
if (action.AttributeRouteModel == null)
{
action.AttributeRouteModel = new ReflectedAttributeRouteModel();
}
if (action.AttributeRouteModel.Name == null)
{
action.AttributeRouteModel.Name = string.Format(
"{0}_{1}",
model.ControllerName,
action.ActionName);
}
}
}
}
}

View File

@ -2,6 +2,7 @@ using Microsoft.AspNet.Mvc;
namespace MvcSample.Web
{
[AutoGenerateRouteNames]
[Route("api/REST")]
public class SimpleRest : Controller
{
@ -23,5 +24,13 @@ namespace MvcSample.Web
{
return Url.Action(action, controller);
}
[HttpGet("Link/{name}")]
public string GenerateLinkByName(string name = null)
{
// This action leverages [AutoGenerateRouteNames]. Try a URL like api/Rest/Link/SimpleRest_ThisIsAGetMethod
// which matches the auto-generated name for the ThisIsAGetMethod action.
return Url.RouteUrl(name);
}
}
}

View File

@ -8,22 +8,8 @@ namespace Microsoft.AspNet.Mvc
{
public class ControllerDescriptor
{
public ControllerDescriptor(TypeInfo controllerTypeInfo)
{
if (controllerTypeInfo == null)
{
throw new ArgumentNullException("controllerTypeInfo");
}
public string Name { get; set; }
ControllerTypeInfo = controllerTypeInfo;
Name = controllerTypeInfo.Name.EndsWith("Controller", StringComparison.Ordinal)
? controllerTypeInfo.Name.Substring(0, controllerTypeInfo.Name.Length - "Controller".Length)
: controllerTypeInfo.Name;
}
public string Name { get; private set; }
public TypeInfo ControllerTypeInfo { get; private set; }
public TypeInfo ControllerTypeInfo { get; set; }
}
}

View File

@ -31,19 +31,16 @@ namespace Microsoft.AspNet.Mvc
private readonly IActionDiscoveryConventions _conventions;
private readonly IReadOnlyList<IFilter> _globalFilters;
private readonly IEnumerable<IReflectedApplicationModelConvention> _modelConventions;
private readonly IInlineConstraintResolver _constraintResolver;
public ReflectedActionDescriptorProvider(IControllerAssemblyProvider controllerAssemblyProvider,
IActionDiscoveryConventions conventions,
IGlobalFilterProvider globalFilters,
IOptionsAccessor<MvcOptions> optionsAccessor,
IInlineConstraintResolver constraintResolver)
IOptionsAccessor<MvcOptions> optionsAccessor)
{
_controllerAssemblyProvider = controllerAssemblyProvider;
_conventions = conventions;
_globalFilters = globalFilters.Filters;
_modelConventions = optionsAccessor.Options.ApplicationModelConventions;
_constraintResolver = constraintResolver;
}
public int Order
@ -60,12 +57,7 @@ namespace Microsoft.AspNet.Mvc
public IEnumerable<ReflectedActionDescriptor> GetDescriptors()
{
var model = BuildModel();
foreach (var convention in _modelConventions)
{
convention.OnModelCreated(model);
}
ApplyConventions(model);
return Build(model);
}
@ -80,7 +72,11 @@ namespace Microsoft.AspNet.Mvc
foreach (var controllerType in controllerTypes)
{
var controllerModel = new ReflectedControllerModel(controllerType);
var controllerModel = new ReflectedControllerModel(controllerType)
{
Application = applicationModel,
};
applicationModel.Controllers.Add(controllerModel);
foreach (var methodInfo in controllerType.AsType().GetMethods())
@ -93,10 +89,13 @@ namespace Microsoft.AspNet.Mvc
foreach (var actionInfo in actionInfos)
{
var actionModel = new ReflectedActionModel(methodInfo);
var actionModel = new ReflectedActionModel(methodInfo)
{
ActionName = actionInfo.ActionName,
Controller = controllerModel,
IsActionNameMatchRequired = actionInfo.RequireActionNameMatch,
};
actionModel.ActionName = actionInfo.ActionName;
actionModel.IsActionNameMatchRequired = actionInfo.RequireActionNameMatch;
actionModel.HttpMethods.AddRange(actionInfo.HttpMethods ?? Enumerable.Empty<string>());
if (actionInfo.AttributeRoute != null)
@ -107,7 +106,10 @@ namespace Microsoft.AspNet.Mvc
foreach (var parameter in methodInfo.GetParameters())
{
actionModel.Parameters.Add(new ReflectedParameterModel(parameter));
actionModel.Parameters.Add(new ReflectedParameterModel(parameter)
{
Action = actionModel,
});
}
controllerModel.Actions.Add(actionModel);
@ -118,6 +120,62 @@ namespace Microsoft.AspNet.Mvc
return applicationModel;
}
public void ApplyConventions(ReflectedApplicationModel model)
{
// Conventions are applied from the outside-in to allow for scenarios where an action overrides
// a controller, etc.
foreach (var convention in _modelConventions)
{
convention.Apply(model);
}
// First apply the conventions from attributes in decreasing order of scope.
foreach (var controller in model.Controllers)
{
// ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it.
var controllerConventions =
controller.Attributes
.OfType<IReflectedControllerModelConvention>()
.ToArray();
foreach (var controllerConvention in controllerConventions)
{
controllerConvention.Apply(controller);
}
foreach (var action in controller.Actions)
{
// ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it.
var actionConventions =
action.Attributes
.OfType<IReflectedActionModelConvention>()
.ToArray();
foreach (var actionConvention in actionConventions)
{
actionConvention.Apply(action);
}
foreach (var parameter in action.Parameters)
{
// ToArray is needed here to prevent issues with modifying the attributes collection
// while iterating it.
var parameterConventions =
parameter.Attributes
.OfType<IReflectedParameterModelConvention>()
.ToArray();
foreach (var parameterConvention in parameterConventions)
{
parameterConvention.Apply(parameter);
}
}
}
}
}
public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model)
{
var actions = new List<ReflectedActionDescriptor>();
@ -132,7 +190,12 @@ namespace Microsoft.AspNet.Mvc
foreach (var controller in model.Controllers)
{
var controllerDescriptor = new ControllerDescriptor(controller.ControllerType);
var controllerDescriptor = new ControllerDescriptor()
{
ControllerTypeInfo = controller.ControllerType,
Name = controller.ControllerName,
};
foreach (var action in controller.Actions)
{
// Controllers with multiple [Route] attributes (or user defined implementation of
@ -351,7 +414,7 @@ namespace Microsoft.AspNet.Mvc
private static void AddApiExplorerInfo(
ReflectedActionDescriptor actionDescriptor,
ReflectedActionModel action,
ReflectedActionModel action,
ReflectedControllerModel controller)
{
var apiExplorerIsVisible = action.ApiExplorerIsVisible ?? controller.ApiExplorerIsVisible ?? false;
@ -360,7 +423,7 @@ namespace Microsoft.AspNet.Mvc
var apiExplorerActionData = new ApiDescriptionActionData()
{
GroupName = action.ApiExplorerGroupName ?? controller.ApiExplorerGroupName,
};
};
actionDescriptor.SetProperty(apiExplorerActionData);
}

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.
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
/// <summary>
/// Allows customization of the of the <see cref="ReflectedActionModel"/>.
/// </summary>
/// <remarks>
/// To use this interface, create an <see cref="System.Attribute"/> class which implements the interface and
/// place it on an action method.
///
/// <see cref="IReflectedActionModelConvention"/> customizations run after
/// <see cref="IReflectedActionModelConvention"/> customications and before
/// <see cref="IReflectedParameterModelConvention"/> customizations.
/// </remarks>
public interface IReflectedActionModelConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="ReflectedActionModel"/>.
/// </summary>
/// <param name="model">The <see cref="ReflectedActionModel"/>.</param>
void Apply([NotNull] ReflectedActionModel model);
}
}

View File

@ -3,8 +3,22 @@
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
/// <summary>
/// Allows customization of the of the <see cref="ReflectedApplicationModel"/>.
/// </summary>
/// <remarks>
/// Implementaions of this interface can be registered in <see cref="MvcOptions.ApplicationModelConventions"/>
/// to customize metadata about the application.
///
/// <see cref="IReflectedApplicationModelConvention"/> run before other types of customizations to the
/// reflected model.
/// </remarks>
public interface IReflectedApplicationModelConvention
{
void OnModelCreated([NotNull] ReflectedApplicationModel model);
/// <summary>
/// Called to apply the convention to the <see cref="ReflectedApplicationModel"/>.
/// </summary>
/// <param name="model">The <see cref="ReflectedApplicationModel"/>.</param>
void Apply([NotNull] ReflectedApplicationModel model);
}
}

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.
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
/// <summary>
/// Allows customization of the of the <see cref="ReflectedControllerModel"/>.
/// </summary>
/// <remarks>
/// To use this interface, create an <see cref="System.Attribute"/> class which implements the interface and
/// place it on a controller class.
///
/// <see cref="IReflectedControllerModelConvention"/> customizations run after
/// <see cref="IReflectedApplicationModelConvention"/> customizations and before
/// <see cref="IReflectedActionModelConvention"/> customizations.
/// </remarks>
public interface IReflectedControllerModelConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="ReflectedControllerModel"/>.
/// </summary>
/// <param name="model">The <see cref="ReflectedControllerModel"/>.</param>
void Apply([NotNull] ReflectedControllerModel model);
}
}

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.
namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
{
/// <summary>
/// Allows customization of the of the <see cref="ReflectedControllerModel"/>.
/// </summary>
/// <remarks>
/// To use this interface, create an <see cref="System.Attribute"/> class which implements the interface and
/// place it on an action method parameter.
///
/// <see cref="IReflectedParameterModelConvention"/> customizations run after
/// <see cref="IReflectedActionModelConvention"/> customizations.
/// </remarks>
public interface IReflectedParameterModelConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="ReflectedParameterModel"/>.
/// </summary>
/// <param name="model">The <see cref="ReflectedParameterModel"/>.</param>
void Apply([NotNull] ReflectedParameterModel model);
}
}

View File

@ -51,6 +51,8 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
public List<object> Attributes { get; private set; }
public ReflectedControllerModel Controller { get; set; }
public List<IFilter> Filters { get; private set; }
public List<string> HttpMethods { get; private set; }

View File

@ -51,6 +51,8 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
public List<ReflectedActionModel> Actions { get; private set; }
public ReflectedApplicationModel Application { get; set; }
public List<object> Attributes { get; private set; }
public string ControllerName { get; set; }

View File

@ -21,6 +21,8 @@ namespace Microsoft.AspNet.Mvc.ReflectedModelBuilder
IsOptional = ParameterInfo.HasDefaultValue;
}
public ReflectedActionModel Action { get; set; }
public List<object> Attributes { get; private set; }
public bool IsOptional { get; set; }

View File

@ -227,8 +227,7 @@ namespace Microsoft.AspNet.Mvc
controllerAssemblyProvider,
actionDiscoveryConventions,
new TestGlobalFilterProvider(),
new MockMvcOptionsAccessor(),
Mock.Of<IInlineConstraintResolver>());
new MockMvcOptionsAccessor());
}
private static HttpContext GetHttpContext(string httpMethod)

View File

@ -197,8 +197,7 @@ namespace Microsoft.AspNet.Mvc
controllerAssemblyProvider.Object,
actionDiscoveryConventions,
new TestGlobalFilterProvider(),
new MockMvcOptionsAccessor(),
Mock.Of<IInlineConstraintResolver>());
new MockMvcOptionsAccessor());
}
private static HttpContext GetHttpContext(string httpMethod)

View File

@ -5,12 +5,12 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
using Microsoft.Framework.OptionsModel;
using Moq;
using Xunit;
using Microsoft.AspNet.Mvc.Filters;
namespace Microsoft.AspNet.Mvc.Test
{
@ -1130,6 +1130,56 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal("Store", action.GetProperty<ApiDescriptionActionData>().GroupName);
}
// Verifies the sequence of conventions running
[Fact]
public void ApplyConventions_RunsInOrderOfDecreasingScope()
{
// Arrange
var sequence = 0;
var applicationConvention = new Mock<IReflectedApplicationModelConvention>();
applicationConvention
.Setup(c => c.Apply(It.IsAny<ReflectedApplicationModel>()))
.Callback(() => { Assert.Equal(0, sequence++); });
var controllerConvention = new Mock<IReflectedControllerModelConvention>();
controllerConvention
.Setup(c => c.Apply(It.IsAny<ReflectedControllerModel>()))
.Callback(() => { Assert.Equal(1, sequence++); });
var actionConvention = new Mock<IReflectedActionModelConvention>();
actionConvention
.Setup(c => c.Apply(It.IsAny<ReflectedActionModel>()))
.Callback(() => { Assert.Equal(2, sequence++); });
var parameterConvention = new Mock<IReflectedParameterModelConvention>();
parameterConvention
.Setup(c => c.Apply(It.IsAny<ReflectedParameterModel>()))
.Callback(() => { Assert.Equal(3, sequence++); });
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);
var action = controller.Actions.Single();
action.Attributes.Add(actionConvention.Object);
var parameter = action.Parameters.Single();
parameter.Attributes.Add(parameterConvention.Object);
// Act
provider.ApplyConventions(model);
// Assert
Assert.Equal(4, sequence);
}
private ReflectedActionDescriptorProvider GetProvider(
TypeInfo controllerTypeInfo,
IEnumerable<IFilter> filters = null)
@ -1145,8 +1195,7 @@ namespace Microsoft.AspNet.Mvc.Test
assemblyProvider.Object,
conventions,
new TestGlobalFilterProvider(filters),
new MockMvcOptionsAccessor(),
Mock.Of<IInlineConstraintResolver>());
new MockMvcOptionsAccessor());
return provider;
}
@ -1165,12 +1214,29 @@ namespace Microsoft.AspNet.Mvc.Test
assemblyProvider.Object,
conventions,
new TestGlobalFilterProvider(),
new MockMvcOptionsAccessor(),
Mock.Of<IInlineConstraintResolver>());
new MockMvcOptionsAccessor());
return provider;
}
private ReflectedActionDescriptorProvider GetProvider(
TypeInfo type,
IOptionsAccessor<MvcOptions> options)
{
var conventions = new StaticActionDiscoveryConventions(type);
var assemblyProvider = new Mock<IControllerAssemblyProvider>();
assemblyProvider
.SetupGet(ap => ap.CandidateAssemblies)
.Returns(new Assembly[] { type.Assembly });
return new ReflectedActionDescriptorProvider(
assemblyProvider.Object,
conventions,
new TestGlobalFilterProvider(),
options);
}
private IEnumerable<ActionDescriptor> GetDescriptors(params TypeInfo[] controllerTypeInfos)
{
var conventions = new StaticActionDiscoveryConventions(controllerTypeInfos);
@ -1184,8 +1250,7 @@ namespace Microsoft.AspNet.Mvc.Test
assemblyProvider.Object,
conventions,
new TestGlobalFilterProvider(),
new MockMvcOptionsAccessor(),
null);
new MockMvcOptionsAccessor());
return provider.GetDescriptors();
}
@ -1588,5 +1653,10 @@ namespace Microsoft.AspNet.Mvc.Test
public void Create() { }
}
private class ConventionsController
{
public void Create(int productId) { }
}
}
}

View File

@ -0,0 +1,70 @@
// 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 System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost;
using Xunit;
using System.Net;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class ReflectedModelTest
{
private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(ReflectedModelWebSite));
private readonly Action<IApplicationBuilder> _app = new ReflectedModelWebSite.Startup().Configure;
[Fact]
public async Task ControllerModel_CustomizedWithAttribute()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/CoolController/GetControllerName");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("CoolController", body);
}
[Fact]
public async Task ActionModel_CustomizedWithAttribute()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/ReflectedActionModel/ActionName");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("ActionName", body);
}
[Fact]
public async Task ParameterModel_CustomizedWithAttribute()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/ReflectedParameterModel/GetParameterIsOptional");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("True", body);
}
}
}

View File

@ -24,6 +24,7 @@
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
"ModelBindingWebSite": "",
"MvcSample.Web": "",
"ReflectedModelWebSite": "",
"RoutingWebSite": "",
"RazorWebSite": "",
"ValueProvidersSite": "",

View File

@ -19,7 +19,7 @@ namespace ApiExplorer
_type = type.GetTypeInfo();
}
public void OnModelCreated(ReflectedApplicationModel model)
public void Apply(ReflectedApplicationModel model)
{
foreach (var controller in model.Controllers)
{

View File

@ -10,7 +10,7 @@ namespace ApiExplorer
// convention
public class ApiExplorerVisibilityEnabledConvention : IReflectedApplicationModelConvention
{
public void OnModelCreated(ReflectedApplicationModel model)
public void Apply(ReflectedApplicationModel model)
{
foreach (var controller in model.Controllers)
{

View File

@ -11,8 +11,10 @@ namespace AutofacWebSite
{
public void Configure(IApplicationBuilder app)
{
var configuration = app.GetTestConfiguration();
app.UseServices(services => {
services.AddMvc();
services.AddMvc(configuration);
services.AddTransient<HelloWorldBuilder>();
var builder = new ContainerBuilder();

View File

@ -2,10 +2,11 @@
"dependencies": {
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Mvc": "",
"Microsoft.AspNet.Mvc.TestConfiguration": "",
"Microsoft.Framework.DependencyInjection.Autofac": "1.0.0-*",
"Autofac": "3.3.0"
},
"frameworks" : {
"aspnet50" : { }
"frameworks": {
"aspnet50": { }
}
}

View File

@ -0,0 +1,37 @@
// 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;
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
namespace ReflectedModelWebSite
{
// This controller uses an reflected model attribute to change an action name, and thus
// the URL.
public class ReflectedActionModelController : Controller
{
[ActionName2("ActionName")]
public string GetActionName()
{
var actionDescriptor = (ReflectedActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.Name;
}
private class ActionName2Attribute : Attribute, IReflectedActionModelConvention
{
private readonly string _actionName;
public ActionName2Attribute(string actionName)
{
_actionName = actionName;
}
public void Apply(ReflectedActionModel model)
{
model.ActionName = _actionName;
}
}
}
}

View File

@ -0,0 +1,37 @@
// 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;
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
namespace ReflectedModelWebSite
{
// This controller uses an reflected model attribute to change the controller name, and thus
// the URL.
[ControllerName("CoolController")]
public class ReflectedControllerModelController : Controller
{
public string GetControllerName()
{
var actionDescriptor = (ReflectedActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.ControllerName;
}
private class ControllerNameAttribute : Attribute, IReflectedControllerModelConvention
{
private readonly string _controllerName;
public ControllerNameAttribute(string controllerName)
{
_controllerName = controllerName;
}
public void Apply(ReflectedControllerModel model)
{
model.ControllerName = _controllerName;
}
}
}
}

View File

@ -0,0 +1,28 @@
// 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;
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
namespace ReflectedModelWebSite
{
// This controller uses an reflected model attribute to change a parameter to optional.
public class ReflectedParameterModelController : Controller
{
public string GetParameterIsOptional([Optional] int? id)
{
var actionDescriptor = (ReflectedActionDescriptor)ActionContext.ActionDescriptor;
return actionDescriptor.Parameters[0].IsOptional.ToString();
}
private class OptionalAttribute : Attribute, IReflectedParameterModelConvention
{
public void Apply(ReflectedParameterModel model)
{
model.IsOptional = true;
}
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>c2ef54f8-8886-4260-a322-44f76245f95d</ProjectGuid>
<OutputType>Web</OutputType>
<RootNamespace>ReflectedModelWebSite</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Console'">
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Web'">
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>9411</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

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.Builder;
using Microsoft.Framework.DependencyInjection;
namespace ReflectedModelWebSite
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var configuration = app.GetTestConfiguration();
app.UseServices(services =>
{
services.AddMvc(configuration);
});
app.UseMvc();
}
}
}

View File

@ -0,0 +1,10 @@
{
"dependencies": {
"Microsoft.AspNet.Mvc": "",
"Microsoft.AspNet.Mvc.TestConfiguration": ""
},
"frameworks": {
"aspnet50": { },
"aspnetcore50": { }
}
}