Abp: Dynamic JavaScript Proxies can't handle complex types

Created on 11 Nov 2020  路  7Comments  路  Source: abpframework/abp

Summary

I'm trying to use my AutoFilterer library to filtering entities. I've complex types in such as Range which includesMin and Max properties in it.

  • ABP Framework version: 3.3.1

  • User Interface: MVC

Example

  • My used dto instead of PagedAndSortedResultRequestDto:
    public class BookFilterDto : PaginationFilterBase
    {
        [StringFilterOptions(StringFilterOption.Contains)]
        public string Title { get; set; }

        [OperatorComparison(OperatorType.Equal)]
        public string Language { get; set; }

        [StringFilterOptions(StringFilterOption.Contains)]
        public string Author { get; set; }
        public Range<int> TotalPage { get; set; }
        public Range<int> Year { get; set; }
        public BookType[] BookType { get; set; }
    }
  • Swagger output:
    Swagger supports and works with complex types in dto:
    image

Also swagger able to send request like domain.com/books?totalPage.min=250.

  • Javascript proxy:

I've tried a couple of way to run my query:

bookstore.books.book.getList({'TotalPage.min':3})
bookstore.books.book.getList({totalPage: { min: 3}})

None of them didn't work.
image

enhancement

All 7 comments

I created an example application service:

````csharp
using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace MyCompanyName.MyProjectName
{
public class BookAppService : ApplicationService
{
public async Task GetTestAsync(TestDto input)
{
return input.Name + " / " + input.Price?.Min + "-" + input.Price?.Max;
}
}

public class TestDto
{
    public string Name { get; set; }
    public Range<int> Price { get; set; }
}

public class Range<T>
{
    public T Min { get; set; }
    public T Max { get; set; }
}

}
````

And requested via JS proxies like that:

js myCompanyName.myProjectName.book.getTest({ name: 'my book', price: { Min: 5, Max: 42 } }, { dataType: 'text' }).then(function(result) { console.log(result); });

It works and the result is my book / 5-42 as expected. The request URL: https://localhost:44303/api/app/book/test?name=my%20book&price.Min=5&price.Max=42

The problem here is that I had to pass Min and Max instead of min and max, because the generated proxy was:

js myCompanyName.myProjectName.book.getTest = function(input, ajaxParams) { return abp.ajax($.extend(true, { url: abp.appPath + 'api/app/book/test' + abp.utils.buildQueryString([{ name: 'name', value: input.name }, { name: 'price.Min', value: input.price.Min }, { name: 'price.Max', value: input.price.Max }]) + '', type: 'GET' }, ajaxParams)); };

I think we only need to solve the PascalCase - camelCase problem while creating the Min & Max naming in the service proxies. It should be:

js myCompanyName.myProjectName.book.getTest = function(input, ajaxParams) { return abp.ajax($.extend(true, { url: abp.appPath + 'api/app/book/test' + abp.utils.buildQueryString([{ name: 'name', value: input.name }, { name: 'price.min', value: input.price.min }, { name: 'price.max', value: input.price.max }]) + '', type: 'GET' }, ajaxParams)); };

I haven't deeply checked the #6290 yet.

It tries to build with each parameter.

For example, even I don't set Price property, but javascript uses it as input.price.min, so price is undefined at that situation.

But if it use as input['price.min'] everything'll work great 馃憣 The PR #6290 solves that situation

I prefer

js myCompanyName.myProjectName.book.getTest({ name: 'my book', price: { min: 5, max: 42 } })

instead of

js myCompanyName.myProjectName.book.getTest({ name: 'my book', 'price.min': 5, 'price.max': 42 })

because the first one better matches to the method and DTO structure defined in the C# side. Proxy script should handle the conversion inside it by tolerating the null case.

As a pattern, that is much more useful, Maybe a null-check might be better solution on javascript side.

When price parameter is optional, following line has error, because of price is null/undefined, and javascript trying to access min or max property of price.

 url: abp.appPath + 'api/app/book/test' + abp.utils.buildQueryString([{ name: 'name', value: input.name }, { name: 'price.min', value: input.price.min }, { name: 'price.max', value: input.price.max }]) + '',

Same example without sending price:

myCompanyName.myProjectName.book.getTest({
    name: 'my book'
})

It throws Uncaught TypeError: Cannot read property 'min' of undefined

I really don't know best practices at javascript but, as I see ? operator is supported 82% of browsers. (here)
May be generated code can has null check like input.price?.min or with any other situations: input.propA?.propB?.propC

In that situation, following generated script might work 82% of browsers according to caniuse Optional chaining operator

myCompanyName.myProjectName.book.getTest = function(input, ajaxParams) {
  return abp.ajax($.extend(true, {
    url: abp.appPath + 'api/app/book/test' + abp.utils.buildQueryString([{ name: 'name', value: input.name }, { name: 'price.min', value: input.price?.min }, { name: 'price.max', value: input.price?.max }]) + '',
    type: 'GET'
  }, ajaxParams));
};

Just Note:

Both of usage is working now. I can use input['name'] = "My Book" and input.name = "My Book".

Javascript allows to set properties with both way. Reading is same, too;

  • console.log(input["name"]) and console.log(input.name) do the same thing.

I've just realized. dot notation doesn't work for nested objects. And this PR breaks structure of DTO as you say @hikalkan

But solves another issues like handling custom ParameterName if it's set in application.

var book1 = { name: "My book" }

console.log(book1.name); // Works great!
console.log(book1["name"]); // Works great!


var book2 = { price: { min: 5, max:42} };

console.log(book2.price.min); // Works fine!
console.log(book2["price.min"]); // Doesn't work!
console.log(book2["price"]["min"]); // Works fine. But throws error if price is undefined, 

What do you think @maliming ?

hi @enisn

My thoughts is:

book.price is an object. but for query string and form-data, they should be key-value.
book.price.min is the key.

var formdata = new FormData();
formdata.append("book.name", "my book");
formdata.append("book.price.max", "10");
formdata.append("book.price.min", "6");

Although the use of objects is friendly to c#, in fact, it is key-values. : )

@enisn

I will create a new PR base on halil's reply

https://github.com/abpframework/abp/pull/6374

Was this page helpful?
0 / 5 - 0 ratings