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
2 changes: 1 addition & 1 deletion Easy-Core-Test/Attributes/ComparisonAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public void GreaterThan_ValidatesProperly(int higher, int lower, int? nullTest,

[Theory]
[InlineData(1, 5, null, true)]
[InlineData(1, 5, 1, true)]
[InlineData(1, 5, 5, true)]
[InlineData(5, 5, 5, false)]
[InlineData(5, 1, 1, false)]
public void LessThan_ValidatesProperly(int lower, int higher, int? nullTest, bool isValid)
Expand Down
8 changes: 4 additions & 4 deletions Easy-Core-Test/Easy-Core-Test.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
Expand All @@ -8,9 +8,9 @@
<Authors>NF Software Inc.</Authors>
<Company>NF Software Inc.</Company>
<Copyright>Copyright 2026 $(Company)</Copyright>
<Version>10.0.2</Version>
<AssemblyVersion>10.0.2.0</AssemblyVersion>
<FileVersion>10.0.2.0</FileVersion>
<Version>10.0.3</Version>
<AssemblyVersion>10.0.3.0</AssemblyVersion>
<FileVersion>10.0.3.0</FileVersion>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

Expand Down
78 changes: 78 additions & 0 deletions Easy-Core-Test/Extensions/AttributeExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,82 @@ public void GetTypeAttribute_ReturnsAttributeOrNull()
Assert.NotNull(typeof(SampleType).GetTypeAttribute<DisplayAttribute>());
Assert.Null(typeof(SampleType).GetTypeAttribute<ObsoleteAttribute>());
}

[Fact]
public void GetTypeAttribute_WithSelector_ReadsValue()
{
Assert.Equal("Sample Display", typeof(SampleType).GetTypeAttribute(d => d.GetName()));
}

[Fact]
public void GetTypeAttribute_WithSelector_ReturnsNullWhenNoAttribute()
{
Assert.Null(typeof(AttributeExtensionsTests).GetTypeAttribute(d => d.GetName()));
}

[Fact]
public void GetPropertyAttribute_ByExpression_WithSelector_ReadsValue()
{
var instance = new SampleType();
Expression<Func<string?>> expression = () => instance.Name;

Assert.Equal("Display Name", expression.GetPropertyAttribute(d => d.GetName()));
}

[Fact]
public void GetPropertyAttribute_ByExpression_WithSelector_ReturnsNullWhenNoAttribute()
{
var instance = new SampleType();
Expression<Func<string?>> expression = () => instance.Plain;

Assert.Null(expression.GetPropertyAttribute(d => d.GetName()));
}

[Fact]
public void GetPropertyAttribute_ByType_WithSelector_ReadsValue()
{
Assert.Equal("Display Name", typeof(SampleType).GetPropertyAttribute(nameof(SampleType.Name), d => d.GetName()));
}

[Fact]
public void GetPropertyAttribute_ByType_WithSelector_ReturnsNullForMissingProperty()
{
Assert.Null(typeof(SampleType).GetPropertyAttribute("Missing", d => d.GetName()));
}

[Fact]
public void GetPropertyAttribute_ByType_WithSelector_ReturnsNullWhenNoAttribute()
{
Assert.Null(typeof(SampleType).GetPropertyAttribute(nameof(SampleType.Plain), d => d.GetName()));
}

[Fact]
public void GetValueAttribute_ByValue_WithSelector_ReadsValue()
{
Assert.Equal("First Option", SampleEnum.First.GetValueAttribute(d => d.GetName()));
}

[Fact]
public void GetValueAttribute_ByValue_WithSelector_ReturnsNullWhenNoAttribute()
{
Assert.Null(SampleEnum.Second.GetValueAttribute(d => d.GetName()));
}

[Fact]
public void GetValueAttribute_ByType_WithSelector_ReadsValue()
{
Assert.Equal("First Option", typeof(SampleEnum).GetValueAttribute("First", d => d.GetName()));
}

[Fact]
public void GetValueAttribute_ByType_WithSelector_ReturnsNullForMissingMember()
{
Assert.Null(typeof(SampleEnum).GetValueAttribute("Missing", d => d.GetName()));
}

