Overload Resolution Skeleton

This commit is contained in:
Ryan Nowak 2014-02-17 17:57:42 -08:00
parent 941a12daea
commit 9d056167e8
14 changed files with 264 additions and 5 deletions

View File

@ -0,0 +1,30 @@

using Microsoft.AspNet.Mvc;
namespace MvcSample
{
public class OverloadController
{
private readonly IActionResultHelper _result;
public OverloadController(IActionResultHelper result)
{
_result = result;
}
public IActionResult Get()
{
return _result.Content("Get()");
}
public IActionResult Get(int id)
{
return _result.Content("Get(id)");
}
public IActionResult Get(int id, string name)
{
return _result.Content("Get(id, name)");
}
}
}

View File

@ -0,0 +1,8 @@

namespace Microsoft.AspNet.Mvc.ModelBinding
{
public interface IValueProvider
{
bool ContainsPrefix(string key);
}
}

View File

@ -1,5 +1,6 @@
using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.FileSystems;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Razor;
namespace Microsoft.AspNet.Mvc.Startup
@ -19,6 +20,9 @@ namespace Microsoft.AspNet.Mvc.Startup
Add<IActionResultHelper, ActionResultHelper>();
Add<IActionResultFactory, ActionResultFactory>();
Add<IActionDescriptorProvider, TypeMethodBasedActionDescriptorProvider>();
Add<IParameterDescriptorFactory, DefaultParameterDescriptorFactory>();
Add<IValueProviderFactory, RouteValueValueProviderFactory>();
Add<IValueProviderFactory, QueryStringValueProviderFactory>();
Add<IActionInvokerProvider, ActionInvokerProvider>();
Add<IControllerAssemblyProvider, AppDomainControllerAssemblyProvider>();
Add<IActionDiscoveryConventions, DefaultActionDiscoveryConventions>();

View File

@ -14,6 +14,8 @@ namespace Microsoft.AspNet.Mvc
public List<HttpMethodConstraint> MethodConstraints { get; set; }
public IEnumerable<IActionConstraint> DynamicConstraints { get; set; }
public List<IActionConstraint> DynamicConstraints { get; set; }
public List<ParameterDescriptor> Parameters { get; set; }
}
}

View File

@ -1,16 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
public class DefaultActionSelector : IActionSelector
{
private readonly IEnumerable<IActionDescriptorProvider> _actionDescriptorProviders;
private readonly IEnumerable<IValueProviderFactory> _valueProviderFactory;
public DefaultActionSelector(IEnumerable<IActionDescriptorProvider> actionDescriptorProviders)
public DefaultActionSelector(IEnumerable<IActionDescriptorProvider> actionDescriptorProviders, IEnumerable<IValueProviderFactory> valueProviderFactories)
{
_actionDescriptorProviders = actionDescriptorProviders;
_valueProviderFactory = valueProviderFactories;
}
public ActionDescriptor Select(RequestContext context)
@ -22,7 +25,19 @@ namespace Microsoft.AspNet.Mvc
var allDescriptors = _actionDescriptorProviders.SelectMany(d => d.GetDescriptors());
return allDescriptors.SingleOrDefault(d => Match(d, context));
var matching = allDescriptors.Where(ad => Match(ad, context)).ToList();
if (matching.Count == 0)
{
return null;
}
else if (matching.Count == 1)
{
return matching[0];
}
else
{
return SelectBestCandidate(context, matching);
}
}
public bool Match(ActionDescriptor descriptor, RequestContext context)
@ -36,5 +51,65 @@ namespace Microsoft.AspNet.Mvc
(descriptor.MethodConstraints == null || descriptor.MethodConstraints.All(c => c.Accept(context))) &&
(descriptor.DynamicConstraints == null || descriptor.DynamicConstraints.All(c => c.Accept(context)));
}
protected virtual ActionDescriptor SelectBestCandidate(RequestContext context, List<ActionDescriptor> candidates)
{
var valueProviders = _valueProviderFactory.Select(vpf => vpf.CreateValueProvider(context)).ToArray();
var applicableCandiates = new List<ActionDescriptorCandidate>();
foreach (var action in candidates)
{
var isApplicable = true;
var candidate = new ActionDescriptorCandidate()
{
Action = action,
};
foreach (var parameter in action.Parameters.Where(p => !p.Binding.IsFromBody))
{
if (valueProviders.Any(vp => vp.ContainsPrefix(parameter.Binding.Prefix)))
{
candidate.FoundParameters++;
if (parameter.Binding.IsOptional)
{
candidate.FoundOptionalParameters++;
}
}
else if (!parameter.Binding.IsOptional)
{
isApplicable = false;
break;
}
}
if (isApplicable)
{
applicableCandiates.Add(candidate);
}
}
var mostParametersSatisfied = applicableCandiates.GroupBy(c => c.FoundParameters).OrderByDescending(g => g.Key).First();
if (mostParametersSatisfied == null)
{
return null;
}
var fewestOptionalParameters = mostParametersSatisfied.GroupBy(c => c.FoundOptionalParameters).OrderBy(g => g.Key).First().ToArray();
if (fewestOptionalParameters.Length > 1)
{
throw new InvalidOperationException("The actions are ambiguious.");
}
return fewestOptionalParameters[0].Action;
}
private class ActionDescriptorCandidate
{
public ActionDescriptor Action { get; set; }
public int FoundParameters { get; set; }
public int FoundOptionalParameters { get; set; }
}
}
}

