-
Notifications
You must be signed in to change notification settings - Fork 9.8k
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
Support custom validation class names #24835
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// 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.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
|
||
namespace Microsoft.AspNetCore.Components.Forms | ||
{ | ||
/// <summary> | ||
/// Holds arbitrary key/value pairs associated with an <see cref="EditContext"/>. | ||
/// This can be used to track additional metadata for application-specific purposes. | ||
/// </summary> | ||
public sealed class EditContextProperties | ||
{ | ||
// We don't want to expose any way of enumerating the underlying dictionary, because that would | ||
// prevent its usage to store private information. So we only expose an indexer and TryGetValue. | ||
private Dictionary<object, object>? _contents; | ||
|
||
/// <summary> | ||
/// Gets or sets a value in the collection. | ||
/// </summary> | ||
/// <param name="key">The key under which the value is stored.</param> | ||
/// <returns>The stored value.</returns> | ||
public object this[object key] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you feel about making this a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what you mean by having less behavior. Can you ping me on IM when you're available to clarify? Overall I don't really mind what the API looks like here since it's so obscure - any reasonable option is fine with me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does not seem like we need the getter at all. If you think it's necessary, we should update the KeyNotFoundException to use the same error message as the dictionary's exception |
||
{ | ||
get => _contents is null ? throw new KeyNotFoundException() : _contents[key]; | ||
set | ||
{ | ||
_contents ??= new Dictionary<object, object>(); | ||
pranavkm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_contents[key] = value; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets the value associated with the specified key, if any. | ||
/// </summary> | ||
/// <param name="key">The key under which the value is stored.</param> | ||
/// <param name="value">The value, if present.</param> | ||
/// <returns>True if the value was present, otherwise false.</returns> | ||
public bool TryGetValue(object key, [NotNullWhen(true)] out object? value) | ||
{ | ||
if (_contents is null) | ||
{ | ||
value = default; | ||
return false; | ||
} | ||
else | ||
{ | ||
return _contents.TryGetValue(key, out value); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Removes the specified entry from the collection. | ||
/// </summary> | ||
/// <param name="key">The key of the entry to be removed.</param> | ||
/// <returns>True if the value was present, otherwise false.</returns> | ||
public bool Remove(object key) | ||
{ | ||
return _contents?.Remove(key) ?? false; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,35 @@ | ||||||||||||||||||||||||||||||||||||
// 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.Linq; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
namespace Microsoft.AspNetCore.Components.Forms | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
/// <summary> | ||||||||||||||||||||||||||||||||||||
/// Supplies CSS class names for form fields to represent their validation state or other | ||||||||||||||||||||||||||||||||||||
/// state information from an <see cref="EditContext"/>. | ||||||||||||||||||||||||||||||||||||
/// </summary> | ||||||||||||||||||||||||||||||||||||
public class FieldCssClassProvider | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you feel about this being an abstract base type here? It makes it very clear to the user what APIs they would need to implement (in this case just one) to get the correct behavior. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you propose we also have a public I did consider that before but TBH it felt no better for usability. We're then relying on people discovering the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not a great deal of code to copy if they need the framework defaults. But if you think it's valuable, we can let this be. |
||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
internal readonly static FieldCssClassProvider Instance = new FieldCssClassProvider(); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/// <summary> | ||||||||||||||||||||||||||||||||||||
/// Gets a string that indicates the status of the specified field as a CSS class. | ||||||||||||||||||||||||||||||||||||
/// </summary> | ||||||||||||||||||||||||||||||||||||
/// <param name="editContext">The <see cref="EditContext"/>.</param> | ||||||||||||||||||||||||||||||||||||
/// <param name="fieldIdentifier">The <see cref="FieldIdentifier"/>.</param> | ||||||||||||||||||||||||||||||||||||
/// <returns>A CSS class name string.</returns> | ||||||||||||||||||||||||||||||||||||
public virtual string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any(); | ||||||||||||||||||||||||||||||||||||
if (editContext.IsModified(fieldIdentifier)) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
return isValid ? "modified valid" : "modified invalid"; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
else | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
return isValid ? "valid" : "invalid"; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
Comment on lines
+24
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Extra pranav points :)
Suggested change
|
||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// 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.Components.Forms; | ||
|
||
namespace BasicTestApp.FormsTest | ||
{ | ||
// For E2E testing, this is a rough example of a field CSS class provider that looks for | ||
// a custom attribute defining CSS class names. It isn't very efficient (it does reflection | ||
// and allocates on every invocation) but is sufficient for testing purposes. | ||
public class CustomFieldCssClassProvider : FieldCssClassProvider | ||
{ | ||
public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier) | ||
{ | ||
var cssClassName = base.GetFieldCssClass(editContext, fieldIdentifier); | ||
|
||
// If we can find a [CustomValidationClassName], use it | ||
var propertyInfo = fieldIdentifier.Model.GetType().GetProperty(fieldIdentifier.FieldName); | ||
if (propertyInfo != null) | ||
{ | ||
var customValidationClassName = (CustomValidationClassNameAttribute)propertyInfo | ||
.GetCustomAttributes(typeof(CustomValidationClassNameAttribute), true) | ||
.FirstOrDefault(); | ||
if (customValidationClassName != null) | ||
{ | ||
cssClassName = string.Join(' ', cssClassName.Split(' ').Select(token => token switch | ||
{ | ||
"valid" => customValidationClassName.Valid ?? token, | ||
"invalid" => customValidationClassName.Invalid ?? token, | ||
_ => token, | ||
})); | ||
} | ||
} | ||
|
||
return cssClassName; | ||
} | ||
} | ||
|
||
[AttributeUsage(AttributeTargets.Property)] | ||
public class CustomValidationClassNameAttribute : Attribute | ||
{ | ||
public string Valid { get; set; } | ||
public string Invalid { get; set; } | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to not use an IDictionary<object,object> on the EditContext class directly? Are we trying to 'hide' functionality from the Dictionary?