Skip to content

Commit f68b2cf

Browse files
authored
Merge pull request #94 from HtmlTags/validators
Validator support
2 parents 90e2dee + acf3c8a commit f68b2cf

File tree

19 files changed

+186
-45
lines changed

19 files changed

+186
-45
lines changed

src/HtmlTags.AspNetCore.TestSite/Views/Home/Index.cshtml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,30 +91,34 @@
9191
Html.LinkButton("Cancel", nameof(HomeController.Index))))
9292

9393
Input Tag value:
94-
<input-tag for="Value" class="foo"></input-tag>
94+
<input-tag for="Value" class="foo" />
9595
Editor for value:
9696
@Html.EditorFor(m => m.Value)
9797
Input tag blarg:
98-
<input-tag for="Blarg"></input-tag>
98+
<input-tag for="Blarg" />
9999
Editor for blarg:
100100
@Html.EditorFor(m => m.Blarg)
101101
Input tag blorg.blorg:
102-
<input-tag for="Blorg.Blorg"></input-tag>
102+
<input-tag for="Blorg.Blorg" />
103103
Editor for blorg.blorg:
104104
@Html.EditorFor(m => m.Blorg.Blorg)
105105
Input tag Blorgs[0].Blarg:
106-
<input-tag for="Blorgs[0].Blorg"></input-tag>
106+
<input-tag for="Blorgs[0].Blorg" />
107107
Editor for Blorgs[0].Blarg:
108108
@Html.EditorFor(m => m.Blorgs[0].Blorg)
109109
@Html.Input(m => m.Value).AddClass("foo")
110110
Display tag for Value:
111-
<display-tag for="Value"></display-tag>
111+
<display-tag for="Value" />
112112
Display for Vlaue:
113113
@Html.Display(m => m.Value)
114114
Label tag for Value:
115-
<label-tag for="Value"></label-tag>
115+
<label-tag for="Value" />
116116
Label for Value:
117117
@Html.Label(m => m.Value)
118+
Valiidator tag for Value:
119+
<validation-message-tag for="Value" />
120+
Validation for Value:
121+
@Html.ValidationMessage(m => m.Value)
118122

