Skip to content

Commit

Permalink
Merge pull request #94 from HtmlTags/validators
Browse files Browse the repository at this point in the history
Validator support
  • Loading branch information
jbogard authored Mar 26, 2018
2 parents 90e2dee + acf3c8a commit f68b2cf
Show file tree
Hide file tree
Showing 19 changed files with 186 additions and 45 deletions.
16 changes: 10 additions & 6 deletions src/HtmlTags.AspNetCore.TestSite/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -91,30 +91,34 @@
Html.LinkButton("Cancel", nameof(HomeController.Index))))

Input Tag value:
<input-tag for="Value" class="foo"></input-tag>
<input-tag for="Value" class="foo" />
Editor for value:
@Html.EditorFor(m => m.Value)
Input tag blarg:
<input-tag for="Blarg"></input-tag>
<input-tag for="Blarg" />
Editor for blarg:
@Html.EditorFor(m => m.Blarg)
Input tag blorg.blorg:
<input-tag for="Blorg.Blorg"></input-tag>
<input-tag for="Blorg.Blorg" />
Editor for blorg.blorg:
@Html.EditorFor(m => m.Blorg.Blorg)
Input tag Blorgs[0].Blarg:
<input-tag for="Blorgs[0].Blorg"></input-tag>
<input-tag for="Blorgs[0].Blorg" />
Editor for Blorgs[0].Blarg:
@Html.EditorFor(m => m.Blorgs[0].Blorg)
@Html.Input(m => m.Value).AddClass("foo")
Display tag for Value:
<display-tag for="Value"></display-tag>
<display-tag for="Value" />
Display for Vlaue:
@Html.Display(m => m.Value)
Label tag for Value:
<label-tag for="Value"></label-tag>
<label-tag for="Value" />
Label for Value:
@Html.Label(m => m.Value)
Valiidator tag for Value:
<validation-message-tag for="Value" />
Validation for Value:
@Html.ValidationMessage(m => m.Value)