[Fact]
public void GetValueAttribute_ByType_WithSelector_ReturnsNullWhenNoAttribute()
{
Assert.Null(typeof(SampleEnum).GetValueAttribute("Second", d => d.GetName()));
}
}
42 changes: 42 additions & 0 deletions Easy-Core-Test/Extensions/DateAndTimeExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,48 @@ public void ListDaysTo_StepGreaterThanOne()
Assert.Equal(new DateOnly(2024, 1, 9), days[^1]);
}

[Fact]
public void ListDaysTo_DateTime_NegativeStep_IteratesInReverse()
{
var start = new DateTime(2024, 1, 5);
var end = new DateTime(2024, 1, 1);

var days = start.ListDaysTo(end, -1).ToList();

Assert.Equal(5, days.Count);
Assert.Equal(start, days[0]);
Assert.Equal(end, days[^1]);
}

[Fact]
public void ListDaysTo_DateOnly_NegativeStep_IteratesInReverse()
{
var start = new DateOnly(2024, 1, 5);
var end = new DateOnly(2024, 1, 1);

var days = start.ListDaysTo(end, -1).ToList();

Assert.Equal(5, days.Count);
Assert.Equal(start, days[0]);
Assert.Equal(end, days[^1]);
}

[Fact]
public void ListDaysTo_DateTime_ZeroStep_ReturnsEmpty()
{
var days = new DateTime(2024, 1, 1).ListDaysTo(new DateTime(2024, 1, 5), 0).ToList();

Assert.Empty(days);
}

[Fact]
public void ListDaysTo_DateOnly_ZeroStep_ReturnsEmpty()
{
var days = new DateOnly(2024, 1, 1).ListDaysTo(new DateOnly(2024, 1, 5), 0).ToList();

Assert.Empty(days);
}

[Fact]
public void ToCustomString_DefaultSettings_FormatsHoursMinutesSeconds()
{
Expand Down
36 changes: 36 additions & 0 deletions Easy-Core-Test/Extensions/GeneralExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,40 @@ public void TryUpdateModel_CopiesSelectedProperties()
Assert.Equal(1, destination.Id);
Assert.Equal("src", destination.Name);
}

[Fact]
public void DeserializeAnonymousType_PopulatesFields()
{
var json = """{"name":"Alice","age":30}""";
var template = new { name = "", age = 0 };

var result = json.DeserializeAnonymousType(template);

Assert.NotNull(result);
Assert.Equal("Alice", result.name);
Assert.Equal(30, result.age);
}

[Fact]
public void DeserializeAnonymousType_WithOptions_RespectsOptions()
{
// JSON keys are uppercase; options enable case-insensitive matching
var json = """{"NAME":"Bob","AGE":25}""";
var template = new { name = "", age = 0 };
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };

var result = json.DeserializeAnonymousType(template, options);

Assert.NotNull(result);
Assert.Equal("Bob", result.name);
Assert.Equal(25, result.age);
}

[Fact]
public void DeserializeAnonymousType_InvalidJson_ThrowsJsonException()
{
var template = new { name = "" };

Assert.Throws<JsonException>(() => "not valid json".DeserializeAnonymousType(template));
}
}
7 changes: 4 additions & 3 deletions Easy-Core/Easy-Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
<Authors>NF Software Inc.</Authors>
<Company>NF Software Inc.</Company>
<Copyright>Copyright 2024 $(Company)</Copyright>
<Version>10.0.2</Version>
<AssemblyVersion>10.0.2.0</AssemblyVersion>
<FileVersion>10.0.2.0</FileVersion>
<Version>10.0.3</Version>
<AssemblyVersion>10.0.3.0</AssemblyVersion>
<FileVersion>10.0.3.0</FileVersion>
<PackageId>Easy.Base</PackageId>
<PackageTags>library extensions converters tools</PackageTags>
<RepositoryUrl>https://github.com/NF-Software-Inc/easy-core</RepositoryUrl>
Expand All @@ -19,6 +19,7 @@
Contains basic functions and classes to simplify application development of modern applications.
</Description>
<PackageReleaseNotes>
10.0.3 Add new extension methods
10.0.1 Update GetValueAttribute to prevent exception
10.0.0 Add support for .NET 10.0

Expand Down
114 changes: 94 additions & 20 deletions Easy-Core/Extensions/AttributeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,37 +105,80 @@ public static string GetValueDisplayName<TModel>(this TModel value)
return value.GetValueAttribute<TModel, DisplayAttribute>()?.GetName() ?? value?.ToString() ?? "";
}

