From aced5fc598adc33a91793bb3b99852235ee679e4 Mon Sep 17 00:00:00 2001 From: Krzysztof Pajak Date: Fri, 16 Feb 2024 19:52:23 +0100 Subject: [PATCH] Remove Newtonsoft.Json (#468) In the project, we have transitioned from using Newtonsoft.Json to System.Text.Json for handling JSON serialization and deserialization. This change aligns with the latest .NET practices and takes advantage of the performance improvements offered by System.Text.Json. However, there is a specific exception to this transition: when dealing with HttpPatch requests. For these cases, we continue to use Microsoft.AspNetCore.Mvc.NewtonsoftJson. This decision is based on Microsoft's documentation, which recommends the use of Newtonsoft.Json for patching operations due to compatibility and functionality reasons. According to the Microsoft ASP.NET Core documentation (https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-8.0), System.Text.Json currently lacks certain features needed for full support of JSON Patch operations, making Newtonsoft.Json a necessary dependency for these specific scenarios. By following this approach, we ensure that our application remains efficient and up-to-date with the core serialization library, while also maintaining full functionality where System.Text.Json's capabilities are currently limited. --- .../ApiExplorer/ApiResponseTypeProvider.cs | 3 ++- .../MetadataApiDescriptionProvider.cs | 2 +- .../Controllers/OData/BrandController.cs | 7 +++-- .../Controllers/OData/CategoryController.cs | 7 +++-- .../Controllers/OData/CollectionController.cs | 7 +++-- .../OData/CustomerGroupController.cs | 7 +++-- .../OData/ProductAttributeController.cs | 7 +++-- .../Controllers/OData/ProductController.cs | 9 ++++--- .../OData/SpecificationAttributeController.cs | 7 +++-- src/API/Grand.Api/Grand.Api.csproj | 2 ++ .../Grand.Api/Infrastructure/ODataStartup.cs | 25 ++++++++++++++++- .../Handlers/ProductDeletedEventHandler.cs | 4 +-- .../PushNotificationsService.cs | 7 +++-- .../Caching/Redis/RedisMessageBus.cs | 8 +++--- .../Extensions/ProviderExtensions.cs | 9 +++++++ .../Grand.Infrastructure.csproj | 1 - .../WidgetsFacebookPixelViewComponent.cs | 4 +-- .../PushNotificationsServiceTests.cs | 4 +-- .../Controllers/BaseAdminController.cs | 17 ------------ .../Components/BaseViewComponent.cs | 4 +-- .../Extensions/SessionExtensions.cs | 6 ++--- .../Filters/CustomerActivityAttribute.cs | 10 +++---- .../ServiceCollectionExtensions.cs | 13 +++++---- .../Captcha/GoogleReCaptchaResponse.cs | 8 ++++-- .../Captcha/GoogleReCaptchaValidator.cs | 27 +++++++------------ .../Validators/CaptchaValidator.cs | 2 +- .../Controllers/BaseVendorController.cs | 17 ------------ 27 files changed, 119 insertions(+), 105 deletions(-) diff --git a/src/API/Grand.Api/ApiExplorer/ApiResponseTypeProvider.cs b/src/API/Grand.Api/ApiExplorer/ApiResponseTypeProvider.cs index f457f2164..cf348a055 100644 --- a/src/API/Grand.Api/ApiExplorer/ApiResponseTypeProvider.cs +++ b/src/API/Grand.Api/ApiExplorer/ApiResponseTypeProvider.cs @@ -73,7 +73,8 @@ private ICollection GetApiResponseTypes( Type defaultErrorType) { var contentTypes = new MediaTypeCollection(); - var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType(); + var responseTypeMetadataProviders = _mvcOptions.OutputFormatters + .OfType(); var responseTypes = ReadResponseMetadata( responseMetadataAttributes, diff --git a/src/API/Grand.Api/ApiExplorer/MetadataApiDescriptionProvider.cs b/src/API/Grand.Api/ApiExplorer/MetadataApiDescriptionProvider.cs index ffadf7e8b..e52c96cc7 100644 --- a/src/API/Grand.Api/ApiExplorer/MetadataApiDescriptionProvider.cs +++ b/src/API/Grand.Api/ApiExplorer/MetadataApiDescriptionProvider.cs @@ -404,7 +404,7 @@ private IReadOnlyList GetSupportedFormats(MediaTypeCollection var results = new List(); foreach (var contentType in contentTypes) { - foreach (var formatter in _mvcOptions.InputFormatters.OfType()) + foreach (var formatter in _mvcOptions.InputFormatters.OfType()) { if (formatter is IApiRequestFormatMetadataProvider requestFormatMetadataProvider) { diff --git a/src/API/Grand.Api/Controllers/OData/BrandController.cs b/src/API/Grand.Api/Controllers/OData/BrandController.cs index 494d0731d..d033b81c1 100644 --- a/src/API/Grand.Api/Controllers/OData/BrandController.cs +++ b/src/API/Grand.Api/Controllers/OData/BrandController.cs @@ -81,13 +81,16 @@ public async Task Put([FromBody] BrandDto model) } [SwaggerOperation(summary: "Partially update entity in Brand", OperationId = "PartiallyUpdateBrand")] - [HttpPatch] + [HttpPatch("{key}")] [ProducesResponseType((int)HttpStatusCode.Forbidden)] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task Patch([FromODataUri] string key, [FromBody] JsonPatchDocument model) + public async Task Patch([FromRoute] string key, [FromBody] JsonPatchDocument model) { + if (string.IsNullOrEmpty(key)) + return BadRequest("Key is null or empty"); + if (!await _permissionService.Authorize(PermissionSystemName.Brands)) return Forbid(); var brand = await _mediator.Send(new GetGenericQuery(key)); diff --git a/src/API/Grand.Api/Controllers/OData/CategoryController.cs b/src/API/Grand.Api/Controllers/OData/CategoryController.cs index 469131e25..3cc29534b 100644 --- a/src/API/Grand.Api/Controllers/OData/CategoryController.cs +++ b/src/API/Grand.Api/Controllers/OData/CategoryController.cs @@ -82,13 +82,16 @@ public async Task Put([FromBody] CategoryDto model) return Ok(model); } [SwaggerOperation(summary: "Update entity in Category (delta)", OperationId = "UpdateCategoryPatch")] - [HttpPatch] + [HttpPatch("{key}")] [ProducesResponseType((int)HttpStatusCode.Forbidden)] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task Patch([FromODataUri] string key, [FromBody] JsonPatchDocument model) + public async Task Patch([FromRoute] string key, [FromBody] JsonPatchDocument model) { + if (string.IsNullOrEmpty(key)) + return BadRequest("Key is null or empty"); + if (!await _permissionService.Authorize(PermissionSystemName.Categories)) return Forbid(); var category = await _mediator.Send(new GetGenericQuery(key)); diff --git a/src/API/Grand.Api/Controllers/OData/CollectionController.cs b/src/API/Grand.Api/Controllers/OData/CollectionController.cs index b107ede3f..ecfba2a8a 100644 --- a/src/API/Grand.Api/Controllers/OData/CollectionController.cs +++ b/src/API/Grand.Api/Controllers/OData/CollectionController.cs @@ -81,13 +81,16 @@ public async Task Put([FromBody] CollectionDto model) } [SwaggerOperation(summary: "Partially update entity in Collection", OperationId = "PartiallyUpdateCollection")] - [HttpPatch] + [HttpPatch("{key}")] [ProducesResponseType((int)HttpStatusCode.Forbidden)] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task Patch([FromODataUri] string key, [FromBody] JsonPatchDocument model) + public async Task Patch([FromRoute] string key, [FromBody] JsonPatchDocument model) { + if (string.IsNullOrEmpty(key)) + return BadRequest("Key is null or empty"); + if (!await _permissionService.Authorize(PermissionSystemName.Collections)) return Forbid(); var collection = await _mediator.Send(new GetGenericQuery(key)); diff --git a/src/API/Grand.Api/Controllers/OData/CustomerGroupController.cs b/src/API/Grand.Api/Controllers/OData/CustomerGroupController.cs index b9094a388..dd5f46f50 100644 --- a/src/API/Grand.Api/Controllers/OData/CustomerGroupController.cs +++ b/src/API/Grand.Api/Controllers/OData/CustomerGroupController.cs @@ -86,13 +86,16 @@ public async Task Put([FromBody] CustomerGroupDto model) } [SwaggerOperation(summary: "Partially update entity in CustomerGroup", OperationId = "PartiallyUpdateCustomerGroup")] - [HttpPatch] + [HttpPatch("{key}")] [ProducesResponseType((int)HttpStatusCode.Forbidden)] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task Patch([FromODataUri] string key, [FromBody] JsonPatchDocument model) + public async Task Patch([FromRoute] string key, [FromBody] JsonPatchDocument model) { + if (string.IsNullOrEmpty(key)) + return BadRequest("Key is null or empty"); + if (!await _permissionService.Authorize(PermissionSystemName.Customers)) return Forbid(); var customerGroup = await _mediator.Send(new GetGenericQuery(key)); diff --git a/src/API/Grand.Api/Controllers/OData/ProductAttributeController.cs b/src/API/Grand.Api/Controllers/OData/ProductAttributeController.cs index 589c07343..e079e6a48 100644 --- a/src/API/Grand.Api/Controllers/OData/ProductAttributeController.cs +++ b/src/API/Grand.Api/Controllers/OData/ProductAttributeController.cs @@ -80,13 +80,16 @@ public async Task Put([FromBody] ProductAttributeDto model) } [SwaggerOperation(summary: "Partially update entity in ProductAttribute", OperationId = "PartiallyUpdateProductAttribute")] - [HttpPatch] + [HttpPatch("{key}")] [ProducesResponseType((int)HttpStatusCode.Forbidden)] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task Patch([FromODataUri] string key, [FromBody] JsonPatchDocument model) + public async Task Patch([FromRoute] string key, [FromBody] JsonPatchDocument model) { + if (string.IsNullOrEmpty(key)) + return BadRequest("Key is null or empty"); + if (!await _permissionService.Authorize(PermissionSystemName.ProductAttributes)) return Forbid(); var productAttribute = await _mediator.Send(new GetGenericQuery(key)); diff --git a/src/API/Grand.Api/Controllers/OData/ProductController.cs b/src/API/Grand.Api/Controllers/OData/ProductController.cs index 790228848..90b790d78 100644 --- a/src/API/Grand.Api/Controllers/OData/ProductController.cs +++ b/src/API/Grand.Api/Controllers/OData/ProductController.cs @@ -80,20 +80,23 @@ public async Task Put([FromBody] ProductDto model) } [SwaggerOperation(summary: "Partially update entity in Product", OperationId = "PartiallyUpdateProduct")] - [HttpPatch] + [HttpPatch("{key}")] [ProducesResponseType((int)HttpStatusCode.Forbidden)] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task Patch([FromODataUri] string key, [FromBody] JsonPatchDocument model) + public async Task Patch([FromRoute] string key, [FromBody] JsonPatchDocument model) { + if (string.IsNullOrEmpty(key)) + return BadRequest("Key is null or empty"); + if (!await _permissionService.Authorize(PermissionSystemName.Products)) return Forbid(); var product = await _mediator.Send(new GetGenericQuery(key)); if (!product.Any()) return NotFound(); var pr = product.FirstOrDefault(); - model.ApplyTo(pr!, ModelState); + model.ApplyTo(pr); await _mediator.Send(new UpdateProductCommand { Model = pr }); return Ok(); } diff --git a/src/API/Grand.Api/Controllers/OData/SpecificationAttributeController.cs b/src/API/Grand.Api/Controllers/OData/SpecificationAttributeController.cs index 3ea848122..12a24be29 100644 --- a/src/API/Grand.Api/Controllers/OData/SpecificationAttributeController.cs +++ b/src/API/Grand.Api/Controllers/OData/SpecificationAttributeController.cs @@ -77,13 +77,16 @@ public async Task Put([FromBody] SpecificationAttributeDto model) } [SwaggerOperation(summary: "Partially update entity in SpecificationAttribute", OperationId = "PartiallyUpdateSpecificationAttribute")] - [HttpPatch] + [HttpPatch("{key}")] [ProducesResponseType((int)HttpStatusCode.Forbidden)] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] - public async Task Patch([FromODataUri] string key, [FromBody] JsonPatchDocument model) + public async Task Patch([FromRoute] string key, [FromBody] JsonPatchDocument model) { + if (string.IsNullOrEmpty(key)) + return BadRequest("Key is null or empty"); + if (!await _permissionService.Authorize(PermissionSystemName.SpecificationAttributes)) return Forbid(); var specification = await _mediator.Send(new GetGenericQuery(key)); diff --git a/src/API/Grand.Api/Grand.Api.csproj b/src/API/Grand.Api/Grand.Api.csproj index dc80b845b..bcaf0acce 100644 --- a/src/API/Grand.Api/Grand.Api.csproj +++ b/src/API/Grand.Api/Grand.Api.csproj @@ -6,6 +6,8 @@ + + diff --git a/src/API/Grand.Api/Infrastructure/ODataStartup.cs b/src/API/Grand.Api/Infrastructure/ODataStartup.cs index 109a1fa39..fbd87bb5f 100644 --- a/src/API/Grand.Api/Infrastructure/ODataStartup.cs +++ b/src/API/Grand.Api/Infrastructure/ODataStartup.cs @@ -12,9 +12,12 @@ using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.OData; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Microsoft.OData.Edm; using Microsoft.OData.ModelBuilder; @@ -47,7 +50,10 @@ public void ConfigureServices(IServiceCollection services, builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); }); //Add OData - services.AddControllers().AddOData(opt => + services.AddControllers(options => + { + options.InputFormatters.Insert(0, GetJsonPatchInputFormatter()); + }).AddOData(opt => { opt.EnableQueryFeatures(Configurations.MaxLimit); opt.AddRouteComponents(Configurations.ODataRoutePrefix, GetEdmModel(apiConfig)); @@ -58,6 +64,21 @@ public void ConfigureServices(IServiceCollection services, } } + private static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter() + { + var builder = new ServiceCollection() + .AddLogging() + .AddMvc() + .AddNewtonsoftJson() + .Services.BuildServiceProvider(); + + return builder + .GetRequiredService>() + .Value + .InputFormatters + .OfType() + .First(); + } public int Priority => 505; public bool BeforeConfigure => false; @@ -143,5 +164,7 @@ private void RegisterRequestHandler(IServiceCollection services) IQueryable>), typeof(GetGenericQueryHandler)); } + } + } diff --git a/src/Business/Grand.Business.Catalog/Events/Handlers/ProductDeletedEventHandler.cs b/src/Business/Grand.Business.Catalog/Events/Handlers/ProductDeletedEventHandler.cs index b45942626..26e27c530 100644 --- a/src/Business/Grand.Business.Catalog/Events/Handlers/ProductDeletedEventHandler.cs +++ b/src/Business/Grand.Business.Catalog/Events/Handlers/ProductDeletedEventHandler.cs @@ -5,7 +5,7 @@ using Grand.Data; using Grand.Domain.Seo; using MediatR; -using Newtonsoft.Json; +using System.Text.Json; namespace Grand.Business.Catalog.Events.Handlers { @@ -74,7 +74,7 @@ public async Task Handle(EntityDeleted notification, CancellationToken } //insert to deleted products - var productDeleted = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(notification.Entity)); + var productDeleted = JsonSerializer.Deserialize(JsonSerializer.Serialize(notification.Entity)); if (productDeleted != null) { productDeleted.DeletedOnUtc = DateTime.UtcNow; diff --git a/src/Business/Grand.Business.Marketing/Services/PushNotifications/PushNotificationsService.cs b/src/Business/Grand.Business.Marketing/Services/PushNotifications/PushNotificationsService.cs index c55e5da0f..5dda9f085 100644 --- a/src/Business/Grand.Business.Marketing/Services/PushNotifications/PushNotificationsService.cs +++ b/src/Business/Grand.Business.Marketing/Services/PushNotifications/PushNotificationsService.cs @@ -6,8 +6,8 @@ using Grand.Infrastructure.Extensions; using MediatR; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using System.Net.Http; +using System.Text.Json; namespace Grand.Business.Marketing.Services.PushNotifications { @@ -178,8 +178,7 @@ public virtual async Task> GetPushReceivers(int pag } }; - var json = JsonConvert.SerializeObject(data); - + var json = JsonSerializer.Serialize(data); try { using var httpRequest = new HttpRequestMessage(HttpMethod.Post, FcmUrl); @@ -196,7 +195,7 @@ public virtual async Task> GetPushReceivers(int pag return (false, responseString); } - var responseMessage = JsonConvert.DeserializeObject(responseString); + var responseMessage = JsonSerializer.Deserialize(responseString); if (responseMessage == null) return (false, "PushNotifications.ResponseMessage.Empty"); await InsertPushMessage(new PushMessage { diff --git a/src/Core/Grand.Infrastructure/Caching/Redis/RedisMessageBus.cs b/src/Core/Grand.Infrastructure/Caching/Redis/RedisMessageBus.cs index 3c26636df..c7047796e 100644 --- a/src/Core/Grand.Infrastructure/Caching/Redis/RedisMessageBus.cs +++ b/src/Core/Grand.Infrastructure/Caching/Redis/RedisMessageBus.cs @@ -1,9 +1,9 @@ -using Newtonsoft.Json; -using StackExchange.Redis; +using StackExchange.Redis; using Microsoft.Extensions.DependencyInjection; using Grand.Infrastructure.Caching.Message; using Grand.Infrastructure.Configuration; using System.Diagnostics; +using System.Text.Json; namespace Grand.Infrastructure.Caching.Redis { @@ -32,7 +32,7 @@ public async Task PublishAsync(TMessage msg) where TMessage : IMessage Key = msg.Key, MessageType = msg.MessageType }; - var message = JsonConvert.SerializeObject(client); + var message = JsonSerializer.Serialize(client); await _subscriber.PublishAsync(RedisChannel.Literal(_redisConfig.RedisPubSubChannel), message); } catch(Exception ex) @@ -47,7 +47,7 @@ public Task SubscribeAsync() { try { - var message = JsonConvert.DeserializeObject(redisValue); + var message = JsonSerializer.Deserialize(redisValue); if (message != null && message.ClientId != ClientId) OnSubscriptionChanged(message); } diff --git a/src/Core/Grand.Infrastructure/Extensions/ProviderExtensions.cs b/src/Core/Grand.Infrastructure/Extensions/ProviderExtensions.cs index a99ba09f5..721e02b40 100644 --- a/src/Core/Grand.Infrastructure/Extensions/ProviderExtensions.cs +++ b/src/Core/Grand.Infrastructure/Extensions/ProviderExtensions.cs @@ -2,6 +2,7 @@ using Grand.Domain.Stores; using Grand.Infrastructure.Plugins; using Grand.SharedKernel.Extensions; +using System.Text.Json; namespace Grand.Infrastructure.Extensions { @@ -42,5 +43,13 @@ public static bool IsAuthenticateGroup(this IProvider method, Customer customer) return method.LimitedToGroups.ContainsAny(customer.Groups.Select(x => x)); } + + public static class JsonSerializerOptionsProvider + { + public static JsonSerializerOptions Options { get; } = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + } } } diff --git a/src/Core/Grand.Infrastructure/Grand.Infrastructure.csproj b/src/Core/Grand.Infrastructure/Grand.Infrastructure.csproj index 06b5a768e..f4eb65441 100644 --- a/src/Core/Grand.Infrastructure/Grand.Infrastructure.csproj +++ b/src/Core/Grand.Infrastructure/Grand.Infrastructure.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Plugins/Widgets.FacebookPixel/Components/WidgetsFacebookPixelViewComponent.cs b/src/Plugins/Widgets.FacebookPixel/Components/WidgetsFacebookPixelViewComponent.cs index a7bebddc0..59c7e6adc 100644 --- a/src/Plugins/Widgets.FacebookPixel/Components/WidgetsFacebookPixelViewComponent.cs +++ b/src/Plugins/Widgets.FacebookPixel/Components/WidgetsFacebookPixelViewComponent.cs @@ -2,8 +2,8 @@ using Grand.Business.Core.Interfaces.Cms; using Grand.Infrastructure; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using System.Globalization; +using System.Text.Json; using Widgets.FacebookPixel.Models; namespace Widgets.FacebookPixel.Components @@ -46,7 +46,7 @@ public async Task InvokeAsync(string widgetZone, object ad //add to cart if (widgetZone == FacebookPixelDefaults.AddToCart) { - var model = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(additionalData)); + var model = JsonSerializer.Deserialize(JsonSerializer.Serialize(additionalData)); if (model != null) { return View("Default", GetAddToCartScript(model)); diff --git a/src/Tests/Grand.Business.Marketing.Tests/Services/PushNotifications/PushNotificationsServiceTests.cs b/src/Tests/Grand.Business.Marketing.Tests/Services/PushNotifications/PushNotificationsServiceTests.cs index 87407ce0b..ead2dba74 100644 --- a/src/Tests/Grand.Business.Marketing.Tests/Services/PushNotifications/PushNotificationsServiceTests.cs +++ b/src/Tests/Grand.Business.Marketing.Tests/Services/PushNotifications/PushNotificationsServiceTests.cs @@ -8,9 +8,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; -using Newtonsoft.Json; using System.Net; using System.Net.Http; +using System.Text.Json; namespace Grand.Business.Marketing.Tests.Services.PushNotifications { @@ -34,7 +34,7 @@ public void Init() var mockMessageHandler = new Mock(); - var output = JsonConvert.SerializeObject(new JsonResponse { success = 1, failure = 0, canonical_ids = 1 }); + var output = JsonSerializer.Serialize(new JsonResponse { success = 1, failure = 0, canonical_ids = 1 }); mockMessageHandler.Protected() .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) diff --git a/src/Web/Grand.Web.Admin/Controllers/BaseAdminController.cs b/src/Web/Grand.Web.Admin/Controllers/BaseAdminController.cs index 99a6a13bc..1cce0ab12 100644 --- a/src/Web/Grand.Web.Admin/Controllers/BaseAdminController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/BaseAdminController.cs @@ -6,7 +6,6 @@ using Grand.Infrastructure; using Grand.Web.Admin.Extensions; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using Microsoft.Extensions.DependencyInjection; using Grand.Business.Core.Interfaces.Common.Directory; @@ -78,21 +77,5 @@ protected async Task GetActiveStore() var store = await storeService.GetStoreById(storeId); return store != null ? store.Id : stores.FirstOrDefault()?.Id; } - - /// - /// Creates a object that serializes the specified object to JavaScript Object Notation (JSON) format using the content type, content encoding, and the JSON request behavior. - /// - /// - /// - /// The result object that serializes the specified object to JSON format. - /// - /// The JavaScript object graph to serialize. - public override JsonResult Json(object data) - { - var serializerSettings = new JsonSerializerSettings { - DateFormatHandling = DateFormatHandling.IsoDateFormat - }; - return base.Json(data, serializerSettings); - } } } \ No newline at end of file diff --git a/src/Web/Grand.Web.Common/Components/BaseViewComponent.cs b/src/Web/Grand.Web.Common/Components/BaseViewComponent.cs index 40fe8ef12..1a036485e 100644 --- a/src/Web/Grand.Web.Common/Components/BaseViewComponent.cs +++ b/src/Web/Grand.Web.Common/Components/BaseViewComponent.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; +using System.Text.Json; namespace Grand.Web.Common.Components { @@ -14,7 +14,7 @@ public abstract class BaseViewComponent : ViewComponent { if(Request?.ContentType == "application/json") { - return new JsonContentViewComponentResult(JsonConvert.SerializeObject(model)); + return new JsonContentViewComponentResult(JsonSerializer.Serialize(model)); } return base.View(model); } diff --git a/src/Web/Grand.Web.Common/Extensions/SessionExtensions.cs b/src/Web/Grand.Web.Common/Extensions/SessionExtensions.cs index 17a8c4dad..cc3f13635 100644 --- a/src/Web/Grand.Web.Common/Extensions/SessionExtensions.cs +++ b/src/Web/Grand.Web.Common/Extensions/SessionExtensions.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; +using System.Text.Json; namespace Grand.Web.Common.Extensions { @@ -17,7 +17,7 @@ public static class SessionExtensions /// Value public static void Set(this ISession session, string key, T value) { - session.SetString(key, JsonConvert.SerializeObject(value)); + session.SetString(key, JsonSerializer.Serialize(value)); } /// @@ -30,7 +30,7 @@ public static void Set(this ISession session, string key, T value) public static T Get(this ISession session, string key) { var value = session.GetString(key); - return value == null ? default : JsonConvert.DeserializeObject(value); + return value == null ? default : JsonSerializer.Deserialize(value); } } } diff --git a/src/Web/Grand.Web.Common/Filters/CustomerActivityAttribute.cs b/src/Web/Grand.Web.Common/Filters/CustomerActivityAttribute.cs index 766a615e0..d3ed715b6 100644 --- a/src/Web/Grand.Web.Common/Filters/CustomerActivityAttribute.cs +++ b/src/Web/Grand.Web.Common/Filters/CustomerActivityAttribute.cs @@ -74,6 +74,10 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE if (!HttpMethods.IsGet(context.HttpContext.Request.Method)) return; + //whether is need to store last visited page URL + if (!_customerSettings.StoreLastVisitedPage) + return; + //update last activity date if (_workContext.CurrentCustomer.LastActivityDateUtc.AddMinutes(3.0) < DateTime.UtcNow) { @@ -82,17 +86,13 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE //get current IP address var currentIpAddress = context.HttpContext?.Connection?.RemoteIpAddress?.ToString(); + //update customer's IP address if (!string.IsNullOrEmpty(currentIpAddress) && !currentIpAddress.Equals(_workContext.CurrentCustomer.LastIpAddress, StringComparison.OrdinalIgnoreCase)) { _workContext.CurrentCustomer.LastIpAddress = currentIpAddress; await _customerService.UpdateCustomerField(_workContext.CurrentCustomer, x => x.LastIpAddress, currentIpAddress); } - - //whether is need to store last visited page URL - if (!_customerSettings.StoreLastVisitedPage) - return; - //get current page var pageUrl = context.HttpContext?.Request?.GetDisplayUrl(); if (string.IsNullOrEmpty(pageUrl)) diff --git a/src/Web/Grand.Web.Common/Infrastructure/ServiceCollectionExtensions.cs b/src/Web/Grand.Web.Common/Infrastructure/ServiceCollectionExtensions.cs index f5fcb8aa4..f9abfe19b 100644 --- a/src/Web/Grand.Web.Common/Infrastructure/ServiceCollectionExtensions.cs +++ b/src/Web/Grand.Web.Common/Infrastructure/ServiceCollectionExtensions.cs @@ -20,7 +20,6 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.WebEncoders; -using Newtonsoft.Json.Serialization; using StackExchange.Redis; using System.Text.Encodings.Web; using System.Text.Unicode; @@ -207,7 +206,11 @@ public static void AddGrandAuthentication(this IServiceCollection services, ICon public static IMvcBuilder AddGrandMvc(this IServiceCollection services, IConfiguration configuration) { //add basic MVC feature - var mvcBuilder = services.AddControllersWithViews(); + var mvcBuilder = services.AddControllersWithViews().AddJsonOptions(options => + { + options.JsonSerializerOptions.PropertyNamingPolicy = null; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + }); //add view localization mvcBuilder.AddViewLocalization(); @@ -248,11 +251,7 @@ public static IMvcBuilder AddGrandMvc(this IServiceCollection services, IConfigu var typeSearcher = new TypeSearcher(); var assemblies = typeSearcher.GetAssemblies(); services.AddValidatorsFromAssemblies(assemblies); - - //MVC now serializes JSON with camel case names by default, use this code to avoid it - mvcBuilder.AddNewtonsoftJson(options => - options.SerializerSettings.ContractResolver = new DefaultContractResolver()); - + //register controllers as services, it'll allow to override them mvcBuilder.AddControllersAsServices(); diff --git a/src/Web/Grand.Web.Common/Security/Captcha/GoogleReCaptchaResponse.cs b/src/Web/Grand.Web.Common/Security/Captcha/GoogleReCaptchaResponse.cs index 1a6f167df..168470326 100644 --- a/src/Web/Grand.Web.Common/Security/Captcha/GoogleReCaptchaResponse.cs +++ b/src/Web/Grand.Web.Common/Security/Captcha/GoogleReCaptchaResponse.cs @@ -1,8 +1,12 @@ -namespace Grand.Web.Common.Security.Captcha +using System.Text.Json.Serialization; + +namespace Grand.Web.Common.Security.Captcha { public class GoogleReCaptchaResponse { - public bool IsValid { get; set; } + public bool Success { get; set; } + [JsonPropertyName("error-codes")] public List ErrorCodes { get; set; } = new(); + public decimal Score { get; set; } } } \ No newline at end of file diff --git a/src/Web/Grand.Web.Common/Security/Captcha/GoogleReCaptchaValidator.cs b/src/Web/Grand.Web.Common/Security/Captcha/GoogleReCaptchaValidator.cs index 8fb907150..dac5fb0a7 100644 --- a/src/Web/Grand.Web.Common/Security/Captcha/GoogleReCaptchaValidator.cs +++ b/src/Web/Grand.Web.Common/Security/Captcha/GoogleReCaptchaValidator.cs @@ -1,6 +1,7 @@ -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json.Linq; +using Grand.Infrastructure.Extensions; +using Microsoft.AspNetCore.Http; using System.Net.Http; +using System.Text.Json; namespace Grand.Web.Common.Security.Captcha { @@ -38,7 +39,7 @@ public async Task Validate(string response) } catch (Exception exc) { - result = new GoogleReCaptchaResponse { IsValid = false }; + result = new GoogleReCaptchaResponse { Success = false }; result.ErrorCodes.Add("Unknown error" + exc.Message); } @@ -47,21 +48,11 @@ public async Task Validate(string response) private GoogleReCaptchaResponse ParseResponseResult(string responseString) { - var result = new GoogleReCaptchaResponse(); - - var resultObject = JObject.Parse(responseString); - var success = resultObject.Value("success"); - if(_captchaSettings.ReCaptchaVersion == GoogleReCaptchaVersion.V3) - { - var score = resultObject.Value("score"); - if (_captchaSettings.ReCaptchaScore > 0) - success = success && score >= _captchaSettings.ReCaptchaScore; - } - result.IsValid = success; - - if (resultObject.Value("error-codes") != null && - resultObject.Value("error-codes")!.Values().Any()) - result.ErrorCodes = resultObject.Value("error-codes")?.Values().ToList(); + var result = JsonSerializer.Deserialize(responseString, ProviderExtensions.JsonSerializerOptionsProvider.Options); + if (_captchaSettings.ReCaptchaVersion != GoogleReCaptchaVersion.V3) return result; + + if (_captchaSettings.ReCaptchaScore > 0) + result.Success = result.Success && result.Score >= _captchaSettings.ReCaptchaScore; return result; } diff --git a/src/Web/Grand.Web.Common/Validators/CaptchaValidator.cs b/src/Web/Grand.Web.Common/Validators/CaptchaValidator.cs index f3ed844e2..505c667c1 100644 --- a/src/Web/Grand.Web.Common/Validators/CaptchaValidator.cs +++ b/src/Web/Grand.Web.Common/Validators/CaptchaValidator.cs @@ -75,7 +75,7 @@ public CaptchaValidator(IEnumerable> vali !StringValues.IsNullOrEmpty(captchaResponseValue) ? captchaResponseValue : gCaptchaResponseValue); - isValid = recaptchaResponse.IsValid; + isValid = recaptchaResponse.Success; return isValid ? (true, string.Empty) : (false, string.Join(',', recaptchaResponse.ErrorCodes)); } diff --git a/src/Web/Grand.Web.Vendor/Controllers/BaseVendorController.cs b/src/Web/Grand.Web.Vendor/Controllers/BaseVendorController.cs index d894c6c17..00b0c7340 100644 --- a/src/Web/Grand.Web.Vendor/Controllers/BaseVendorController.cs +++ b/src/Web/Grand.Web.Vendor/Controllers/BaseVendorController.cs @@ -1,7 +1,6 @@ using Grand.Web.Common.Controllers; using Grand.Web.Common.Filters; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using Grand.Web.Vendor.Extensions; namespace Grand.Web.Vendor.Controllers @@ -47,21 +46,5 @@ protected async Task SaveSelectedTabIndex(int? index = null, bool persistForTheN } } } - - /// - /// Creates a object that serializes the specified object to JavaScript Object Notation (JSON) format using the content type, content encoding, and the JSON request behavior. - /// - /// - /// - /// The result object that serializes the specified object to JSON format. - /// - /// The JavaScript object graph to serialize. - public override JsonResult Json(object data) - { - var serializerSettings = new JsonSerializerSettings { - DateFormatHandling = DateFormatHandling.IsoDateFormat - }; - return base.Json(data, serializerSettings); - } } } \ No newline at end of file