-
Notifications
You must be signed in to change notification settings - Fork 97
/
ResourceViewModelValidationMetadataProvider.cs
112 lines (97 loc) · 4.67 KB
/
ResourceViewModelValidationMetadataProvider.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
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
using System.Resources;
using DotVVM.Framework.Utils;
using DotVVM.Framework.ViewModel.Validation;
namespace DotVVM.AutoUI.Metadata
{
/// <summary>
/// Validation metadata provider which loads missing error messages from the RESX file.
/// </summary>
public class ResourceViewModelValidationMetadataProvider : IViewModelValidationMetadataProvider
{
private readonly IViewModelValidationMetadataProvider baseValidationMetadataProvider;
private readonly ConcurrentDictionary<MemberInfo, ValidationAttribute[]> cache = new();
private readonly ResourceManager errorMessages;
private static readonly FieldInfo internalErrorMessageField;
/// <summary>
/// Gets the type of the resource file that contains the default error message patterns.
/// The resource key is the name of the attribute without the trailing Attribute (e.g. Required for RequiredAttribute etc.).
/// </summary>
public Type ErrorMessagesResourceFileType { get; }
public ResourceViewModelValidationMetadataProvider(Type errorMessagesResourceFileType, IViewModelValidationMetadataProvider baseValidationMetadataProvider)
{
ErrorMessagesResourceFileType = errorMessagesResourceFileType;
errorMessages = new ResourceManager(errorMessagesResourceFileType);
this.baseValidationMetadataProvider = baseValidationMetadataProvider;
}
static ResourceViewModelValidationMetadataProvider()
{
internalErrorMessageField = typeof(ValidationAttribute).GetField("_errorMessage", BindingFlags.Instance | BindingFlags.NonPublic).NotNull();
}
/// <summary>
/// Gets validation attributes for the specified property.
/// </summary>
public IEnumerable<ValidationAttribute> GetAttributesForProperty(MemberInfo property)
{
return cache.GetOrAdd(property, GetAttributesForPropertyCore);
}
/// <summary>
/// Determines validation attributes for the specified property and loads missing error messages from the resource file.
/// </summary>
private ValidationAttribute[] GetAttributesForPropertyCore(MemberInfo property)
{
// process all validation attributes
var results = new List<ValidationAttribute>();
foreach (var attribute in baseValidationMetadataProvider.GetAttributesForProperty(property))
{
if (HasDefaultErrorMessage(attribute) && GetErrorMessageKey(attribute) is {} errorMessageKey)
{
var clone = CloneAttribute(attribute);
// update the attribute
clone.ErrorMessageResourceType = ErrorMessagesResourceFileType;
clone.ErrorMessageResourceName = errorMessageKey;
results.Add(clone);
}
else
{
results.Add(attribute);
}
}
return results.Count == 0 ? Array.Empty<ValidationAttribute>() : results.ToArray();
}
private bool HasDefaultErrorMessage(ValidationAttribute attribute)
{
return string.IsNullOrEmpty((string?)internalErrorMessageField.GetValue(attribute))
&& attribute.ErrorMessageResourceType == null
&& string.IsNullOrEmpty(attribute.ErrorMessageResourceName);
}
/// <summary>
/// Creates a copy of the specified validation attribute instance.
/// </summary>
protected virtual ValidationAttribute CloneAttribute(ValidationAttribute attribute)
{
return (ValidationAttribute)attribute.GetType()
.GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance)!
.Invoke(attribute, null)!;
}
/// <summary>
/// Gets the error message for the specified attribute.
/// </summary>
public virtual string? GetErrorMessageKey(ValidationAttribute attribute)
{
var attributeName = attribute.GetType().Name;
if (attributeName.EndsWith("Attribute", StringComparison.OrdinalIgnoreCase))
{
attributeName = attributeName.Substring(0, attributeName.Length - "Attribute".Length);
}
return errorMessages.GetString(attributeName) != null ? attributeName :
errorMessages.GetString("Unknown") != null ? "Unknown" :
null;
}
}
}