TheEdge
TheEdge

Reputation: 9891

HTTP Post with FormData is not reached in WebAPI

I have a Web API controller defined as follows:

[HttpPost]
public class EmailActivitiesController : ApiController
{
    public HttpResponseMessage EmailAClientWithNewMailMessage(FormDataCollection aFormData)
    {
    }
}

which is invoked with the following jQuery:

    var aFormData =  {};
    $.support.cors = true;
    $.ajax({
        cache: false,
        url: '/api/EmailActivities/EmailAClientWithNewMailMessage',
        type: "POST",
        dataType: "json",
        data: aFormData,
        success: function (callback) {
            //Platform.LoadingPanelHide();
            alert("Successfully sent the email.");
        },
        error: function (data) {
            var errorObject = JSON.parse(data);

            //Platform.LoadingPanelHide();
            alert('There was a problem sending the email message to the individual selected.\n\n' + errorObject.responseText.message);
        }
    });

and that all works and the API method is reached. However as soon as I populate aFormData as below:

    var aFormData =  {};

    aFormData['aMessageInfo'] = "test";

    //And sending via form data
    $.support.cors = true;
    $.ajax({
        cache: false,
        url: '/api/EmailActivities/EmailAClientWithNewMailMessage',
        type: "POST",
        dataType: "json",
        data: aFormData,
        success: function (callback) {
            //Platform.LoadingPanelHide();
            alert("Successfully sent the email.");
        },
        error: function (data) {
            var errorObject = JSON.parse(data);

            //Platform.LoadingPanelHide();
            alert('There was a problem sending the email message to the individual selected.\n\n' + errorObject.responseText.message);
        }
    });

then the method is not reached and I receieve instead a HttpResonseCode.OK with the following JSON response:

{
"result": null,
"success": false,
"error": {
"code": 0,
"message": "An internal error occured during your request!",
"details": null,
"validationErrors": null
},
"unAuthorizedRequest": false
}

For the life of me I cannot figure out why this is occurring. Has anyone got any insights.

UPDATE

Here is the POST request.enter image description here

And the exception log:

    ERROR 2015-01-29 15:51:08,250 [17   ] Default                                  - Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.
System.Web.Http.HttpResponseException: Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.
   at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
   at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger)
   at System.Web.Http.ModelBinding.FormatterParameterBinding.<ExecuteBindingAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at System.Web.Http.Controllers.HttpActionBinding.<ExecuteBindingAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()

UPDATE #2

I have now gone with the suggested approach of using a DTO as follows:

var aMessageInfo = {
            subject: Base64.encode($("#txtSendEmailSubject").val()),
            body: Base64.encode($("#txtSendEmailBody").val())
        };

 $.support.cors = true;
        $.ajax({
            cache: false,
            url: '/api/EmailActivities/Dono2',
            type: "POST",
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(aMessageInfo),
            success: function (callback) {
                //Platform.LoadingPanelHide();
                alert("Successfully sent the email.");
            },
            error: function (data) {
                var errorObject = JSON.parse(data);

                //Platform.LoadingPanelHide();
                alert('There was a problem sending the email message to the individual selected.\n\n' + errorObject.responseText.message);
            }
        });

and then in the WebAPI it works if I only have:

   public class EmailActivitiesController : ApiController
    {
    [HttpPost]
        public HttpResponseMessage Dono2(MessageInfo aMessageInfo)
        {
        }
    }