@{
var tag = new HtmlTag("canvas");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;

namespace HtmlTags.Conventions.Elements.Builders
{
public class DefaultValidationMessageBuilder : IElementBuilder
{
public HtmlTag Build(ElementRequest request)
{
var viewContext = request.Get<ViewContext>() ?? throw new InvalidOperationException("Validation messages require a ViewContext");

var formContext = viewContext.ClientValidationEnabled ? viewContext.FormContext : null;
if (!viewContext.ViewData.ModelState.ContainsKey(request.ElementId) && formContext == null)
{
return HtmlTag.Empty();
}

var tryGetModelStateResult = viewContext.ViewData.ModelState.TryGetValue(request.ElementId, out var entry);
var modelErrors = tryGetModelStateResult ? entry.Errors : null;

ModelError modelError = null;
if (modelErrors != null && modelErrors.Count != 0)
{
modelError = modelErrors.FirstOrDefault(m => !string.IsNullOrEmpty(m.ErrorMessage)) ?? modelErrors[0];
}

if (modelError == null && formContext == null)
{
return HtmlTag.Empty();
}

var tag = new HtmlTag(viewContext.ValidationMessageElement);

var className = modelError != null ?
HtmlHelper.ValidationMessageCssClassName :
HtmlHelper.ValidationMessageValidCssClassName;
tag.AddClass(className);

if (modelError != null)
{
var modelExplorer = request.Get<ModelExplorer>() ?? throw new InvalidOperationException("Validation messages require a ModelExplorer");
tag.Text(ValidationHelpers.GetModelErrorMessageOrDefault(modelError, entry, modelExplorer));
}

if (formContext != null)
{
tag.Attr("data-valmsg-for", request.ElementId);

tag.Attr("data-valmsg-replace", "true");
}

return tag;
}
}
}
7 changes: 7 additions & 0 deletions src/HtmlTags.AspNetCore/HtmlHelperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ public static HtmlTag Input<T, TResult>(this IHtmlHelper<T> helper, Expression<F
return generator.InputFor(expression);
}

public static HtmlTag ValidationMessage<T, TResult>(this IHtmlHelper<T> helper, Expression<Func<T, TResult>> expression)
where T : class
{
var generator = GetGenerator(helper, expression);
return generator.ValidationMessageFor(expression);
}

public static HtmlTag Label<T, TResult>(this IHtmlHelper<T> helper, Expression<Func<T, TResult>> expression)
where T : class
{
Expand Down
4 changes: 3 additions & 1 deletion src/HtmlTags.AspNetCore/ModelMetadataTagExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ namespace HtmlTags
{
public static class ModelMetadataTagExtensions
{
public static void ModelMetadata(this HtmlConventionRegistry registry)
public static HtmlConventionRegistry ModelMetadata(this HtmlConventionRegistry registry)
{
registry.Labels.Modifier<DisplayNameElementModifier>();
registry.Displays.Modifier<MetadataModelDisplayModifier>();
registry.Editors.Modifier<MetadataModelEditModifier>();
registry.Editors.Modifier<PlaceholderElementModifier>();
registry.Editors.Modifier<ModelStateErrorsModifier>();
registry.Editors.Modifier<ClientSideValidationModifier>();

return registry;
}

private class DisplayNameElementModifier : IElementModifier
Expand Down
17 changes: 17 additions & 0 deletions src/HtmlTags.AspNetCore/ModelStateTagExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using HtmlTags.Conventions;
using HtmlTags.Conventions.Elements;
using HtmlTags.Conventions.Elements.Builders;

namespace HtmlTags
{
public static class ModelStateTagExtensions
{
public static HtmlConventionRegistry ModelState(this HtmlConventionRegistry registry)
{
registry.ValidationMessages.Always.BuildBy<DefaultValidationMessageBuilder>();
registry.ValidationMessages.NamingConvention(new DotNotationElementNamingConvention());

return registry;
}
}
}
29 changes: 26 additions & 3 deletions src/HtmlTags.AspNetCore/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,50 @@

public static class ServiceCollectionExtensions
{
/// <summary>
/// Configures HtmlTags without ASP.NET Core defaults without modifying the library
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="library">Convention library</param>
/// <returns>Service collection</returns>
public static IServiceCollection AddHtmlTags(this IServiceCollection services, HtmlConventionLibrary library) => services.AddSingleton(library);

/// <summary>
/// Configures HtmlTags with ASP.NET Core defaults
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="registries">Custom convention registries</param>
/// <returns>Service collection</returns>
public static IServiceCollection AddHtmlTags(this IServiceCollection services, params HtmlConventionRegistry[] registries)
{
var library = new HtmlConventionLibrary();
foreach (var registry in registries)
{
registry.Apply(library);
}

var defaultRegistry = new HtmlConventionRegistry()
.Defaults()
.ModelMetadata()
.ModelState();

defaultRegistry.Apply(library);

return services.AddHtmlTags(library);
}

/// <summary>
/// Configures HtmlTags with ASP.NET Core defaults
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="config">Additional configuration callback</param>
/// <returns>Service collection</returns>
public static IServiceCollection AddHtmlTags(this IServiceCollection services, Action<HtmlConventionRegistry> config)
{
var registry = new HtmlConventionRegistry();

config(registry);

registry.Defaults();
registry.ModelMetadata();

return services.AddHtmlTags(registry);
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/HtmlTags.AspNetCore/ValidationMessageTagHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace HtmlTags
{
using Conventions.Elements;
using Microsoft.AspNetCore.Razor.TagHelpers;

[HtmlTargetElement("validation-message-tag", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class ValidationMessageTagHelper : HtmlTagTagHelper
{
protected override string Category { get; } = ElementConstants.ValidationMessage;
}
}
6 changes: 4 additions & 2 deletions src/HtmlTags/Cache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ public Cache(IDictionary<TKey, TValue> dictionary)
_values = dictionary;
}

public Func<TKey, TValue> OnMissing { set { _onMissing = value; } }
public Func<TKey, TValue> OnMissing { set => _onMissing = value; }

public Func<TValue, TKey> GetKey { get { return _getKey; } set { _getKey = value; } }
public Func<TValue, TKey> GetKey { get => _getKey;
set => _getKey = value;
}

public int Count => _values.Count;

Expand Down
11 changes: 8 additions & 3 deletions src/HtmlTags/Conventions/ElementGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ private ElementGenerator(ITagGenerator tags)

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

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

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

public HtmlTag ValidationMessageFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null)
=> Build(expression, ElementConstants.ValidationMessage, profile, model);

public HtmlTag DisplayFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null)
=> Build(expression, ElementConstants.Display, profile, model);

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

public T Model
{
get { return _model.Value; }
set { _model = new Lazy<T>(() => value); }
get => _model.Value;
set => _model = new Lazy<T>(() => value);
}

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

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

public HtmlTag ValidationMessageFor(ElementRequest request, string profile = null, T model = null) => Build(request, ElementConstants.ValidationMessage, profile, model);

public HtmlTag DisplayFor(ElementRequest request, string profile = null, T model = null) => Build(request, ElementConstants.Display, profile, model);

public HtmlTag TagFor(ElementRequest request, string category, string profile = null, T model = null) => Build(request, category, profile, model);
Expand Down
2 changes: 0 additions & 2 deletions src/HtmlTags/Conventions/Elements/Builders/TextboxBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ namespace HtmlTags.Conventions.Elements.Builders
{
public class TextboxBuilder : IElementBuilder
{
public bool Matches(ElementRequest subject) => true;

public HtmlTag Build(ElementRequest request)
{
return new TextboxTag().Attr("value", (request.RawValue ?? string.Empty).ToString());
Expand Down
1 change: 1 addition & 0 deletions src/HtmlTags/Conventions/Elements/ElementConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public static class ElementConstants
public static readonly string Label = "Label";
public static readonly string Display = "Display";
public static readonly string Editor = "Editor";
public static readonly string ValidationMessage = "ValidationMessage";

public static readonly string Templates = "Templates";
}
Expand Down
2 changes: 2 additions & 0 deletions src/HtmlTags/Conventions/Elements/IElementGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ public interface IElementGenerator<T> where T : class
T Model { get; set; }
HtmlTag LabelFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null);
HtmlTag InputFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null);
HtmlTag ValidationMessageFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null);
HtmlTag DisplayFor<TResult>(Expression<Func<T, TResult>> expression, string profile = null, T model = null);
HtmlTag TagFor<TResult>(Expression<Func<T, TResult>> expression, string category, string profile = null, T model = null);

HtmlTag LabelFor(ElementRequest request, string profile = null, T model = null);
HtmlTag InputFor(ElementRequest request, string profile = null, T model = null);
HtmlTag ValidationMessageFor(ElementRequest request, string profile = null, T model = null);
HtmlTag DisplayFor(ElementRequest request, string profile = null, T model = null);
HtmlTag TagFor(ElementRequest request, string category, string profile = null, T model = null);
}
Expand Down
2 changes: 1 addition & 1 deletion src/HtmlTags/Conventions/Formatting/GetStringRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public Type PropertyType

return _propertyType;
}
set { _propertyType = value; }
set => _propertyType = value;
}

public PropertyInfo Property { get; }
Expand Down
4 changes: 2 additions & 2 deletions src/HtmlTags/Conventions/HtmlConventionRegistryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace HtmlTags.Conventions

public static class HtmlConventionRegistryExtensions
{
public static void Defaults(this HtmlConventionRegistry registry)
public static HtmlConventionRegistry Defaults(this HtmlConventionRegistry registry)
{
registry.Editors.BuilderPolicy<CheckboxBuilder>();

Expand All @@ -21,7 +21,7 @@ public static void Defaults(this HtmlConventionRegistry registry)

registry.Labels.Always.BuildBy<DefaultLabelBuilder>();


return registry;
}
}
}
2 changes: 2 additions & 0 deletions src/HtmlTags/Conventions/ProfileExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public ProfileExpression(HtmlConventionLibrary library, string profileName)

