< Summary

Information
Class: MyNet.IO.Attributes.FileExtensionsAllowedAttribute
Assembly: MyNet.IO
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.IO/Attributes/FileExtensionsAllowedAttribute.cs
Tag: 323_28699572109
Line coverage
100%
Covered lines: 33
Uncovered lines: 0
Coverable lines: 33
Total lines: 124
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
.ctor(...)100%11100%
get_Extensions()100%11100%
FormatErrorMessage(...)100%11100%
IsValid(...)100%88100%
Normalize(...)100%66100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.IO/Attributes/FileExtensionsAllowedAttribute.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="FileExtensionsAllowedAttribute.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using System.ComponentModel.DataAnnotations;
 10using System.Globalization;
 11using System.IO;
 12using System.Linq;
 13using MyNet.IO.FileExtensions;
 14using MyNet.IO.Localization;
 15
 16namespace MyNet.IO.Attributes;
 17
 18/// <summary>
 19/// Validation attribute that restricts a file path property to a specific set of allowed file extensions.
 20/// Extensions are normalized (case-insensitive, leading dot always added) so that both <c>txt</c> and <c>.txt</c>
 21/// are treated identically.
 22/// </summary>
 23[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
 24public sealed class FileExtensionsAllowedAttribute : ValidationAttribute
 25{
 26    private readonly HashSet<string> _extensions;
 27
 28    /// <summary>
 29    /// Initializes a new instance of the <see cref="FileExtensionsAllowedAttribute"/> class with the specified allowed 
 30    /// </summary>
 31    /// <param name="extensions">The allowed file extensions.</param>
 32    /// <exception cref="ArgumentNullException">Thrown if the <paramref name="extensions"/> array is null.</exception>
 33    /// <exception cref="ArgumentException">Thrown if the <paramref name="extensions"/> array is empty.</exception>
 3634    public FileExtensionsAllowedAttribute(params string[] extensions)
 35    {
 3636        ArgumentNullException.ThrowIfNull(extensions);
 37
 3338        _extensions = extensions
 3339            .Select(Normalize)
 3340            .Where(static x => !string.IsNullOrEmpty(x))
 3341            .ToHashSet(StringComparer.OrdinalIgnoreCase);
 42
 3343        if (_extensions.Count == 0)
 644            throw new ArgumentException("At least one extension must be provided.", nameof(extensions));
 45
 2746        ErrorMessageResourceName = nameof(InternalResources.FieldXMustContainsAllowedExtensionsYError);
 2747        ErrorMessageResourceType = typeof(InternalResources);
 2748    }
 49
 50    /// <summary>
 51    /// Initializes a new instance of the <see cref="FileExtensionsAllowedAttribute"/> class with the specified allowed 
 52    /// </summary>
 53    /// <param name="extensions">The allowed file extensions.</param>
 54    /// <exception cref="ArgumentNullException">Thrown if the <paramref name="extensions"/> array is null.</exception>
 55    /// <exception cref="ArgumentException">Thrown if the <paramref name="extensions"/> array is empty.</exception>
 56    public FileExtensionsAllowedAttribute(params FileExtension[] extensions)
 357        : this(extensions.Select(x => x.Value).ToArray())
 58    {
 359    }
 60
 61    /// <summary>
 62    /// Gets or sets a value indicating whether null or empty file paths are considered valid. If set to <c>true</c>, nu
 63    /// </summary>
 64    public bool AllowEmpty { get; set; } = true;
 65
 66    /// <summary>
 67    /// Gets the collection of allowed file extensions that are used for validation. The extensions are stored in a hash
 68    /// </summary>
 1269    public IReadOnlyCollection<string> Extensions => _extensions;
 70
 71    /// <summary>
 72    /// Formats the error message to include the field name and the list of allowed extensions. The error message is con
 73    /// </summary>
 74    /// <param name="name">The name of the field being validated.</param>
 75    /// <returns>The formatted error message.</returns>
 76    public override string FormatErrorMessage(string name)
 377        => string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, string.Join(" | ", _extensions));
 78
 79    /// <summary>
 80    /// Determines whether the specified value is valid based on the allowed file extensions. The method checks if the v
 81    /// </summary>
 82    /// <param name="value">The value to validate.</param>
 83    /// <returns><c>true</c> if the value is valid; otherwise, <c>false</c>.</returns>
 84    public override bool IsValid(object? value)
 85    {
 3386        if (value is null)
 687            return AllowEmpty;
 88
 2789        if (value is not string path)
 390            return false;
 91
 2492        if (string.IsNullOrWhiteSpace(path))
 693            return AllowEmpty;
 94
 1895        if (_extensions.Contains("*"))
 996            return true;
 97
 998        var extension = Normalize(Path.GetExtension(path));
 99
 9100        return _extensions.Contains(extension);
 101    }
 102
 103    /// <summary>
 104    /// Normalizes a file extension by trimming whitespace, converting it to lowercase, and ensuring it starts with a do
 105    /// </summary>
 106    /// <param name="extension">The file extension to normalize.</param>
 107    /// <returns>The normalized file extension.</returns>
 108    private static string Normalize(string extension)
 109    {
 54110        if (string.IsNullOrWhiteSpace(extension))
 6111            return string.Empty;
 112
 48113        extension = extension.Trim().ToLower(CultureInfo.CurrentCulture);
 114
 48115        if (extension == "*")
 3116            return "*";
 117
 45118        if (!extension.StartsWith('.'))
 27119            extension = "." + extension;
 120
 45121        return extension;
 122    }
 123}
 124