/// <summary>
/// Returns the matching attribute from the specified type if found.
/// </summary>
/// <typeparam name="TAttribute">The datatype of the attribute to return.</typeparam>
/// <param name="type">The type to check.</param>
public static TAttribute? GetTypeAttribute<TAttribute>(this Type type) where TAttribute : Attribute
/// <summary>
/// Returns the matching attribute from the specified type if found.
/// </summary>
/// <typeparam name="TAttribute">The datatype of the attribute to return.</typeparam>
/// <param name="type">The type to check.</param>
public static TAttribute? GetTypeAttribute<TAttribute>(this Type type) where TAttribute : Attribute
{
return (TAttribute?)Attribute.GetCustomAttribute(type, typeof(TAttribute));
}

/// <summary>
/// Returns the matching attribute from the specified property if found.
/// </summary>
/// <typeparam name="TModel">The datatype of the property to check.</typeparam>
/// <typeparam name="TAttribute">The datatype of the attribute to return.</typeparam>
/// <param name="property">The property to check.</param>
public static TAttribute? GetPropertyAttribute<TModel, TAttribute>(this Expression<Func<TModel>> property) where TAttribute : Attribute
/// <summary>
/// Returns the value of a specified property from the <see cref="DisplayAttribute"/> of a provided type if found.
/// </summary>
/// <param name="type">The type to check.</param>
/// <param name="propertyAccessor">A function to access the desired property from the <see cref="DisplayAttribute"/>.</param>
/// <returns>The value of the specified property from the <see cref="DisplayAttribute"/> if found; otherwise, null.</returns>
public static string? GetTypeAttribute(this Type type, Func<DisplayAttribute, string?> propertyAccessor)
{
DisplayAttribute? displayAttribute = type.GetTypeAttribute<DisplayAttribute>();
return displayAttribute != null ? propertyAccessor(displayAttribute) : null;
}
Comment thread
junc-nf marked this conversation as resolved.

/// <summary>
/// Returns the matching attribute from the specified property if found.
/// </summary>
/// <typeparam name="TModel">The datatype of the property to check.</typeparam>
/// <typeparam name="TAttribute">The datatype of the attribute to return.</typeparam>
/// <param name="property">The property to check.</param>
public static TAttribute? GetPropertyAttribute<TModel, TAttribute>(this Expression<Func<TModel>> property) where TAttribute : Attribute
{
var expression = (MemberExpression)property.Body;
var attribute = expression.Member.GetCustomAttribute(typeof(TAttribute)) as TAttribute;

return attribute;
}

/// <summary>
/// Returns the matching attribute from the specified value if found.
/// </summary>
/// <typeparam name="TModel">The datatype of the property to check.</typeparam>
/// <typeparam name="TAttribute">The datatype of the attribute to return.</typeparam>
/// <param name="value">The value to check.</param>
public static TAttribute? GetValueAttribute<TModel, TAttribute>(this TModel value) where TAttribute : Attribute
/// <summary>
/// Returns the value of a specified property from the <see cref="DisplayAttribute"/> of a provided property if found.
/// </summary>
/// <typeparam name="TModel">The datatype of the property to check.</typeparam>
/// <param name="property">The property to check.</param>
/// <param name="propertyAccessor">A function to access the desired property from the <see cref="DisplayAttribute"/>.</param>
/// <returns>The value of the specified property from the <see cref="DisplayAttribute"/> if found; otherwise, null.</returns>
public static string? GetPropertyAttribute<TModel>(this Expression<Func<TModel>> property, Func<DisplayAttribute, string?> propertyAccessor)
{
var displayAttribute = property.GetPropertyAttribute<TModel, DisplayAttribute>();
return displayAttribute != null ? propertyAccessor(displayAttribute) : null;
}
Comment thread
junc-nf marked this conversation as resolved.

/// <summary>
/// Returns the value of a specified property from the <see cref="DisplayAttribute"/> of a provided property if found.
/// </summary>
/// <param name="type">The type of the parent class of the property.</param>
/// <param name="propertyName">The name of the property in the parent class.</param>
/// <param name="propertyAccessor">A function to access the desired property from the <see cref="DisplayAttribute"/>.</param>
/// <returns>The value of the specified property from the <see cref="DisplayAttribute"/> if found; otherwise, null.</returns>
public static string? GetPropertyAttribute(this Type type, string propertyName, Func<DisplayAttribute, string?> propertyAccessor)
{
var property = type.GetProperty(propertyName);

if (property == null)
return null;

var displayAttribute = property.GetCustomAttribute<DisplayAttribute>();
return displayAttribute != null ? propertyAccessor(displayAttribute) : null;
}

