Open Closed

Internal 500 error on recursive entity lookup #3851


0
garysund created

I am getting the following error when query a recursive entity lookup.

{"message":"An error has occurred.","exceptionMessage":"There is an action GetCategories defined for api controller app/category but with a different HTTP Verb. Request verb is GET. It should be Post","exceptionType":"System.Web.HttpException","stackTrace":" at Abp.WebApi.Controllers.Dynamic.Selectors.AbpApiControllerActionSelector.GetActionDescriptorByActionName(HttpControllerContext controllerContext, DynamicApiControllerInfo controllerInfo, String actionName)\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 Castle.Proxies.DynamicApiController1Proxy_5.ExecuteAsync_callback(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n at Castle.Proxies.Invocations.ApiController_ExecuteAsync_5.InvokeMethodOnTarget()\r\n at Castle.DynamicProxy.AbstractInvocation.Proceed()\r\n at Abp.WebApi.Controllers.Dynamic.Interceptors.AbpDynamicApiControllerInterceptor1.Intercept(IInvocation invocation)\r\n at Castle.DynamicProxy.AbstractInvocation.Proceed()\r\n at Castle.Proxies.DynamicApiController`1Proxy_5.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()"}

The error only occurs after I added the first entry that has a ParentId

Model

[Table("Categories")]
     public class Category : FullAuditedEntity
     {
        [Required]
        public string Name { get; set; }
        [Required]
        public string SharepointMapping { get; set; }
        public int? ParentId { get; set; }
        public Category Parent { get; set; }
       public List<Category> Children { get; set; }
     }

CategoryAppService

public ListResultDto<CategoryListDto> GetCategories(GetCategoriesInput input)
{
    var categories = _categoryRepository
        .GetAll()
        .WhereIf(
            !input.Filter.IsNullOrEmpty(),
            p => p.Name.Contains(input.Filter) 
        )
        .OrderBy(p => p.Name)
        .ToList();

    return new ListResultDto<CategoryListDto>(categories.MapTo<List<CategoryListDto>>());
}

CategoryListDto

[AutoMapFrom(typeof(Category))]
public class CategoryListDto : FullAuditedEntityDto
{
    public string Name { get; set; }
    public string SharepointMapping { get; set; }
    public int? ParentId { get; set; }
    public virtual Category Parent { get; set; }

}

Thanks I am pulling my hair out on this one


