From f770bf29832201e47fe49e15e14fa6b42bd8b311 Mon Sep 17 00:00:00 2001 From: "R.Noei" Date: Thu, 8 Jun 2023 15:45:37 +0330 Subject: [PATCH 1/4] Make response types Grpc Compatible. --- .../Responses/GenericResponseBase.cs | 13 --- .../GenericResponseBaseWithPagination.cs | 17 ---- .../Responses/InvalidItem.cs | 23 +++++ .../Responses/PaginatedResponse.cs | 45 +++++++++ .../Responses/PaginationMetadata.cs | 13 ++- .../Responses/Response.cs | 93 ++++++++++++++++--- 6 files changed, 158 insertions(+), 46 deletions(-) delete mode 100644 Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs delete mode 100644 Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs create mode 100644 Source/BSN.Commons.PresentationInfrastructure/Responses/InvalidItem.cs create mode 100644 Source/BSN.Commons.PresentationInfrastructure/Responses/PaginatedResponse.cs diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs deleted file mode 100644 index 5710686..0000000 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace BSN.Commons.Responses -{ - /// - /// Add data as given type to response class. - /// - /// The type of object or value handled by the class. - public class GenericResponseBase : Response where T : class - { - public GenericResponseBase() { } - - public T Data { get; set; } - } -} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs deleted file mode 100644 index 80edcb1..0000000 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace BSN.Commons.Responses -{ - /// - /// Generic response base for paginated data. - /// - /// - /// Paginated response provides metadata for navigation purpose. - /// - /// Data type. - public class GenericResponseBaseWithPagination : GenericResponseBase where T : class - { - /// - /// Pagination metada used by the client as the parameters for navigation through whole records. - /// - public PaginationMetadata Meta { get; set; } - } -} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/InvalidItem.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/InvalidItem.cs new file mode 100644 index 0000000..006ba66 --- /dev/null +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/InvalidItem.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace BSN.Commons.Responses +{ + /// + /// Represents a request validation issue for the API consumers. + /// + [DataContract] + public class InvalidItem + { + /// + /// Name of the request item. + /// + [DataMember(Order = 1)] + public string Name { get; set; } + + /// + /// The reason caused the validation issue. + /// + [DataMember(Order = 2)] + public string Reason { get; set; } + } +} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginatedResponse.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginatedResponse.cs new file mode 100644 index 0000000..19d2631 --- /dev/null +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginatedResponse.cs @@ -0,0 +1,45 @@ +using BSN.Commons.Converters; +using BSN.Commons.PresentationInfrastructure; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace BSN.Commons.Responses +{ + /// + /// Generic response base for paginated data. + /// + /// + /// Paginated response provides metadata for navigation purposes. + /// + /// Data type. + [DataContract] + public class PaginatedResponse where T : class + { + /// + /// Corresponding HttpStatusCode. + /// + [DataMember(Order = 2)] + [JsonConverter(typeof(JsonForceDefaultConverter))] + public ResponseStatusCode StatusCode { get; set; } + + /// + /// Data payload (Collection). + /// + [DataMember(Order = 1)] + public IEnumerable Data { get; set; } + + [DataMember(Order = 3)] + public string Message { get; set; } + + [DataMember(Order = 4)] + public PaginationMetadata Meta { get; set; } + + [DataMember(Order = 5)] + public IList InvalidItems { get; set; } + + public bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; + } + +} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginationMetadata.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginationMetadata.cs index f57abdc..5dd5e62 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginationMetadata.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/PaginationMetadata.cs @@ -1,28 +1,35 @@ -namespace BSN.Commons.Responses +using System.Runtime.Serialization; + +namespace BSN.Commons.Responses { /// - /// Stores the paginated meta data. + /// Pagination meta data. /// + [DataContract] public class PaginationMetadata { /// /// Current page number /// + [DataMember(Order = 1)] public uint Page { get; set; } /// /// Total number of pages /// + [DataMember(Order = 2)] public uint PageCount { get; set; } /// /// Number of records per page /// + [DataMember(Order = 3)] public uint PageSize { get; set; } /// - /// Total number of records that exist + /// Total number of records /// + [DataMember(Order = 4)] public uint RecordCount { get; set; } } } diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/Response.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/Response.cs index 3524f93..bb244d7 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/Response.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/Response.cs @@ -1,19 +1,86 @@ using BSN.Commons.Converters; using BSN.Commons.PresentationInfrastructure; +using System.Collections.Generic; +using System.Runtime.Serialization; using System.Text.Json.Serialization; namespace BSN.Commons.Responses -{ - /// - /// During serialization and deserialization operations, enumeration values are always converted to and from strings. - /// This is possible through registering the 'JsonStringEnumConverter' in our DI infrastructure. There is once exception, - /// namely the 'StatusCode' property of the 'ResponseBase' class which should keep it's default numeral value when being converted. - /// - public class Response : ResponseBase - { - public new bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; - - [JsonConverter(typeof(JsonForceDefaultConverter))] - public new ResponseStatusCode StatusCode { get; set; } - } +{ + /// + /// Basic response for command services to return the result of an operation. + /// + /// + /// During serialization and deserialization operations, enumeration values are always converted to and from strings. + /// This is possible through registering the 'JsonStringEnumConverter' in our DI infrastructure. There is once exception, + /// namely the 'StatusCode' property of the 'ResponseBase' class which should keep it's default numeral value when being converted. + /// + [DataContract] + public class Response + { + /// + /// Corresponding HttpStatusCode. + /// + [DataMember(Order = 1)] + [JsonConverter(typeof(JsonForceDefaultConverter))] + public ResponseStatusCode StatusCode { get; set; } + + /// + /// Human-readable message for the End-User. + /// + [DataMember(Order = 2)] + public string Message { get; set; } + + /// + /// Invalid items of the request object. + /// + [DataMember(Order = 3)] + public IList InvalidItems { get; set; } + + /// + /// Distinction between successful and unsuccessful result. + /// + public bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; + } + + /// + /// Generic response type for command/query services to return the results. + /// + /// + /// During serialization and deserialization operations, enumeration values are always converted to and from strings. + /// This is possible through registering the 'JsonStringEnumConverter' in our DI infrastructure. There is once exception, + /// namely the 'StatusCode' property of the 'ResponseBase' class which should keep it's default numeral value when being converted. + /// + [DataContract] + public class Response where T : class + { + /// + /// Corresponding HttpStatusCode. + /// + [DataMember(Order = 1)] + [JsonConverter(typeof(JsonForceDefaultConverter))] + public ResponseStatusCode StatusCode { get; set; } + + /// + /// Data payload. + /// + [DataMember(Order = 2)] + public T Data { get; set; } + + /// + /// Human-readable message for the End-User. + /// + [DataMember(Order = 3)] + public string Message { get; set; } + + /// + /// Invalid items of the request object. + /// + [DataMember(Order = 4)] + public IList InvalidItems { get; set; } + + /// + /// Distinction between successful and unsuccessful result. + /// + public bool IsSuccess => (int)StatusCode >= 200 && (int)StatusCode <= 299; + } } From 6f4c38be4283b53f25743094f572cdc063cb9163 Mon Sep 17 00:00:00 2001 From: "R.Noei" Date: Thu, 8 Jun 2023 15:53:41 +0330 Subject: [PATCH 2/4] Add support for asynchronous paginiation. --- .../BSN.Commons.Orm.EntityFramework.csproj | 159 +++++++++--------- .../Extensions/IQueryableExtensions.cs | 29 +++- .../Extensions/IQueryableExtensions.cs | 60 +++++++ 3 files changed, 168 insertions(+), 80 deletions(-) rename Source/{BSN.Commons => BSN.Commons.Orm.EntityFramework}/Extensions/IQueryableExtensions.cs (50%) create mode 100644 Source/BSN.Commons.Orm.EntityFrameworkCore/Extensions/IQueryableExtensions.cs diff --git a/Source/BSN.Commons.Orm.EntityFramework/BSN.Commons.Orm.EntityFramework.csproj b/Source/BSN.Commons.Orm.EntityFramework/BSN.Commons.Orm.EntityFramework.csproj index d8cc6e8..8c663bf 100644 --- a/Source/BSN.Commons.Orm.EntityFramework/BSN.Commons.Orm.EntityFramework.csproj +++ b/Source/BSN.Commons.Orm.EntityFramework/BSN.Commons.Orm.EntityFramework.csproj @@ -1,80 +1,81 @@ - - - - - - Debug - AnyCPU - {746AF465-DE8E-41C9-B558-CFFC6E3638D4} - Library - Properties - BSN.Commons.Orm.EntityFramework - BSN.Commons.Orm.EntityFramework - v4.6.2 - 512 - true - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\packages\EntityFramework.6.4.4\lib\net45\EntityFramework.dll - - - ..\..\packages\EntityFramework.6.4.4\lib\net45\EntityFramework.SqlServer.dll - - - ..\..\packages\Microsoft.AspNet.Identity.Core.2.2.3\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - - - - - - - - - - - - - - - - - - - - {ffef752b-4e11-44ff-82fa-9aec0b607990} - BSN.Commons - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + + Debug + AnyCPU + {746AF465-DE8E-41C9-B558-CFFC6E3638D4} + Library + Properties + BSN.Commons.Orm.EntityFramework + BSN.Commons.Orm.EntityFramework + v4.6.2 + 512 + true + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\EntityFramework.6.4.4\lib\net45\EntityFramework.dll + + + ..\..\packages\EntityFramework.6.4.4\lib\net45\EntityFramework.SqlServer.dll + + + ..\..\packages\Microsoft.AspNet.Identity.Core.2.2.3\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + + + + + + + + + + + + + + + + + + + + + {ffef752b-4e11-44ff-82fa-9aec0b607990} + BSN.Commons + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/Source/BSN.Commons/Extensions/IQueryableExtensions.cs b/Source/BSN.Commons.Orm.EntityFramework/Extensions/IQueryableExtensions.cs similarity index 50% rename from Source/BSN.Commons/Extensions/IQueryableExtensions.cs rename to Source/BSN.Commons.Orm.EntityFramework/Extensions/IQueryableExtensions.cs index 0fb384b..b068a67 100644 --- a/Source/BSN.Commons/Extensions/IQueryableExtensions.cs +++ b/Source/BSN.Commons.Orm.EntityFramework/Extensions/IQueryableExtensions.cs @@ -1,6 +1,8 @@ using System; +using System.Data.Entity; using System.Linq; - +using System.Threading.Tasks; + namespace BSN.Commons.Extensions { public static partial class IQueryableExtensions @@ -27,6 +29,31 @@ public static PagedEntityCollection Paginate(this IQueryable query, uin result.PageCount = (uint)Math.Ceiling((double)result.RecordCount / pageSize); + return result; + } + + /// + /// Paginate IQueryable of + /// with given pageNumber and pageSize + /// + public static async Task> PaginateAsync(this IQueryable query, uint pageNumber, uint pageSize) + { + if (pageNumber == 0) + throw new ArgumentException("Must be greater than zero.", nameof(pageNumber)); + + if (pageSize == 0) + throw new ArgumentException("Must be greater than zero.", nameof(pageSize)); + + var result = new PagedEntityCollection + { + CurrentPage = pageNumber, + PageSize = pageSize, + RecordCount = (uint) await query.CountAsync(), + Results = await query.Skip((int)((pageNumber - 1) * pageSize)).Take((int)pageSize).ToListAsync() + }; + + result.PageCount = (uint)Math.Ceiling((double)result.RecordCount / pageSize); + return result; } } diff --git a/Source/BSN.Commons.Orm.EntityFrameworkCore/Extensions/IQueryableExtensions.cs b/Source/BSN.Commons.Orm.EntityFrameworkCore/Extensions/IQueryableExtensions.cs new file mode 100644 index 0000000..8316a31 --- /dev/null +++ b/Source/BSN.Commons.Orm.EntityFrameworkCore/Extensions/IQueryableExtensions.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace BSN.Commons.Extensions +{ + public static partial class IQueryableExtensions + { + /// + /// Paginate IQueryable of + /// with given pageNumber and pageSize + /// + public static PagedEntityCollection Paginate(this IQueryable query, uint pageNumber, uint pageSize) + { + if (pageNumber == 0) + throw new ArgumentException("Must be greater than zero.", nameof(pageNumber)); + + if (pageSize == 0) + throw new ArgumentException("Must be greater than zero.", nameof(pageSize)); + + var result = new PagedEntityCollection + { + CurrentPage = pageNumber, + PageSize = pageSize, + RecordCount = (uint)query.Count(), + Results = query.Skip((int)((pageNumber - 1) * pageSize)).Take((int)pageSize).ToList() + }; + + result.PageCount = (uint)Math.Ceiling((double)result.RecordCount / pageSize); + + return result; + } + + /// + /// Paginate IQueryable of + /// with given pageNumber and pageSize + /// + public static async Task> PaginateAsync(this IQueryable query, uint pageNumber, uint pageSize) + { + if (pageNumber == 0) + throw new ArgumentException("Must be greater than zero.", nameof(pageNumber)); + + if (pageSize == 0) + throw new ArgumentException("Must be greater than zero.", nameof(pageSize)); + + var result = new PagedEntityCollection + { + CurrentPage = pageNumber, + PageSize = pageSize, + RecordCount = (uint) await query.CountAsync(), + Results = await query.Skip((int)((pageNumber - 1) * pageSize)).Take((int)pageSize).ToListAsync() + }; + + result.PageCount = (uint)Math.Ceiling((double)result.RecordCount / pageSize); + + return result; + } + } +} From 3dcb0b136e940fd2ca1a9ff2be20d5467845ddb0 Mon Sep 17 00:00:00 2001 From: "R.Noei" Date: Thu, 8 Jun 2023 18:27:34 +0330 Subject: [PATCH 3/4] Revert old response types. --- .../Responses/GenericResponseBase.cs | 11 ++++++++++ .../GenericResponseBaseWithPagination.cs | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs create mode 100644 Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs new file mode 100644 index 0000000..b77a26b --- /dev/null +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs @@ -0,0 +1,11 @@ +namespace BSN.Commons.Responses +{ + /// + /// Generic base response. + /// + /// The type of object or value handled by the class. + public class GenericResponseBase : Response where T : class + { + public T Data { get; set; } + } +} diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs new file mode 100644 index 0000000..165a117 --- /dev/null +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs @@ -0,0 +1,20 @@ +using System; + +namespace BSN.Commons.Responses +{ + /// + /// Generic response base for paginated data. + /// + /// + /// Paginated response provides metadata for navigation purpose. + /// + /// Data type. + [Obsolete("Due to incompatability with Grpc this response type is only used for backward compatibility.")] + public class GenericResponseBaseWithPagination : GenericResponseBase where T : class + { + /// + /// Pagination metada used by the client as the parameters for navigation through whole records. + /// + public PaginationMetadata Meta { get; set; } + } +} From 9843b2dd1fe044be20cebdb1ab3860dbc3a1c7c6 Mon Sep 17 00:00:00 2001 From: "R.Noei" Date: Thu, 8 Jun 2023 18:42:02 +0330 Subject: [PATCH 4/4] add constructor for generic response base. --- .../Responses/GenericResponseBase.cs | 7 ++++++- .../Responses/GenericResponseBaseWithPagination.cs | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs index b77a26b..aeb0f26 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBase.cs @@ -1,11 +1,16 @@ -namespace BSN.Commons.Responses +using System; + +namespace BSN.Commons.Responses { /// /// Generic base response. /// /// The type of object or value handled by the class. + [Obsolete("Due to incompatability with Grpc this response type is only used for backward compatibility.")] public class GenericResponseBase : Response where T : class { + public GenericResponseBase() { } + public T Data { get; set; } } } diff --git a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs index 165a117..a8fa17c 100644 --- a/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs +++ b/Source/BSN.Commons.PresentationInfrastructure/Responses/GenericResponseBaseWithPagination.cs @@ -12,6 +12,8 @@ namespace BSN.Commons.Responses [Obsolete("Due to incompatability with Grpc this response type is only used for backward compatibility.")] public class GenericResponseBaseWithPagination : GenericResponseBase where T : class { + public GenericResponseBaseWithPagination() { } + /// /// Pagination metada used by the client as the parameters for navigation through whole records. ///