< Summary - Code Coverage

Information
Class: Plainquire.Sort.Mvc.ModelBinders
Assembly: Plainquire.Sort.Mvc
File(s): /home/runner/work/plainquire/plainquire/Plainquire.Sort/Plainquire.Sort.Mvc/ModelBinders/EntitySortModelBinder.cs
Tag: 64_13932151703
Line coverage
96%
Covered lines: 56
Uncovered lines: 2
Coverable lines: 58
Total lines: 126
Line coverage: 96.5%
Branch coverage
86%
Covered branches: 19
Total branches: 22
Branch coverage: 86.3%
Method coverage
100%
Covered methods: 6
Total methods: 6
Method coverage: 100%

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
BindModelAsync(...)83.33%6693.75%
IsSortByParameter(...)100%11100%
GetParameterValues(...)100%11100%
CreateEntitySort(...)87.5%88100%
Apply(...)100%22100%
MapToPropertyPath(...)83.33%6693.33%

File(s)

/home/runner/work/plainquire/plainquire/Plainquire.Sort/Plainquire.Sort.Mvc/ModelBinders/EntitySortModelBinder.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Mvc.ModelBinding;
 2using Microsoft.Extensions.DependencyInjection;
 3using Microsoft.Extensions.Options;
 4using Plainquire.Filter.Abstractions;
 5using Plainquire.Sort.Abstractions;
 6using System;
 7using System.Collections.Generic;
 8using System.Diagnostics.CodeAnalysis;
 9using System.Linq;
 10using System.Reflection;
 11using System.Text.RegularExpressions;
 12using System.Threading.Tasks;
 13
 14namespace Plainquire.Sort.Mvc.ModelBinders;
 15
 16/// <summary>
 17/// ModelBinder for <see cref="EntitySort{TEntity}"/>
 18/// Implements <see cref="IModelBinder" />
 19/// </summary>
 20/// <seealso cref="IModelBinder" />
 21public class EntitySortModelBinder : IModelBinder
 22{
 23    /// <inheritdoc />
 24    public Task BindModelAsync(ModelBindingContext bindingContext)
 25    {
 3826        if (bindingContext == null)
 027            throw new ArgumentNullException(nameof(bindingContext));
 28
 3829        var serviceProvider = bindingContext.ActionContext.HttpContext.RequestServices;
 30
 3831        var sortedType = bindingContext.ModelType.GetGenericArguments()[0];
 3832        var entitySort = CreateEntitySort(sortedType, serviceProvider);
 33
 3834        var entitySortConfiguration = entitySort.Configuration;
 3835        var configuration = entitySortConfiguration ?? SortConfiguration.Default ?? new SortConfiguration();
 36
 3837        var sortByParameterName = bindingContext.OriginalModelName;
 3838        var sortByParameterValues = bindingContext.HttpContext.Request.Query.Keys
 3839            .Where(queryParameter => IsSortByParameter(queryParameter, sortByParameterName))
 3840            .SelectMany(queryParameter => GetParameterValues(queryParameter, bindingContext))
 3841            .SelectMany(value => value.SplitCommaSeparatedValues())
 3842            .ToList();
 43
 3844        var entityEntitySort = entitySort.Apply(sortByParameterValues, configuration);
 3845        bindingContext.Result = ModelBindingResult.Success(entityEntitySort);
 3846        return Task.CompletedTask;
 47    }
 48
 49    private static bool IsSortByParameter(string queryParameterName, string sortByParameterName)
 5850        => Regex.IsMatch(queryParameterName, @$"{sortByParameterName}(\[\d*\])?", RegexOptions.IgnoreCase, RegexDefaults
 51
 52    private static ValueProviderResult GetParameterValues(string queryParameter, ModelBindingContext bindingContext)
 3853        => bindingContext.ValueProvider.GetValue(queryParameter);
 54
 55    private static EntitySort CreateEntitySort(Type sortedType, IServiceProvider serviceProvider)
 56    {
 3857        var entitySortType = typeof(EntitySort<>).MakeGenericType(sortedType);
 3858        var entitySortInstance = Activator.CreateInstance(entitySortType)
 3859            ?? throw new InvalidOperationException($"Unable to create instance of type {entitySortType.Name}");
 60
 3861        var entitySort = (EntitySort)entitySortInstance;
 62
 3863        var prototypeConfiguration = ((EntitySort?)serviceProvider.GetService(entitySortType))?.Configuration;
 3864        var injectedConfiguration = serviceProvider.GetService<IOptions<SortConfiguration>>()?.Value;
 3865        entitySort.Configuration = prototypeConfiguration ?? injectedConfiguration;
 66
 3867        return entitySort;
 68    }
 69}
 70
 71file static class Extensions
 72{
 73    public static EntitySort Apply(this EntitySort entitySort, IEnumerable<string> sortParameters, SortConfiguration con
 74    {
 3875        var sortedType = entitySort.GetType().GenericTypeArguments[0];
 3876        var entityFilterAttribute = sortedType.GetCustomAttribute<EntityFilterAttribute>();
 77
 3878        var sortablePropertyNameToParameterMap = sortedType
 3879            .GetSortableProperties()
 3880            .Select(property => new PropertyNameToParameterMap(
 3881                PropertyName: property.Name,
 3882                ParameterName: property.GetSortParameterName(entityFilterAttribute?.Prefix)
 3883            ))
 3884            .ToList();
 85
 3886        var propertySorts = sortParameters
 3887            .Select(parameter => MapToPropertyPath(parameter, sortablePropertyNameToParameterMap, configuration))
 3888            .Select((propertyPath, index) => propertyPath != null ? PropertySort.Create(propertyPath, index, configurati
 3889            .WhereNotNull()
 3890            .ToList();
 91
 18492        foreach (var propertySort in propertySorts)
 5493            entitySort.PropertySorts.Add(propertySort);
 94
 3895        return entitySort;
 96    }
 97
 98    private static string? MapToPropertyPath(string parameter, IReadOnlyCollection<PropertyNameToParameterMap> sortableP
 99    {
 72100        var sortSyntaxMatch = Regex.Match(parameter, configuration.SortDirectionPattern, RegexOptions.IgnoreCase, RegexD
 72101        if (!sortSyntaxMatch.Success)
 0102            return null;
 103
 72104        var prefix = sortSyntaxMatch.Groups["prefix"].Value;
 72105        var propertyPath = sortSyntaxMatch.Groups["propertyPath"].Value;
 72106        var postfix = sortSyntaxMatch.Groups["postfix"].Value;
 107
 72108        var propertyPathSegments = propertyPath
 72109            .Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
 110
 72111        var primaryParameterName = propertyPathSegments.FirstOrDefault();
 112
 72113        var property = sortableProperties.FirstOrDefault(x => x.ParameterName.EqualsOrdinal(primaryParameterName)) ??
 72114                       sortableProperties.FirstOrDefault(x => x.ParameterName.Equals(primaryParameterName, StringCompari
 115
 72116        if (property == null)
 18117            return null;
 118
 54119        propertyPathSegments[0] = property.PropertyName;
 120
 54121        return prefix + string.Join('.', propertyPathSegments) + postfix;
 122    }
 123
 124    [ExcludeFromCodeCoverage]
 125    private record PropertyNameToParameterMap(string PropertyName, string ParameterName);
 126}