Blazor recaptcha 验证属性 IHttpContextAccessor 始终为 null
Blazor recaptcha validation attribute IHttpContextAccessor is always null
我想我会尝试使用 Blazor 服务器端,到目前为止,我已经设法以某种方式克服了大多数令人头疼的问题并享受它,直到现在。
我正在尝试为 Google Recaptcha v3 编写一个验证程序,它需要用户的 IP 地址。通常我会通过以下方式获取 IHttpContextAccessor:
var httpContextAccessor = (IHttpContextAccessor)validationContext.GetService(typeof(IHttpContextAccessor));
但那现在returns无效!我还发现尝试以同样的方式获取 IConfiguration 失败了,但为此,我可以在 Startup.cs.
中创建一个静态 属性
这是一天工作的最后一关,搞得我莫名其妙。
关于如何将该 IP 地址放入验证器的任何想法?
谢谢!
编辑:
我刚刚发现使 httpContextAccessor 为 null 的错误!
((System.RuntimeType)validationContext.ObjectType).DeclaringMethodthrew an exception of type 'System.InvalidOperationException'
这是验证器:
public class GoogleReCaptchaValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Lazy<ValidationResult> errorResult = new Lazy<ValidationResult>(() => new ValidationResult("Google reCAPTCHA validation failed", new String[] { validationContext.MemberName }));
if (value == null || String.IsNullOrWhiteSpace(value.ToString()))
{
return errorResult.Value;
}
var configuration = Startup.Configuration;
string reCaptchResponse = value.ToString();
string reCaptchaSecret = configuration["GoogleReCaptcha:SecretKey"];
IHttpContextAccessor httpContextAccessor = validationContext.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("secret", reCaptchaSecret),
new KeyValuePair<string, string>("response", reCaptchResponse),
new KeyValuePair<string, string>("remoteip", httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString())
});
HttpClient httpClient = new HttpClient();
var httpResponse = httpClient.PostAsync("https://www.google.com/recaptcha/api/siteverify", content).Result;
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
return errorResult.Value;
}
String jsonResponse = httpResponse.Content.ReadAsStringAsync().Result;
dynamic jsonData = JObject.Parse(jsonResponse);
if (jsonData.success != true.ToString().ToLower())
{
return errorResult.Value;
}
return ValidationResult.Success;
}
}
这个问题是由于DataAnnotationsValidator call AddDataAnnotationsValidation时没有将IServiceProvider传递给ValidationContext
造成的。
private static void ValidateModel(EditContext editContext, ValidationMessageStore messages)
{
var validationContext = new ValidationContext(editContext.Model);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true);
// Transfer results to the ValidationMessageStore
messages.Clear();
foreach (var validationResult in validationResults)
{
foreach (var memberName in validationResult.MemberNames)
{
messages.Add(editContext.Field(memberName), validationResult.ErrorMessage);
}
}
editContext.NotifyValidationStateChanged();
}
对于解决方法,您可以实现自己的 DataAnnotationsValidator
和 AddDataAnnotationsValidation
。
按照以下步骤操作:
自定义DataAnnotationsValidator
public class DIDataAnnotationsValidator: DataAnnotationsValidator
{
[CascadingParameter] EditContext DICurrentEditContext { get; set; }
[Inject]
protected IServiceProvider ServiceProvider { get; set; }
protected override void OnInitialized()
{
if (DICurrentEditContext == null)
{
throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " +
$"inside an EditForm.");
}
DICurrentEditContext.AddDataAnnotationsValidationWithDI(ServiceProvider);
}
}
自定义EditContextDataAnnotationsExtensions
public static class EditContextDataAnnotationsExtensions
{
private static ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo> _propertyInfoCache
= new ConcurrentDictionary<(Type, string), PropertyInfo>();
public static EditContext AddDataAnnotationsValidationWithDI(this EditContext editContext, IServiceProvider serviceProvider)
{
if (editContext == null)
{
throw new ArgumentNullException(nameof(editContext));
}
var messages = new ValidationMessageStore(editContext);
// Perform object-level validation on request
editContext.OnValidationRequested +=
(sender, eventArgs) => ValidateModel((EditContext)sender, serviceProvider, messages);
// Perform per-field validation on each field edit
editContext.OnFieldChanged +=
(sender, eventArgs) => ValidateField(editContext, serviceProvider, messages, eventArgs.FieldIdentifier);
return editContext;
}
private static void ValidateModel(EditContext editContext, IServiceProvider serviceProvider,ValidationMessageStore messages)
{
var validationContext = new ValidationContext(editContext.Model, serviceProvider, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true);
// Transfer results to the ValidationMessageStore
messages.Clear();
foreach (var validationResult in validationResults)
{
foreach (var memberName in validationResult.MemberNames)
{
messages.Add(editContext.Field(memberName), validationResult.ErrorMessage);
}
}
editContext.NotifyValidationStateChanged();
}
private static void ValidateField(EditContext editContext, IServiceProvider serviceProvider, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier)
{
if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo))
{
var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
var validationContext = new ValidationContext(fieldIdentifier.Model, serviceProvider, null)
{
MemberName = propertyInfo.Name
};
var results = new List<ValidationResult>();
Validator.TryValidateProperty(propertyValue, validationContext, results);
messages.Clear(fieldIdentifier);
messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage));
// We have to notify even if there were no messages before and are still no messages now,
// because the "state" that changed might be the completion of some async validation task
editContext.NotifyValidationStateChanged();
}
}
private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, out PropertyInfo propertyInfo)
{
var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName);
if (!_propertyInfoCache.TryGetValue(cacheKey, out propertyInfo))
{
// DataAnnotations only validates public properties, so that's all we'll look for
// If we can't find it, cache 'null' so we don't have to try again next time
propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName);
// No need to lock, because it doesn't matter if we write the same value twice
_propertyInfoCache[cacheKey] = propertyInfo;
}
return propertyInfo != null;
}
}
将DataAnnotationsValidator
替换为DIDataAnnotationsValidator
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
@*<DataAnnotationsValidator />*@
<DIDataAnnotationsValidator />
<ValidationSummary />
</EditForm>
对于IHttpContextAccessor
,您需要像
一样在Startup.cs
注册
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHttpContextAccessor();
}
我想我会尝试使用 Blazor 服务器端,到目前为止,我已经设法以某种方式克服了大多数令人头疼的问题并享受它,直到现在。
我正在尝试为 Google Recaptcha v3 编写一个验证程序,它需要用户的 IP 地址。通常我会通过以下方式获取 IHttpContextAccessor:
var httpContextAccessor = (IHttpContextAccessor)validationContext.GetService(typeof(IHttpContextAccessor));
但那现在returns无效!我还发现尝试以同样的方式获取 IConfiguration 失败了,但为此,我可以在 Startup.cs.
中创建一个静态 属性这是一天工作的最后一关,搞得我莫名其妙。
关于如何将该 IP 地址放入验证器的任何想法?
谢谢!
编辑:
我刚刚发现使 httpContextAccessor 为 null 的错误!
((System.RuntimeType)validationContext.ObjectType).DeclaringMethodthrew an exception of type 'System.InvalidOperationException'
这是验证器:
public class GoogleReCaptchaValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Lazy<ValidationResult> errorResult = new Lazy<ValidationResult>(() => new ValidationResult("Google reCAPTCHA validation failed", new String[] { validationContext.MemberName }));
if (value == null || String.IsNullOrWhiteSpace(value.ToString()))
{
return errorResult.Value;
}
var configuration = Startup.Configuration;
string reCaptchResponse = value.ToString();
string reCaptchaSecret = configuration["GoogleReCaptcha:SecretKey"];
IHttpContextAccessor httpContextAccessor = validationContext.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("secret", reCaptchaSecret),
new KeyValuePair<string, string>("response", reCaptchResponse),
new KeyValuePair<string, string>("remoteip", httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString())
});
HttpClient httpClient = new HttpClient();
var httpResponse = httpClient.PostAsync("https://www.google.com/recaptcha/api/siteverify", content).Result;
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
return errorResult.Value;
}
String jsonResponse = httpResponse.Content.ReadAsStringAsync().Result;
dynamic jsonData = JObject.Parse(jsonResponse);
if (jsonData.success != true.ToString().ToLower())
{
return errorResult.Value;
}
return ValidationResult.Success;
}
}
这个问题是由于DataAnnotationsValidator call AddDataAnnotationsValidation时没有将IServiceProvider传递给ValidationContext
造成的。
private static void ValidateModel(EditContext editContext, ValidationMessageStore messages)
{
var validationContext = new ValidationContext(editContext.Model);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true);
// Transfer results to the ValidationMessageStore
messages.Clear();
foreach (var validationResult in validationResults)
{
foreach (var memberName in validationResult.MemberNames)
{
messages.Add(editContext.Field(memberName), validationResult.ErrorMessage);
}
}
editContext.NotifyValidationStateChanged();
}
对于解决方法,您可以实现自己的 DataAnnotationsValidator
和 AddDataAnnotationsValidation
。
按照以下步骤操作:
自定义
DataAnnotationsValidator
public class DIDataAnnotationsValidator: DataAnnotationsValidator { [CascadingParameter] EditContext DICurrentEditContext { get; set; } [Inject] protected IServiceProvider ServiceProvider { get; set; } protected override void OnInitialized() { if (DICurrentEditContext == null) { throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " + $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " + $"inside an EditForm."); } DICurrentEditContext.AddDataAnnotationsValidationWithDI(ServiceProvider); } }
自定义
EditContextDataAnnotationsExtensions
public static class EditContextDataAnnotationsExtensions { private static ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo> _propertyInfoCache = new ConcurrentDictionary<(Type, string), PropertyInfo>(); public static EditContext AddDataAnnotationsValidationWithDI(this EditContext editContext, IServiceProvider serviceProvider) { if (editContext == null) { throw new ArgumentNullException(nameof(editContext)); } var messages = new ValidationMessageStore(editContext); // Perform object-level validation on request editContext.OnValidationRequested += (sender, eventArgs) => ValidateModel((EditContext)sender, serviceProvider, messages); // Perform per-field validation on each field edit editContext.OnFieldChanged += (sender, eventArgs) => ValidateField(editContext, serviceProvider, messages, eventArgs.FieldIdentifier); return editContext; } private static void ValidateModel(EditContext editContext, IServiceProvider serviceProvider,ValidationMessageStore messages) { var validationContext = new ValidationContext(editContext.Model, serviceProvider, null); var validationResults = new List<ValidationResult>(); Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true); // Transfer results to the ValidationMessageStore messages.Clear(); foreach (var validationResult in validationResults) { foreach (var memberName in validationResult.MemberNames) { messages.Add(editContext.Field(memberName), validationResult.ErrorMessage); } } editContext.NotifyValidationStateChanged(); } private static void ValidateField(EditContext editContext, IServiceProvider serviceProvider, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier) { if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo)) { var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model); var validationContext = new ValidationContext(fieldIdentifier.Model, serviceProvider, null) { MemberName = propertyInfo.Name }; var results = new List<ValidationResult>(); Validator.TryValidateProperty(propertyValue, validationContext, results); messages.Clear(fieldIdentifier); messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage)); // We have to notify even if there were no messages before and are still no messages now, // because the "state" that changed might be the completion of some async validation task editContext.NotifyValidationStateChanged(); } } private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, out PropertyInfo propertyInfo) { var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName); if (!_propertyInfoCache.TryGetValue(cacheKey, out propertyInfo)) { // DataAnnotations only validates public properties, so that's all we'll look for // If we can't find it, cache 'null' so we don't have to try again next time propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName); // No need to lock, because it doesn't matter if we write the same value twice _propertyInfoCache[cacheKey] = propertyInfo; } return propertyInfo != null; } }
将
DataAnnotationsValidator
替换为DIDataAnnotationsValidator
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit"> @*<DataAnnotationsValidator />*@ <DIDataAnnotationsValidator /> <ValidationSummary /> </EditForm>
对于
一样在IHttpContextAccessor
,您需要像Startup.cs
注册public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.AddHttpContextAccessor(); }