Custom adaptor fails for ComboBox and MultiSelect

Hello, 

I'm trying to use a custom adaptor with an SfComboBox and SfMultiSelect controls however during runtime it always fails. It does not show any error in the console (exception or otherwise) but I always see the ActionFailureTemplate and no items.
I'm using your latest package version (upgraded today).
I print at the end of the ReadAsync the number of returned items and the correct items are returned and no exception is thrown.

Is there any way to make your controls verbose in order to understand why it fails?
The following code shows the most basic control setup I've been using and the adaptor code (the result is a collection of GenericListValue instances).

<SfComboBox TValue="string" TItem="GenericListValue" Placeholder="Click Me" CssClass="e-outline">
     <SfDataManager Adaptor="Adaptors.CustomAdaptor">
          <ListDataAdaptor ListField="@TemplateItem" ParentComponent="@this" ResponseMocker="@this"></ListDataAdaptor>
     </SfDataManager>
     <ComboBoxFieldSettings Text="Label" Value="Id"></ComboBoxFieldSettings>
</SfComboBox>

<SfMultiSelect TValue="string[]" ModelType="@typeof(GenericListValue)" Placeholder="Click Me Multiple" Mode="VisualMode.CheckBox" CssClass="e-outline">
     <SfDataManager Adaptor="Adaptors.CustomAdaptor">
          <ListDataAdaptor ListField="@TemplateItem" ParentComponent="@this" ResponseMocker="@this"></ListDataAdaptor>
     </SfDataManager>
     <MultiSelectTemplates>
          <ItemTemplate>
               <span title="@((context as GenericListValue).Description)">@((context as GenericListValue).Label)</span>
          </ItemTemplate>
          <ValueTemplate>
               <span title="@((context as GenericListValue).Description)">@((context as GenericListValue).Label)</span>
          </ValueTemplate>
          <NoRecordsTemplate>
               <span>No Values Found</span>
          </NoRecordsTemplate>
          <ActionFailureTemplate>
               <span>Failed Loading Values</span>
          </ActionFailureTemplate>
     </MultiSelectTemplates>
     <MultiSelectFieldSettings Value="Id"></MultiSelectFieldSettings>
</SfMultiSelect>

The adaptor:

@using Newtonsoft.Json
@using Syncfusion.Blazor;
@using Syncfusion.Blazor.Data;

@inherits DataAdaptor

<CascadingValue Value="@this">
    @ChildContent
</CascadingValue>

@inject IListService ListService

@code {
    [Parameter]
    [JsonIgnore]
    public RenderFragment ChildContent { get; set; }

    private IEnumerable<GenericListValue> _listItems;

    private GenericList<GenericListValue> _list { get; set; }

    [Parameter]
    public IHttpClientResponseMocker ResponseMocker { get; set; }

    [Parameter]
    public FormTemplateFieldList ListField { get; set; }

    [Parameter]
    public FormFieldComponent ParentComponent { get; set; }

    public async override Task<object> ReadAsync(DataManagerRequest dataManagerRequest, string key = null)
    {
        try
        {
            if (_list == null)
            {
                _list =
                    await ListService.RetrieveAsync<GenericList<GenericListValue>, GenericListValue>(ResponseMocker,
                    ListField.ListDomain, ListField.ListId);
               _listItems = _list.Values.ToArray();
            }

            var DataSource = _listItems;

            if (dataManagerRequest.Search != null && dataManagerRequest.Search.Count > 0)
            {
                DataSource = DataOperations.PerformSearching(DataSource, dataManagerRequest.Search);
            }

            if (dataManagerRequest.Sorted != null && dataManagerRequest.Sorted.Count > 0)
            {
                DataSource = DataOperations.PerformSorting(DataSource, dataManagerRequest.Sorted);
            }

            if (dataManagerRequest.Where != null && dataManagerRequest.Where.Count > 0)
            {
                DataSource = DataOperations.PerformFiltering(DataSource, dataManagerRequest.Where, dataManagerRequest.Where[0].Operator);
            }

            if (dataManagerRequest.Skip != 0)
            {
                DataSource = DataOperations.PerformSkip(DataSource, dataManagerRequest.Skip);
            }

            if (dataManagerRequest.Take != 0)
            {
                DataSource = DataOperations.PerformTake(DataSource, dataManagerRequest.Take);
            }

            Console.WriteLine($"DataSource count: {DataSource.Count()}");

            return (dataManagerRequest.RequiresCounts ? new DataResult() { Result = DataSource, Count = DataSource.Count() } : (object)DataSource);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            throw;
        }
    }
}

Thank you.

Koby

6 Replies

SP Sureshkumar P Syncfusion Team April 29, 2020 12:45 PM UTC

Hi Koby, 
 
Greetings from Syncfusion support.  
 
Based on your shared information with sample code. We have prepared the sample and we cannot able to replicate the reported issue from our end.  
 
Kindly refer the below code example 
<SfComboBox TValue="string" TItem="Order" Placeholder="Click Me" CssClass="e-outline"> 
    <SfDataManager Adaptor="Adaptors.CustomAdaptor"> 
        <FetchData></FetchData> 
    </SfDataManager> 
    <ComboBoxFieldSettings Text="CustomerID" Value="CustomerID"></ComboBoxFieldSettings> 
</SfComboBox> 
 
<SfMultiSelect TValue="string[]" ModelType="@typeof(Order)" Placeholder="Click Me Multiple" Mode="VisualMode.CheckBox" CssClass="e-outline"> 
    <SfDataManager Adaptor="Adaptors.CustomAdaptor"> 
        <FetchData></FetchData> 
    </SfDataManager> 
    <MultiSelectTemplates> 
        <ItemTemplate> 
            <span title="@((context as Order).CustomerID)">@((context as Order).CustomerID)</span> 
        </ItemTemplate> 
        <ValueTemplate> 
            <span title="@((context as Order).CustomerID)">@((context as Order).CustomerID)</span> 
        </ValueTemplate> 
        <NoRecordsTemplate> 
            <span>No Values Found</span> 
        </NoRecordsTemplate> 
        <ActionFailureTemplate> 
            <span>Failed Loading Values</span> 
        </ActionFailureTemplate> 
    </MultiSelectTemplates> 
    <MultiSelectFieldSettings Value="CustomerID"></MultiSelectFieldSettings> 
</SfMultiSelect> 
 
@code{ 
     public class Order 
    { 
        public int OrderID { get; set; } 
        public string CustomerID { get; set; } 
        public double Freight { get; set; } 
    } 
} 
 
FetchData: 
 
@inherits DataAdaptor 
 
<CascadingValue Value="@this"> 
    @ChildContent 
</CascadingValue> 
 
 
@code { 
 
    public static IEnumerable<Order> Orders { get; set; } 
    protected override void OnInitialized() 
    { 
        Orders = Enumerable.Range(1, 75).Select(x => new Order() 
        { 
            OrderID = 1000 + x, 
            CustomerID = (new string[] { "ALFKI", "ANANTR", "ANTON", "BLONP", "BOLID" })[new Random().Next(5)], 
            Freight = 2.1 * x, 
        }).ToList(); 
    } 
 
    [Parameter] 
    [JsonIgnore] 
    public RenderFragment ChildContent { get; set; } 
 
    private IEnumerable<Order> _listItems; 
 
    //private Order<Order> _list { get; set; } 
 
    //[Parameter] 
    //public IHttpClientResponseMocker ResponseMocker { get; set; } 
 
    //[Parameter] 
    //public FormTemplateFieldList ListField { get; set; } 
 
    //[Parameter] 
    //public FormFieldComponent ParentComponent { get; set; } 
 
    public async override Task<object> ReadAsync(DataManagerRequest dataManagerRequest, string key = null) 
    { 
        try 
        { 
            //if (_list == null) 
            //{ 
            //    _list = 
            //        await ListService.RetrieveAsync<GenericList<GenericListValue>, GenericListValue>(ResponseMocker, 
            //        ListField.ListDomain, ListField.ListId); 
            //    _listItems = _list.Values.ToArray(); 
            //} 
            await Task.Delay(100);  
            var DataSource = Orders; 
 
            if (dataManagerRequest.Search != null && dataManagerRequest.Search.Count > 0) 
            { 
                DataSource = DataOperations.PerformSearching(DataSource, dataManagerRequest.Search); 
            } 
 
            if (dataManagerRequest.Sorted != null && dataManagerRequest.Sorted.Count > 0) 
            { 
                DataSource = DataOperations.PerformSorting(DataSource, dataManagerRequest.Sorted); 
            } 
 
            if (dataManagerRequest.Where != null && dataManagerRequest.Where.Count > 0) 
            { 
                DataSource = DataOperations.PerformFiltering(DataSource, dataManagerRequest.Where, dataManagerRequest.Where[0].Operator); 
            } 
 
            if (dataManagerRequest.Skip != 0) 
            { 
                DataSource = DataOperations.PerformSkip(DataSource, dataManagerRequest.Skip); 
            } 
 
            if (dataManagerRequest.Take != 0) 
            { 
                DataSource = DataOperations.PerformTake(DataSource, dataManagerRequest.Take); 
            } 
 
            Console.WriteLine($"DataSource count: {DataSource.Count()}"); 
 
            return (dataManagerRequest.RequiresCounts ? new DataResult() { Result = DataSource, Count = DataSource.Count() } : (object)DataSource); 
        } 
        catch (Exception ex) 
        { 
            Console.WriteLine(ex.ToString()); 
            throw; 
        } 
    } 
} 
 