13 Answer(s)
  • 0
    strix20 created

    The error you posted doesn't appear to have anything to do with your database.

    It looks like you are sending a 'get' request to a web api method that's defined as a 'post' method.

  • 0
    garysund created

    Thanks for the reply thats what I thought.

    I have not created any custom API for this.

    Its being called by jTable in javascript.

    _$categoriesTable.jtable({

            title: app.localize('Categories'),
    
            actions: {
                listAction: {
                    method: _categoryService.getCategories
                }
            },
    

    I removed all the records with ParentIds and did not get this error. When I added a parentId to one of them I got the error again.

    Not sure what is happening

    Could it be an issue with jtable when the navigation property is null.

    Category.Parent = null

    So then category.parent.name will throw an exception error

    My controller and AppService returns data correctly.

    Here is full javascript

    (function () {
        $(function () {
    
            var _$categoriesTable = $('#CategoriesTable');
            var _categoryService = abp.services.app.category;
    
            var _permissions = {
                create: abp.auth.hasPermission('Pages.Administration.Categories'),
                edit: abp.auth.hasPermission('Pages.Administration.Categories'),
                'delete': abp.auth.hasPermission('Pages.Administration.Categories')
            };
    
            var _createOrEditModal = new app.ModalManager({
                viewUrl: abp.appPath + 'Mpa/Categories/CreateOrEditModal',
                scriptUrl: abp.appPath + 'Areas/Mpa/Views/Categories/_CreateOrEditModal.js',
                modalClass: 'CreateOrEditCategoryModal'
            });
    
            _$categoriesTable.jtable({
    
                title: app.localize('Categories'),
    
                actions: {
                    listAction: {
                        method: _categoryService.getCategories
                    }
                },
    
                fields: {
                    id: {
                        key: true,
                        list: false
                    },
                    actions: {
                        type: 'record-actions',
                        cssClass: 'btn btn-xs btn-primary blue',
                        text: '<i class="fa fa-cog"></i> ' + app.localize('Actions') + ' <span class="caret"></span>',
                        items: [{
                            text: app.localize('Edit'),
                            visible: function () {
                                return _permissions.edit;
                            },
                            action: function (data) {
                                _createOrEditModal.open({ id: data.record.id });
                            }
                        }, {
                            text: app.localize('Delete'),
                            visible: function (data) {
                                return !data.record.isStatic && _permissions.delete;
                            },
                            action: function (data) {
                                deleteRole(data.record);
                            }
                        }]
                    },
                    Name: {
                        title: app.localize('CategoryName'),
                        width: '35%',
                        display: function (data) {
                            var $span = $('<span></span>');
                            $span.append(data.record.name + " &nbsp; ");
                            return $span;
                        }
                    },
                    SharePointMapping: {
                        title: app.localize('SharePointName'),
                        width: '35%',
                        display: function (data) {
                            var $span = $('<span></span>');
                            $span.append(data.record.sharepointMapping + " &nbsp; ");
                            return $span;
                        }
                    },
                    ParentName: {
                        title: app.localize('ParentName'),
                        width: '35%',
                        
                        display: function (data) {
                            var $span = $('<span></span>');
                            $span.append(data.record.parent + " &nbsp; ");
                            return $span;
                        }
                    },
                   
                }
    
            });
    
            function deleteRole(role) {
                abp.message.confirm(
                    app.localize('RoleDeleteWarningMessage', role.displayName),
                    function (isConfirmed) {
                        if (isConfirmed) {
                            _roleService.deleteRole({
                                id: role.id
                            }).done(function () {
                                getRoles();
                                abp.notify.success(app.localize('SuccessfullyDeleted'));
                            });
                        }
                    }
                );
            };
    
            $('#CreateNewCategoryButton').click(function () {
                _createOrEditModal.open();
            });
    
            $('#RefreshCategoriesButton').click(function (e) {
                e.preventDefault();
                getCategories();
            });
    
            function getCategories() {
                _$categoriesTable.jtable('load');
            }
    
            abp.event.on('app.createOrEditRoleModalSaved', function () {
                getCategories();
            });
    
            getCategories();
        });
    })();
    
  • 0
    strix20 created

    You didn't post your controller code, but I suspect what's happening is that your definition for your get controller does not take a parentId, and your post controller does expect a parentId.

    When the parentId is null, then web api tries to resolve to the get action, because it has the closest matching parameters.

    You should be able to either make the parameter optional in the post controller, or force it to be included with a null value in your javascript service.

  • 0
    garysund created

    Hi There

    Please find controller code below. Still getting issues after forcing javascript to force null values.

    public ActionResult Index(GetCategoriesInput input)
            {
                var output = _categoryAppService.GetCategories(input);
    
                var model = new IndexViewModel(output);
    
               
    
                return View(model);
            }
    
  • 0
    strix20 created

    Can you show the entire controller? That's just a single action, and you don't include the verb attributes.

  • 0
    ismcagdas created
    Support Team

    Hi @Garysund,

    Since you are using MVC 5.x, all requests must be made with HTTP POST. Can you try to execute "abp.services.app.category.getCategories({add required params here...})" to see if this makes a POST request ?

  • 0
    garysund created

    Full controller

    namespace Clarity.BabcockCRM.Web.Areas.Mpa.Controllers
    {
        [AbpMvcAuthorize(AppPermissions.Pages_Administration_Categories)]
        public class CategoriesController : BabcockCRMControllerBase
        {
            
    
            private readonly ICategoryAppService _categoryAppService;
    
            public CategoriesController(ICategoryAppService categoryAppService)
            {
                _categoryAppService = categoryAppService;
            }
    
            public ActionResult Index(GetCategoriesInput input)
            {
                var output = _categoryAppService.GetCategories(input);
    
                var model = new IndexViewModel(output);
    
               
    
                return View(model);
            }
    
            public async Task<PartialViewResult> CreateOrEditModal(int? id)
            {
                var output = await _categoryAppService.GetCategoryForEdit(new NullableIdDto { Id = id });
                var viewModel = new CreateOrEditCategoryModalViewModel(output);
    
                return PartialView("_CreateOrEditModal", viewModel);
            }
        }
    }
    

    Full Category App Service. The Javascript calls the app service directly

    [AbpAuthorize(AppPermissions.Pages_Administration_Categories)]
    public class CategoryAppService : BabcockCRMServiceBase, ICategoryAppService
    {
        private readonly IRepository<Category> _categoryRepository;
    
        public CategoryAppService(IRepository<Category> categoryRepository)
        {
            _categoryRepository = categoryRepository;
        }
    
        public ListResultDto<CategoryListDto> GetCategories(GetCategoriesInput input)
        {
            var categories = _categoryRepository
                .GetAll()
                .WhereIf(
                    !input.Filter.IsNullOrEmpty(),
                    p => p.Name.Contains(input.Filter)
                )
                .OrderBy(p => p.Name)
                .ToList();
    
            return new ListResultDto<CategoryListDto>(categories.MapTo<List<CategoryListDto>>());
        }
    
        public async Task CreateOrUpdateCategory(CreateOrUpdateCategoryInput input)
        {
            if (input.Category.Id.HasValue)
            {
                await UpdateCategoryAsync(input);
            }
            else
            {
                await CreateCategoryAsync(input);
            }
        }
    
       
        protected virtual async Task UpdateCategoryAsync(CreateOrUpdateCategoryInput input)
        {
            Debug.Assert(input.Category.Id != null, "input.Category.Id should be set.");
    
            var category = await _categoryRepository.GetAsync(input.Category.Id.Value);
            category.Name = input.Category.Name;
            category.SharepointMapping = input.Category.SharepointMapping;
            category.ParentId = input.Category.ParentId;
    
            await _categoryRepository.UpdateAsync(category);
        }
    
      
        protected virtual async Task CreateCategoryAsync(CreateOrUpdateCategoryInput input)
        {
            var category = new Category()
            { Name = input.Category.Name,
            SharepointMapping = input.Category.SharepointMapping,
            ParentId = input.Category.ParentId};
            await _categoryRepository.InsertAsync(category);
            await CurrentUnitOfWork.SaveChangesAsync(); //It's done to get Id of the role.
           
        }
    
    
        public async Task CreateCategory(CreateCategoryInput input)
        {
            var category = input.MapTo<Category>();
            await _categoryRepository.InsertAsync(category);
        }
    
        public async Task<GetCategoryForEditOutput> GetCategoryForEdit(NullableIdDto input)
        {
            var categorylist = _categoryRepository.GetAll().ToList();
            CategoryEditDto categoryEditDto;
    
            if (input.Id.HasValue) //Editing existing role?
            {
                var category = await _categoryRepository.GetAsync(input.Id.Value);
                categoryEditDto = category.MapTo<CategoryEditDto>();
            }
            else
            {
                categoryEditDto = new CategoryEditDto();
            }
    
            return new GetCategoryForEditOutput
            {
                Category = categoryEditDto,
                CategoryList = categorylist.MapTo<List<Category>>().OrderBy(p => p.Name).ToList(),
               
            };
        }
    
       
        public async Task DeleteCategory(EntityDto input)
        {
            var category = await _categoryRepository.GetAsync(input.Id);
            await _categoryRepository.DeleteAsync(category);
        }
    
    
    }
    
  • 0
    garysund created

    Hi There

    I removed all parameters. I am retrieving all records so dont need any parameters for now. So the GetCategories class doesnt accept any parameters any more and it still does a get.

    Same error.

    The funny thing is it only returns the error when there is a record with a ParentId ie the Parent Property is not null. Else the call works fine.

    Should I decorate the service with httppost annotation.

  • 0
    strix20 created

    It doesn't appear to have anything to do with the service.

    It looks like ABP is using a dynamic api controller to resolve api actions?

    I haven't done much with their webapi, so I'm not really familiar with how the dynamic controller resolution works.

    But you can find a similar issue reported here:

    <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues/1713">https://github.com/aspnetboilerplate/as ... ssues/1713</a>

    Try reading the documentation here on how it works (I don't have the time right now, but I will look into it later.)

    <a class="postlink" href="https://aspnetboilerplate.com/Pages/Documents/Dynamic-Web-API">https://aspnetboilerplate.com/Pages/Doc ... ic-Web-API</a>

  • 0
    garysund created

    Hi All Thanks for all the help. I think I have got to the bottom of the issue.

    I changed the following code to use default verbs.

    Configuration.Modules.AbpWebApi().DynamicApiControllerBuilder
                    .ForAll<IApplicationService>(typeof(BabcockCRMApplicationModule).Assembly, "app")
                    .WithConventionalVerbs()
                    .Build();
    

    So now it will use a get and not post.

    When I ran the code I got the following error.

    "message":"An error has occurred.","exceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.","exceptionType":"System.InvalidOperationException","stackTrace":null,"innerException":{"message":"An error has occurred.","exceptionMessage":"Self referencing loop detected for property 'parent' with type 'Clarity.BabcockCRM.Models.Category'

    So in my opinion this is the error that was causing the issue in the first place. Is there any way to solve this?

  • 0
    strix20 created

    The default JSON serializer in MVC cannot handle recursive loops. You either need to create a custom serializer, remove the parent or child object from the DTO, or mark it to be ignore by json.

  • 0
    garysund created

    FYI Fixed with a little hack returning the parent as a string

    public ListResultDto<CategoryListDto>GetCategories()
        {
             var categories = _categoryRepository.GetAll().ToList()
                   
                   .Select(r => new CategoryListDto
                   {
                       Id = r.Id,
                       Name = r.Name,
                       SharepointMapping = r.SharepointMapping,
                       Parent = GetCategoryName(r.ParentId)
                   });
    
            return  new ListResultDto<CategoryListDto>(categories.MapTo<List<CategoryListDto>>()); ;
        }
    
  • 0
    ismcagdas created
    Support Team

    Thanks for sharing your solutio─▒n @Garysund. Probably this was the root cause of your first problem.

    Thanks again.