View File

@ -0,0 +1,30 @@

using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public class DefaultParameterDescriptorFactory : IParameterDescriptorFactory
{
public ParameterDescriptor GetDescriptor(ParameterInfo parameter)
{
var bindingInfo = new ParameterBindingInfo()
{
IsOptional = parameter.IsOptional,
IsFromBody = IsFromBody(parameter),
Prefix = parameter.Name,
};
return new ParameterDescriptor()
{
Name = parameter.Name,
Binding = bindingInfo,
};
}
public virtual bool IsFromBody(ParameterInfo parameter)
{
// Assume for now everything is read from value providers
return false;
}
}
}

View File

@ -0,0 +1,10 @@

using System.Reflection;
namespace Microsoft.AspNet.Mvc
{
public interface IParameterDescriptorFactory
{
ParameterDescriptor GetDescriptor(ParameterInfo parameter);
}
}

View File

@ -0,0 +1,9 @@

namespace Microsoft.AspNet.Mvc.ModelBinding
{
public interface IValueProviderFactory
{
IValueProvider CreateValueProvider(RequestContext context);
}
}

View File

@ -0,0 +1,29 @@

using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
// This is a temporary placeholder
public class QueryStringValueProviderFactory : IValueProviderFactory
{
public IValueProvider CreateValueProvider(RequestContext context)
{
return new QueryStringValueProvider(context.HttpContext.Request.Query);
}
private class QueryStringValueProvider : IValueProvider
{
private readonly IReadableStringCollection _values;
public QueryStringValueProvider(IReadableStringCollection values)
{
_values = values;
}
public bool ContainsPrefix(string key)
{
return _values.Get(key) != null;
}
}
}
}

View File

@ -0,0 +1,12 @@

namespace Microsoft.AspNet.Mvc.ModelBinding
{
// This is a temporary placeholder
public class RouteValueValueProviderFactory : IValueProviderFactory
{
public IValueProvider CreateValueProvider(RequestContext context)
{
return new ValueProvider(context.RouteValues);
}
}
}

View File

@ -0,0 +1,21 @@

using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
// This is a temporary placeholder
public class ValueProvider : IValueProvider
{
private readonly IDictionary<string, object> _values;
public ValueProvider(IDictionary<string, object> values)
{
_values = values;
}
public bool ContainsPrefix(string key)
{
return _values.ContainsKey(key);
}
}
}

View File

@ -0,0 +1,13 @@

namespace Microsoft.AspNet.Mvc
{
// This is a placeholder and is missing things that we'll need for real model binding
public class ParameterBindingInfo
{
public bool IsOptional { get; set; }
public bool IsFromBody { get; set; }
public string Prefix { get; set; }
}
}

View File

@ -0,0 +1,11 @@

namespace Microsoft.AspNet.Mvc
{
public class ParameterDescriptor
{
public string Name { get; set; }
public ParameterBindingInfo Binding { get; set; }
}
}

View File

@ -9,14 +9,17 @@ namespace Microsoft.AspNet.Mvc
private readonly IControllerAssemblyProvider _controllerAssemblyProvider;
private readonly IActionDiscoveryConventions _conventions;
private readonly IControllerDescriptorFactory _controllerDescriptorFactory;
private readonly IParameterDescriptorFactory _parameterDescriptorFactory;
public TypeMethodBasedActionDescriptorProvider(IControllerAssemblyProvider controllerAssemblyProvider,
IActionDiscoveryConventions conventions,
IControllerDescriptorFactory controllerDescriptorFactory)
IControllerDescriptorFactory controllerDescriptorFactory,
IParameterDescriptorFactory parameterDescriptorFactory)
{
_controllerAssemblyProvider = controllerAssemblyProvider;
_conventions = conventions;
_controllerDescriptorFactory = controllerDescriptorFactory;
_parameterDescriptorFactory = parameterDescriptorFactory;
}
public IEnumerable<ActionDescriptor> GetDescriptors()
@ -45,7 +48,7 @@ namespace Microsoft.AspNet.Mvc
}
}
private static TypeMethodBasedActionDescriptor BuildDescriptor(ControllerDescriptor controllerDescriptor, MethodInfo methodInfo, ActionInfo actionInfo)
private TypeMethodBasedActionDescriptor BuildDescriptor(ControllerDescriptor controllerDescriptor, MethodInfo methodInfo, ActionInfo actionInfo)
{
var ad = new TypeMethodBasedActionDescriptor
{
@ -77,6 +80,8 @@ namespace Microsoft.AspNet.Mvc
ad.RouteConstraints.Add(new RouteDataActionConstraint("action", RouteKeyHandling.DenyKey));
}
ad.Parameters = methodInfo.GetParameters().Select(p => _parameterDescriptorFactory.GetDescriptor(p)).ToList();
return ad;
}