This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
SelectTagHelper.cs
172 lines (150 loc) · 6.66 KB
/
SelectTagHelper.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// 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.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TagHelpers.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting <select> elements with <c>asp-for</c> and/or
/// <c>asp-items</c> attribute(s).
/// </summary>
[HtmlTargetElement("select", Attributes = ForAttributeName)]
[HtmlTargetElement("select", Attributes = ItemsAttributeName)]
public class SelectTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
private const string ItemsAttributeName = "asp-items";
private bool _allowMultiple;
private ICollection<string> _currentValues;
/// <summary>
/// Creates a new <see cref="SelectTagHelper"/>.
/// </summary>
/// <param name="generator">The <see cref="IHtmlGenerator"/>.</param>
public SelectTagHelper(IHtmlGenerator generator)
{
Generator = generator;
}
/// <inheritdoc />
public override int Order => -1000;
protected IHtmlGenerator Generator { get; }
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
/// <summary>
/// An expression to be evaluated against the current model.
/// </summary>
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
/// <summary>
/// A collection of <see cref="SelectListItem"/> objects used to populate the <select> element with
/// <optgroup> and <option> elements.
/// </summary>
[HtmlAttributeName(ItemsAttributeName)]
public IEnumerable<SelectListItem> Items { get; set; }
/// <summary>
/// The name of the <input> element.
/// </summary>
/// <remarks>
/// Passed through to the generated HTML in all cases. Also used to determine whether <see cref="For"/> is
/// valid with an empty <see cref="ModelExpression.Name"/>.
/// </remarks>
public string Name { get; set; }
/// <inheritdoc />
public override void Init(TagHelperContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (For == null)
{
// Informs contained elements that they're running within a targeted <select/> element.
context.Items[typeof(SelectTagHelper)] = null;
return;
}
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
// IHtmlGenerator will enforce name requirements.
if (For.Metadata == null)
{
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
"<select>",
ForAttributeName,
nameof(IModelMetadataProvider),
For.Name));
}
// Base allowMultiple on the instance or declared type of the expression to avoid a
// "SelectExpressionNotEnumerable" InvalidOperationException during generation.
// Metadata.IsEnumerableType is similar but does not take runtime type into account.
var realModelType = For.ModelExplorer.ModelType;
_allowMultiple = typeof(string) != realModelType &&
typeof(IEnumerable).IsAssignableFrom(realModelType);
_currentValues = Generator.GetCurrentValues(ViewContext, For.ModelExplorer, For.Name, _allowMultiple);
// Whether or not (not being highly unlikely) we generate anything, could update contained <option/>
// elements. Provide selected values for <option/> tag helpers.
var currentValues = _currentValues == null ? null : new CurrentValues(_currentValues);
context.Items[typeof(SelectTagHelper)] = currentValues;
}
/// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
// Pass through attribute that is also a well-known HTML attribute. Must be done prior to any copying
// from a TagBuilder.
if (Name != null)
{
output.CopyHtmlAttribute(nameof(Name), context);
}
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
var items = Items ?? Enumerable.Empty<SelectListItem>();
if (For == null)
{
var options = Generator.GenerateGroupsAndOptions(optionLabel: null, selectList: items);
output.PostContent.AppendHtml(options);
return;
}
// Ensure Generator does not throw due to empty "fullName" if user provided a name attribute.
IDictionary<string, object> htmlAttributes = null;
if (string.IsNullOrEmpty(For.Name) &&
string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) &&
!string.IsNullOrEmpty(Name))
{
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "name", Name },
};
}
var tagBuilder = Generator.GenerateSelect(
ViewContext,
For.ModelExplorer,
optionLabel: null,
expression: For.Name,
selectList: items,
currentValues: _currentValues,
allowMultiple: _allowMultiple,
htmlAttributes: htmlAttributes);
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
if (tagBuilder.HasInnerHtml)
{
output.PostContent.AppendHtml(tagBuilder.InnerHtml);
}
}
}
}
}