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