Skip to content

Commit

Permalink
Merge pull request #239 from Lombiq/issue/OSOE-795
Browse files Browse the repository at this point in the history
OSOE-795: Upgrade to latest OC preview to test System.Text.Json
  • Loading branch information
dministro authored May 15, 2024
2 parents 29f36e1 + 79a8f35 commit 9ae7c6d
Show file tree
Hide file tree
Showing 31 changed files with 350 additions and 155 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Html;
using Newtonsoft.Json;
using System.Text.Json;

namespace System;

Expand All @@ -10,6 +10,5 @@ public static class JsonStringExtensions
/// tags in a Razor view.
/// </summary>
public static IHtmlContent JsonHtmlContent(this string htmlString) =>
new HtmlString(JsonConvert.SerializeObject(
htmlString, new JsonSerializerSettings { StringEscapeHandling = StringEscapeHandling.EscapeHtml }));
new HtmlString(JsonSerializer.Serialize(htmlString));
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,41 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace Lombiq.HelpfulLibraries.AspNetCore.Localization;

public class LocalizedHtmlStringConverter : JsonConverter<LocalizedHtmlString>
{
public override void WriteJson(JsonWriter writer, LocalizedHtmlString value, JsonSerializer serializer)
public override void Write(Utf8JsonWriter writer, LocalizedHtmlString value, JsonSerializerOptions options)
{
writer.WriteStartObject();

writer.WritePropertyName(nameof(LocalizedHtmlString.Name));
writer.WriteValue(value.Name);

writer.WritePropertyName(nameof(LocalizedHtmlString.Value));
writer.WriteValue(value.Html());

writer.WritePropertyName(nameof(LocalizedHtmlString.IsResourceNotFound));
writer.WriteValue(value.IsResourceNotFound);
writer.WriteString(nameof(LocalizedHtmlString.Name), value.Name);
writer.WriteString(nameof(LocalizedHtmlString.Value), value.Html());
writer.WriteBoolean(nameof(LocalizedHtmlString.IsResourceNotFound), value.IsResourceNotFound);

writer.WriteEndObject();
}

public override LocalizedHtmlString ReadJson(
JsonReader reader,
Type objectType,
LocalizedHtmlString existingValue,
bool hasExistingValue,
JsonSerializer serializer)
public override LocalizedHtmlString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var token = JToken.Load(reader);
var token = JsonNode.Parse(ref reader);

if (token.Type == JTokenType.String)
if (token is JsonValue jsonValue)
{
var text = token.Value<string>();
var text = jsonValue.GetValue<string>();
return new LocalizedHtmlString(text, text);
}

if (token is JObject jObject)
if (token is JsonObject jsonObject)
{
var name = jObject.GetMaybe(nameof(LocalizedHtmlString.Name))?.ToObject<string>();
var value = jObject.GetMaybe(nameof(LocalizedHtmlString.Value))?.ToObject<string>() ?? name;
var isResourceNotFound = jObject.GetMaybe(nameof(LocalizedHtmlString.IsResourceNotFound))?.ToObject<bool>();
var dictionary = jsonObject.ToDictionaryIgnoreCase();
var name = dictionary.GetMaybe(nameof(LocalizedHtmlString.Name))?.Deserialize<string>();
var value = dictionary.GetMaybe(nameof(LocalizedHtmlString.Value))?.Deserialize<string>() ?? name;
var isResourceNotFound = dictionary.GetMaybe(nameof(LocalizedHtmlString.IsResourceNotFound))?.Deserialize<bool>();

name ??= value;
if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Missing name.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Html;
using Newtonsoft.Json;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;

namespace Microsoft.AspNetCore.Mvc.Localization;

Expand All @@ -15,9 +15,7 @@ public static class LocalizedHtmlStringExtensions
/// </summary>
public static IHtmlContent Json(this LocalizedHtmlString htmlString) =>
htmlString?.Html() is { } html
? new HtmlString(JsonConvert.SerializeObject(
html,
new JsonSerializerSettings { StringEscapeHandling = StringEscapeHandling.EscapeHtml }))
? new HtmlString(JsonSerializer.Serialize(html))
: new HtmlString("null");

/// <summary>
Expand Down Expand Up @@ -47,6 +45,9 @@ public static LocalizedHtmlString Concat(this LocalizedHtmlString first, params
return new LocalizedHtmlString(html, html);
}

/// <summary>
/// Concatenates the <paramref name="items"/> with the provided <paramref name="separator"/> in-between.
/// </summary>
public static LocalizedHtmlString Join(this IHtmlContent separator, params LocalizedHtmlString[] items)
{
if (items.Length == 0) return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OrchardCore.Abstractions" Version="1.8.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Lombiq.HelpfulLibraries.Common\Lombiq.HelpfulLibraries.Common.csproj" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Lombiq.HelpfulLibraries.Common/Docs/Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
- `ExpressionExtensions`: Adds `System.Linq.Expressions`. For example, `StripResult()` turns a `Func<T1, T2>` expression into an `Action<T1>` one.
- `HttpContextExtensions`: Some shortcuts for managing cookies.
- `IoExtensions`: Adds extensions for `String.IO` types. For example, `TextWriter.WriteLineInvariant()` writes interpolated string in a culture invariant manner.
- `JsonExtensions`: Adds extensions for `Newtonsoft.Json.Linq` types. For example, `JObject.TryParse<T>(out var result)` attempts to convert the JSON object into a C# object.
- `JsonExtensions`: Adds extensions for `System.Text.Json.Nodes` types. For example, `JsonObject.TryParse<T>(out var result)` attempts to convert the JSON object into a C# object.
- `MemoryCacheExtensions`: Adds extensions for `IMemoryCache` manipulation. For example, `GetOrNew<T>()` type-safely returns the item or creates a new instance.
- `MulticastDelegateExtensions`: Extensions for `MulticastDelegate`s, e.g. to invoke async delegates in a safe fashion.
- `NumberExtensions`: Adds extensions for primitive numeric types. For example, `ToTechnicalString()` converts `int` into culture invariant `string`.
Expand Down
30 changes: 27 additions & 3 deletions Lombiq.HelpfulLibraries.Common/Extensions/JsonExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
namespace Newtonsoft.Json.Linq;
using System.Text.Json.Nodes;

namespace System.Text.Json;

public static class JsonExtensions
{
public static bool TryParse<T>(this JObject jObject, out T result)
/// <summary>
/// Attempts to parse the provided <paramref name="jsonNode"/>. If successful, returns <see langword="true"/> and
/// <paramref name="result"/> contains the serialized object. Otherwise returns <see langword="false"/> and
/// <paramref name="result"/> contains <see langword="default"/> of <typeparamref name="T"/>.
/// </summary>
public static bool TryParse<T>(this JsonNode jsonNode, out T result)
{
try
{
result = jObject.ToObject<T>();
result = jsonNode.Deserialize<T>();
return true;
}
catch
Expand All @@ -15,4 +22,21 @@ public static bool TryParse<T>(this JObject jObject, out T result)
return false;
}
}

/// <summary>
/// Converts the provided <paramref name="node"/> into the appropriate primitive types of <see langword="string"/>,
/// <see langword="decimal"/>, <see langword="bool"/> or <see langword="null"/>. If the <paramref name="node"/> is
/// array or object then it's serialized into JSON <see langword="string"/>.
/// </summary>
public static IComparable ToComparable(this JsonNode node) =>
node.GetValueKind() switch
{
JsonValueKind.String => node.GetValue<string>(),
JsonValueKind.Number => node.GetValue<decimal>(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Object => node.ToString(),
JsonValueKind.Array => node.ToString(),
_ => null,
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Extensions.DependencyInjection;
Expand All @@ -9,16 +10,57 @@ namespace Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Removes implementations of type <typeparamref name="T"/> from an <see cref="IServiceCollection"/> instance.
/// Removes all service descriptors where the <see cref="ServiceDescriptor.ImplementationType"/> is <typeparamref
/// name="T"/>.
/// </summary>
/// <param name="includeKeyed">
/// If <see langword="true"/>, it also considers keyed services by checking the <see
/// cref="ServiceDescriptor.KeyedImplementationType"/>.
/// </param>
public static IServiceCollection RemoveByImplementation<T>(this IServiceCollection services, bool includeKeyed = true) =>
services.RemoveByImplementation(typeof(T), includeKeyed);

/// <summary>
/// Removes all service descriptors where the <see cref="ServiceDescriptor.ImplementationType"/> is <paramref
/// name="implementationType"/>.
/// </summary>
/// <param name="includeKeyed">
/// If <see langword="true"/>, it also considers keyed services by checking the <see
/// cref="ServiceDescriptor.KeyedImplementationType"/>.
/// </param>
public static IServiceCollection RemoveByImplementation(
this IServiceCollection services,
Type implementationType,
bool includeKeyed = true)
{
// Have to check "service.IsKeyedService" to avoid new breaking behavior described here:
// https://github.com/dotnet/runtime/issues/95789
services.RemoveAll(service =>
(includeKeyed || !service.IsKeyedService) &&
(service.IsKeyedService ? service.KeyedImplementationType : service.ImplementationType) == implementationType);

return services;
}

[Obsolete($"Use {nameof(RemoveImplementationsOf)} instead (renamed for clarity).")]
public static IServiceCollection RemoveImplementations<T>(this IServiceCollection services) =>
RemoveImplementations(services, typeof(T).FullName);
services.RemoveImplementationsOf<T>();

[Obsolete($"Use {nameof(RemoveImplementationsOf)} instead (renamed for clarity).")]
public static IServiceCollection RemoveImplementations(this IServiceCollection services, string serviceFullName) =>
services.RemoveImplementationsOf(serviceFullName);

/// <summary>
/// Removes implementations of type <typeparamref name="T"/> from an <see cref="IServiceCollection"/> instance.
/// </summary>
public static IServiceCollection RemoveImplementationsOf<T>(this IServiceCollection services) =>
RemoveImplementationsOf(services, typeof(T).FullName);

/// <summary>
/// Removes the implementations specified in <paramref name="serviceFullName"/> from an
/// <see cref="IServiceCollection"/> instance.
/// </summary>
public static IServiceCollection RemoveImplementations(this IServiceCollection services, string serviceFullName)
public static IServiceCollection RemoveImplementationsOf(this IServiceCollection services, string serviceFullName)
{
var servicesToRemove = services
.Where(service => service.ServiceType?.FullName == serviceFullName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

</Project>
14 changes: 14 additions & 0 deletions Lombiq.HelpfulLibraries.Common/Utilities/ICopier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Lombiq.HelpfulLibraries.Common.Utilities;

/// <summary>
/// Types implementing this interface can copy their values to a <typeparamref name="TTarget"/> object.
/// </summary>
/// <typeparam name="TTarget">The type of the object to copy to.</typeparam>
public interface ICopier<in TTarget>
{
/// <summary>
/// Copies the applicable contents of the current instance into <paramref name="target"/>, overwriting its original
/// values.
/// </summary>
void CopyTo(TTarget target);
}
Loading

0 comments on commit 9ae7c6d

Please sign in to comment.