Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validator support #94

Merged
merged 3 commits into from
Mar 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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