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.Http;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||||
|
|
||||||
namespace System.Web.Http
|
namespace System.Web.Http
|
||||||
{
|
{
|
||||||
|
[UseWebApiActionConventions]
|
||||||
|
[UseWebApiOverloading]
|
||||||
public abstract class ApiController : IDisposable
|
public abstract class ApiController : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>Gets the action context.</summary>
|
/// <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)
|
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)
|
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();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
// Act
|
// 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();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -44,13 +44,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
|
||||||
// Act
|
// 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();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
"Visited: /BasicApi/GenerateUrl",
|
"Visited: /api/Blog/BasicApi/GenerateUrl",
|
||||||
content);
|
content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// 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 content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
var formatters = JsonConvert.DeserializeObject<string[]>(content);
|
var formatters = JsonConvert.DeserializeObject<string[]>(content);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
using Microsoft.AspNet.Mvc.Filters;
|
using Microsoft.AspNet.Mvc.Filters;
|
||||||
|
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.DependencyInjection.NestedProviders;
|
using Microsoft.Framework.DependencyInjection.NestedProviders;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
|
@ -16,8 +17,6 @@ namespace System.Web.Http
|
||||||
{
|
{
|
||||||
public class ApiControllerActionDiscoveryTest
|
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]
|
[Fact]
|
||||||
public void GetActions_ApiControllerWithControllerSuffix_IsController()
|
public void GetActions_ApiControllerWithControllerSuffix_IsController()
|
||||||
{
|
{
|
||||||
|
|
@ -32,9 +31,9 @@ namespace System.Web.Http
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var controllerType = typeof(TestControllers.ProductsController).GetTypeInfo();
|
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]
|
[Fact]
|
||||||
|
|
@ -51,9 +50,179 @@ namespace System.Web.Http
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var controllerType = typeof(TestControllers.Blog).GetTypeInfo();
|
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()
|
private INestedProviderManager<ActionDescriptorProviderContext> CreateProvider()
|
||||||
|
|
@ -70,13 +239,17 @@ namespace System.Web.Http
|
||||||
|
|
||||||
var conventions = new NamespaceLimitedActionDiscoveryConventions();
|
var conventions = new NamespaceLimitedActionDiscoveryConventions();
|
||||||
|
|
||||||
|
var options = new MvcOptions();
|
||||||
|
options.ApplicationModelConventions.Add(new WebApiActionConventionsGlobalModelConvention());
|
||||||
|
options.ApplicationModelConventions.Add(new WebApiOverloadingGlobalModelConvention());
|
||||||
|
|
||||||
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
|
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
|
||||||
optionsAccessor
|
optionsAccessor
|
||||||
.SetupGet(o => o.Options)
|
.SetupGet(o => o.Options)
|
||||||
.Returns(new MvcOptions());
|
.Returns(options);
|
||||||
|
|
||||||
var provider = new ControllerActionDescriptorProvider(
|
var provider = new ControllerActionDescriptorProvider(
|
||||||
assemblyProvider.Object,
|
assemblyProvider.Object,
|
||||||
conventions,
|
conventions,
|
||||||
filterProvider.Object,
|
filterProvider.Object,
|
||||||
optionsAccessor.Object);
|
optionsAccessor.Object);
|
||||||
|
|
@ -92,7 +265,7 @@ namespace System.Web.Http
|
||||||
{
|
{
|
||||||
public override bool IsController(TypeInfo typeInfo)
|
public override bool IsController(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
typeInfo.Namespace == "System.Web.Http.TestControllers" &&
|
typeInfo.Namespace == "System.Web.Http.TestControllers" &&
|
||||||
base.IsController(typeInfo);
|
base.IsController(typeInfo);
|
||||||
}
|
}
|
||||||
|
|
@ -110,16 +283,6 @@ namespace System.Web.Http.TestControllers
|
||||||
{
|
{
|
||||||
return null;
|
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
|
// Not a controller, because there's no controller suffix
|
||||||
|
|
@ -130,4 +293,29 @@ namespace System.Web.Http.TestControllers
|
||||||
return null;
|
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.
|
// 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.Builder;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
namespace WebApiCompatShimWebSite
|
namespace WebApiCompatShimWebSite
|
||||||
|
|
@ -19,7 +21,15 @@ namespace WebApiCompatShimWebSite
|
||||||
services.AddWebApiConventions();
|
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