Fix #1276 - Implement WebAPI action conventions and overloading
This change adds ApplicationModel conventions that can enable WebAPI action conventions (verb mapping) and WebAPI overloading. The conventions activate when a controller has a marker attribute. ApiController has this attribute, so any ported code will automatically opt-in. Also ported some old tests for action selection to our new functional test framework.
This commit is contained in:
parent
2578b8107f
commit
9b11c1d90f
|
|
@ -5,9 +5,12 @@ using System.Security.Principal;
|
|||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
|
||||
namespace System.Web.Http
|
||||
{
|
||||
[UseWebApiActionConventions]
|
||||
[UseWebApiOverloading]
|
||||
public abstract class ApiController : IDisposable
|
||||
{
|
||||
/// <summary>Gets the action context.</summary>
|
||||
|
|
|
|||
|
|
@ -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 Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public interface IUseWebApiActionConventions
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public interface IUseWebApiOverloading
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class UseWebApiActionConventionsAttribute : Attribute, IUseWebApiActionConventions
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class UseWebApiOverloadingAttribute : Attribute, IUseWebApiOverloading
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// 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.AspNet.Mvc.ApplicationModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public class WebApiActionConventionsGlobalModelConvention : IGlobalModelConvention
|
||||
{
|
||||
private static readonly string[] SupportedHttpMethodConventions = new string[]
|
||||
{
|
||||
"GET",
|
||||
"PUT",
|
||||
"POST",
|
||||
"DELETE",
|
||||
"PATCH",
|
||||
"HEAD",
|
||||
"OPTIONS",
|
||||
};
|
||||
|
||||
public void Apply(GlobalModel model)
|
||||
{
|
||||
foreach (var controller in model.Controllers)
|
||||
{
|
||||
if (IsConventionApplicable(controller))
|
||||
{
|
||||
Apply(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConventionApplicable(ControllerModel controller)
|
||||
{
|
||||
return controller.Attributes.OfType<IUseWebApiActionConventions>().Any();
|
||||
}
|
||||
|
||||
private void Apply(ControllerModel model)
|
||||
{
|
||||
var newActions = new List<ActionModel>();
|
||||
|
||||
foreach (var action in model.Actions)
|
||||
{
|
||||
SetHttpMethodFromConvention(action);
|
||||
|
||||
// Action Name doesn't really come into play with attribute routed actions. However for a
|
||||
// non-attribute-routed action we need to create a 'named' version and an 'unnamed' version.
|
||||
if (!IsActionAttributeRouted(action))
|
||||
{
|
||||
var namedAction = action;
|
||||
|
||||
var unnamedAction = new ActionModel(namedAction);
|
||||
unnamedAction.IsActionNameMatchRequired = false;
|
||||
newActions.Add(unnamedAction);
|
||||
}
|
||||
}
|
||||
|
||||
model.Actions.AddRange(newActions);
|
||||
}
|
||||
|
||||
private bool IsActionAttributeRouted(ActionModel action)
|
||||
{
|
||||
if (action.Controller.AttributeRoutes.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return action.AttributeRouteModel?.Template != null;
|
||||
}
|
||||
|
||||
private void SetHttpMethodFromConvention(ActionModel action)
|
||||
{
|
||||
if (action.HttpMethods.Count > 0)
|
||||
{
|
||||
// If the HttpMethods are set from attributes, don't override it with the convention
|
||||
return;
|
||||
}
|
||||
|
||||
// The Method name is used to infer verb constraints. Changing the action name has not impact.
|
||||
foreach (var verb in SupportedHttpMethodConventions)
|
||||
{
|
||||
if (action.ActionMethod.Name.StartsWith(verb, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
action.HttpMethods.Add(verb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If no convention matches, then assume POST
|
||||
action.HttpMethods.Add("POST");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public class WebApiOverloadingGlobalModelConvention : IGlobalModelConvention
|
||||
{
|
||||
public void Apply(GlobalModel model)
|
||||
{
|
||||
foreach (var controller in model.Controllers)
|
||||
{
|
||||
if (IsConventionApplicable(controller))
|
||||
{
|
||||
Apply(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConventionApplicable(ControllerModel controller)
|
||||
{
|
||||
return controller.Attributes.OfType<IUseWebApiOverloading>().Any();
|
||||
}
|
||||
|
||||
private void Apply(ControllerModel model)
|
||||
{
|
||||
foreach (var action in model.Actions)
|
||||
{
|
||||
action.ActionConstraints.Add(new OverloadActionConstraint());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// 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.Net.Http.Formatting;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public static class FormDataCollectionExtensions
|
||||
{
|
||||
// This is a helper method to use Model Binding over a JQuery syntax.
|
||||
// Normalize from JQuery to MVC keys. The model binding infrastructure uses MVC keys
|
||||
// x[] --> x
|
||||
// [] --> ""
|
||||
// x[field] --> x.field, where field is not a number
|
||||
public static string NormalizeJQueryToMvc(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
StringBuilder sb = null;
|
||||
var i = 0;
|
||||
while (true)
|
||||
{
|
||||
int indexOpen = key.IndexOf('[', i);
|
||||
if (indexOpen < 0)
|
||||
{
|
||||
// Fast path, no normalization needed.
|
||||
// This skips the string conversion and allocating the string builder.
|
||||
if (i == 0)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
sb = sb ?? new StringBuilder();
|
||||
sb.Append(key, i, key.Length - i);
|
||||
break; // no more brackets
|
||||
}
|
||||
|
||||
sb = sb ?? new StringBuilder();
|
||||
sb.Append(key, i, indexOpen - i); // everything up to "["
|
||||
|
||||
// Find closing bracket.
|
||||
var indexClose = key.IndexOf(']', indexOpen);
|
||||
if (indexClose == -1)
|
||||
{
|
||||
throw new ArgumentException(Resources.JQuerySyntaxMissingClosingBracket, "key");
|
||||
}
|
||||
|
||||
if (indexClose == indexOpen + 1)
|
||||
{
|
||||
// Empty bracket. Signifies array. Just remove.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (char.IsDigit(key[indexOpen + 1]))
|
||||
{
|
||||
// array index. Leave unchanged.
|
||||
sb.Append(key, indexOpen, indexClose - indexOpen + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Field name. Convert to dot notation.
|
||||
sb.Append('.');
|
||||
sb.Append(key, indexOpen + 1, indexClose - indexOpen - 1);
|
||||
}
|
||||
}
|
||||
|
||||
i = indexClose + 1;
|
||||
if (i >= key.Length)
|
||||
{
|
||||
break; // end of string
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static IEnumerable<KeyValuePair<string, string>> GetJQueryNameValuePairs(
|
||||
[NotNull] this FormDataCollection formData)
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
foreach (var kv in formData)
|
||||
{
|
||||
ThrowIfMaxHttpCollectionKeysExceeded(count);
|
||||
|
||||
var key = NormalizeJQueryToMvc(kv.Key);
|
||||
var value = kv.Value ?? string.Empty;
|
||||
yield return new KeyValuePair<string, string>(key, value);
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowIfMaxHttpCollectionKeysExceeded(int count)
|
||||
{
|
||||
if (count >= MediaTypeFormatter.MaxHttpCollectionKeys)
|
||||
{
|
||||
var message = Resources.FormatMaxHttpCollectionKeyLimitReached(
|
||||
MediaTypeFormatter.MaxHttpCollectionKeys,
|
||||
typeof(MediaTypeFormatter));
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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.ApplicationModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class FromUriAttribute : Attribute, IParameterModelConvention
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public void Apply(ParameterModel model)
|
||||
{
|
||||
if (Name != null)
|
||||
{
|
||||
model.ParameterName = Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
// 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.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using System.Net.Http.Formatting;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public class OverloadActionConstraint : IActionConstraint
|
||||
{
|
||||
public int Order { get; } = Int32.MaxValue;
|
||||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var candidates = context.Candidates.Select(c => new
|
||||
{
|
||||
Action = c,
|
||||
Parameters = GetOverloadableParameters(c),
|
||||
});
|
||||
|
||||
// Combined route value keys and query string keys. These are the values available for overload selection.
|
||||
var requestKeys = GetCombinedKeys(context.RouteContext);
|
||||
|
||||
// Group candidates by the highest number of keys, and then process them until we find an action
|
||||
// with all parameters satisfied.
|
||||
foreach (var group in candidates.GroupBy(c => c.Parameters?.Count ?? 0).OrderByDescending(g => g.Key))
|
||||
{
|
||||
var foundMatch = false;
|
||||
foreach (var candidate in group)
|
||||
{
|
||||
var allFound = true;
|
||||
if (candidate.Parameters != null)
|
||||
{
|
||||
foreach (var parameter in candidate.Parameters)
|
||||
{
|
||||
if (!requestKeys.Contains(parameter.ParameterBindingInfo.Prefix))
|
||||
{
|
||||
if (candidate.Action.Action == context.CurrentCandidate.Action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
allFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allFound)
|
||||
{
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundMatch)
|
||||
{
|
||||
return group.Any(c => c.Action.Action == context.CurrentCandidate.Action);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ParameterDescriptor> GetOverloadableParameters(ActionSelectorCandidate candidate)
|
||||
{
|
||||
if (candidate.Action.Parameters == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var isOverloaded = false;
|
||||
foreach (var constraint in candidate.Constraints)
|
||||
{
|
||||
if (constraint is OverloadActionConstraint)
|
||||
{
|
||||
isOverloaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOverloaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// We only consider parameters that are bound from the URL.
|
||||
return candidate.Action.Parameters.Where(
|
||||
p =>
|
||||
p.ParameterBindingInfo != null &&
|
||||
!p.IsOptional &&
|
||||
ValueProviderResult.CanConvertFromString(p.ParameterBindingInfo.ParameterType))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static ISet<string> GetCombinedKeys(RouteContext routeContext)
|
||||
{
|
||||
var keys = new HashSet<string>(routeContext.RouteData.Values.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
keys.Remove("controller");
|
||||
keys.Remove("action");
|
||||
|
||||
var queryString = routeContext.HttpContext.Request.QueryString.ToUriComponent();
|
||||
|
||||
if (queryString.Length > 0)
|
||||
{
|
||||
// We need to chop off the leading '?'
|
||||
var queryData = new FormDataCollection(queryString.Substring(1));
|
||||
|
||||
var queryNameValuePairs = queryData.GetJQueryNameValuePairs();
|
||||
|
||||
if (queryNameValuePairs != null)
|
||||
{
|
||||
foreach (var queryNameValuePair in queryNameValuePairs)
|
||||
{
|
||||
keys.Add(queryNameValuePair.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNet.Mvc.WebApiCompatShim.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// The key is invalid JQuery syntax because it is missing a closing bracket.
|
||||
/// </summary>
|
||||
internal static string JQuerySyntaxMissingClosingBracket
|
||||
{
|
||||
get { return GetString("JQuerySyntaxMissingClosingBracket"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key is invalid JQuery syntax because it is missing a closing bracket.
|
||||
/// </summary>
|
||||
internal static string FormatJQuerySyntaxMissingClosingBracket()
|
||||
{
|
||||
return GetString("JQuerySyntaxMissingClosingBracket");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of keys in a NameValueCollection has exceeded the limit of '{0}'. You can adjust it by modifying the MaxHttpCollectionKeys property on the '{1}' class.
|
||||
/// </summary>
|
||||
internal static string MaxHttpCollectionKeyLimitReached
|
||||
{
|
||||
get { return GetString("MaxHttpCollectionKeyLimitReached"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of keys in a NameValueCollection has exceeded the limit of '{0}'. You can adjust it by modifying the MaxHttpCollectionKeys property on the '{1}' class.
|
||||
/// </summary>
|
||||
internal static string FormatMaxHttpCollectionKeyLimitReached(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("MaxHttpCollectionKeyLimitReached"), p0, p1);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
|
||||
if (formatterNames != null)
|
||||
{
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="JQuerySyntaxMissingClosingBracket" xml:space="preserve">
|
||||
<value>The key is invalid JQuery syntax because it is missing a closing bracket.</value>
|
||||
</data>
|
||||
<data name="MaxHttpCollectionKeyLimitReached" xml:space="preserve">
|
||||
<value>The number of keys in a NameValueCollection has exceeded the limit of '{0}'. You can adjust it by modifying the MaxHttpCollectionKeys property on the '{1}' class.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -18,7 +18,9 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
|
||||
public void Invoke(MvcOptions options)
|
||||
{
|
||||
// Placeholder
|
||||
// Add webapi behaviors to controllers with the appropriate attributes
|
||||
options.ApplicationModelConventions.Add(new WebApiActionConventionsGlobalModelConvention());
|
||||
options.ApplicationModelConventions.Add(new WebApiOverloadingGlobalModelConvention());
|
||||
}
|
||||
|
||||
public void Invoke(WebApiCompatShimOptions options)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,627 @@
|
|||
// 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.
|
||||
|
||||
#if ASPNET50
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class WebApiCompatShimActionSelectionTest
|
||||
{
|
||||
private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(WebApiCompatShimWebSite));
|
||||
private readonly Action<IApplicationBuilder> _app = new WebApiCompatShimWebSite.Startup().Configure;
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "GetItems")]
|
||||
[InlineData("PUT", "PutItems")]
|
||||
[InlineData("POST", "PostItems")]
|
||||
[InlineData("DELETE", "DeleteItems")]
|
||||
[InlineData("PATCH", "PatchItems")]
|
||||
[InlineData("HEAD", "HeadItems")]
|
||||
[InlineData("OPTIONS", "OptionsItems")]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromPrefix_UnnamedAction(string httpMethod, string actionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod(httpMethod),
|
||||
"http://localhost/api/Admin/WebAPIActionConventions");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(actionName, result.ActionName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "GetItems")]
|
||||
[InlineData("PUT", "PutItems")]
|
||||
[InlineData("POST", "PostItems")]
|
||||
[InlineData("DELETE", "DeleteItems")]
|
||||
[InlineData("PATCH", "PatchItems")]
|
||||
[InlineData("HEAD", "HeadItems")]
|
||||
[InlineData("OPTIONS", "OptionsItems")]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromPrefix_NamedAction(string httpMethod, string actionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod(httpMethod),
|
||||
"http://localhost/api/Blog/WebAPIActionConventions/" + actionName);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(actionName, result.ActionName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromPrefix_NamedAction_MismatchedVerb()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("POST"),
|
||||
"http://localhost/api/Blog/WebAPIActionConventions/GetItems");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromPrefix_UnnamedAction_DefaultVerbIsPost_Success()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("POST"),
|
||||
"http://localhost/api/Admin/WebApiActionConventionsDefaultPost");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("DefaultVerbIsPost", result.ActionName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromPrefix_NamedAction_DefaultVerbIsPost_Success()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("POST"),
|
||||
"http://localhost/api/Blog/WebAPIActionConventionsDefaultPost/DefaultVerbIsPost");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("DefaultVerbIsPost", result.ActionName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromPrefix_UnnamedAction_DefaultVerbIsPost_VerbMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("GET"),
|
||||
"http://localhost/api/Admin/WebApiActionConventionsDefaultPost");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromPrefix_NamedAction_DefaultVerbIsPost_VerbMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("PUT"),
|
||||
"http://localhost/api/Blog/WebApiActionConventionsDefaultPost/DefaultVerbIsPost");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromMethodName_NotActionName_UnnamedAction_Success()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("POST"),
|
||||
"http://localhost/api/Admin/WebAPIActionConventionsActionName");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("GetItems", result.ActionName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromMethodName_NotActionName_NamedAction_Success()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("POST"),
|
||||
"http://localhost/api/Blog/WebAPIActionConventionsActionName/GetItems");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("GetItems", result.ActionName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromMethodName_NotActionName_UnnamedAction_VerbMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("Get"),
|
||||
"http://localhost/api/Admin/WebAPIActionConventionsActionName");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_TakesHttpMethodFromMethodName_NotActionName_NamedAction_VerbMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("GET"),
|
||||
"http://localhost/api/Blog/WebAPIActionConventionsActionName/GetItems");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_HttpMethodOverride_UnnamedAction_Success()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("GET"),
|
||||
"http://localhost/api/Admin/WebAPIActionConventionsVerbOverride");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("PostItems", result.ActionName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_HttpMethodOverride_NamedAction_Success()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("GET"),
|
||||
"http://localhost/api/Blog/WebAPIActionConventionsVerbOverride/PostItems");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("PostItems", result.ActionName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_HttpMethodOverride_UnnamedAction_VerbMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("POST"),
|
||||
"http://localhost/api/Admin/WebAPIActionConventionsVerbOverride");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebAPIConvention_HttpMethodOverride_NamedAction_VerbMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
new HttpMethod("POST"),
|
||||
"http://localhost/api/Blog/WebAPIActionConventionsVerbOverride/PostItems");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
// This was ported from the WebAPI 5.2 codebase. Kept the same intentionally for compatability.
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Admin/Test", "GetUsers")]
|
||||
[InlineData("GET", "api/Admin/Test/2", "GetUser")]
|
||||
[InlineData("GET", "api/Admin/Test/3?name=mario", "GetUserByNameAndId")]
|
||||
[InlineData("GET", "api/Admin/Test/3?name=mario&ssn=123456", "GetUserByNameIdAndSsn")]
|
||||
[InlineData("GET", "api/Admin/Test?name=mario&ssn=123456", "GetUserByNameAndSsn")]
|
||||
[InlineData("GET", "api/Admin/Test?name=mario&ssn=123456&age=3", "GetUserByNameAgeAndSsn")]
|
||||
[InlineData("GET", "api/Admin/Test/5?random=9", "GetUser")]
|
||||
[InlineData("POST", "api/Admin/Test", "PostUser")]
|
||||
[InlineData("POST", "api/Admin/Test?name=mario&age=10", "PostUserByNameAndAge")]
|
||||
|
||||
// Note: Normally the following would not match DeleteUserByIdAndOptName because it has 'id' and 'age' as parameters while the DeleteUserByIdAndOptName action has 'id' and 'name'.
|
||||
// However, because the default value is provided on action parameter 'name', having the 'id' in the request was enough to match the action.
|
||||
[InlineData("DELETE", "api/Admin/Test/6?age=10", "DeleteUserByIdAndOptName")]
|
||||
[InlineData("DELETE", "api/Admin/Test", "DeleteUserByOptName")]
|
||||
[InlineData("DELETE", "api/Admin/Test?name=user", "DeleteUserByOptName")]
|
||||
[InlineData("DELETE", "api/Admin/Test/6?email=user@test.com", "DeleteUserById_Email_OptName_OptPhone")]
|
||||
[InlineData("DELETE", "api/Admin/Test/6?email=user@test.com&name=user", "DeleteUserById_Email_OptName_OptPhone")]
|
||||
[InlineData("DELETE", "api/Admin/Test/6?email=user@test.com&name=user&phone=123456789", "DeleteUserById_Email_OptName_OptPhone")]
|
||||
[InlineData("DELETE", "api/Admin/Test/6?email=user@test.com&height=1.8", "DeleteUserById_Email_Height_OptName_OptPhone")]
|
||||
[InlineData("DELETE", "api/Admin/Test/6?email=user@test.com&height=1.8&name=user", "DeleteUserById_Email_Height_OptName_OptPhone")]
|
||||
[InlineData("DELETE", "api/Admin/Test/6?email=user@test.com&height=1.8&name=user&phone=12345678", "DeleteUserById_Email_Height_OptName_OptPhone")]
|
||||
[InlineData("HEAD", "api/Admin/Test/6", "Head_Id_OptSize_OptIndex")]
|
||||
[InlineData("HEAD", "api/Admin/Test/6?size=2", "Head_Id_OptSize_OptIndex")]
|
||||
[InlineData("HEAD", "api/Admin/Test/6?index=2", "Head_Id_OptSize_OptIndex")]
|
||||
[InlineData("HEAD", "api/Admin/Test/6?index=2&size=10", "Head_Id_OptSize_OptIndex")]
|
||||
[InlineData("HEAD", "api/Admin/Test/6?index=2&otherParameter=10", "Head_Id_OptSize_OptIndex")]
|
||||
[InlineData("HEAD", "api/Admin/Test/6?otherQueryParameter=1234", "Head_Id_OptSize_OptIndex")]
|
||||
[InlineData("HEAD", "api/Admin/Test", "Head")]
|
||||
[InlineData("HEAD", "api/Admin/Test?otherParam=2", "Head")]
|
||||
[InlineData("HEAD", "api/Admin/Test?index=2&size=10", "Head")]
|
||||
public async Task LegacyActionSelection_OverloadedAction_WithUnnamedAction(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Store/Test", "GetUsers")]
|
||||
[InlineData("GET", "api/Store/Test/2", "GetUsersByName")]
|
||||
[InlineData("GET", "api/Store/Test/luigi?ssn=123456", "GetUserByNameAndSsn")]
|
||||
[InlineData("GET", "api/Store/Test/luigi?ssn=123456&id=2&ssn=12345", "GetUserByNameIdAndSsn")]
|
||||
[InlineData("GET", "api/Store/Test?age=10&ssn=123456", "GetUsers")]
|
||||
[InlineData("GET", "api/Store/Test?id=3&ssn=123456&name=luigi", "GetUserByNameIdAndSsn")]
|
||||
[InlineData("POST", "api/Store/Test/luigi?age=20", "PostUserByNameAndAge")]
|
||||
public async Task LegacyActionSelection_OverloadedAction_NonIdRouteParameter(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Admin/Test/3?NAME=mario", "GetUserByNameAndId")]
|
||||
[InlineData("GET", "api/Admin/Test/3?name=mario&SSN=123456", "GetUserByNameIdAndSsn")]
|
||||
[InlineData("GET", "api/Admin/Test?nAmE=mario&ssn=123456&AgE=3", "GetUserByNameAgeAndSsn")]
|
||||
[InlineData("DELETE", "api/Admin/Test/6?AGe=10", "DeleteUserByIdAndOptName")]
|
||||
public async Task LegacyActionSelection_OverloadedAction_Parameter_Casing(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Blog/Test/GetUsers", "GetUsers")]
|
||||
[InlineData("GET", "api/Blog/Test/GetUser/7", "GetUser")]
|
||||
[InlineData("GET", "api/Blog/Test/GetUser?id=3", "GetUser")]
|
||||
[InlineData("GET", "api/Blog/Test/GetUser/4?id=3", "GetUser")]
|
||||
[InlineData("GET", "api/Blog/Test/GetUserByNameAgeAndSsn?name=user&age=90&ssn=123456789", "GetUserByNameAgeAndSsn")]
|
||||
[InlineData("GET", "api/Blog/Test/GetUserByNameAndSsn?name=user&ssn=123456789", "GetUserByNameAndSsn")]
|
||||
[InlineData("POST", "api/Blog/Test/PostUserByNameAndAddress?name=user", "PostUserByNameAndAddress")]
|
||||
public async Task LegacyActionSelection_RouteWithActionName(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Blog/Test/getusers", "GetUsers")]
|
||||
[InlineData("GET", "api/Blog/Test/getuseR/1", "GetUser")]
|
||||
[InlineData("GET", "api/Blog/Test/Getuser?iD=3", "GetUser")]
|
||||
[InlineData("GET", "api/Blog/Test/GetUser/4?Id=3", "GetUser")]
|
||||
[InlineData("GET", "api/Blog/Test/GetUserByNameAgeandSsn?name=user&age=90&ssn=123456789", "GetUserByNameAgeAndSsn")]
|
||||
[InlineData("GET", "api/Blog/Test/getUserByNameAndSsn?name=user&ssn=123456789", "GetUserByNameAndSsn")]
|
||||
[InlineData("POST", "api/Blog/Test/PostUserByNameAndAddress?name=user", "PostUserByNameAndAddress")]
|
||||
public async Task LegacyActionSelection_RouteWithActionName_Casing(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Admin/Test", "GetUsers")]
|
||||
[InlineData("GET", "api/Admin/Test/?name=peach", "GetUsersByName")]
|
||||
[InlineData("GET", "api/Admin/Test?name=peach", "GetUsersByName")]
|
||||
[InlineData("GET", "api/Admin/Test?name=peach&ssn=123456", "GetUserByNameAndSsn")]
|
||||
[InlineData("GET", "api/Admin/Test?name=peach&ssn=123456&age=3", "GetUserByNameAgeAndSsn")]
|
||||
public async Task LegacyActionSelection_RouteWithoutActionName(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Admin/ParameterAttribute/2", "GetUser")]
|
||||
[InlineData("GET", "api/Admin/ParameterAttribute?id=2", "GetUser")]
|
||||
[InlineData("GET", "api/Admin/ParameterAttribute?myId=2", "GetUserByMyId")]
|
||||
[InlineData("POST", "api/Admin/ParameterAttribute/3?name=user", "PostUserNameFromUri")]
|
||||
[InlineData("POST", "api/Admin/ParameterAttribute/3", "PostUserNameFromBody")]
|
||||
[InlineData("DELETE", "api/Admin/ParameterAttribute/3?name=user", "DeleteUserWithNullableIdAndName")]
|
||||
[InlineData("DELETE", "api/Admin/ParameterAttribute?address=userStreet", "DeleteUser")]
|
||||
public async Task LegacyActionSelection_ModelBindingParameterAttribute_AreAppliedWhenSelectingActions(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Support/notActionParameterValue1/Test", "GetUsers")]
|
||||
[InlineData("GET", "api/Support/notActionParameterValue2/Test/2", "GetUser")]
|
||||
[InlineData("GET", "api/Support/notActionParameterValue1/Test?randomQueryVariable=val1", "GetUsers")]
|
||||
[InlineData("GET", "api/Support/notActionParameterValue2/Test/2?randomQueryVariable=val2", "GetUser")]
|
||||
public async Task LegacyActionSelection_ActionsThatHaveSubsetOfRouteParameters_AreConsideredForSelection(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
// This would result in ambiguous match because complex parameter is not considered for matching.
|
||||
// Therefore, PostUserByNameAndAddress(string name, Address address) would conflicts with PostUserByName(string name)
|
||||
[Fact]
|
||||
public async Task LegacyActionSelection_RequestToAmbiguousAction_OnDefaultRoute()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod("POST"), "http://localhost/api/Admin/Test?name=mario");
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<AmbiguousActionException>(async () => await client.SendAsync(request));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "api/Admin/EnumParameterOverloads", "Get")]
|
||||
[InlineData("GET", "api/Admin/EnumParameterOverloads?scope=global", "GetWithEnumParameter")]
|
||||
[InlineData("GET", "api/Admin/EnumParameterOverloads?level=off&kind=trace", "GetWithTwoEnumParameters")]
|
||||
[InlineData("GET", "api/Admin/EnumParameterOverloads?level=", "GetWithNullableEnumParameter")]
|
||||
public async Task LegacyActionSelection_SelectAction_ReturnsActionDescriptor_ForEnumParameterOverloads(string httpMethod, string requestUrl, string expectedActionName)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActionSelectionResult>(body);
|
||||
|
||||
//Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expectedActionName, result.ActionName);
|
||||
}
|
||||
|
||||
// Verify response has all the methods in its Allow header. values are unsorted.
|
||||
private void AssertAllowedHeaders(HttpResponseMessage response, params HttpMethod[] allowedMethods)
|
||||
{
|
||||
foreach (var method in allowedMethods)
|
||||
{
|
||||
Assert.Contains(method.ToString(), response.Content.Headers.Allow);
|
||||
}
|
||||
Assert.Equal(allowedMethods.Length, response.Content.Headers.Allow.Count);
|
||||
}
|
||||
|
||||
private class ActionSelectionResult
|
||||
{
|
||||
public string ActionName { get; set; }
|
||||
|
||||
public string ControllerName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/BasicApi/WriteToHttpContext");
|
||||
var response = await client.GetAsync("http://localhost/api/Blog/BasicApi/WriteToHttpContext");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
|
|
@ -44,13 +44,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/BasicApi/GenerateUrl");
|
||||
var response = await client.GetAsync("http://localhost/api/Blog/BasicApi/GenerateUrl");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(
|
||||
"Visited: /BasicApi/GenerateUrl",
|
||||
"Visited: /api/Blog/BasicApi/GenerateUrl",
|
||||
content);
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
};
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("http://localhost/BasicApi/GetFormatters");
|
||||
var response = await client.GetAsync("http://localhost/api/Blog/BasicApi/GetFormatters");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var formatters = JsonConvert.DeserializeObject<string[]>(content);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.NestedProviders;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
|
@ -16,8 +17,6 @@ namespace System.Web.Http
|
|||
{
|
||||
public class ApiControllerActionDiscoveryTest
|
||||
{
|
||||
// For now we just want to verify that an ApiController is-a controller and produces
|
||||
// actions. When we implement the conventions for action discovery, this test will be revised.
|
||||
[Fact]
|
||||
public void GetActions_ApiControllerWithControllerSuffix_IsController()
|
||||
{
|
||||
|
|
@ -32,9 +31,9 @@ namespace System.Web.Http
|
|||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.ProductsController).GetTypeInfo();
|
||||
var filtered = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
|
||||
var actions = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
|
||||
|
||||
Assert.Equal(3, filtered.Length);
|
||||
Assert.NotEmpty(actions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -51,9 +50,179 @@ namespace System.Web.Http
|
|||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.Blog).GetTypeInfo();
|
||||
var filtered = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
|
||||
var actions = results.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType).ToArray();
|
||||
|
||||
Assert.Empty(filtered);
|
||||
Assert.Empty(actions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_CreatesNamedAndUnnamedAction()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.Where(ad => ad.MethodInfo.Name == "GetAll")
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(2, actions.Length);
|
||||
|
||||
var action = Assert.Single(
|
||||
actions,
|
||||
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "GetAll"));
|
||||
Assert.Equal(
|
||||
new string[] { "GET" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodConstraint>()).HttpMethods);
|
||||
|
||||
action = Assert.Single(
|
||||
actions,
|
||||
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == ""));
|
||||
Assert.Equal(
|
||||
new string[] { "GET" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodConstraint>()).HttpMethods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_CreatesNamedAndUnnamedAction_DefaultVerbIsPost()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.Where(ad => ad.MethodInfo.Name == "Edit")
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(2, actions.Length);
|
||||
|
||||
var action = Assert.Single(
|
||||
actions,
|
||||
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "Edit"));
|
||||
Assert.Equal(
|
||||
new string[] { "POST" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodConstraint>()).HttpMethods);
|
||||
|
||||
action = Assert.Single(
|
||||
actions,
|
||||
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == ""));
|
||||
Assert.Equal(
|
||||
new string[] { "POST" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodConstraint>()).HttpMethods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_CreatesNamedAndUnnamedAction_RespectsVerbAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.Where(ad => ad.MethodInfo.Name == "Delete")
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(2, actions.Length);
|
||||
|
||||
var action = Assert.Single(
|
||||
actions,
|
||||
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "Delete"));
|
||||
Assert.Equal(
|
||||
new string[] { "PUT" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodConstraint>()).HttpMethods);
|
||||
|
||||
action = Assert.Single(
|
||||
actions,
|
||||
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == ""));
|
||||
Assert.Equal(
|
||||
new string[] { "PUT" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodConstraint>()).HttpMethods);
|
||||
}
|
||||
|
||||
// The method name is used to infer a verb, not the action name
|
||||
[Fact]
|
||||
public void GetActions_CreatesNamedAndUnnamedAction_VerbBasedOnMethodName()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.Where(ad => ad.MethodInfo.Name == "Options")
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(2, actions.Length);
|
||||
|
||||
var action = Assert.Single(
|
||||
actions,
|
||||
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == "GetOptions"));
|
||||
Assert.Equal(
|
||||
new string[] { "OPTIONS" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodConstraint>()).HttpMethods);
|
||||
|
||||
action = Assert.Single(
|
||||
actions,
|
||||
a => a.RouteConstraints.Any(rc => rc.RouteKey == "action" && rc.RouteValue == ""));
|
||||
Assert.Equal(
|
||||
new string[] { "OPTIONS" },
|
||||
Assert.Single(action.ActionConstraints.OfType<HttpMethodConstraint>()).HttpMethods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AllWebApiActionsAreOverloaded()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.ToArray();
|
||||
|
||||
Assert.NotEmpty(actions);
|
||||
foreach (var action in actions)
|
||||
{
|
||||
Assert.Single(action.ActionConstraints, c => c is OverloadActionConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
private INestedProviderManager<ActionDescriptorProviderContext> CreateProvider()
|
||||
|
|
@ -70,13 +239,17 @@ namespace System.Web.Http
|
|||
|
||||
var conventions = new NamespaceLimitedActionDiscoveryConventions();
|
||||
|
||||
var options = new MvcOptions();
|
||||
options.ApplicationModelConventions.Add(new WebApiActionConventionsGlobalModelConvention());
|
||||
options.ApplicationModelConventions.Add(new WebApiOverloadingGlobalModelConvention());
|
||||
|
||||
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
|
||||
optionsAccessor
|
||||
.SetupGet(o => o.Options)
|
||||
.Returns(new MvcOptions());
|
||||
.Returns(options);
|
||||
|
||||
var provider = new ControllerActionDescriptorProvider(
|
||||
assemblyProvider.Object,
|
||||
assemblyProvider.Object,
|
||||
conventions,
|
||||
filterProvider.Object,
|
||||
optionsAccessor.Object);
|
||||
|
|
@ -92,7 +265,7 @@ namespace System.Web.Http
|
|||
{
|
||||
public override bool IsController(TypeInfo typeInfo)
|
||||
{
|
||||
return
|
||||
return
|
||||
typeInfo.Namespace == "System.Web.Http.TestControllers" &&
|
||||
base.IsController(typeInfo);
|
||||
}
|
||||
|
|
@ -110,16 +283,6 @@ namespace System.Web.Http.TestControllers
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public IActionResult Get(int id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public IActionResult Edit(int id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Not a controller, because there's no controller suffix
|
||||
|
|
@ -130,4 +293,29 @@ namespace System.Web.Http.TestControllers
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class StoreController : ApiController
|
||||
{
|
||||
public IActionResult GetAll()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public IActionResult Edit(int id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public IActionResult Delete(int id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[ActionName("GetOptions")]
|
||||
public IActionResult Options()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public class FormDataCollectionExtensionsTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("", null)]
|
||||
[InlineData("", "")] // empty
|
||||
[InlineData("x", "x")] // normal key
|
||||
[InlineData("", "[]")] // trim []
|
||||
[InlineData("x", "x[]")] // trim []
|
||||
[InlineData("x[234]", "x[234]")] // array index
|
||||
[InlineData("x.y", "x[y]")] // field lookup
|
||||
[InlineData("x.y.z", "x[y][z]")] // nested field lookup
|
||||
[InlineData("x.y[234].x", "x[y][234][x]")] // compound
|
||||
public void TestNormalize(string expectedMvc, string jqueryString)
|
||||
{
|
||||
Assert.Equal(expectedMvc, FormDataCollectionExtensions.NormalizeJQueryToMvc(jqueryString));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetJQueryNameValuePairs()
|
||||
{
|
||||
// Arrange
|
||||
var formData = new FormDataCollection("x.y=30&x[y]=70&x[z][20]=cool");
|
||||
|
||||
// Act
|
||||
var actual = FormDataCollectionExtensions.GetJQueryNameValuePairs(formData).ToArray();
|
||||
|
||||
// Assert
|
||||
var arraySetter = Assert.Single(actual, kvp => kvp.Key == "x.z[20]");
|
||||
Assert.Equal("cool", arraySetter.Value);
|
||||
|
||||
Assert.Single(actual, kvp => kvp.Key == "x.y" && kvp.Value == "30");
|
||||
Assert.Single(actual, kvp => kvp.Key == "x.y" && kvp.Value == "70");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,444 @@
|
|||
// 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 Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public class OverloadActionConstraintTest
|
||||
{
|
||||
[Fact]
|
||||
public void Accept_RejectsActionMatchWithMissingParameter()
|
||||
{
|
||||
// Arrange
|
||||
var action = new ActionDescriptor();
|
||||
action.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext();
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_AcceptsActionWithSatisfiedParameters()
|
||||
{
|
||||
// Arrange
|
||||
var action = new ActionDescriptor();
|
||||
action.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5", new { id = 17});
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_AcceptsActionWithSatisfiedParameters_QueryStringOnly()
|
||||
{
|
||||
// Arrange
|
||||
var action = new ActionDescriptor();
|
||||
action.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5&id=7", new { });
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_AcceptsActionWithSatisfiedParameters_RouteDataOnly()
|
||||
{
|
||||
// Arrange
|
||||
var action = new ActionDescriptor();
|
||||
action.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?", new { quantity = 9, id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_AcceptsActionWithUnsatisfiedOptionalParameter()
|
||||
{
|
||||
// Arrange
|
||||
var action = new ActionDescriptor();
|
||||
action.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
IsOptional = true,
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?store=5", new { id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_AcceptsOneAndRejectsAnother()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new ActionDescriptor();
|
||||
action1.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var action2 = new ActionDescriptor();
|
||||
action2.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity_ordered",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity_ordered", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action1, new [] { constraint }),
|
||||
new ActionSelectorCandidate(action2, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5", new { id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
|
||||
context.CurrentCandidate = context.Candidates[1];
|
||||
Assert.False(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_RejectsWorseMatch()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new ActionDescriptor();
|
||||
action1.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var action2 = new ActionDescriptor();
|
||||
action2.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action1, new [] { constraint }),
|
||||
new ActionSelectorCandidate(action2, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5", new { id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_RejectsWorseMatch_OptionalParameter()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new ActionDescriptor();
|
||||
action1.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
IsOptional = true,
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var action2 = new ActionDescriptor();
|
||||
action2.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action1, new [] { constraint }),
|
||||
new ActionSelectorCandidate(action2, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5", new { id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_AcceptsActionsOnSameTier()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new ActionDescriptor();
|
||||
action1.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var action2 = new ActionDescriptor();
|
||||
action2.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "price",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("price", typeof(decimal)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action1, new [] { constraint }),
|
||||
new ActionSelectorCandidate(action2, new [] { constraint }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5&price=5.99", new { id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
|
||||
context.CurrentCandidate = context.Candidates[1];
|
||||
Assert.True(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_AcceptsAction_WithFewerParameters_WhenOtherIsNotOverloaded()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new ActionDescriptor();
|
||||
action1.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var action2 = new ActionDescriptor();
|
||||
action2.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action1, new [] { constraint }),
|
||||
new ActionSelectorCandidate(action2, new IActionConstraint[] { }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5", new { id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
}
|
||||
|
||||
private static RouteContext CreateRouteContext(string queryString = null, object routeValues = null)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
if (queryString != null)
|
||||
{
|
||||
httpContext.Request.QueryString = new QueryString(queryString);
|
||||
}
|
||||
|
||||
var routeContext = new RouteContext(httpContext);
|
||||
routeContext.RouteData = new RouteData()
|
||||
{
|
||||
Values = new RouteValueDictionary(routeValues),
|
||||
};
|
||||
|
||||
return routeContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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 WebApiCompatShimWebSite
|
||||
{
|
||||
public class ActionSelectionFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
var action = (ControllerActionDescriptor)context.ActionDescriptor;
|
||||
context.Result = new JsonResult(new
|
||||
{
|
||||
ActionName = action.Name,
|
||||
ControllerName = action.ControllerName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
// This was ported from the WebAPI 5.2 codebase. Kept the same intentionally for compatability.
|
||||
[ActionSelectionFilter]
|
||||
public class EnumParameterOverloadsController : ApiController
|
||||
{
|
||||
public IEnumerable<string> Get()
|
||||
{
|
||||
return new string[] { "get" };
|
||||
}
|
||||
|
||||
public string GetWithEnumParameter(UserKind scope)
|
||||
{
|
||||
return scope.ToString();
|
||||
}
|
||||
|
||||
public string GetWithTwoEnumParameters([FromUri]UserKind level, UserKind kind)
|
||||
{
|
||||
return level.ToString() + kind.ToString();
|
||||
}
|
||||
|
||||
public string GetWithNullableEnumParameter(TraceLevel? level)
|
||||
{
|
||||
return level.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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.Web.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
// This was ported from the WebAPI 5.2 codebase. Kept the same intentionally for compatability.
|
||||
[ActionSelectionFilter]
|
||||
public class ParameterAttributeController : ApiController
|
||||
{
|
||||
public User GetUserByMyId(int myId) { return null; }
|
||||
public User GetUser([FromUri(Name = "id")] int myId) { return null; }
|
||||
public List<User> PostUserNameFromUri(int id, [FromUri]string name) { return null; }
|
||||
public List<User> PostUserNameFromBody(int id, [FromBody] string name) { return null; }
|
||||
public void DeleteUserWithNullableIdAndName(int? id, string name) { }
|
||||
public void DeleteUser(string address) { }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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.Web.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
// This was ported from the WebAPI 5.2 codebase. Kept the same intentionally for compatability.
|
||||
[ActionSelectionFilter]
|
||||
public class TestController : ApiController
|
||||
{
|
||||
public User GetUser(int id) { return null; }
|
||||
public List<User> GetUsers() { return null; }
|
||||
|
||||
public List<User> GetUsersByName(string name) { return null; }
|
||||
|
||||
[AcceptVerbs("PATCH")]
|
||||
public void PutUser(User user) { }
|
||||
|
||||
public User GetUserByNameAndId(string name, int id) { return null; }
|
||||
public User GetUserByNameAndAge(string name, int age) { return null; }
|
||||
public User GetUserByNameAgeAndSsn(string name, int age, int ssn) { return null; }
|
||||
public User GetUserByNameIdAndSsn(string name, int id, int ssn) { return null; }
|
||||
public User GetUserByNameAndSsn(string name, int ssn) { return null; }
|
||||
public User PostUser(User user) { return null; }
|
||||
public User PostUserByNameAndAge(string name, int age) { return null; }
|
||||
public User PostUserByName(string name) { return null; }
|
||||
public User PostUserByNameAndAddress(string name, UserAddress address) { return null; }
|
||||
public User DeleteUserByOptName(string name = null) { return null; }
|
||||
public User DeleteUserByIdAndOptName(int id, string name = "DefaultName") { return null; }
|
||||
public User DeleteUserByIdNameAndAge(int id, string name, int age) { return null; }
|
||||
public User DeleteUserById_Email_OptName_OptPhone(int id, string email, string name = null, int phone = 0) { return null; }
|
||||
public User DeleteUserById_Email_Height_OptName_OptPhone(int id, string email, double height, string name = "DefaultName", int? phone = null) { return null; }
|
||||
public void Head_Id_OptSize_OptIndex(int id, int size = 10, int index = 0) { }
|
||||
public void Head() { }
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Web.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
// The verb is still inferred by the METHOD NAME not the action name.
|
||||
[ActionSelectionFilter]
|
||||
public class WebAPIActionConventionsActionNameController : ApiController
|
||||
{
|
||||
[ActionName("GetItems")]
|
||||
public void PostItems()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// 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.Web.Http;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
// Each of these is mapped to an unnamed action with the corresponding http verb, and also
|
||||
// a named action with the corresponding http verb.
|
||||
[ActionSelectionFilter]
|
||||
public class WebAPIActionConventionsController : ApiController
|
||||
{
|
||||
public void GetItems()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostItems()
|
||||
{
|
||||
}
|
||||
|
||||
public void PutItems()
|
||||
{
|
||||
}
|
||||
|
||||
public void DeleteItems()
|
||||
{
|
||||
}
|
||||
|
||||
public void PatchItems()
|
||||
{
|
||||
}
|
||||
|
||||
public void HeadItems()
|
||||
{
|
||||
}
|
||||
|
||||
public void OptionsItems()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Web.Http;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
// This action only accepts POST by default
|
||||
[ActionSelectionFilter]
|
||||
public class WebAPIActionConventionsDefaultPostController : ApiController
|
||||
{
|
||||
public void DefaultVerbIsPost()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Web.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
// The verb is overridden by the attribute
|
||||
[ActionSelectionFilter]
|
||||
public class WebAPIActionConventionsVerbOverrideController : ApiController
|
||||
{
|
||||
[HttpGet]
|
||||
public void PostItems()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// 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;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
public class User
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// 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;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
public class UserAddress
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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 WebApiCompatShimWebSite
|
||||
{
|
||||
public enum UserKind
|
||||
{
|
||||
Normal,
|
||||
Admin,
|
||||
SuperAdmin,
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
// 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 WebApiCompatShimWebSite
|
||||
|
|
@ -19,7 +21,15 @@ namespace WebApiCompatShimWebSite
|
|||
services.AddWebApiConventions();
|
||||
});
|
||||
|
||||
app.UseMvc();
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
// Tests include different styles of WebAPI conventional routing and action selection - the prefix keeps
|
||||
// them from matching too eagerly.
|
||||
routes.MapRoute("named-action", "api/Blog/{controller}/{action}/{id?}");
|
||||
routes.MapRoute("unnamed-action", "api/Admin/{controller}/{id?}");
|
||||
routes.MapRoute("name-as-parameter", "api/Store/{controller}/{name?}");
|
||||
routes.MapRoute("extra-parameter", "api/Support/{extra}/{controller}/{id?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue