20250420
This commit is contained in:
118
anbs_cpfn/Classes/CustomValidationMessages.cs
Normal file
118
anbs_cpfn/Classes/CustomValidationMessages.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using anbs_cp.Classes;
|
||||
using anbs_cp.ForNet.Constants;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
|
||||
namespace anbs_cp.ForNet.Classes;
|
||||
|
||||
/// <summary>
|
||||
/// Кастомные сообщения валидации формы
|
||||
/// </summary>
|
||||
/// <param name="messageList">Кастомный список сообщений формы</param>
|
||||
public class CustomValidationMessages (KeyValueOrderedList<string>? messageList): IValidationMetadataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Список сообщений формы
|
||||
/// </summary>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public readonly KeyValueOrderedList<string> MessageList = messageList ?? ValidationMessagesConstants.English;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт данные для локализации сообщений валидации
|
||||
/// </summary>
|
||||
/// <param name="context">Контекст</param>
|
||||
public void CreateValidationMetadata (ValidationMetadataProviderContext context)
|
||||
{
|
||||
// Если context = null, то прерываем с ошибкой
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
// Получаю мета-данные
|
||||
IList<object>? validators = context.ValidationMetadata.ValidatorMetadata;
|
||||
|
||||
// Получаю тип модели
|
||||
Type? theType = context.Key.ModelType;
|
||||
|
||||
// Получаю фундаментальный тип
|
||||
Type? underlyingType = Nullable.GetUnderlyingType(theType);
|
||||
|
||||
// Проверяю получение и соответствие
|
||||
if (theType.IsValueType && underlyingType == null &&
|
||||
validators.All(m => m.GetType() != typeof(RequiredAttribute)))
|
||||
// - задаю валидацию
|
||||
validators.Add(new RequiredAttribute());
|
||||
|
||||
// Для каждого объекта валидации
|
||||
foreach (object? obj in validators)
|
||||
{
|
||||
// - если он не атрибут
|
||||
if (obj is not ValidationAttribute attribute)
|
||||
// -- то продолжаю
|
||||
continue;
|
||||
|
||||
// RequiredAttribute
|
||||
FillErrorMessage<RequiredAttribute>(attribute);
|
||||
|
||||
// MinLengthAttribute
|
||||
FillErrorMessage<MinLengthAttribute>(attribute);
|
||||
|
||||
// MaxLengthAttribute
|
||||
FillErrorMessage<MaxLengthAttribute>(attribute);
|
||||
|
||||
// EmailAddressAttribute
|
||||
FillErrorMessage<EmailAddressAttribute>(attribute);
|
||||
|
||||
// RangeAttribute
|
||||
FillErrorMessage<RangeAttribute>(attribute);
|
||||
|
||||
// CompareAttribute
|
||||
FillErrorMessage<CompareAttribute>(attribute);
|
||||
|
||||
// RegularExpressionAttribute
|
||||
FillErrorMessage<RegularExpressionAttribute>(attribute);
|
||||
|
||||
// PhoneAttribute
|
||||
FillErrorMessage<PhoneAttribute>(attribute);
|
||||
|
||||
// CreditCardAttribute
|
||||
FillErrorMessage<CreditCardAttribute>(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает сообщение об ошибке валидации
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип атрибута валидации</typeparam>
|
||||
/// <param name="attribute">Атрибут</param>
|
||||
private void FillErrorMessage<T> (object attribute) where T : ValidationAttribute
|
||||
{
|
||||
// Проверяю на соответствие типа
|
||||
if (attribute is not T validationAttribute)
|
||||
// - и прерываю, если не соответствует
|
||||
return;
|
||||
|
||||
// Если нет сообщения об ошибке
|
||||
if (validationAttribute.ErrorMessage == null && validationAttribute.ErrorMessageResourceName == null)
|
||||
{
|
||||
// - получаю имя аттрибута
|
||||
string attributeTypeName = typeof(T).Name;
|
||||
|
||||
// - если список сообщений не содержит ключа с таким именем аттрибута
|
||||
if (MessageList.Keys.All(s => s != attributeTypeName))
|
||||
// -- то прерываю
|
||||
return;
|
||||
|
||||
// - получаю предопределённое сообщение об ошибке
|
||||
string? errorMessage = MessageList.GetValue(attributeTypeName);
|
||||
|
||||
// - если предопределённое сообщение пусто
|
||||
if (errorMessage == null)
|
||||
// -- то прерываю
|
||||
return;
|
||||
|
||||
// - задаю значение
|
||||
validationAttribute.ErrorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
41
anbs_cpfn/Constants/ValidationMessagesConstants.cs
Normal file
41
anbs_cpfn/Constants/ValidationMessagesConstants.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using anbs_cp.Classes;
|
||||
|
||||
namespace anbs_cp.ForNet.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Сообщения валидации
|
||||
/// </summary>
|
||||
public static class ValidationMessagesConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Сообщения на русском языке
|
||||
/// </summary>
|
||||
public static readonly KeyValueOrderedList<string> Russian =
|
||||
[
|
||||
new("RequiredAttribute", "Поле '{0}' обязательно к заполнению."),
|
||||
new("MinLengthAttribute", "Минимальная длина поля '{0}' от {1} символа(-ов)."),
|
||||
new("MaxLengthAttribute", "Максимальная длина поля '{0}' до {1} символа(-ов)."),
|
||||
new("EmailAddressAttribute", "Адрес email некорректен."),
|
||||
new("RangeAttribute", "Значение поля '{0}' должно быть между {1} и {2}."),
|
||||
new("CompareAttribute", "Значение поля '{0}' не совпадаем с требуемым."),
|
||||
new("RegularExpressionAttribute", "Значение поля '{0}' не удовлетворяет шаблону."),
|
||||
new ("PhoneAttribute", "Значение поля '{0}' не является номером телефона."),
|
||||
new ("CreditCardAttribute", "Значение поля '{0}' не является номером кредитной карты.")
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Сообщения на английском языке
|
||||
/// </summary>
|
||||
public static readonly KeyValueOrderedList<string> English =
|
||||
[
|
||||
new("RequiredAttribute", "You must fill in '{0}'."),
|
||||
new("MinLengthAttribute", "Min length of '{0}' is {1}."),
|
||||
new("MaxLengthAttribute", "Max length of '{0}' is {1}."),
|
||||
new("EmailAddressAttribute", "Invalid email address."),
|
||||
new("RangeAttribute", "The value of the field '{0}' must be between {1} and {2}."),
|
||||
new("CompareAttribute", "Unmatched field '{0}' value."),
|
||||
new("RegularExpressionAttribute", "Invalid field '{0}' value."),
|
||||
new ("PhoneAttribute", "The value of the field '{0}' is not valid phone number."),
|
||||
new ("CreditCardAttribute", "The value of the field '{0}' is not valid credit card number.")
|
||||
];
|
||||
}
|
108
anbs_cpfn/Extensions/FormFileExtension.cs
Normal file
108
anbs_cpfn/Extensions/FormFileExtension.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ImageMagick;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
using static System.Text.RegularExpressions.Regex;
|
||||
|
||||
namespace anbs_cp.ForNet.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Расширение интерфейса IFormFile
|
||||
/// </summary>
|
||||
public static class FormFileExtension
|
||||
{
|
||||
#region Константы
|
||||
/// <summary>
|
||||
/// Минимальный размер изображения (в байтах)
|
||||
/// </summary>
|
||||
private const int ImageMinimumBytes = 512;
|
||||
|
||||
/// <summary>
|
||||
/// Список поддерживаемых Mime-типов
|
||||
/// </summary>
|
||||
private static readonly List<string> AllowedMimeTypes =
|
||||
["image/jpg", "image/jpeg", "image/pjpeg", "image/gif", "image/x-png", "image/png"];
|
||||
|
||||
/// <summary>
|
||||
/// Список поддерживаемых расширений файлов
|
||||
/// </summary>
|
||||
private static readonly List<string> AllowedExtensions = [".jpg", ".png", ".gif", ".jpeg"];
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Получение содержимого файла
|
||||
/// </summary>
|
||||
/// <param name="file">Файл из формы</param>
|
||||
/// <returns>Содержимое файла</returns>
|
||||
public static byte[] GetFileContent (this IFormFile file)
|
||||
{
|
||||
// Читаем содержимое файла
|
||||
using BinaryReader binaryReader = new(file.OpenReadStream());
|
||||
|
||||
// Создаю контент изображения
|
||||
byte[] content = binaryReader.ReadBytes((int)file.Length);
|
||||
|
||||
// Вывожу результат
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверка, является ли файл изображением
|
||||
/// </summary>
|
||||
/// <param name="postedFile">Файл из формы</param>
|
||||
/// <returns>Является ли файл изображением</returns>
|
||||
public static bool IsImage (this IFormFile postedFile)
|
||||
{
|
||||
// Проверяю Mime-тип
|
||||
if (!AllowedMimeTypes.Contains(postedFile.ContentType.ToLower()))
|
||||
return false;
|
||||
|
||||
// Проверяю расширение
|
||||
if (!AllowedExtensions.Contains(Path.GetExtension(postedFile.FileName).ToLower()))
|
||||
return false;
|
||||
|
||||
// Пытаюсь прочитать файл и проверить первые байты
|
||||
try
|
||||
{
|
||||
if (!postedFile.OpenReadStream().CanRead)
|
||||
return false;
|
||||
|
||||
// Проверяю, не меньше ли размер изображения установленного предела
|
||||
if (postedFile.Length < ImageMinimumBytes)
|
||||
return false;
|
||||
|
||||
byte[] buffer = new byte[ImageMinimumBytes];
|
||||
int _ = postedFile.OpenReadStream().Read(buffer, 0, ImageMinimumBytes);
|
||||
string content = System.Text.Encoding.UTF8.GetString(buffer);
|
||||
if (IsMatch(content,
|
||||
@"<script|<html|<head|<title|<body|<pre|<table|<a\s+href|<img|<plaintext|<cross\-domain\-policy",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline))
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// - если .NET выдаст исключение, то можно предположить, что это недопустимое изображение
|
||||
return false;
|
||||
}
|
||||
|
||||
// Попробую создать экземпляр MagickImageCollection,
|
||||
try
|
||||
{
|
||||
using MagickImageCollection images = new(postedFile.OpenReadStream());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// - если .NET выдаст исключение, то можно предположить, что это недопустимое изображение
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
postedFile.OpenReadStream().Position = 0;
|
||||
}
|
||||
|
||||
// Возвращаю, что это изображение
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageId>ANBSoftware.ComponentsPackForNet</PackageId>
|
||||
<Version>2024.3.27</Version>
|
||||
<Version>2024.9.1</Version>
|
||||
<Authors>Александр Бабаев</Authors>
|
||||
<Product>Набор компонентов ANB Software для ASP.NET Core</Product>
|
||||
<Description>Библиотека полезных методов языка C# для ASP.NET Core</Description>
|
||||
@@ -20,20 +20,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlSanitizer" Version="8.0.843" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.3" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="8.1.870" />
|
||||
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.10.0" />
|
||||
<PackageReference Include="Magick.NET.Core" Version="13.10.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.TagHelpers" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\anbs_cp\anbs_cp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Extensions\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Reference in New Issue
Block a user