This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
/
InferParameterBindingInfoConvention.cs
133 lines (112 loc) · 4.99 KB
/
InferParameterBindingInfoConvention.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright (c) .NET Foundation. 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.Linq;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Routing.Template;
using Resources = Microsoft.AspNetCore.Mvc.Core.Resources;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// An <see cref="IActionModelConvention"/> that infers <see cref="BindingInfo.BindingSource"/> for parameters.
/// </summary>
/// <remarks>
/// The goal of this covention is to make intuitive and easy to document <see cref="BindingSource"/> inferences. The rules are:
/// <list type="number">
/// <item>A previously specified <see cref="BindingInfo.BindingSource" /> is never overwritten.</item>
/// <item>A complex type parameter (<see cref="ModelMetadata.IsComplexType"/>) is assigned <see cref="BindingSource.Body"/>.</item>
/// <item>Parameter with a name that appears as a route value in ANY route template is assigned <see cref="BindingSource.Path"/>.</item>
/// <item>All other parameters are <see cref="BindingSource.Query"/>.</item>
/// </list>
/// </remarks>
public class InferParameterBindingInfoConvention : IActionModelConvention
{
private readonly IModelMetadataProvider _modelMetadataProvider;
public InferParameterBindingInfoConvention(
IModelMetadataProvider modelMetadataProvider)
{
_modelMetadataProvider = modelMetadataProvider ?? throw new ArgumentNullException(nameof(modelMetadataProvider));
}
internal bool AllowInferringBindingSourceForCollectionTypesAsFromQuery { get; set; }
protected virtual bool ShouldApply(ActionModel action) => true;
public void Apply(ActionModel action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
if (!ShouldApply(action))
{
return;
}
InferParameterBindingSources(action);
}
internal void InferParameterBindingSources(ActionModel action)
{
for (var i = 0; i < action.Parameters.Count; i++)
{
var parameter = action.Parameters[i];
var bindingSource = parameter.BindingInfo?.BindingSource;
if (bindingSource == null)
{
bindingSource = InferBindingSourceForParameter(parameter);
parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo();
parameter.BindingInfo.BindingSource = bindingSource;
}
}
var fromBodyParameters = action.Parameters.Where(p => p.BindingInfo.BindingSource == BindingSource.Body).ToList();
if (fromBodyParameters.Count > 1)
{
var parameters = string.Join(Environment.NewLine, fromBodyParameters.Select(p => p.DisplayName));
var message = Resources.FormatApiController_MultipleBodyParametersFound(
action.DisplayName,
nameof(FromQueryAttribute),
nameof(FromRouteAttribute),
nameof(FromBodyAttribute));
message += Environment.NewLine + parameters;
throw new InvalidOperationException(message);
}
}
// Internal for unit testing.
internal BindingSource InferBindingSourceForParameter(ParameterModel parameter)
{
if (IsComplexTypeParameter(parameter))
{
return BindingSource.Body;
}
if (ParameterExistsInAnyRoute(parameter.Action, parameter.ParameterName))
{
return BindingSource.Path;
}
return BindingSource.Query;
}
private bool ParameterExistsInAnyRoute(ActionModel action, string parameterName)
{
foreach (var (route, _, _) in ActionAttributeRouteModel.GetAttributeRoutes(action))
{
if (route == null)
{
continue;
}
var parsedTemplate = TemplateParser.Parse(route.Template);
if (parsedTemplate.GetParameter(parameterName) != null)
{
return true;
}
}
return false;
}
private bool IsComplexTypeParameter(ParameterModel parameter)
{
// No need for information from attributes on the parameter. Just use its type.
var metadata = _modelMetadataProvider
.GetMetadataForType(parameter.ParameterInfo.ParameterType);
if (AllowInferringBindingSourceForCollectionTypesAsFromQuery && metadata.IsCollectionType)
{
return false;
}
return metadata.IsComplexType;
}
}
}