Adding Support for consumes.
Consumes has overriding behavior and the one closest to action wins.
This commit is contained in:
parent
dfb02e58f8
commit
60fa4a6f45
17
Mvc.sln
17
Mvc.sln
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.22416.0
|
VisualStudioVersion = 14.0.22303.1
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -116,6 +116,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoggingWebSite", "test\WebS
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ErrorPageMiddlewareWebSite", "test\WebSites\ErrorPageMiddlewareWebSite\ErrorPageMiddlewareWebSite.kproj", "{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ErrorPageMiddlewareWebSite", "test\WebSites\ErrorPageMiddlewareWebSite\ErrorPageMiddlewareWebSite.kproj", "{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActionConstraintsWebSite", "test\WebSites\ActionConstraintsWebSite\ActionConstraintsWebSite.kproj", "{AF210F69-9D31-43AF-AC3A-CD366E252218}"
|
||||||
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CustomRouteWebSite", "test\WebSites\CustomRouteWebSite\CustomRouteWebSite.kproj", "{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CustomRouteWebSite", "test\WebSites\CustomRouteWebSite\CustomRouteWebSite.kproj", "{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResponseCacheWebSite", "test\WebSites\ResponseCacheWebSite\ResponseCacheWebSite.kproj", "{BDEEBE09-C0C4-433C-B0B8-8478C9776996}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResponseCacheWebSite", "test\WebSites\ResponseCacheWebSite\ResponseCacheWebSite.kproj", "{BDEEBE09-C0C4-433C-B0B8-8478C9776996}"
|
||||||
|
|
@ -644,6 +646,18 @@ Global
|
||||||
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|x86.ActiveCfg = Release|Any CPU
|
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|x86.Build.0 = Release|Any CPU
|
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
|
@ -724,6 +738,7 @@ Global
|
||||||
{0A6BB4C0-48D3-4E7F-952B-B8917345E075} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{0A6BB4C0-48D3-4E7F-952B-B8917345E075} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
|
{AF210F69-9D31-43AF-AC3A-CD366E252218} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{BDEEBE09-C0C4-433C-B0B8-8478C9776996} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{BDEEBE09-C0C4-433C-B0B8-8478C9776996} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the allowed content types which can be used to select the action based on request's content-type.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class ConsumesAttribute : Attribute, IResourceFilter, IConsumesActionConstraint
|
||||||
|
{
|
||||||
|
public static readonly int ConsumesActionConstraintOrder = 200;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ConsumesAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ConsumesAttribute([NotNull] string contentType, params string[] otherContentTypes)
|
||||||
|
{
|
||||||
|
ContentTypes = GetContentTypes(contentType, otherContentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The value used is a non default value so that it avoids getting mixed with other action constraints
|
||||||
|
// with default order.
|
||||||
|
/// <inheritdoc />
|
||||||
|
int IActionConstraint.Order { get; } = ConsumesActionConstraintOrder;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IList<MediaTypeHeaderValue> ContentTypes { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void OnResourceExecuting([NotNull] ResourceExecutingContext context)
|
||||||
|
{
|
||||||
|
// Only execute if the current filter is the one which is closest to the action.
|
||||||
|
// Ignore all other filters. This is to ensure we have a overriding behavior.
|
||||||
|
if (IsApplicable(context.ActionDescriptor))
|
||||||
|
{
|
||||||
|
MediaTypeHeaderValue requestContentType = null;
|
||||||
|
MediaTypeHeaderValue.TryParse(context.HttpContext.Request.ContentType, out requestContentType);
|
||||||
|
|
||||||
|
// Only execute if this is the last filter before calling the action.
|
||||||
|
// This ensures that we only run the filter which is closest to the action.
|
||||||
|
if (requestContentType != null &&
|
||||||
|
!ContentTypes.Any(contentType => contentType.IsSubsetOf(requestContentType)))
|
||||||
|
{
|
||||||
|
context.Result = new UnsupportedMediaTypeResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void OnResourceExecuted([NotNull] ResourceExecutedContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Accept(ActionConstraintContext context)
|
||||||
|
{
|
||||||
|
// If this constraint is not closest to the action, it will be skipped.
|
||||||
|
if (!IsApplicable(context.CurrentCandidate.Action))
|
||||||
|
{
|
||||||
|
// Since the constraint is to be skipped, returning true here
|
||||||
|
// will let the current candidate ignore this constraint and will
|
||||||
|
// be selected based on other constraints for this action.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaTypeHeaderValue requestContentType = null;
|
||||||
|
MediaTypeHeaderValue.TryParse(context.RouteContext.HttpContext.Request.ContentType, out requestContentType);
|
||||||
|
|
||||||
|
// If the request content type is null we need to act like pass through.
|
||||||
|
// In case there is a single candidate with a constraint it should be selected.
|
||||||
|
// If there are multiple actions with consumes action constraints this should result in ambiguous exception
|
||||||
|
// unless there is another action without a consumes constraint.
|
||||||
|
if (requestContentType == null)
|
||||||
|
{
|
||||||
|
var isActionWithoutConsumeConstraintPresent = context.Candidates.Any(
|
||||||
|
candidate => candidate.Constraints == null ||
|
||||||
|
!candidate.Constraints.Any(constraint => constraint is IConsumesActionConstraint));
|
||||||
|
|
||||||
|
return !isActionWithoutConsumeConstraintPresent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContentTypes.Any(c => c.IsSubsetOf(requestContentType)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstCandidate = context.Candidates[0];
|
||||||
|
if (firstCandidate != context.CurrentCandidate)
|
||||||
|
{
|
||||||
|
// If the current candidate is not same as the first candidate,
|
||||||
|
// we need not probe other candidates to see if they apply.
|
||||||
|
// Only the first candidate is allowed to probe other candidates and based on the result select itself.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the matching logic for all IConsumesActionConstraints we can find, and see what matches.
|
||||||
|
// 1). If we have a unique best match, then only that constraint should return true.
|
||||||
|
// 2). If we have multiple matches, then all constraints that match will return true
|
||||||
|
// , resulting in ambiguity(maybe).
|
||||||
|
// 3). If we have no matches, then we choose the first constraint to return true.It will later return a 415
|
||||||
|
foreach (var candidate in context.Candidates)
|
||||||
|
{
|
||||||
|
if (candidate == firstCandidate)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempContext = new ActionConstraintContext()
|
||||||
|
{
|
||||||
|
Candidates = context.Candidates,
|
||||||
|
RouteContext = context.RouteContext,
|
||||||
|
CurrentCandidate = candidate
|
||||||
|
};
|
||||||
|
|
||||||
|
if (candidate.Constraints == null || candidate.Constraints.Count() == 0 ||
|
||||||
|
candidate.Constraints.Any(constraint => constraint is IConsumesActionConstraint &&
|
||||||
|
constraint.Accept(tempContext)))
|
||||||
|
{
|
||||||
|
// There is someone later in the chain which can handle the request.
|
||||||
|
// end the process here.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no one later in the chain that can handle this content type return a false positive so that
|
||||||
|
// later we can detect and return a 415.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsApplicable(ActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
// If there are multiple IConsumeActionConstraints which are defined at the class and
|
||||||
|
// at the action level, the one closest to the action overrides the others. To ensure this
|
||||||
|
// we take advantage of the fact that ConsumesAttribute is both an IActionFilter and an
|
||||||
|
// IConsumeActionConstraint. Since filterdescriptor collection is ordered (the last filter is the one
|
||||||
|
// closest to the action), we apply this constraint only if there is no IConsumeActionConstraint after this.
|
||||||
|
return actionDescriptor.FilterDescriptors.Last(
|
||||||
|
filter => filter.Filter is IConsumesActionConstraint).Filter == this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MediaTypeHeaderValue> GetContentTypes(string firstArg, string[] args)
|
||||||
|
{
|
||||||
|
var contentTypes = new List<MediaTypeHeaderValue>();
|
||||||
|
contentTypes.Add(MediaTypeHeaderValue.Parse(firstArg));
|
||||||
|
foreach (var item in args)
|
||||||
|
{
|
||||||
|
var contentType = MediaTypeHeaderValue.Parse(item);
|
||||||
|
contentTypes.Add(contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IActionConstraint"/> constraint that identifies a type which can be used to select an action
|
||||||
|
/// based on incoming request.
|
||||||
|
/// </summary>
|
||||||
|
public interface IConsumesActionConstraint : IActionConstraint
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="HttpStatusCodeResult"/> that when
|
||||||
|
/// executed will produce a UnsupportedMediaType (415) response.
|
||||||
|
/// </summary>
|
||||||
|
public class UnsupportedMediaTypeResult : HttpStatusCodeResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="UnsupportedMediaTypeResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
public UnsupportedMediaTypeResult() : base(415)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Http.Core;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class ConsumesAttributeTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application")]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData(null)]
|
||||||
|
public void Constructor_ForInvalidContentType_Throws(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedMessage = string.Format("Invalid value '{0}'.", contentType ?? "<null>");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<FormatException>(() => new ConsumesAttribute(contentType));
|
||||||
|
Assert.Equal(expectedMessage, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/json")]
|
||||||
|
[InlineData("application/json;Parameter1=12")]
|
||||||
|
[InlineData("text/xml")]
|
||||||
|
public void Accept_MatchesForMachingRequestContentType(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var constraint = new ConsumesAttribute("application/json", "text/xml");
|
||||||
|
var action = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = new ActionConstraintContext();
|
||||||
|
context.Candidates = new List<ActionSelectorCandidate>()
|
||||||
|
{
|
||||||
|
new ActionSelectorCandidate(action, new [] { constraint }),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.CurrentCandidate = context.Candidates[0];
|
||||||
|
context.RouteContext = CreateRouteContext(contentType: contentType);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.True(constraint.Accept(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Accept_TheFirstCandidateReturnsFalse_IfALaterOneMatches()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var constraint1 = new ConsumesAttribute("application/json", "text/xml");
|
||||||
|
var action1 = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint1, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var constraint2 = new Mock<ITestConsumeConstraint>();
|
||||||
|
var action2 = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint2.Object, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
constraint2.Setup(o => o.Accept(It.IsAny<ActionConstraintContext>()))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var context = new ActionConstraintContext();
|
||||||
|
context.Candidates = new List<ActionSelectorCandidate>()
|
||||||
|
{
|
||||||
|
new ActionSelectorCandidate(action1, new [] { constraint1 }),
|
||||||
|
new ActionSelectorCandidate(action2, new [] { constraint2.Object }),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.CurrentCandidate = context.Candidates[0];
|
||||||
|
context.RouteContext = CreateRouteContext(contentType: "application/custom");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.False(constraint1.Accept(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/custom")]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData(null)]
|
||||||
|
public void Accept_ForNoMatchingCandidates_SelectsTheFirstCandidate(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var constraint1 = new ConsumesAttribute("application/json", "text/xml");
|
||||||
|
var action1 = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint1, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var constraint2 = new Mock<ITestConsumeConstraint>();
|
||||||
|
var action2 = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint2.Object, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
constraint2.Setup(o => o.Accept(It.IsAny<ActionConstraintContext>()))
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var context = new ActionConstraintContext();
|
||||||
|
context.Candidates = new List<ActionSelectorCandidate>()
|
||||||
|
{
|
||||||
|
new ActionSelectorCandidate(action1, new [] { constraint1 }),
|
||||||
|
new ActionSelectorCandidate(action2, new [] { constraint2.Object }),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.CurrentCandidate = context.Candidates[0];
|
||||||
|
context.RouteContext = CreateRouteContext(contentType: contentType);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.True(constraint1.Accept(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData(null)]
|
||||||
|
public void Accept_ForNoRequestType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var constraint1 = new ConsumesAttribute("application/json");
|
||||||
|
var actionWithConstraint = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint1, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var constraint2 = new ConsumesAttribute("text/xml");
|
||||||
|
var actionWithConstraint2 = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint2, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionWithoutConstraint = new ActionDescriptor();
|
||||||
|
|
||||||
|
var context = new ActionConstraintContext();
|
||||||
|
context.Candidates = new List<ActionSelectorCandidate>()
|
||||||
|
{
|
||||||
|
new ActionSelectorCandidate(actionWithConstraint, new [] { constraint1 }),
|
||||||
|
new ActionSelectorCandidate(actionWithConstraint2, new [] { constraint2 }),
|
||||||
|
new ActionSelectorCandidate(actionWithoutConstraint, new List<IActionConstraint>()),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.RouteContext = CreateRouteContext(contentType: contentType);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
context.CurrentCandidate = context.Candidates[0];
|
||||||
|
Assert.False(constraint1.Accept(context));
|
||||||
|
context.CurrentCandidate = context.Candidates[1];
|
||||||
|
Assert.False(constraint2.Accept(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
[InlineData("application/custom")]
|
||||||
|
[InlineData("invalid/invalid")]
|
||||||
|
public void Accept_UnrecognizedMediaType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionWithoutConstraint = new ActionDescriptor();
|
||||||
|
var constraint1 = new ConsumesAttribute("application/json");
|
||||||
|
var actionWithConstraint = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint1, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var constraint2 = new ConsumesAttribute("text/xml");
|
||||||
|
var actionWithConstraint2 = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint2, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = new ActionConstraintContext();
|
||||||
|
context.Candidates = new List<ActionSelectorCandidate>()
|
||||||
|
{
|
||||||
|
new ActionSelectorCandidate(actionWithConstraint, new [] { constraint1 }),
|
||||||
|
new ActionSelectorCandidate(actionWithConstraint2, new [] { constraint2 }),
|
||||||
|
new ActionSelectorCandidate(actionWithoutConstraint, new List<IActionConstraint>()),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.RouteContext = CreateRouteContext(contentType: contentType);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
context.CurrentCandidate = context.Candidates[0];
|
||||||
|
Assert.False(constraint1.Accept(context));
|
||||||
|
|
||||||
|
context.CurrentCandidate = context.Candidates[1];
|
||||||
|
Assert.False(constraint2.Accept(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData(null)]
|
||||||
|
public void Accept_ForNoRequestType_ReturnsTrueForAllConstraints(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var constraint1 = new ConsumesAttribute("application/json");
|
||||||
|
var actionWithConstraint = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint1, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var constraint2 = new ConsumesAttribute("text/xml");
|
||||||
|
var actionWithConstraint2 = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(constraint2, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionWithoutConstraint = new ActionDescriptor();
|
||||||
|
|
||||||
|
var context = new ActionConstraintContext();
|
||||||
|
context.Candidates = new List<ActionSelectorCandidate>()
|
||||||
|
{
|
||||||
|
new ActionSelectorCandidate(actionWithConstraint, new [] { constraint1 }),
|
||||||
|
new ActionSelectorCandidate(actionWithConstraint2, new [] { constraint2 }),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.RouteContext = CreateRouteContext(contentType: contentType);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
context.CurrentCandidate = context.Candidates[0];
|
||||||
|
Assert.True(constraint1.Accept(context));
|
||||||
|
context.CurrentCandidate = context.Candidates[1];
|
||||||
|
Assert.True(constraint2.Accept(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
[InlineData("application/custom")]
|
||||||
|
public void OnResourceExecuting_ForNoContentTypeMatch_SetsUnsupportedMediaTypeResult(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = contentType;
|
||||||
|
var consumesFilter = new ConsumesAttribute("application/json");
|
||||||
|
var actionWithConstraint = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
ActionConstraints = new List<IActionConstraintMetadata>() { consumesFilter },
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(consumesFilter, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint);
|
||||||
|
|
||||||
|
var resourceExecutingContext = new ResourceExecutingContext(actionContext, new[] { consumesFilter });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
consumesFilter.OnResourceExecuting(resourceExecutingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(resourceExecutingContext.Result);
|
||||||
|
Assert.IsType<UnsupportedMediaTypeResult>(resourceExecutingContext.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData(null)]
|
||||||
|
public void OnResourceExecuting_NullOrEmptyRequestContentType_IsNoOp(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = contentType;
|
||||||
|
var consumesFilter = new ConsumesAttribute("application/json");
|
||||||
|
var actionWithConstraint = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
ActionConstraints = new List<IActionConstraintMetadata>() { consumesFilter },
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(consumesFilter, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint);
|
||||||
|
|
||||||
|
var resourceExecutingContext = new ResourceExecutingContext(actionContext, new[] { consumesFilter });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
consumesFilter.OnResourceExecuting(resourceExecutingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(resourceExecutingContext.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/xml")]
|
||||||
|
[InlineData("application/json")]
|
||||||
|
public void OnResourceExecuting_ForAContentTypeMatch_IsNoOp(string contentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.ContentType = contentType;
|
||||||
|
var consumesFilter = new ConsumesAttribute("application/json", "application/xml");
|
||||||
|
var actionWithConstraint = new ActionDescriptor()
|
||||||
|
{
|
||||||
|
ActionConstraints = new List<IActionConstraintMetadata>() { consumesFilter },
|
||||||
|
FilterDescriptors =
|
||||||
|
new List<FilterDescriptor>() { new FilterDescriptor(consumesFilter, FilterScope.Action) }
|
||||||
|
};
|
||||||
|
var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint);
|
||||||
|
var resourceExecutingContext = new ResourceExecutingContext(actionContext, new[] { consumesFilter });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
consumesFilter.OnResourceExecuting(resourceExecutingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(resourceExecutingContext.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RouteContext CreateRouteContext(string contentType = null, object routeValues = null)
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
if (contentType != null)
|
||||||
|
{
|
||||||
|
httpContext.Request.ContentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeContext = new RouteContext(httpContext);
|
||||||
|
routeContext.RouteData = new RouteData();
|
||||||
|
|
||||||
|
foreach (var kvp in new RouteValueDictionary(routeValues))
|
||||||
|
{
|
||||||
|
routeContext.RouteData.Values.Add(kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return routeContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITestConsumeConstraint : IConsumesActionConstraint, IResourceFilter
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using ActionConstraintsWebSite;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class ConsumesAttributeTests
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _provider = TestHelper.CreateServices("ActionConstraintsWebSite");
|
||||||
|
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NoRequestContentType_SelectsActionWithoutConstraint()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var request = new HttpRequestMessage(
|
||||||
|
HttpMethod.Post,
|
||||||
|
"http://localhost/ConsumesAttribute_Company/CreateProduct");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
var product = JsonConvert.DeserializeObject<Product>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||||
|
Assert.Null(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NoRequestContentType_Throws_IfMultipleActionsWithConstraints()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var request = new HttpRequestMessage(
|
||||||
|
HttpMethod.Post,
|
||||||
|
"http://localhost/ConsumesAttribute_AmbiguousActions/CreateProduct");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
var exception = response.GetServerException();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
||||||
|
Assert.Equal(typeof(AmbiguousActionException).FullName, exception.ExceptionType);
|
||||||
|
Assert.Equal(
|
||||||
|
"Multiple actions matched. The following actions matched route data and had all constraints "+
|
||||||
|
"satisfied:____ActionConstraintsWebSite.ConsumesAttribute_NoFallBackActionController."+
|
||||||
|
"CreateProduct__ActionConstraintsWebSite.ConsumesAttribute_NoFallBackActionController.CreateProduct",
|
||||||
|
exception.ExceptionMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(
|
||||||
|
HttpMethod.Post,
|
||||||
|
"http://localhost/ConsumesAttribute_PassThrough/CreateProduct");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
var product = JsonConvert.DeserializeObject<Product>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||||
|
Assert.Null(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/json")]
|
||||||
|
[InlineData("text/json")]
|
||||||
|
public async Task Selects_Action_BasedOnRequestContentType(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var input = "{SampleString:\""+requestContentType+"\"}";
|
||||||
|
var request = new HttpRequestMessage(
|
||||||
|
HttpMethod.Post,
|
||||||
|
"http://localhost/ConsumesAttribute_AmbiguousActions/CreateProduct");
|
||||||
|
request.Content = new StringContent(input, Encoding.UTF8, requestContentType);
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
var product = JsonConvert.DeserializeObject<Product>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(requestContentType, product.SampleString);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("application/json")]
|
||||||
|
[InlineData("text/json")]
|
||||||
|
public async Task ActionLevelAttribute_OveridesClassLevel(string requestContentType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var input = "{SampleString:\"" + requestContentType + "\"}";
|
||||||
|
var request = new HttpRequestMessage(
|
||||||
|
HttpMethod.Post,
|
||||||
|
"http://localhost/ConsumesAttribute_OverridesBase/CreateProduct");
|
||||||
|
request.Content = new StringContent(input, Encoding.UTF8, requestContentType);
|
||||||
|
var expectedString = "ConsumesAttribute_OverridesBaseController_" + requestContentType;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
var product = JsonConvert.DeserializeObject<Product>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(expectedString, product.SampleString);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DerivedClassLevelAttribute_OveridesBaseClassLevel()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var input = "<Product xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" " +
|
||||||
|
"xmlns=\"http://schemas.datacontract.org/2004/07/ActionConstraintsWebSite\">" +
|
||||||
|
"<SampleString>application/xml</SampleString></Product>";
|
||||||
|
var request = new HttpRequestMessage(
|
||||||
|
HttpMethod.Post,
|
||||||
|
"http://localhost/ConsumesAttribute_Overrides/CreateProduct");
|
||||||
|
request.Content = new StringContent(input, Encoding.UTF8, "application/xml");
|
||||||
|
var expectedString = "ConsumesAttribute_OverridesController_application/xml";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
var product = JsonConvert.DeserializeObject<Product>(responseString);
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(expectedString, product.SampleString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
"warningsAsErrors": "true"
|
"warningsAsErrors": "true"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"ActionConstraintsWebSite": "1.0.0",
|
||||||
"ActionResultsWebSite": "1.0.0",
|
"ActionResultsWebSite": "1.0.0",
|
||||||
"ActivatorWebSite": "1.0.0",
|
"ActivatorWebSite": "1.0.0",
|
||||||
"AddServicesWebSite": "1.0.0",
|
"AddServicesWebSite": "1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.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>af210f69-9d31-43af-ac3a-cd366e252218</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<DevelopmentServerPort>41642</DevelopmentServerPort>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
namespace ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
[Route("ConsumesAttribute_AmbiguousActions/[action]")]
|
||||||
|
public class ConsumesAttribute_NoFallBackActionController : Controller
|
||||||
|
{
|
||||||
|
[Consumes("application/json", "text/json")]
|
||||||
|
public Product CreateProduct([FromBody] Product_Json jsonInput)
|
||||||
|
{
|
||||||
|
return jsonInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Consumes("application/xml")]
|
||||||
|
public Product CreateProduct([FromBody] Product_Xml xmlInput)
|
||||||
|
{
|
||||||
|
return xmlInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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 ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
[Consumes("application/json")]
|
||||||
|
public class ConsumesAttribute_OverridesBaseController : Controller
|
||||||
|
{
|
||||||
|
[Consumes("text/json")]
|
||||||
|
public Product CreateProduct([FromBody] Product_Json product)
|
||||||
|
{
|
||||||
|
// should be picked if request content type is application/xml and not application/json.
|
||||||
|
product.SampleString = "ConsumesAttribute_OverridesBaseController_text/json";
|
||||||
|
return product;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual IActionResult CreateProduct([FromBody] Product product)
|
||||||
|
{
|
||||||
|
// should be picked if request content type is application/json.
|
||||||
|
product.SampleString = "ConsumesAttribute_OverridesBaseController_application/json";
|
||||||
|
return new ObjectResult(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 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 ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
[Consumes("application/xml")]
|
||||||
|
public class ConsumesAttribute_OverridesController : ConsumesAttribute_OverridesBaseController
|
||||||
|
{
|
||||||
|
public override IActionResult CreateProduct([FromBody] Product product)
|
||||||
|
{
|
||||||
|
// should be picked if request content type is text/json.
|
||||||
|
product.SampleString = "ConsumesAttribute_OverridesController_application/xml";
|
||||||
|
return new JsonResult(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
[Route("ConsumesAttribute_PassThrough/[action]")]
|
||||||
|
public class ConsumesAttribute_PassThroughController : Controller
|
||||||
|
{
|
||||||
|
[Consumes("application/json")]
|
||||||
|
public Product CreateProduct([FromBody] Product_Json jsonInput)
|
||||||
|
{
|
||||||
|
return jsonInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
[Route("ConsumesAttribute_Company/[action]")]
|
||||||
|
public class ConsumesAttribute_WithFallbackActionController : Controller
|
||||||
|
{
|
||||||
|
[Consumes("application/json")]
|
||||||
|
public Product CreateProduct([FromBody] Product_Json jsonInput)
|
||||||
|
{
|
||||||
|
return jsonInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Consumes("application/xml")]
|
||||||
|
public Product CreateProduct([FromBody] Product_Xml xmlInput)
|
||||||
|
{
|
||||||
|
return xmlInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Product CreateProduct([FromBody] Product_Text defaultInput)
|
||||||
|
{
|
||||||
|
return defaultInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
public class Product
|
||||||
|
{
|
||||||
|
[Range(10, 100)]
|
||||||
|
public int SampleInt { get; set; }
|
||||||
|
|
||||||
|
[MinLength(15)]
|
||||||
|
public string SampleString { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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 ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
public class Product_Json : Product
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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 ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
public class Product_Xml : Product
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// 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 ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
public class Product_Text : Product
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// 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.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace ActionConstraintsWebSite
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var configuration = app.GetTestConfiguration();
|
||||||
|
|
||||||
|
app.UseServices(services =>
|
||||||
|
{
|
||||||
|
services.AddMvc(configuration);
|
||||||
|
services.Configure<MvcOptions>(options =>
|
||||||
|
{
|
||||||
|
options.AddXmlDataContractSerializerFormatter();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseErrorReporter();
|
||||||
|
|
||||||
|
app.UseMvc(routes =>
|
||||||
|
{
|
||||||
|
routes.MapRoute(
|
||||||
|
name: "default",
|
||||||
|
template: "{controller}/{action}/{id?}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"commands": {
|
||||||
|
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
|
||||||
|
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"Kestrel": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.Mvc": "6.0.0-*",
|
||||||
|
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
|
||||||
|
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"aspnet50": { },
|
||||||
|
"aspnetcore50": { }
|
||||||
|
},
|
||||||
|
"webroot": "wwwroot"
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
Reference in New Issue