-
Notifications
You must be signed in to change notification settings - Fork 40
/
LocalizedModelValidatorProvider.cs
243 lines (208 loc) · 10.5 KB
/
LocalizedModelValidatorProvider.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/*
* Copyright (c) 2011, Jonas Gauffin. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Web.Mvc;
using Griffin.MvcContrib.Localization.Types;
using Griffin.MvcContrib.Localization.ValidationMessages;
using Griffin.MvcContrib.Logging;
namespace Griffin.MvcContrib.Localization
{
/// <summary>
/// Used to localize DataAnnotation attribute error messages and view models
/// </summary>
/// <remarks>
/// <para>Hacks the attributes by assigning custom (localized) messages to them to get localized error messages.</para>
/// <para>
/// Check for namespace documentation for an example on how to use the provider.
/// </para>
/// <para>Are you missing validation rules for an attribute? Do not try to use the original validation rules. The standard attributes
/// uses some nasty delegates to handle the error message. Screwing with them should be handled with care.
/// </para>
/// <para>Create a new <see cref="IValidationMessageDataSource"/> and register it in <see cref="ValidationMessageProviders"/> to customized the translated strings.</para>
/// <para>You have to let the results returned from <c>Validate()</c> implement <see cref="IClientValidationRule"/> if you want to enable client validation when using <see cref="IValidatableObject"/>.</para>
/// </remarks>
public class LocalizedModelValidatorProvider : DataAnnotationsModelValidatorProvider, IDisposable
{
private const string WorkaroundMarker = "#g#";
private readonly ValidationAttributeAdapterFactory _adapterFactory = new ValidationAttributeAdapterFactory();
private readonly ILogger _logger = LogProvider.Current.GetLogger<LocalizedModelValidatorProvider>();
#region IDisposable Members
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
}
#endregion
/// <summary>
/// Gets a list of validators.
/// </summary>
/// <param name="metadata">The metadata.</param>
/// <param name="context">The context.</param>
/// <param name="attributes">The list of validation attributes.</param>
/// <returns>
/// A list of validators.
/// </returns>
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context,
IEnumerable<Attribute> attributes)
{
var items = attributes.ToList();
if (AddImplicitRequiredAttributeForValueTypes && metadata.IsRequired &&
!items.Any(a => a is RequiredAttribute))
items.Add(new RequiredAttribute());
var validators = new List<ModelValidator>();
foreach (var attr in items.OfType<ValidationAttribute>())
{
// custom message, use the default localization
if (attr.ErrorMessageResourceName != null && attr.ErrorMessageResourceType != null)
{
validators.Add(new DataAnnotationsModelValidator(metadata, context, attr));
continue;
}
// specified a message, do nothing
if (attr.ErrorMessage != null && attr.ErrorMessage != WorkaroundMarker)
{
validators.Add(new DataAnnotationsModelValidator(metadata, context, attr));
continue;
}
var ctx = new GetMessageContext(attr, metadata.ContainerType, metadata.PropertyName,
Thread.CurrentThread.CurrentUICulture);
var errorMessage = ValidationMessageProviders.GetMessage(ctx);
var formattedError = errorMessage == null
? GetMissingTranslationMessage(metadata, attr)
: FormatErrorMessage(metadata, attr, errorMessage);
var clientRules = GetClientRules(metadata, context, attr,
formattedError);
validators.Add(new MyValidator(attr, formattedError, metadata, context, clientRules));
}
if (metadata.Model is IValidatableObject)
validators.Add(new Griffin.MvcContrib.Localization.ValidatableObjectAdapter(metadata, context));
return validators;
}
/// <summary>
/// Get default message if the localized string is missing
/// </summary>
/// <param name="metadata">Model meta data</param>
/// <param name="attr">Attribute to translate</param>
/// <returns>Formatted message</returns>
protected virtual string GetMissingTranslationMessage(ModelMetadata metadata, ValidationAttribute attr)
{
_logger.Warning("Failed to find translation for " + attr.GetType().Name + " on " +
metadata.ContainerType + "." + metadata.PropertyName);
return string.Format("[{0}: {1}]", CultureInfo.CurrentUICulture.Name,
attr.GetType().Name.Replace("Attribute", ""));
}
/// <summary>
///
/// </summary>
/// <param name="metadata">Model meta data</param>
/// <param name="attr">Attribute to localize</param>
/// <param name="errorMessage">Localized message with <c>{}</c> formatters.</param>
/// <returns>Formatted message (<c>{}</c> has been replaced with values)</returns>
protected virtual string FormatErrorMessage(ModelMetadata metadata, ValidationAttribute attr,
string errorMessage)
{
string formattedError;
try
{
lock (attr)
{
attr.ErrorMessage = errorMessage;
formattedError = attr.FormatErrorMessage(metadata.GetDisplayName());
attr.ErrorMessage = WorkaroundMarker;
}
}
catch (Exception err)
{
formattedError = err.Message;
}
return formattedError;
}
/// <summary>
/// Get client rules
/// </summary>
/// <param name="metadata">Model meta data</param>
/// <param name="context">Controller context</param>
/// <param name="attr">Attribute being localized</param>
/// <param name="formattedError">Localized error message</param>
/// <returns>Collection (may be empty) with error messages for client side</returns>
protected virtual IEnumerable<ModelClientValidationRule> GetClientRules(ModelMetadata metadata,
ControllerContext context,
ValidationAttribute attr,
string formattedError)
{
var clientValidable = attr as IClientValidatable;
var clientRules = clientValidable == null
? _adapterFactory.Create(attr, formattedError)
: clientValidable.GetClientValidationRules(
metadata, context).ToList();
foreach (var clientRule in clientRules)
{
clientRule.ErrorMessage = formattedError;
}
return clientRules;
}
#region Nested type: MyValidator
private class MyValidator : ModelValidator
{
private readonly ValidationAttribute _attribute;
private readonly IEnumerable<ModelClientValidationRule> _clientRules;
private readonly string _errorMsg;
public MyValidator(ValidationAttribute attribute, string errorMsg, ModelMetadata metadata,
ControllerContext controllerContext, IEnumerable<ModelClientValidationRule> clientRules)
: base(metadata, controllerContext)
{
_attribute = attribute;
_errorMsg = errorMsg;
_clientRules = clientRules;
}
public override bool IsRequired
{
get { return _attribute is RequiredAttribute; }
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
return _clientRules;
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
var context = new ValidationContext(container, null, null);
var result = _attribute.GetValidationResult(Metadata.Model, context);
if (result == null)
yield break;
//if (_attribute.IsValid(Metadata.Model))
// yield break;
string errorMsg;
lock (_attribute)
{
_attribute.ErrorMessage = _errorMsg;
errorMsg = _attribute.FormatErrorMessage(Metadata.GetDisplayName());
_attribute.ErrorMessage = WorkaroundMarker;
}
yield return new ModelValidationResult { Message = errorMsg };
}
}
#endregion
}
}