but as soon as I also have EmailAClientWithNewMailMessage as a method:

   public class EmailActivitiesController : ApiController
    {
    [HttpPost]
        public HttpResponseMessage Dono2(MessageInfo aMessageInfo)
        {
        }


  [HttpPost]
        public HttpResponseMessage EmailAClientWithNewMailMessage(FormDataCollection aFormData)
        {
}

I get the error:

{  
   "message":"An error has occurred.",
   "exceptionMessage":"Multiple actions were found that match the request: \r\nDono2 on type MakersLane.WebApi.Controllers.EmailActivitiesController\r\nEmailAClientWithNewMailMessage on type MakersLane.WebApi.Controllers.EmailActivitiesController",
   "exceptionType":"System.InvalidOperationException",
   "stackTrace":"   at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)\r\n   at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)\r\n   at Abp.WebApi.Controllers.Dynamic.Selectors.AbpApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)\r\n   at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
}

Now while there are two actions on the controller Dono2 and EmailClientWithNewMessage I do not understand why they cannot be told apart as they clearly have different names and although both have a single parameter these are named differently and are of different types.

Can anyone explain why this is occurring?

Upvotes: 1

Views: 2044

Answers (5)

hikalkan
hikalkan

Reputation: 2282

Problem is that: ABP system configured to work with JSON data only. When you change code like that, it works:

$.ajax({
    cache: false,
    url: '/api/EmailActivities/EmailAClientWithNewMailMessage',
    type: "POST",
    contentType: 'application/json',
    dataType: "json",
    data: JSON.stringify({ aMessageInfo: 'test' }),
    success: function (callback) {
        //Platform.LoadingPanelHide();
        alert("Successfully sent the email.");
    },
    error: function (data) {
        var errorObject = JSON.parse(data);

        //Platform.LoadingPanelHide();
        alert('There was a problem sending the email message to the individual selected.\n\n' + errorObject.responseText.message);
    }
});

I added contentType. In that way, method is called but, form data does not come to the method. Maybe FormDataCollection does not like JSON posts.

Why you don't use just a simple DTO class instead of FormDataCollection? This is more natural for ABP projects.

In AbpWebApiModule, it clears all formatters and adds JSON and plain text.

        private static void InitializeFormatters()
        {
            GlobalConfiguration.Configuration.Formatters.Clear();
            var formatter = new JsonMediaTypeFormatter();
            formatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            GlobalConfiguration.Configuration.Formatters.Add(formatter);
            GlobalConfiguration.Configuration.Formatters.Add(new PlainTextFormatter());
        }

Maybe you can use GlobalConfiguration.Configuration.Formatters.Add to add other formatters.

How did I find the problem? ABP defines an event to handle exceptions (starting from v0.3.2):

enter image description here

This can help you while finding errors.

EventBus.Default.Register<AbpHandledExceptionData>(
    data =>
    {
        //use data.Exception to see exception details
    });

Upvotes: 0

Jeff Tindall
Jeff Tindall

Reputation: 301

I think Joel is correct. I had issues passing strings in as parameters and thus wrap all my inputs in DTO objects and it works fine.

Cheers Jeff

Upvotes: 1

Joel Gregory
Joel Gregory

Reputation: 469

I am not on a windows machine so can't test this write now but I will offer the alternative solution below. It is a problem with the mapping from the query string sent in the POST to the FormDataCollection and I think it can be solved by using a JSON object like this:

{ aFormData : { aMessageInfo : "test" } }

but I cannot confirm. I believe the following solution to be better, so use it unless you have to use FormDataCollection.

I would recommend using a view model instead of FormDataCollection as the mapping of values becomes tedious:

public class Message
{
    public string Client { get; set; }
    public string Content { get; set; }
}

Then change your controller to:

public HttpResponseMessage EmailAClientWithNewMailMessage(Message data)
{
    // use the deserialised object here
    return data.Client;
}

And your jquery would look like this (notice the addition of the content-type parameter and the JSON.stringify call):

var aFormData =  {};

aFormData['aMessageInfo'] = "test";

//And sending via form data
$.support.cors = true;
$.ajax({
    cache: false,
    url: '/api/EmailActivities/EmailAClientWithNewMailMessage',
    type: "POST",
    dataType: "json",
    contentType: "application/json; charset=utf-8",
    data: JSON.stringify(aFormData),
    success: function (callback) {
        //Platform.LoadingPanelHide();
        alert("Successfully sent the email.");
    },
    error: function (data) {
        var errorObject = JSON.parse(data);

        //Platform.LoadingPanelHide();
        alert('There was a problem sending the email message to the individual selected.\n\n' + errorObject.responseText.message);
    }
});

Upvotes: 2

Jeff Tindall
Jeff Tindall

Reputation: 301

Did you look at the Logs/logs.txt file? It will have the exception thrown in there.

Cheers Jeff

Upvotes: 1

https://github.com/aspnetboilerplate/aspnetboilerplate/blob/593c47050c345cfa4617e06bc7f4ac19f5b81a15/src/Abp.Web.Api/WebApi/Controllers/Dynamic/Selectors/DyanamicHttpActionDescriptor.cs

I think this code is overriding the result.

try
                    {
                        if (task.Result == null)
                        {
                            return new AjaxResponse();
                        }

                        if (task.Result is AjaxResponse)
                        {
                            return task.Result;
                        }

                        return new AjaxResponse(task.Result);
                    }

Upvotes: 1

Related Questions