diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpDeleteAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/HttpDeleteAttribute.cs
index 58793de99d..6a1fb69183 100644
--- a/src/Microsoft.AspNet.Mvc.Core/HttpDeleteAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/HttpDeleteAttribute.cs
@@ -3,17 +3,41 @@
using System;
using System.Collections.Generic;
+using Microsoft.AspNet.Mvc.Routing;
namespace Microsoft.AspNet.Mvc
{
+ ///
+ /// Identifies an action that only supports the HTTP DELETE method.
+ ///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public sealed class HttpDeleteAttribute : Attribute, IActionHttpMethodProvider
+ public class HttpDeleteAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
private static readonly IEnumerable _supportedMethods = new string[] { "DELETE" };
+ ///
+ /// Creates a new .
+ ///
+ public HttpDeleteAttribute()
+ {
+ }
+
+ ///
+ /// Creates a new with the given route template.
+ ///
+ /// The route template. May not be null.
+ public HttpDeleteAttribute([NotNull] string template)
+ {
+ Template = template;
+ }
+
+ ///
public IEnumerable HttpMethods
{
get { return _supportedMethods; }
}
+
+ ///
+ public string Template { get; private set; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpGetAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/HttpGetAttribute.cs
index 7b37883259..72c3145108 100644
--- a/src/Microsoft.AspNet.Mvc.Core/HttpGetAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/HttpGetAttribute.cs
@@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc
/// Identifies an action that only supports the HTTP GET method.
///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public sealed class HttpGetAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
+ public class HttpGetAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
private static readonly IEnumerable _supportedMethods = new string[] { "GET" };
diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpPatchAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/HttpPatchAttribute.cs
index 97e6f91600..4710ff29b7 100644
--- a/src/Microsoft.AspNet.Mvc.Core/HttpPatchAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/HttpPatchAttribute.cs
@@ -3,17 +3,41 @@
using System;
using System.Collections.Generic;
+using Microsoft.AspNet.Mvc.Routing;
namespace Microsoft.AspNet.Mvc
{
+ ///
+ /// Identifies an action that only supports the HTTP PATCH method.
+ ///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public sealed class HttpPatchAttribute : Attribute, IActionHttpMethodProvider
+ public class HttpPatchAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
private static readonly IEnumerable _supportedMethods = new string[] { "PATCH" };
+ ///
+ /// Creates a new .
+ ///
+ public HttpPatchAttribute()
+ {
+ }
+
+ ///
+ /// Creates a new with the given route template.
+ ///
+ /// The route template. May not be null.
+ public HttpPatchAttribute([NotNull] string template)
+ {
+ Template = template;
+ }
+
+ ///
public IEnumerable HttpMethods
{
get { return _supportedMethods; }
}
+
+ ///
+ public string Template { get; private set; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpPostAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/HttpPostAttribute.cs
index 14cc912b02..5a771992d4 100644
--- a/src/Microsoft.AspNet.Mvc.Core/HttpPostAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/HttpPostAttribute.cs
@@ -3,17 +3,42 @@
using System;
using System.Collections.Generic;
+using Microsoft.AspNet.Mvc.Routing;
namespace Microsoft.AspNet.Mvc
{
+ ///
+ /// Identifies an action that only supports the HTTP POST method.
+ ///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public sealed class HttpPostAttribute : Attribute, IActionHttpMethodProvider
+ public class HttpPostAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
private static readonly IEnumerable _supportedMethods = new string[] { "POST" };
+ ///
+ /// Creates a new .
+ ///
+ /// The route template. May not be null.
+ public HttpPostAttribute()
+ {
+ }
+
+ ///
+ /// Creates a new with the given route template.
+ ///
+ /// The route template. May not be null.
+ public HttpPostAttribute([NotNull] string template)
+ {
+ Template = template;
+ }
+
+ ///
public IEnumerable HttpMethods
{
get { return _supportedMethods; }
}
+
+ ///
+ public string Template { get; private set; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpPutAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/HttpPutAttribute.cs
index 37e1632250..9ef79a1bb4 100644
--- a/src/Microsoft.AspNet.Mvc.Core/HttpPutAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/HttpPutAttribute.cs
@@ -3,17 +3,41 @@
using System;
using System.Collections.Generic;
+using Microsoft.AspNet.Mvc.Routing;
namespace Microsoft.AspNet.Mvc
{
+ ///
+ /// Identifies an action that only supports the HTTP PUT method.
+ ///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public sealed class HttpPutAttribute : Attribute, IActionHttpMethodProvider
+ public class HttpPutAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
private static readonly IEnumerable _supportedMethods = new string[] { "PUT" };
+ ///
+ /// Creates a new .
+ ///
+ public HttpPutAttribute()
+ {
+ }
+
+ ///
+ /// Creates a new with the given route template.
+ ///
+ /// The route template. May not be null.
+ public HttpPutAttribute([NotNull] string template)
+ {
+ Template = template;
+ }
+
+ ///
public IEnumerable HttpMethods
{
get { return _supportedMethods; }
}
+
+ ///
+ public string Template { get; private set; }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/HttpMethodProviderAttributesTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/HttpMethodProviderAttributesTests.cs
new file mode 100644
index 0000000000..a0ef8cfe52
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/HttpMethodProviderAttributesTests.cs
@@ -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.Collections.Generic;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc
+{
+ public class HttpMethodProviderAttributesTests
+ {
+ [Theory]
+ [MemberData("HttpMethodProviderTestData")]
+ public void HttpMethodProviderAttributes_ReturnsCorrectHttpMethodSequence(
+ IActionHttpMethodProvider httpMethodProvider,
+ IEnumerable expectedHttpMethods)
+ {
+ // Act & Assert
+ Assert.Equal(expectedHttpMethods, httpMethodProvider.HttpMethods);
+ }
+
+ public static TheoryData> HttpMethodProviderTestData
+ {
+ get
+ {
+ var data = new TheoryData>();
+ data.Add(new HttpGetAttribute(), new[] { "GET" });
+ data.Add(new HttpPostAttribute(), new[] { "POST" });
+ data.Add(new HttpPutAttribute(), new[] { "PUT" });
+ data.Add(new HttpPatchAttribute(), new[] { "PATCH" });
+ data.Add(new HttpDeleteAttribute(), new[] { "DELETE" });
+ data.Add(new AcceptVerbsAttribute("MERGE", "OPTIONS"), new[] { "MERGE", "OPTIONS" });
+
+ return data;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs
index bec2033ccf..e6495a539a 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs
@@ -260,16 +260,18 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("List", result.Action);
}
- // There's an [HttpGet] with its own template on the action here.
- [Fact]
- public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable()
+ // There's no [HttpGet] on the action here.
+ [Theory]
+ [InlineData("PUT")]
+ [InlineData("PATCH")]
+ public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbs_IsReachable(string verb)
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.Handler;
// Act
- var response = await client.GetAsync("http://localhost/api/Employee/5/Boss");
+ var response = await client.SendAsync(verb, "http://localhost/api/Employee");
// Assert
Assert.Equal(200, response.StatusCode);
@@ -277,9 +279,56 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var body = await response.ReadBodyAsStringAsync();
var result = JsonConvert.DeserializeObject(body);
- Assert.Contains("/api/Employee/5/Boss", result.ExpectedUrls);
+ // Assert
+ Assert.Contains("/api/Employee", result.ExpectedUrls);
Assert.Equal("Employee", result.Controller);
- Assert.Equal("GetBoss", result.Action);
+ Assert.Equal("UpdateEmployee", result.Action);
+ }
+
+ [Fact]
+ public async Task AttributeRoutedAction_WithCustomHttpAttributes_IsReachable()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.Handler;
+
+ // Act
+ var response = await client.SendAsync("MERGE", "http://localhost/api/Employee/5");
+
+ // Assert
+ Assert.Equal(200, response.StatusCode);
+
+ var body = await response.ReadBodyAsStringAsync();
+ var result = JsonConvert.DeserializeObject(body);
+
+ // Assert
+ Assert.Contains("/api/Employee/5", result.ExpectedUrls);
+ Assert.Equal("Employee", result.Controller);
+ Assert.Equal("MergeEmployee", result.Action);
+ }
+
+ // There's an [HttpGet] with its own template on the action here.
+ [Theory]
+ [InlineData("GET", "GetAdministrator")]
+ [InlineData("DELETE", "DeleteAdministrator")]
+ public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable(string verb, string action)
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.Handler;
+
+ // Act
+ var response = await client.SendAsync(verb, "http://localhost/api/Employee/5/Administrator");
+
+ // Assert
+ Assert.Equal(200, response.StatusCode);
+
+ var body = await response.ReadBodyAsStringAsync();
+ var result = JsonConvert.DeserializeObject(body);
+
+ Assert.Contains("/api/Employee/5/Administrator", result.ExpectedUrls);
+ Assert.Equal("Employee", result.Controller);
+ Assert.Equal(action, result.Action);
Assert.Contains(
new KeyValuePair("id", "5"),
diff --git a/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs b/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs
index 2c3a1ffdba..9c9b796ccc 100644
--- a/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs
+++ b/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs
@@ -1,5 +1,5 @@
-using Microsoft.AspNet.Mvc;
-using System;
+using System;
+using Microsoft.AspNet.Mvc;
namespace RoutingWebSite
{
@@ -20,16 +20,28 @@ namespace RoutingWebSite
return _generator.Generate("/api/Employee");
}
+ [AcceptVerbs("PUT", "PATCH")]
+ public IActionResult UpdateEmployee()
+ {
+ return _generator.Generate("/api/Employee");
+ }
+
+ [HttpMerge("{id}")]
+ public IActionResult MergeEmployee(int id)
+ {
+ return _generator.Generate("/api/Employee/" + id);
+ }
+
[HttpGet("{id}")]
public IActionResult Get(int id)
{
return _generator.Generate("/api/Employee/" + id);
}
- [HttpGet("{id}/Boss")]
- public IActionResult GetBoss(int id)
+ [HttpGet("{id}/Administrator")]
+ public IActionResult GetAdministrator(int id)
{
- return _generator.Generate("/api/Employee/" + id + "/Boss");
+ return _generator.Generate("/api/Employee/" + id + "/Administrator");
}
[HttpGet("~/Manager/{id}")]
@@ -37,5 +49,11 @@ namespace RoutingWebSite
{
return _generator.Generate("/Manager/" + id);
}
+
+ [HttpDelete("{id}/Administrator")]
+ public IActionResult DeleteAdministrator(int id)
+ {
+ return _generator.Generate("/api/Employee/" + id + "/Administrator");
+ }
}
}
\ No newline at end of file
diff --git a/test/WebSites/RoutingWebSite/HttpMergeAttribute.cs b/test/WebSites/RoutingWebSite/HttpMergeAttribute.cs
new file mode 100644
index 0000000000..9c10c7118a
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/HttpMergeAttribute.cs
@@ -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 System.Collections.Generic;
+using Microsoft.AspNet.Mvc;
+using Microsoft.AspNet.Mvc.Routing;
+using Microsoft.AspNet.Routing;
+
+namespace RoutingWebSite
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class HttpMergeAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
+ {
+ private static readonly IEnumerable _supportedMethods = new[] { "MERGE" };
+
+ public HttpMergeAttribute(string template)
+ {
+ Template = template;
+ }
+
+ public IEnumerable HttpMethods
+ {
+ get { return _supportedMethods; }
+ }
+
+ public string Template { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RoutingWebSite/RoutingWebSite.kproj b/test/WebSites/RoutingWebSite/RoutingWebSite.kproj
index 8557aedcb0..8eef3bf9bd 100644
--- a/test/WebSites/RoutingWebSite/RoutingWebSite.kproj
+++ b/test/WebSites/RoutingWebSite/RoutingWebSite.kproj
@@ -36,6 +36,7 @@
+