While introducing a screenshot feature to one of our applications, we're experiencing unexpected behavior.
The screenshots are grabbed as a blob, client-side, converted to a base64 string, then saved in a RadListBox. We found that if greater than five base64 strings are saved in the RadListBox, the item count is zero, and triggers an error on our server side save functionality.
In most cases the limit of five works as expected, however, at times, even with the limit of five images (as base64 strings), the list box item count is zero.
Is there a size limit to the RadListBox? So far, the only explanation I can come up with is the list box control is overwhelmed with data and purges itself.
What else could cause this?
2 Answers, 1 is accepted
Hi Jeff,
Thank you for the clarification!
Now I was able to completely replicate the described problem on my side.
The unexpected behavior is a result of failing deserialization of the RadListBox's ClientState due to exceeding the maximum length of JSON strings that are accepted by the internally used JavaScriptSerializer object. Currently, the MaxJsonLength property of the serializer is set to its default value of 2097152 characters (=4MB).
What you can do to overcome this limitation:
One possible workaround would be to use a HiddenField to hold and transfer the long value strings to the server. That way you will be able to manually deserialize them with a custom instance of the JavaScriptSerializer. For instance, you can use key-value pairs to store the images in the HiddenField and use just the keys as ListBox values:
<asp:HiddenField id="HiddenField1" runat="server"/>
var images = {};
var addItemToListBox = (item) => {
let lstBox = $find('<%= RadListBox1.ClientID %>');
var hf = $get("<%= HiddenField1.ClientID %>");
var myId = "myID" + counter;
images[myId] = item;
hf.value = JSON.stringify(image);
let newItem = new Telerik.Web.UI.RadListBoxItem();
newItem.set_value(myId);
newItem.set_text("ListBoxItem " + counter);
lstBox.trackChanges();
lstBox.get_items().add(newItem);
lstBox.commitChanges();
}
removeItemFromListBox = (item) => {
let lstBox = $find('<%= RadListBox1.ClientID %>');
let deleteItem = lstBox.findItemByText(item);
if (deleteItem)
{
var hf = $get("<%= HiddenField1.ClientID %>");
delete images[deleteItem.get_value()];
hf.value = JSON.stringify(images);
lstBox.trackChanges();
lstBox.get_items().remove(deleteItem);
lstBox.commitChanges();
}
}
var serializer = new JavaScriptSerializer();
serializer.MaxJsonLength = int.MaxValue;
var images = serializer.Deserialize<Dictionary<string,string>>(HiddenField1.Value);
For your reference here is the relevant piece of the source code:
RadListBox.cs
protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
....
var clientStateValue = postCollection[ClientStateFieldID];
.....
var serializer = new JavaScriptSerializer();
try
{
clientState = serializer.Deserialize<RadListBoxClientState>(clientStateValue);
....
}
catch (InvalidOperationException)
{
}
catch (ArgumentException)
{
}
....
I hope you will find this information helpful.
Kind regards,
Doncho
Progress Telerik
Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.
Doncho,
This solution resolved my problem. Many thanks!
Regarding this line of code:
serializer.MaxJsonLength = int.MaxValue;
Is it necessary to set it to the max value? We are limiting the accumulative size to 3MB. Would it be appropriate to set it to, or near, the maxRequestLength property, or even the 3MB threshold?
Thanks,
Jeff.
Jeff,
I am glad to hear that the problem is resolved.
I have used the int.MaxValue just for demonstrating the suggested approach. Please feel free to set any desired MaxJsonLength that would fit your requirements.
Doncho,
I see now that this value is representing the maximum characters, not size in bytes. So, I will leave this as is to avoid the same problem.
Thanks.
Jeff.
Hi Jeff,
Thank you for all the additional information!
It seems that the issue is related to exceeding the maximum allowed request length.
The default value of the maxRequestLength is 4096 KB and if you're operating a large amount of data, its threshold will more likely be exceeded. You can try to increase it to a higher value, for example to 32 MB.This can be done by adding the following to the system.web section in your web.config file:
<httpRuntime maxRequestLength="32768" />
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="33554432" />
</requestFiltering>
</security>
Kind regards,
Doncho
Progress Telerik
Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.
Doncho,
We do not believe this is related, as our maxRequestLength value is already much higher than the default.
Additionally, there are no issues with the RadAsyncUpload control used on the same page, with much larger data than 1.5MB.
Based upon what I'm experiencing, it appears that maybe the clientstate isn't being cleared/refreshed when an item is removed from the RadListBox, by the time it gets to the server.
Were you able to replicate the issue?
Jeff.
Upon further inspection, it appears that using the client method to remove the item doesn't actually remove all of the data/values. This screenshot from dev tools, after removing all items, shows the base64 string value remains. I've also included a screenshot of the client side remove function.
Hi Jeff,
I am afraid I was not able to replicate the problem on my side.
Could you please share a sample or code-snippets of the RadListBox declaration and the interactions with its items? We would need to see a repro of the problem in order to help with troubleshooting it.
Doncho,
I have isolated the issue to the removal process (removeItemFromListBox method below). From the client, the item count is as expected, regardless of how many times the remove function is triggered. However, once it gets to the server, and if the RadListBox item count/size has reached it's threshold, the item count is zero. Apparently, the items are purged for some reason, between the client and server.
Attached is an image from the client showing the item count as five, and an image from the server showing 0. These images are taken immediately following each other, showing that the RadListBox items are purged by the time it gets to the server side code.
Here is the javascript I use to create, and add/remove the base64String representation of the jpeb/blob data:
// Code not shown is methods using the geDisplayMedia functionality which creates a blob from a canvas // This can be sampled by converting any image/jpeg to a blob, then using the FileReader() function to convert that to a base64 string, as shown below // Convert blob to base64 string, add to RadListBox let reader = new FileReader(); reader.readAsDataURL(screenshotJpegBlob); reader.onloadend = async () => { let base64String = reader.result; ssCounter++; addItemToListBox(base64String); }; // Add to RadListBox // "item" = base64String addItemToListBox = (item) => { let lstBox = $find('<%= RadListBox1.ClientID %>'); let newItem = new Telerik.Web.UI.RadListBoxItem(); newItem.set_value(item); newItem.set_text(ssCounter); lstBox.trackChanges(); lstBox.get_items().add(newItem); lstBox.commitChanges(); } // "item" argument is the text value of each RadListBox item, as set in the addItemToListBox method above removeItemFromListBox = (item) => { let lstBox = $find('<%= RadListBox1.ClientID %>'); let deleteItem = lstBox.findItemByText(item); if (deleteItem) { // Delete base64String from RadListBox lstBox.trackChanges(); lstBox.get_items().remove(deleteItem); lstBox.commitChanges(); }
Here is the RadListBox declaration:
When deployed, it is a hidden inside a div with style="display: none". For troubleshooting purposes, the display: none styling is removed. This problem occurs whether it's hidden or not.
<div> <telerik:RadListBox RenderMode="Lightweight" runat="server" ID="RadListBox1" Width="500" Height="500"></telerik:RadListBox> </div>