please check the above code example and let us know whether it suits your requirement. if not please get revert us with details. 
1.     Share you have passed parameters (custom adaptor component parameter)  
2.     If possible, please replicate the issue in above attached sample and revert us with detailed issue replication procedure. 
 
These details will help us to provide exact solution as earlier as possible.  
 
Query 2: It does not show any error in the console (exception or otherwise) but I always see the ActionFailureTemplate and no items.  
Is there any way to make your controls verbose in order to understand why it fails? 
 
Answer:  
            The case usually occurs there a mismatch between adaptor type set in the control and the type of service. Example if your service is OData service used webAPi adaptor this issue occurs. Since you have using custom adaptor, we suspect this will not be your case. 
 
 
Regards, 
Sureshkumar P 



KO Koby April 29, 2020 03:46 PM UTC

Hi,

I could not reproduce it using a side project as well at first.
I thought it might be since I'm using multiple components dynamically but that wasn't it.

After checking the differences between our samples I found the reason: all you have to do is remove the Task.Delay from the adaptor's ReadAsync method and let it complete synchronously (you can also see it in the attached project - the scenario is reproduced).
In my case, this happens when the results are mocked during development or in case they are cached in-memory.
Once I added in my original code a permanent "await Task.Delay(1);" it worked normally.

Please let me know if you consider this as a bug to be fixed or if I need to find a permanent workaround for this scenario.
Thank you.

Koby

Attachment: BlazorApp1_95a0cc97.zip


SP Sureshkumar P Syncfusion Team April 30, 2020 07:47 AM UTC

Hi Koby, 
 
Thanks for your update.  
 
Based on your shared information, we suspect that your data source is loaded Synchronous manner. Please follow the below steps to resolve the issue from your end.  
1.     Whether local data source loaded synchronously then use Read() method instead of ReadAsync() method.  
2.     If you want to load the ReadAsync() method your data source will load with some delay so you need to use await Task.Delay to resolve the issue.  
 
Regards, 
Sureshkumar P 
 



KO Koby April 30, 2020 08:24 AM UTC

I know it's loaded in a synchronous manner - I mentioned that in my previous post as the cause for the problem.
That doesn't mean that the way the adaptor works is valid from an engineering point of view.

When I call the service to retrieve data, I don't know in advance if the call will complete in a synchronous manner or not (e.g. if it will get a cache hit or cache moss and in turn will make a call to the server) - therefore the option of using Read is not a valid one.
Async methods do not guarantee a consistent way of executing (sync vs. async completion) and using Task.Delay as a workaround is a patch that should not exist normally in production code.

Can you please run this by your engineering team? We can sweep this under the rug and other developers will keep running into the same issue since the adaptor component does not adhere to the async development paradigm (unless you can show me that I'm doing something that should not be done from a pure C# perspective) or you can simply fix this behavior and save us the need to introduce garbage code and permanent unwanted patches into our products.

Thank you.

Koby



BM Bishan Moteria April 30, 2020 09:30 AM UTC

Hi Sureshkumar,

I am having the same issue as described in original post but with AutoComplete control.

v18.1.45 - AutoComplete control having custom adaptor which overrides ReadAsync doesn't return any data. It returns "The Request Failed" error as a result. There are no errors in the console window. Although it works with ReadAsync is when having "await Task.Deplay(1);" inside ReadAsync() method.

v18.1.46 - AutoComplete control having custom adaptor does not work at all. The auto complete dropdown result doesn't show. There are no errors in the console window. Even when AllowCustom is set to false, it allows custom values. The custom value doesn't get cleared after focus out.

I have created samples with both versions and is attached with this response.

Thanks,
Bishan M.

Attachment: BlazorAutocompleteCustomAdaptor_9fbf2538.zip


SP Sureshkumar P Syncfusion Team May 1, 2020 05:21 AM UTC

Hi Koby / Bishan, 
 
Thanks for your update.  
 
We suspect that you are also facing the same issue with different versions as like above mentioned 18.1.0.45 and 18.1.0.46. then this is already known issue at our end in the latest 18.1.0.46 version and this fix will be included in our upcoming patch release scheduled on 5th May 2020. We appreciate your patience until then.   
   
You can track the status of the reported issue from the below feedback link.  
 
Regards, 
Sureshkumar P 


Loader.
Up arrow icon