public ElementCategoryExpression Editors => new ElementCategoryExpression(BuildersFor(ElementConstants.Editor));

public ElementCategoryExpression ValidationMessages => new ElementCategoryExpression(BuildersFor(ElementConstants.ValidationMessage));

public void Apply(HtmlConventionLibrary library) => library.Import(Library);
}
}
4 changes: 3 additions & 1 deletion src/HtmlTags/HtmlDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ public HtmlDocument()
public HtmlTag RootTag { get; }
public HtmlTag Head { get; }
public HtmlTag Body { get; }
public string Title { get { return _title.Text(); } set { _title.Text(value); } }
public string Title { get => _title.Text();
set => _title.Text(value);
}

public HtmlTag Current => _currentStack.Any() ? _currentStack.Peek() : Body;
public HtmlTag Last { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ public StringStartsWithPropertyOperation()
{
}

public override string Text
{
get { return "starts with"; }
}
public override string Text => "starts with";
}

public class CollectionContainsPropertyOperation : IPropertyOperation
Expand Down
15 changes: 3 additions & 12 deletions src/HtmlTags/Reflection/MethodValueGetter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,11 @@ public string Name
}
}

public Type DeclaringType
{
get { return _methodInfo.DeclaringType; }
}
public Type DeclaringType => _methodInfo.DeclaringType;

public Type ValueType
{
get { return _methodInfo.ReturnType; }
}
public Type ValueType => _methodInfo.ReturnType;

public Type ReturnType
{
get { return _methodInfo.ReturnType; }
}
public Type ReturnType => _methodInfo.ReturnType;

public Expression ChainExpression(Expression body)
{
Expand Down
Loading

0 comments on commit f68b2cf

Please sign in to comment.