119123
@{
120124
var tag = new HtmlTag("canvas");
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Linq;
3+
using Microsoft.AspNetCore.Mvc.ModelBinding;
4+
using Microsoft.AspNetCore.Mvc.Rendering;
5+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
6+
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
7+
8+
namespace HtmlTags.Conventions.Elements.Builders
9+
{
10+
public class DefaultValidationMessageBuilder : IElementBuilder
11+
{
12+
public HtmlTag Build(ElementRequest request)
13+
{
14+
var viewContext = request.Get<ViewContext>() ?? throw new InvalidOperationException("Validation messages require a ViewContext");
15+
16+
var formContext = viewContext.ClientValidationEnabled ? viewContext.FormContext : null;
17+
if (!viewContext.ViewData.ModelState.ContainsKey(request.ElementId) && formContext == null)
18+
{
19+
return HtmlTag.Empty();
20+
}
21+
22+
var tryGetModelStateResult = viewContext.ViewData.ModelState.TryGetValue(request.ElementId, out var entry);
23+
var modelErrors = tryGetModelStateResult ? entry.Errors : null;
24+
25+
ModelError modelError = null;
26+
if (modelErrors != null && modelErrors.Count != 0)
27+
{
28+
modelError = modelErrors.FirstOrDefault(m => !string.IsNullOrEmpty(m.ErrorMessage)) ?? modelErrors[0];
29+
}
30+
31+
if (modelError == null && formContext == null)
32+
{
33+
return HtmlTag.Empty();
34+
}
35+
36+
var tag = new HtmlTag(viewContext.ValidationMessageElement);
37+
38+
var className = modelError != null ?
39+
HtmlHelper.ValidationMessageCssClassName :
40+
HtmlHelper.ValidationMessageValidCssClassName;
41+
tag.AddClass(className);
42+
43+
if (modelError != null)
44+
{
45+
var modelExplorer = request.Get<ModelExplorer>() ?? throw new InvalidOperationException("Validation messages require a ModelExplorer");
46+
tag.Text(ValidationHelpers.GetModelErrorMessageOrDefault(modelError, entry, modelExplorer));
47+
}
48+
49+
if (formContext != null)
50+
{
51+
tag.Attr("data-valmsg-for", request.ElementId);
52+
53+
tag.Attr("data-valmsg-replace", "true");
54+
}
55+
56+
return tag;
57+
}
58+
}
59+
}

src/HtmlTags.AspNetCore/HtmlHelperExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ public static HtmlTag Input<T, TResult>(this IHtmlHelper<T> helper, Expression<F
2323
return generator.InputFor(expression);
2424
}
2525

26+
public static HtmlTag ValidationMessage<T, TResult>(this IHtmlHelper<T> helper, Expression<Func<T, TResult>> expression)
27+
where T : class
28+
{
29+
var generator = GetGenerator(helper, expression);
30+
return generator.ValidationMessageFor(expression);
31+
}
32+
2633
public static HtmlTag Label<T, TResult>(this IHtmlHelper<T> helper, Expression<Func<T, TResult>> expression)
2734
where T : class
2835
{

src/HtmlTags.AspNetCore/ModelMetadataTagExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ namespace HtmlTags
1111
{
1212
public static class ModelMetadataTagExtensions
1313
{
14-
public static void ModelMetadata(this HtmlConventionRegistry registry)
14+
public static HtmlConventionRegistry ModelMetadata(this HtmlConventionRegistry registry)
1515
{
1616
registry.Labels.Modifier<DisplayNameElementModifier>();
1717
registry.Displays.Modifier<MetadataModelDisplayModifier>();
1818
registry.Editors.Modifier<MetadataModelEditModifier>();
1919
registry.Editors.Modifier<PlaceholderElementModifier>();
2020
registry.Editors.Modifier<ModelStateErrorsModifier>();
2121
registry.Editors.Modifier<ClientSideValidationModifier>();
22+
23+
return registry;
2224
}
2325

2426
private class DisplayNameElementModifier : IElementModifier
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using HtmlTags.Conventions;
2+
using HtmlTags.Conventions.Elements;
3+
using HtmlTags.Conventions.Elements.Builders;
4+
5+
namespace HtmlTags
6+
{
7+
public static class ModelStateTagExtensions
8+
{
9+
public static HtmlConventionRegistry ModelState(this HtmlConventionRegistry registry)
10+
{
11+
registry.ValidationMessages.Always.BuildBy<DefaultValidationMessageBuilder>();
12+
registry.ValidationMessages.NamingConvention(new DotNotationElementNamingConvention());
13+
14+
return registry;
15+
}
16+
}
17+
}

src/HtmlTags.AspNetCore/ServiceCollectionExtensions.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,50 @@
66

77
public static class ServiceCollectionExtensions
88
{
9+
/// <summary>
10+
/// Configures HtmlTags without ASP.NET Core defaults without modifying the library
11+
/// </summary>
12+
/// <param name="services">Service collection</param>
13+
/// <param name="library">Convention library</param>
14+
/// <returns>Service collection</returns>
915
public static IServiceCollection AddHtmlTags(this IServiceCollection services, HtmlConventionLibrary library) => services.AddSingleton(library);
1016

17+
/// <summary>
18+
/// Configures HtmlTags with ASP.NET Core defaults
19+
/// </summary>
20+
/// <param name="services">Service collection</param>
21+
/// <param name="registries">Custom convention registries</param>
22+
/// <returns>Service collection</returns>
1123
public static IServiceCollection AddHtmlTags(this IServiceCollection services, params HtmlConventionRegistry[] registries)
1224
{
1325
var library = new HtmlConventionLibrary();
1426
foreach (var registry in registries)
1527
{
1628
registry.Apply(library);
1729
}
30+
31+
var defaultRegistry = new HtmlConventionRegistry()
32+
.Defaults()
33+
.ModelMetadata()
34+
.ModelState();
35+
36+
defaultRegistry.Apply(library);
37+
1838
return services.AddHtmlTags(library);
1939
}
2040

41+
/// <summary>
42+
/// Configures HtmlTags with ASP.NET Core defaults
43+
/// </summary>
44+
/// <param name="services">Service collection</param>
45+
/// <param name="config">Additional configuration callback</param>
46+
/// <returns>Service collection</returns>
2147
public static IServiceCollection AddHtmlTags(this IServiceCollection services, Action<HtmlConventionRegistry> config)
2248
{
2349
var registry = new HtmlConventionRegistry();
2450

2551
config(registry);
2652

27-
registry.Defaults();
28-
registry.ModelMetadata();
29-
3053
return services.AddHtmlTags(registry);
3154
}
3255
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace HtmlTags
2+
{
3+
using Conventions.Elements;
4+
using Microsoft.AspNetCore.Razor.TagHelpers;
5+
6+
[HtmlTargetElement("validation-message-tag", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
7+
public class ValidationMessageTagHelper : HtmlTagTagHelper
8+
{
9+
protected override string Category { get; } = ElementConstants.ValidationMessage;
10+
}
11+
}

src/HtmlTags/Cache.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ public Cache(IDictionary<TKey, TValue> dictionary)
4242
_values = dictionary;
4343
}
4444

45-
public Func<TKey, TValue> OnMissing { set { _onMissing = value; } }
45+
public Func<TKey, TValue> OnMissing { set => _onMissing = value; }
4646

47-
public Func<TValue, TKey> GetKey { get { return _getKey; } set { _getKey = value; } }
47+
public Func<TValue, TKey> GetKey { get => _getKey;
48+
set => _getKey = value;
49+
}
4850

4951
public int Count => _values.Count;
5052

src/HtmlTags/Conventions/ElementGenerator.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ private ElementGenerator(ITagGenerator tags)
1717

1818
public static ElementGenerator<T> For(HtmlConventionLibrary library, Func<Type, object> serviceLocator = null, T model = null)
1919
{
20-
serviceLocator = serviceLocator ?? (Activator.CreateInstance);
20+
serviceLocator = serviceLocator ?? Activator.CreateInstance;
2121

2222
var tags = new TagGenerator(library.TagLibrary, new ActiveProfile(), serviceLocator);
2323

@@ -33,6 +33,9 @@ public HtmlTag LabelFor<TResult>(Expression<Func<T, TResult>> expression, string
3333
public HtmlTag InputFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null)
3434
=> Build(expression, ElementConstants.Editor, profile, model);
3535

36+
public HtmlTag ValidationMessageFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null)
37+
=> Build(expression, ElementConstants.ValidationMessage, profile, model);
38+
3639
public HtmlTag DisplayFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null)
3740
=> Build(expression, ElementConstants.Display, profile, model);
3841

@@ -41,8 +44,8 @@ public HtmlTag TagFor<TResult>(Expression<Func<T, TResult>> expression, string c
4144

4245
public T Model
4346
{
44-
get { return _model.Value; }
45-
set { _model = new Lazy<T>(() => value); }
47+
get => _model.Value;
48+
set => _model = new Lazy<T>(() => value);
4649
}
4750

4851
public ElementRequest GetRequest<TResult>(Expression<Func<T, TResult>> expression, T model = null)
@@ -70,6 +73,8 @@ private HtmlTag Build(ElementRequest request, string category, string profile =
7073

7174
public HtmlTag InputFor(ElementRequest request, string profile = null, T model = null) => Build(request, ElementConstants.Editor, profile, model);
7275

76+
public HtmlTag ValidationMessageFor(ElementRequest request, string profile = null, T model = null) => Build(request, ElementConstants.ValidationMessage, profile, model);
77+
7378
public HtmlTag DisplayFor(ElementRequest request, string profile = null, T model = null) => Build(request, ElementConstants.Display, profile, model);
7479

7580
public HtmlTag TagFor(ElementRequest request, string category, string profile = null, T model = null) => Build(request, category, profile, model);

src/HtmlTags/Conventions/Elements/Builders/TextboxBuilder.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ namespace HtmlTags.Conventions.Elements.Builders
22
{
33
public class TextboxBuilder : IElementBuilder
44
{
5-
public bool Matches(ElementRequest subject) => true;
6-
75
public HtmlTag Build(ElementRequest request)
86
{
97
return new TextboxTag().Attr("value", (request.RawValue ?? string.Empty).ToString());

0 commit comments

Comments
 (0)