From 1797306ec987e61753b9d4a24fd195f0f3b83d01 Mon Sep 17 00:00:00 2001 From: Milos Pantic Date: Sun, 13 Mar 2022 22:41:33 +0100 Subject: [PATCH 1/2] Int64/long support --- .../WebApi/Controllers/FruitsController.cs | 56 ++++++++++++++++++ samples/WebApi/Model/FruitDto.cs | 15 +++++ .../Json/LongHashidsJsonConverter.cs | 59 +++++++++++++++++++ .../Json/NullableLongHashidsJsonConverter.cs | 47 +++++++++++++++ .../Mvc/HashidsModelBinder.cs | 24 ++++++-- 5 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 samples/WebApi/Controllers/FruitsController.cs create mode 100644 samples/WebApi/Model/FruitDto.cs create mode 100644 src/AspNetCore.Hashids/Json/LongHashidsJsonConverter.cs create mode 100644 src/AspNetCore.Hashids/Json/NullableLongHashidsJsonConverter.cs diff --git a/samples/WebApi/Controllers/FruitsController.cs b/samples/WebApi/Controllers/FruitsController.cs new file mode 100644 index 0000000..f832c80 --- /dev/null +++ b/samples/WebApi/Controllers/FruitsController.cs @@ -0,0 +1,56 @@ +using AspNetCore.Hashids.Mvc; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mime; +using WebApi.Model; + +namespace WebApi.Controllers +{ + [Route("api/fruits")] + [ApiController] + public class FruitsController : ControllerBase + { + private static readonly IEnumerable fruits = new FruitDto[] + { + new FruitDto + { + Id = long.MaxValue, + NonHashid = 10000, + NullableId = 66666, + Name = "Apple" + }, + new FruitDto + { + Id = 8888, + NonHashid = 20000, + Name = "Banana" + } + }; + + [HttpGet] + [Route("")] + [Produces(MediaTypeNames.Application.Json)] + public ActionResult> Get() + { + return Ok(fruits); + } + + [HttpGet] + [Route("{id:hashids}")] + [Produces(MediaTypeNames.Application.Json)] + public ActionResult Get( + [FromRoute][ModelBinder(typeof(HashidsModelBinder))] long id) + { + return Ok(fruits.SingleOrDefault(c => c.Id == id)); + } + + [HttpPost] + [Route("")] + [Produces(MediaTypeNames.Application.Json)] + public ActionResult Post(FruitDto dto) + { + return Ok(); + } + } +} diff --git a/samples/WebApi/Model/FruitDto.cs b/samples/WebApi/Model/FruitDto.cs new file mode 100644 index 0000000..6db727a --- /dev/null +++ b/samples/WebApi/Model/FruitDto.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; +using AspNetCore.Hashids.Json; + +namespace WebApi.Model +{ + public class FruitDto + { + [JsonConverter(typeof(LongHashidsJsonConverter))] + public long Id { get; set; } + [JsonConverter(typeof(NullableLongHashidsJsonConverter))] + public long? NullableId { get; set; } + public long NonHashid { get; set; } + public string Name { get; set; } + } +} diff --git a/src/AspNetCore.Hashids/Json/LongHashidsJsonConverter.cs b/src/AspNetCore.Hashids/Json/LongHashidsJsonConverter.cs new file mode 100644 index 0000000..c517a63 --- /dev/null +++ b/src/AspNetCore.Hashids/Json/LongHashidsJsonConverter.cs @@ -0,0 +1,59 @@ +using AspNetCore.Hashids.Options; +using HashidsNet; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AspNetCore.Hashids.Json +{ + public class LongHashidsJsonConverter : JsonConverter + { + public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var stringValue = reader.GetString(); + + var hashid = GetHashids(options) + .DecodeLong(stringValue); + + if (hashid.Length == 0) + { + throw new JsonException("Invalid hash."); + } + + return hashid[0]; + } + else if (reader.TokenType == JsonTokenType.Number) + { + if ( GetHashIdOptions(options).AcceptNonHashedIds) + { + return reader.GetInt64(); + } + + throw new JsonException(@$"Element is decorated with {nameof(HashidsJsonConverter)} +but is reading a non hashed id. To allow deserialize numbers set AcceptNonHashedIds to true."); + } + + throw new JsonException(); + } + + + public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) + { + writer.WriteStringValue( + GetHashids(options).EncodeLong(value)); + } + + private IHashids GetHashids(JsonSerializerOptions options) + { + return options.GetServiceProvider().GetRequiredService(); + } + + private HashidsOptions GetHashIdOptions(JsonSerializerOptions options) + { + return options.GetServiceProvider().GetRequiredService(); + } + } +} diff --git a/src/AspNetCore.Hashids/Json/NullableLongHashidsJsonConverter.cs b/src/AspNetCore.Hashids/Json/NullableLongHashidsJsonConverter.cs new file mode 100644 index 0000000..100bf49 --- /dev/null +++ b/src/AspNetCore.Hashids/Json/NullableLongHashidsJsonConverter.cs @@ -0,0 +1,47 @@ +using HashidsNet; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AspNetCore.Hashids.Json +{ + public class NullableLongHashidsJsonConverter : JsonConverter + { + public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var stringValue = reader.GetString(); + var hashid = GetHashids(options).Decode(stringValue); + + if (hashid.Length == 0) + { + throw new JsonException("Invalid hash."); + } + + return hashid[0]; + } + else if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt64(); + } + + throw new JsonException(); + } + + + public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options) + { + if (value.HasValue) + { + writer.WriteStringValue(GetHashids(options).EncodeLong(value.Value)); + } + } + + private IHashids GetHashids(JsonSerializerOptions options) + { + return options.GetServiceProvider().GetRequiredService(); + } + } +} diff --git a/src/AspNetCore.Hashids/Mvc/HashidsModelBinder.cs b/src/AspNetCore.Hashids/Mvc/HashidsModelBinder.cs index ab8e1f6..d759e6d 100644 --- a/src/AspNetCore.Hashids/Mvc/HashidsModelBinder.cs +++ b/src/AspNetCore.Hashids/Mvc/HashidsModelBinder.cs @@ -39,14 +39,28 @@ public Task BindModelAsync(ModelBindingContext bindingContext) return Task.CompletedTask; } - var ids = hashids.Decode(value); + if (bindingContext.ModelMetadata.ModelType == typeof(long)) + { + var ids = hashids.DecodeLong(value); + + if (ids.Length == 0) + { + return Task.CompletedTask; + } - if (ids.Length == 0) + bindingContext.Result = ModelBindingResult.Success(ids.First()); + } else { - return Task.CompletedTask; - } + var ids = hashids.Decode(value); + + if (ids.Length == 0) + { + return Task.CompletedTask; + } - bindingContext.Result = ModelBindingResult.Success(ids.First()); + bindingContext.Result = ModelBindingResult.Success(ids.First()); + } + return Task.CompletedTask; } From ca87a95327c23fcfe37e76a3e1dd867d6a9b6942 Mon Sep 17 00:00:00 2001 From: Milos Pantic Date: Sun, 13 Mar 2022 22:53:23 +0100 Subject: [PATCH 2/2] Removing POST --- samples/WebApi/Controllers/FruitsController.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/samples/WebApi/Controllers/FruitsController.cs b/samples/WebApi/Controllers/FruitsController.cs index f832c80..fc14b56 100644 --- a/samples/WebApi/Controllers/FruitsController.cs +++ b/samples/WebApi/Controllers/FruitsController.cs @@ -44,13 +44,5 @@ public ActionResult Get( { return Ok(fruits.SingleOrDefault(c => c.Id == id)); } - - [HttpPost] - [Route("")] - [Produces(MediaTypeNames.Application.Json)] - public ActionResult Post(FruitDto dto) - { - return Ok(); - } } }