Base solution for your next web application
Open Closed

Change content type to application/x-www-form-urlencoded #1516


User avatar
0
carelearning created

We are using DevExtreme's dxDatagrid which currently posts back to API controller methods with content-type of "application/x-www-form-urlencoded." If we try to invoke this grid's actions, then we get a 500 server error.

We get this error: nHandling.AbpApiExceptionFilterAttribute - 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, IEnumerable1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken) at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable1 formatters, IFormatterLogger formatterLogger) at System.Web.Http.ModelBinding.FormatterParameterBinding.<ExecuteBindingAsyncCore>d__0.MoveNext()

When we invoke the action using Fiddler with JSON then it works perfectly. Is there a way to allow

abp.services.app.[controller].[action]()

to submit as "application/x-www-form-urlencoded"?

We have also asked DevExpress for any way to make their control encode using JSON here (this shows code snippets if needed).


7 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Can you share your web api controller code ? I will try to generate same situation in a test project.

    By the way, have you tried [FromBody] attribute ?

    [HttpDelete]
    public void Delete([FormBody]DeleteItemDto item)
    {
        if (!String.IsNullOrWhiteSpace(item.Key))
            _repository.Delete(item.Key);
    }
    
  • User Avatar
    0
    carelearning created

    Here is the webapi controller:

    namespace MyCompanyName.AbpZeroTemplate.Web.Controllers.api
    {
        using Abp.WebApi.Controllers;
        using Departments;
        using Departments.Dto;
        using Domain;
        using Models.Dto;
        using System;
        using System.Collections.Generic;
        using System.Web.Http;
    
        public class DepartmentController : AbpApiController
        {
    
            public DepartmentController()
            {
    
            }
    
            [HttpGet]
            public IHttpActionResult Get()
            {
               var departments;
            //retrieve data and pass it on--this works
                return Json(departments);           
            }
    
    
    //This fails with a 500 error:
            [HttpDelete]
            public void Delete(DeleteItem item)
            {
                if (!String.IsNullOrWhiteSpace(item.key))
                    _departmentAppService.Delete(item.key);
            }
    
        }
    }
    

    We tried the [FromBody] attribute and received the same result.

    Thanks

  • User Avatar
    0
    hikalkan created
    Support Team

    Why not just use id as the single primitive argument:

    [HttpDelete]
            public void Delete(string id)
            {
                if (!String.IsNullOrWhiteSpace(id))
                    _departmentAppService.Delete(id);
            }
    

    Sure, the 'id' may be different based on your request.

  • User Avatar
    0
    hikalkan created
    Support Team

    Hi,

    We investigated the problem in deep.

    First of all, this is not related to AspNet Zero or ABP framework. This is a basic ASP.NET Web API Routing problem. I'll explain it:

    1. Default ASP.NET Web API route is defined (in RouteConfig.cs) as shown below:
    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    )
    
    1. Your Controller is like that:
    public class DepartmentController : ApiController
    {
        [HttpGet]
        public IHttpActionResult Get()
        {
            ...
        }
    
        [HttpDelete]
        public void Delete([FormBody]DeleteItemDto item)
        {
            ...
        }
    }
    
    1. Devexpress sends a request like that:
    http://localhost:6240/api/Department?action=Delete
    
    Method: DELETE
    Body: key=123
    

    In that case, it's impossible to match this request to the Delete action defined above. You can create an empty web api project (not use ABP or AspNet Zero) and see it. Why?

    Because http://localhost:6240/api/Department?action=Delete request matches to the Get method! action=Delete at the end is not considered at all. So, URL is actuall like that: http://localhost:6240/api/Department and this is same as the Get request. So, this request has a 'routing' problem rather than 'binding'.

    As I understand, DevExpress's request is designed for MVC, not Web API. Because it perfectly works for MVC routing & binding system. I don't want to go so long. You can search for alternative approaches if you want to use Web API instead of MVC. One thing I can offer is that:

    Remove the parameter of Delete method.

    [HttpDelete]
        public void Delete()
        {
            ...
        }
    

    And add a breakpoint. You will see that Delete method is called at that time. Then you can try to get 'key' value from request body. You can search on the web for how, since this is not related to our framework and out of scope of our support. I just tried to explain the problem's reason. We could share the exact code if we would have, but we don't have and we should also search for it.

    Thanks, Have a nice day.

  • User Avatar
    0
    carelearning created

    Thank you for your reply. We could not recreate the problem you described above in an empty MVC5/WebAPI project with only the DevExteme widgets added. To be clear, we are not directly using

    abp.services.app.department.delete(1);
    

    to invoke our DepartmentAppService. Instead we created a wrapper DepartmentApiController that proxies the requests to the DepartmentAppService because it appears that DevExpress' dxDataGrid.DataSource requires it.

    Upon further investigation we discovered an issue using mediaformatters. We found a possible solution by adding FormUrlEncodedMediaTypeFormatter and JQueryMvcFormUrlEncodedFormatter in the PostInitialize Method of the DxAbpWebApiModule class.

    To illustrate this case, we have a simple project using: Asp.Net Boilerplate project with DevExpress dxDataGrid and dxButtons. You can find it here: git clone [http://git.carelearning.com/dxabp.git])

    You can see the issue if you comment out this method:

    public override void PostInitialize()
            {
                base.PostInitialize();
    
                Configuration.Modules.AbpWebApi().HttpConfiguration.Formatters.Add(new FormUrlEncodedMediaTypeFormatter());
                Configuration.Modules.AbpWebApi().HttpConfiguration.Formatters.Add(new JQueryMvcFormUrlEncodedFormatter());
            }
    

    And then try to delete a department:

    • Check a department
    • Click Delete button
    • Click Done button

    Is this approach advisable? Do you know of a better approach? We see that at aspnetboilerplate/src/Abp.Web.Api/WebApi/AbpWebApiModule.cs you are clearing all formatters except JSON. Therefore we are not sure if adding formatters will cause other problems.

  • User Avatar
    0
    hikalkan created
    Support Team

    Hi,

    If adding formatter solves it, you can add it. As we know, it will not be a problem. If it will be, then please write again.

    I'll also try to repeat problem on a non ABP project.

    PLEASE DELETE NOW the project on <a class="postlink" href="http://git.carelearning.com/dxabp.git">http://git.carelearning.com/dxabp.git</a> if it's AspNet Zero template. Because it makes AspNet Zero public.

  • User Avatar
    0
    carelearning created

    Thank you. The project is intentionally only Asp.Net Boilerplate so as not to make anything public. I am sorry for not specifying that earlier. Thanks again for all your hard work.