Skip to content
Open
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
16 changes: 16 additions & 0 deletions EntityInjector.Core/EntityInjector.Core.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.3.0"/>
<PackageReference Include="System.Text.Json" Version="9.0.7"/>
</ItemGroup>

</Project>
8 changes: 8 additions & 0 deletions EntityInjector.Core/Exceptions/EntityBindingException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace EntityInjector.Core.Exceptions;

public abstract class EntityBindingException(string message, Exception? inner = null)
: Exception(message, inner)
{
public abstract int StatusCode { get; }
public virtual string? Description => Message;
}
7 changes: 7 additions & 0 deletions EntityInjector.Core/Exceptions/IExceptionMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace EntityInjector.Core.Exceptions;

public interface IExceptionMetadata
{
int StatusCode { get; }
string DefaultDescription { get; }
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace EntityInjector.Route.Exceptions.Middleware;
namespace EntityInjector.Core.Exceptions.Middleware;

public class DefaultRouteBindingProblemDetailsFactory : IRouteBindingProblemDetailsFactory
public class DefaultEntityBindingProblemDetailsFactory : IEntityBindingProblemDetailsFactory
{
public ProblemDetails Create(HttpContext context, RouteBindingException exception)
public ProblemDetails Create(HttpContext context, EntityBindingException exception)
{
return new ProblemDetails
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Builder;

namespace EntityInjector.Core.Exceptions.Middleware;

public static class EntityBindingApplicationBuilderExtensions
{
public static IApplicationBuilder UseEntityBinding(this IApplicationBuilder app)
{
return app.UseMiddleware<EntityBindingExceptionMiddleware>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,30 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace EntityInjector.Route.Exceptions.Middleware;
namespace EntityInjector.Core.Exceptions.Middleware;

public class RouteBindingExceptionMiddleware(
public class EntityBindingExceptionMiddleware(
RequestDelegate next,
ILogger<RouteBindingExceptionMiddleware> logger,
IRouteBindingProblemDetailsFactory? problemDetailsFactory = null)
ILogger<EntityBindingExceptionMiddleware> logger,
IEntityBindingProblemDetailsFactory? problemDetailsFactory = null)
{
private readonly IRouteBindingProblemDetailsFactory _problemDetailsFactory = problemDetailsFactory ?? new DefaultRouteBindingProblemDetailsFactory();
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

private readonly IEntityBindingProblemDetailsFactory _problemDetailsFactory =
problemDetailsFactory ?? new DefaultEntityBindingProblemDetailsFactory();

public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (RouteBindingException ex)
catch (EntityBindingException ex)
{
logger.LogWarning(ex, "Route binding error: {Message}", ex.Message);
logger.LogWarning(ex, "Entity binding error: {Message}", ex.Message);

var problemDetails = _problemDetailsFactory.Create(context, ex);

Expand All @@ -34,5 +36,4 @@ public async Task Invoke(HttpContext context)
await context.Response.WriteAsync(json);
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace EntityInjector.Core.Exceptions.Middleware;

public static class EntityBindingServiceCollectionExtensions
{
public static IServiceCollection AddEntityBinding(this IServiceCollection services)
{
// Register default formatter if user hasn't already
services.TryAddSingleton<IEntityBindingProblemDetailsFactory, DefaultEntityBindingProblemDetailsFactory>();
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace EntityInjector.Core.Exceptions.Middleware;

public interface IEntityBindingProblemDetailsFactory
{
ProblemDetails Create(HttpContext context, EntityBindingException exception);
}
85 changes: 85 additions & 0 deletions EntityInjector.Core/Exceptions/StatusExceptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Microsoft.AspNetCore.Http;

namespace EntityInjector.Core.Exceptions;

public sealed class EntityNotFoundException(string entityName, object? id)
: EntityBindingException($"No {entityName} found for ID '{id}'."), IExceptionMetadata
{
public string EntityName { get; } = entityName;
public object? Id { get; } = id;
public override int StatusCode => StatusCodes.Status404NotFound;
public string DefaultDescription => "The requested entity was not found.";
}

public sealed class MissingEntityAttributeException(string parameterName, string expectedAttribute)
: EntityBindingException($"Missing required {expectedAttribute} on action parameter '{parameterName}'."),
IExceptionMetadata
{
public override int StatusCode => StatusCodes.Status400BadRequest;
public string DefaultDescription => "A required parameter attribute was missing.";
}

public sealed class UnsupportedBindingTypeException(Type targetType)
: EntityBindingException($"The type '{targetType.Name}' is not supported for route binding.")
{
public override int StatusCode => StatusCodes.Status400BadRequest;

public Type TargetType { get; } = targetType;
}

public sealed class BindingReceiverNotRegisteredException(Type receiverType)
: EntityBindingException($"No binding receiver registered for type '{receiverType.FullName}'.")
{
public override int StatusCode => StatusCodes.Status500InternalServerError;

public Type ReceiverType { get; } = receiverType;
}

public sealed class BindingReceiverContractException(string methodName, Type receiverType)
: EntityBindingException($"Expected method '{methodName}' not found on receiver type '{receiverType.Name}'.")
{
public override int StatusCode => StatusCodes.Status500InternalServerError;

public string MethodName { get; } = methodName;
public Type ReceiverType { get; } = receiverType;
}

public sealed class UnexpectedBindingResultException(Type expected, Type? actual)
: EntityBindingException($"Expected result of type '{expected.Name}', but got '{actual?.Name ?? "null"}'.")
{
public override int StatusCode => StatusCodes.Status500InternalServerError;

public Type ExpectedType { get; } = expected;
public Type? ActualType { get; } = actual;
}

public sealed class MissingEntityParameterException(string parameterName)
: EntityBindingException(
$"Route parameter '{parameterName}' was not found. Ensure it is correctly specified in the route."),
IExceptionMetadata
{
public string ParameterName { get; } = parameterName;
public override int StatusCode => StatusCodes.Status400BadRequest;
public string DefaultDescription => "A required route parameter was missing.";
}

public sealed class InvalidEntityParameterFormatException(string parameterName, Type expectedType, Type actualType)
: EntityBindingException(
$"Route parameter '{parameterName}' is of type '{actualType.Name}', but type '{expectedType.Name}' was expected."),
IExceptionMetadata
{
public string ParameterName { get; } = parameterName;
public Type ExpectedType { get; } = expectedType;
public Type ActualType { get; } = actualType;
public override int StatusCode => StatusCodes.Status422UnprocessableEntity;
public string DefaultDescription => "A route parameter was not in the expected format.";
}

public sealed class EmptyEntitySegmentListException(string parameterName)
: EntityBindingException($"Route parameter '{parameterName}' did not contain any valid string segments."),
IExceptionMetadata
{
public string ParameterName { get; } = parameterName;
public override int StatusCode => StatusCodes.Status422UnprocessableEntity;
public string DefaultDescription => "The route parameter did not contain any valid values.";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace EntityInjector.Route.Middleware.Attributes;
namespace EntityInjector.Core.Helpers;

public static class MetadataParsingHelper
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Http;

namespace EntityInjector.Route.Interfaces;
namespace EntityInjector.Core.Interfaces;

public interface IBindingModelDataReceiver<TKey, TType> where TKey : notnull
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using EntityInjector.Core.Helpers;

namespace EntityInjector.Property.Attributes;

[AttributeUsage(AttributeTargets.Property)]
public class FromPropertyToEntityAttribute(string propertyName, string? metaData = null) : Attribute
{
public readonly Dictionary<string, string> MetaData = MetadataParsingHelper.ParseMetaData(metaData);
public readonly string PropertyName = propertyName;
}
28 changes: 28 additions & 0 deletions EntityInjector.Property/EntityInjector.Property.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>EntityInjector.Property</PackageId>
<Version>1.0.0</Version>
<Company>Devies</Company>
<Authors>John Johansson; Erik Jergéus</Authors>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>A small library for injecting entities into proerties into Entity Framework contexts.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/DeviesDevelopment/entity-injector</PackageProjectUrl>
<RepositoryUrl>https://github.com/DeviesDevelopment/entity-injector</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\EntityInjector.Core\EntityInjector.Core.csproj"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="8.1.4"/>
</ItemGroup>

</Project>
Loading