/// <summary>
/// Returns the matching attribute from the specified value if found.
/// </summary>
/// <typeparam name="TModel">The datatype of the property to check.</typeparam>
/// <typeparam name="TAttribute">The datatype of the attribute to return.</typeparam>
/// <param name="value">The value to check.</param>
public static TAttribute? GetValueAttribute<TModel, TAttribute>(this TModel value) where TAttribute : Attribute
{
var memberName = value?.ToString();

Expand All @@ -149,4 +192,35 @@ public static string GetValueDisplayName<TModel>(this TModel value)

return attribute;
}

/// <summary>
/// Returns the value of a specified property from the <see cref="DisplayAttribute"/> of a provided property value if found.
/// </summary>
/// <typeparam name="TModel">The type of the property.</typeparam>
/// <param name="value">The value to find the property of.</param>
/// <param name="propertyAccessor">A function to access the desired property from the <see cref="DisplayAttribute"/>.</param>
/// <returns>The value of the specified property from the <see cref="DisplayAttribute"/> if found; otherwise, null.</returns>
public static string? GetValueAttribute<TModel>(this TModel value, Func<DisplayAttribute, string?> propertyAccessor)
{
DisplayAttribute? displayAttribute = value.GetValueAttribute<TModel, DisplayAttribute>();
return displayAttribute != null ? propertyAccessor(displayAttribute) : null;
}
Comment thread
junc-nf marked this conversation as resolved.

/// <summary>
/// Returns the value of a specified property from the <see cref="DisplayAttribute"/> of a provided property if found.
/// </summary>
/// <param name="type">The type of the parent class of the property.</param>
/// <param name="memberName">The name of the member in the parent class.</param>
/// <param name="propertyAccessor">A function to access the desired property from the <see cref="DisplayAttribute"/>.</param>
/// <returns>The value of the specified property from the <see cref="DisplayAttribute"/> if found; otherwise, null.</returns>
public static string? GetValueAttribute(this Type type, string memberName, Func<DisplayAttribute, string?> propertyAccessor)
{
var member = type.GetMember(memberName).FirstOrDefault();

if (member == null)
return null;

var displayAttribute = member.GetCustomAttribute<DisplayAttribute>();
return displayAttribute != null ? propertyAccessor(displayAttribute) : null;
}
}
14 changes: 10 additions & 4 deletions Easy-Core/Extensions/DateAndTimeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,13 @@ private static DateOnly GetXthDayInMonth(this IEnumerable<DateOnly> days, int xt
/// </summary>
/// <param name="start">The date to start at.</param>
/// <param name="end">The date to end at.</param>
/// <param name="step">The number of days use as an interval.</param>
/// <param name="step">The number of days used as an interval. Use a negative value to iterate in reverse.</param>
public static IEnumerable<DateTime> ListDaysTo(this DateTime start, DateTime end, int step = 1)
{
for (var day = start.Date; day.Date <= end.Date; day = day.AddDays(step))
if (step == 0)
yield break;
Comment thread
thirstyape marked this conversation as resolved.

for (var day = start.Date; step > 0 ? day.Date <= end.Date : day.Date >= end.Date; day = day.AddDays(step))
yield return day;
}

Expand All @@ -251,10 +254,13 @@ public static IEnumerable<DateTime> ListDaysTo(this DateTime start, DateTime end
/// </summary>
/// <param name="start">The date to start at.</param>
/// <param name="end">The date to end at.</param>
/// <param name="step">The number of days use as an interval.</param>
/// <param name="step">The number of days used as an interval. Use a negative value to iterate in reverse.</param>
public static IEnumerable<DateOnly> ListDaysTo(this DateOnly start, DateOnly end, int step = 1)
{
for (var day = start; day <= end; day = day.AddDays(step))
if (step == 0)
yield break;
Comment thread
thirstyape marked this conversation as resolved.

for (var day = start; step > 0 ? day <= end : day >= end; day = day.AddDays(step))
yield return day;
}

Expand Down
Loading
Loading