Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ByteGuard.FileValidator.Extensions.DependencyInjection.slnx
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
<Solution>
<Folder Name="/assets/">
<File Path="Build.ps1" />
<File Path="Directory.Build.props" />
<File Path="Directory.Version.props" />
<File Path="LICENSE" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/FileValidator.Extensions.DependencyInjection.Tests.Unit/FileValidator.Extensions.DependencyInjection.Tests.Unit.csproj" />
</Folder>
<Project Path="src/ByteGuard.FileValidator.Extensions.DependencyInjection/ByteGuard.FileValidator.Extensions.DependencyInjection.csproj" />
</Solution>
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,83 @@
# ByteGuard.FileValidator.Extensions.DependencyInjection ![NuGet Version](https://img.shields.io/nuget/v/ByteGuard.FileValidator.Extensions.DependencyInjection)

`ByteGuard.FileValidator.Extensions.DependencyInjection` provides first-class integration of `ByteGuard.FileValidator` with `Microsoft.Extensions.DependencyInjection`.

It gives you:
- Extension methods to register the file validator in the DI container
- Easy configuration via appsettings.json or fluent configuration in code

> This package is the `Microsoft.Extensions.DependencyInjection` integration layer.
> The core validation logic lives in [`ByteGuard.FileValidator`](https://github.com/ByteGuard-HQ/byteguard-file-validator-net).

## Getting Started

### Installation
This package is published and installed via NuGet.

Reference the package in your project:
```bash
dotnet add package ByteGuard.FileValidator.Extensions.DependencyInjection
```

## Usage

### Add to DI container
In your `Program.cs` (or `Startup.cs` in older projects), register the validator:

```csharp
using ByteGuard.FileValidator;
using ByteGuard.FileValidator.Extensions.DependencyInjection;

// Using inline configuration
builder.Services.AddFileValidator(options =>
{
options.AllowFileTypes(FileExtensions.Pdf, FileExtensions.Jpg, FileExtensions.Png);
options.FileSizeLimit = ByteSize.MegaBytes(25);
options.ThrowOnInvalidFiles(false);
});

// Using configuration from appsettings.json
builder.Services.AddFileValidator(options => configuration.GetSection("FileValidatorConfiguration").Bind(options));
```

### Injection & Usage
You can then inject `FileValidator` into your services and other classes.

```csharp
public class MyService
{
private readonly FileValidator _fileValidator;

public MyService(FileValidator fileValidator)
{
_fileValidator = fileValidator;
}

public bool SaveFile(Stream fileStream, string fileName)
{
var isValid = _fileValidator.IsValidFile(fileName, fileStream);

// ...
}
}
```

### Configuration via appsettings
It's possible to configure the `FileValidator` through `appsettings.json`.

> _ℹ️ As you'll notice below, you can either define the `FileSizeLimit` in raw byte size, or use the `UnitFileSizeLimit` to define
> the file size in a more human readable format. When both are defined, `FileSizeLimit` always wins over `UnitFileSizeLimit`._

```json
{
"FileValidatorConfiguration": {
"SupportedFileTypes": [ ".pdf", ".jpg", ".png" ],
"FileSizeLimit": 26214400,
"UnitFileSizeLimit": "25MB",
"ThrowExceptionOnInvalidFile": true
}
}
```

## License
_ByteGuard.FileValidator.Extensions.DpendencyInjection is Copyright © ByteGuard Contributors - Provided under the MIT license._
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<Authors>ByteGuard Contributors, detilium</Authors>
<Description>ByteGuard File Validator support for ASP.NET Core.</Description>
<Authors>ByteGuard Contributors</Authors>
<Description>ByteGuard File Validator support for Microsoft.Extensions.DependencyInjection.</Description>
<PackageProjectUrl>https://github.com/ByteGuard-HQ/byteguard-file-validator-extensions-dependency-injection</PackageProjectUrl>
<RepositoryUrl>https://github.com/ByteGuard-HQ/byteguard-file-validator-extensions-dependency-injection</RepositoryUrl>
<RepositoryType>git</RepositoryType>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using ByteGuard.FileValidator.Configuration;
using Microsoft.Extensions.Options;

namespace ByteGuard.FileValidator.Extensions.DependencyInjection.Configuration;

/// <summary>
/// File validator configuration options validator for use with the options pattern.
/// </summary>
public class FileValidatorConfigurationOptionsValidator : IValidateOptions<FileValidatorConfiguration>
{
/// <inheritdoc />
public ValidateOptionsResult Validate(string? name, FileValidatorConfiguration config)
{
try
{
ConfigurationValidator.ThrowIfInvalid(config);
return ValidateOptionsResult.Success;
}
catch (Exception ex)
{
return ValidateOptionsResult.Fail(ex.Message);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
namespace ByteGuard.FileValidator.Extensions.DependencyInjection.Configuration;

/// <summary>
/// Appsettings specific configuration for the File Validator.
/// </summary>
public class FileValidatorSettingsConfiguration
{
/// <summary>
/// Supported file types.
/// </summary>
/// <remarks>
/// Setup which file types are supported by the file validator.
/// File types should be specified with their leading dot, e.g. <c>.jpg</c>, <c>.png</c>, <c>.pdf</c>, etc.
/// Currently supported file types can be found in <see cref="FileExtensions"/>.
/// </remarks>
public List<string> SupportedFileTypes { get; set; } = new List<string>();

/// <summary>
/// Maximum file size limit in bytes.
/// </summary>
/// <remarks>
/// Defines the file size limit of files. See <see cref="ByteSize"/> for conversion help.
/// </remarks>
public long FileSizeLimit { get; set; } = -1;

/// <summary>
/// Maximum file size limit in unit string representation (e.g. "25MB", "2 GB", etc.).
/// </summary>
/// <remarks>
/// Will be ignored if <see cref="FileSizeLimit"/> is defined.
/// Spacing (<c>"25 MB"</c> vs. <c>"25MB"</c>) is irrelevant.
/// <para>Supported string representation are:
/// <ul>
/// <li><c>B</c>: Bytes</li>
/// <li><c>KB</c>: Kilobytes</li>
/// <li><c>MB</c>: Megabytes</li>
/// <li><c>GB</c>: Gigabytes</li>
/// </ul>
/// </para>
/// </remarks>
public string? UnitFileSizeLimit { get; set; }

/// <summary>
/// Whether to throw an exception if an unsupported/invalid file is encountered. Defaults to <c>true</c>.
/// </summary>
public bool ThrowExceptionOnInvalidFile { get; set; } = true;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
using ByteGuard.FileValidator.Configuration;
using ByteGuard.FileValidator.Extensions.DependencyInjection.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace ByteGuard.FileValidator.Extensions.DependencyInjection;

/// <summary>
/// Extension methods for registering the File Validator services in an <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddFileValidator(this IServiceCollection services, Action<Configuration.FileValidatorSettingsConfiguration> configureSettings)
/// <summary>
/// Adds the File Validator services to the specified <see cref="IServiceCollection"/> with custom configuration options.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="options">Configuration options.</param>
public static IServiceCollection AddFileValidator(this IServiceCollection services, Action<FileValidatorSettingsConfiguration> options)
{
// TODO.
// Validate and setup configuration options.
services.AddSingleton<IValidateOptions<FileValidatorConfiguration>,
FileValidatorConfigurationOptionsValidator>();

services.Configure(options);

services.AddOptions<FileValidatorConfiguration>()
.Configure<IOptions<FileValidatorSettingsConfiguration>>((cfg, settings) =>
{
// Convert from FileValidatorSettingsConfiguration to FileValidatorConfiguration.
cfg.SupportedFileTypes = settings.Value.SupportedFileTypes;
cfg.ThrowExceptionOnInvalidFile = settings.Value.ThrowExceptionOnInvalidFile;

if (settings.Value.FileSizeLimit != -1)
{
cfg.FileSizeLimit = settings.Value.FileSizeLimit;
}
else if (!string.IsNullOrWhiteSpace(settings.Value.UnitFileSizeLimit))
{
cfg.FileSizeLimit = ByteSize.Parse(settings.Value.UnitFileSizeLimit);
}
})
.ValidateOnStart();

// Register the FileValidator service.
services.AddSingleton(serviceProvider =>
{
var configuration = serviceProvider.GetRequiredService<IOptions<FileValidatorConfiguration>>().Value;
return new FileValidator(configuration);
});

return services;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../../src/ByteGuard.FileValidator.Extensions.DependencyInjection/ByteGuard.FileValidator.Extensions.DependencyInjection.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using ByteGuard.FileValidator.Configuration;
using ByteGuard.FileValidator.Extensions.DependencyInjection.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace ByteGuard.FileValidator.Extensions.DependencyInjection.Tests.Unit;

public class ServiceCollectionExtensionsTests
{
[Fact(DisplayName = "AddFileValidator adds FileValidator to the service collection")]
public void AddFileValidator_AddsFileValidatorToServiceCollection()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddFileValidator(config =>
{
config.SupportedFileTypes = new List<string>() { ".png", ".jpg" };
config.UnitFileSizeLimit = "25MB";
});

var serviceProvider = services.BuildServiceProvider();

// Assert
var fileValidator = serviceProvider.GetService<FileValidator>();
Assert.NotNull(fileValidator);
}

[Fact(DisplayName = "AddFileValidator should throws exception when configuration is invalid")]
public void AddFileValidator_ShouldThrowException_WhenConfigurationIsInvalid()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddFileValidator(config =>
{
config.SupportedFileTypes = []; // Invalid configuration - missing supported file types
});

var serviceProvider = services.BuildServiceProvider();

// Assert
Assert.ThrowsAny<Exception>(() => serviceProvider.GetRequiredService<FileValidator>());
}

[Fact(DisplayName = "AddFileValidator should set correct file size limit when using the friendly file size limit property")]
public void AddFileValidator_FriendlyFileSizeLimitSet_ShouldAddAFileValidator()
{
// Arrange
var services = new ServiceCollection();
Action<FileValidatorSettingsConfiguration> configAction = options =>
{
options.SupportedFileTypes = [".pdf"];
options.UnitFileSizeLimit = "25MB";
};

// Act
services.AddFileValidator(configAction);

// Assert
var sp = services.BuildServiceProvider();
var options = sp.GetRequiredService<IOptions<FileValidatorConfiguration>>();
Assert.Equal(ByteSize.MegaBytes(25), options.Value.FileSizeLimit);
}

[Fact(DisplayName = "AddFileValidator should use file size limit over friendly file size limit if both are set")]
public void AddFileValidator_FileSizeLimitAndFriendlyFileSizeLimitBothSet_ShouldUseFileSizeLimitOverFriendlyFileSizeLimit()
{
// Arrange
var services = new ServiceCollection();
Action<FileValidatorSettingsConfiguration> configAction = options =>
{
options.SupportedFileTypes = [".pdf"];
options.FileSizeLimit = ByteSize.MegaBytes(10);
options.UnitFileSizeLimit = "25MB";
};

// Act
services.AddFileValidator(configAction);

// Assert
var sp = services.BuildServiceProvider();
var options = sp.GetRequiredService<IOptions<FileValidatorConfiguration>>();
Assert.Equal(ByteSize.MegaBytes(10), options.Value.FileSizeLimit);
}
}