Команда nopCommerce расширяется и приглашает на работу первоклассных .NET разработчиков, маркетологов и менеджеров по продажам.

ProductDetails is invoked when calling widget controller

2 месяца назад
Previously I had posted because of this issue. I am working on a digital book customization widget and need to send data from the widget's view to a controller with Ajax. The problem is that when the call is made from the view, ProductDetails page is refreshed.   This is the VS output:

Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request starting HTTP/2.0 POST https://localhost:44331/aliento-de-ogro application/x-www-form-urlencoded 308328
Excepción producida: 'System.ArgumentException' en System.Private.CoreLib.dll
Microsoft.AspNetCore.Routing.EndpointMiddleware: Information: Executing endpoint 'Nop.Web.Controllers.ProductController.ProductDetails (Nop.Web)'
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Information: Route matched with {action = "ProductDetails", controller = "Product", area = ""}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult ProductDetails(Int32, Int32) on controller Nop.Web.Controllers.ProductController (Nop.Web).
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor: Information: Executing ViewResult, running view ProductTemplate.Simple.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor: Information: Executed ViewResult - view ProductTemplate.Simple executed in 239.0003ms.
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Information: Executed action Nop.Web.Controllers.ProductController.ProductDetails (Nop.Web) in 414.8933ms
Microsoft.AspNetCore.Routing.EndpointMiddleware: Information: Executed endpoint 'Nop.Web.Controllers.ProductController.ProductDetails (Nop.Web)'
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished in 919.9449ms 200 text/html; charset=utf-8
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request starting HTTP/2.0 GET https://localhost:44331/Plugins/Widgets.Expressions/Content/Ajax-loader.gif  
Excepción producida: 'System.ArgumentException' en System.Private.CoreLib.dll
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished in 474.4998ms 404

Two things call my attention: first, why ProductDetailsController is invoked? Initially,  I was pretty sure it was a problem with my plugin's RouteProvider, but it had no sense to me because other controllers' Action methods in my plugin worked fine. Today I have found this issue on Github. Could this be what is happening with my plugin? How can I solve it?

On the other side, the System.ArgumentException (The AppId option must be provided). I searched on the internet and the only reference I found was this, but it is not clear if this can be the problem.

Here is my RouteProvider:
namespace Nop.Plugin.Widgets.Expressions
    public partial class RouteProvider : IRouteProvider
        public void RegisterRoutes(IEndpointRouteBuilder endPointRouteBuilder)
            endPointRouteBuilder.MapControllerRoute("Plugin.Widgets.Expressions.WidgetsExpressions.Configure", "Plugins/WidgetsExpressions/Configure",
                new { controller = "WidgetsExpressions", action = "Configure" });

            endPointRouteBuilder.MapControllerRoute("Plugin.Widgets.Expressions.BookExpressions.AddExpressiontoBook", "Plugins/BookExpressions/AddExpressiontoBook",
                new { controller = "BookExpressions", action = "AddExpressiontoBook" });

            endPointRouteBuilder.MapControllerRoute("Plugin.Widgets.Expressions.WidgetsExpressions.Photo", "Plugins/WidgetsExpressions/Photo",
                new { controller = "WidgetsExpressions", action = "Photo" });

        public int Priority => 0;

This is my action method:
namespace Nop.Plugin.Widgets.Expressions.Controllers
    public class WidgetsExpressionsController : BasePluginController
    public async Task<JsonResult> Photo([FromBody] PhotoExpression photoExpression) {
      //First, check if we have image data, let's proceed. If not, notify the user.
      if (photoExpression.Photo != null)

             //Data processing....

              return Json(new { PhotoStored, Message = "Foto procesada con éxito. Continúa con la siguiente expresión" });

            // We have to check return error and inform the user.

              return Json(new { PhotoStored, Message = "Tu foto no pudo ser procesada. Inténtalo de nuevo" });
          Response.StatusCode = (int)HttpStatusCode.InternalServerError;
          return Json(new { IsCreated = false, Message = "No se pudo procesar la foto." });

And this is the Ajax call:
$(function () {
        $("#AjaxForm").submit(function (e) {
                type: "post",  // Verbo HTTP
                url: "@Url.Action("Photo", "WidgetsExpressions")",
                dataType: "application/Json",
                headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken: "]').val() },
                data: JSON.stringify(
                        // Datos / Parámetros
                        ProductId: $("#productId").val(),
                        Role: role, //$("#role").val(),
                        ExpressionName: expressionName, //$("#expressionName").val(),
                        Photo: $("#photo").attr('src')
                    contentType: "application/Json; charset=utf-8"
                // Se ejecuta si todo fue bien.
                .done(function (result) {
                    if (result.PhotoStored == true) {
                        // Limpia el formulario

                        // Escondemos el Ajax Loader

                        // Habilitamos el botón de Submit
                        $("#SubmitBtn").prop("disabled", false);

                        //Incrementamos el contador
                        counter = ++counter;
                        //To do: si Counter == Expressions.lenght, indicar que se ha terminado y proceder con el siguinet paso del Checkout.
                        alert("Now counter is: " + counter + ". And expression is : " + exp[counter].ExpressionFileName);
                        //Cambiamos la expression
                        currentExpression.src = "./Plugins/Widgets.Expressions/Content/Expressions/sample-expressions/" + exp[counter].ExpressionFileName;
                        expressionName = exp[counter].ExpressionName;
                        role = exp[counter].Role;

                        // Mostramos un mensaje de éxito.
                // Se ejecuta si se produjo un error.
                .fail(function (xhr, status, error) {
                    // Mostramos un mensaje de error.

                    // Escondemos el Ajax Loader

                    // Habilitamos el botón de Submit
                    $("#SubmitBtn").prop("disabled", false);
                // Hacer algo siempre, haya sido exitosa o no.
                .always(function () {
            return false;

Sorry, I am not an expert and really need help to solve this.
Thanks in advance.
один месяц назад
I have finally found the answer. The problem was with the Ajax request. I had to add this:
        'beforeSend': function (xhr) {
            xhr.overrideMimeType('application/json; charset=utf-8');

And that did the job. Beginner's mistake.