This is a migrated thread and some comments may be shown as answers.

[Solved] Ajax Bound Grid will not insert/update/delete

16 Answers 357 Views
Grid
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Paul
Top achievements
Rank 1
Paul asked on 02 Sep 2010, 11:13 AM
I am using the latest MVC 2010_2_713 extensions on Visual Studio 2008 and seem to be having trouble using Ajax calls to update the database via the Grid. I have pretty much followed all instructions as I could and dissected the example (which works) although that uses a HtmlHelpersExtensions class, but without me spending another day going dissecting this I wondered if anyone could provide a quick solution as to where the problem may be occurring?

My View Code is as follows:

<%= Html.Telerik().Grid(Model)
    .Name("DiscountTypeGrid")
    .ToolBar(commands => commands.Insert())
    .DataKeys(keys => { keys.Add(d => d.DiscountType_code); })
    .DataBinding(dataBinding =>
    {
        dataBinding.Ajax()
            .Select("SelectAjax", "DiscountType")
            .Insert("InsertAjax", "DiscountType")
            .Update("SaveAjax", "DiscountType")
            .Delete("DeleteAjax", "DiscountType");
    })
    .Columns(columns =>
    {
        columns.Bound(d => d.DiscountType_code).Title("Discount Type Code");
        columns.Bound(d => d.DiscountType_desc).Title("Discount Type Description");
 
        //Template column which shows an action link
        columns.Command(commands =>
        {
            commands.Edit();
            commands.Delete();
        }).Width(200).Title("Commands");
    })
    .Editable(editing => editing.Mode(GridEditMode.InLine))
    .Scrollable()
    .Pageable()
    .Sortable()
 
%>

Top Line of this View reads:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<CRPManager.Models.Database.DiscountType>>" %>


The associated controller is:

public class DiscountTypeController : Controller
{
    private DiscountRepository _discountRepository = new DiscountRepository();
 
    #region Views
 
    public ActionResult Index()
    {
        return View(_discountRepository.ListAllDiscountTypes());
    }
 
    [GridAction]
    public ActionResult SelectAjax()
    {
        return View(new GridModel(_discountRepository.ListAllDiscountTypes()));
    }
 
    #endregion
 
 
    #region Posts
 
    [AcceptVerbs(HttpVerbs.Post)]
    [GridAction]
    public ActionResult SaveAjax(string code)
    {
        // Find an existing DiscountType
        DiscountType discountType = _discountRepository.GetDiscountType(code);
 
        // Update it and Save
        TryUpdateModel(discountType);
        _discountRepository.Save();
 
        // Rebind the grid
        return View(new GridModel(_discountRepository.ListAllDiscountTypes()));
    }
 
 
    [AcceptVerbs(HttpVerbs.Post)]
    [GridAction]
    public ActionResult InsertAjax()
    {
        // Create a new instance of the DiscountType class.
        DiscountType discountType = new DiscountType();
 
        // Perform model binding (fill the product properties and validate it).
        if (discountType != null)
        {
            if (TryUpdateModel(discountType))
            {
                //The model is valid - insert the product.
                _discountRepository.Add(discountType);
            }
        }
 
        _discountRepository.Save();
 
        // Rebind the grid
        return View(new GridModel(_discountRepository.ListAllDiscountTypes()));
    }
 
 
    [AcceptVerbs(HttpVerbs.Post)]
    [GridAction]
    public ActionResult DeleteAjax(string code)
    {
        // Find a discountType with code equal to the value passed in
        DiscountType discountType = _discountRepository.GetDiscountType(code);
 
        // If not null, delete
        if (discountType != null)
        {
            //Delete the record
             _discountRepository.Delete(discountType);
        }
 
        //Rebind the grid
        return View(new GridModel(_discountRepository.ListAllDiscountTypes()));
    }
 
    #endregion
}

If I press Edit on an existing row that has a discount type code "TEST", I get the Url:

 http://localhost:4349/DiscountType/Index/TEST?DiscountTypeGrid-mode=edit

however if I change this and press update, it goes back to the original value and the Url remains the same until I cancel at which time it goes back to the original Url.

It is a similar situation with insert, Add New Record works and changes the Url to

 http://localhost:4349/DiscountType/Index/DiscountTypeGrid-mode=insert

however the Insert feature doesn't insert any rows and simply leaves the Url the same.

Thanks in advance,
Paul

16 Answers, 1 is accepted

Sort by
0
Atanas Korchev
Telerik team
answered on 02 Sep 2010, 11:40 AM
Hi Paul,

First you can try upgrading to the latest official release which is 2010.2.825. You seem to be using the beta version. If the problem remains you may be experiencing this problem. We have a forum thread which explains what the problem is.

All the best,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Paul
Top achievements
Rank 1
answered on 02 Sep 2010, 12:07 PM
Hi Atanas,

Thanks for the quick reply, my mistake re the version, I was reading the incorrect version from my downloads folder, I actually have 2010.2.825 already installed onto my system through the msi installer which is referenced correctly in Visual Studio. I had a look at the thread you mentioned but I already have the latest version of that script file, however I updated it again just to make sure but it made no difference.

Regards,
Paul
0
Atanas Korchev
Telerik team
answered on 02 Sep 2010, 12:11 PM
Hello Paul,

Make sure there is no file called jquery.validate.js (or min.js) which is older than 1.7. I suspect the old file is still being included.

If this is not the case - check for any JavaScript errors in your page. If there are any - do let is know what they are. Finally if nothing helps I suggest you send us a working sample showing the problem. Since we don't allow zip attachments in the forum (for security reasons) I suggest you use a public file hosting service. Dropbox.com and skydrive.live.com are such sites which are free for use.

Regards,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Paul
Top achievements
Rank 1
answered on 02 Sep 2010, 01:37 PM
Hi Atanas,

The current version of jquery.validate.min.js is 1.7, Im not using the non min version and there are no javascript errors whatsoever.

The rest of code is pretty much as you see apart from the standard LINQ TO SQL Repository code and the database and I cannot send the database so it wouldn't work for you anyway in that respect. From what I can see, it does apear to be posting, however it is not updating the database

When i hit the Insert button and enter test data the Post displays the following in Firebug:

Parametersapplication/x-www-form-urlencoded
DiscountType_code   Test_code
DiscountType_code  
DiscountType_desc   Test Description
Source
Content-Type: application/x-www-form-urlencoded Content-Length: 81 DiscountType_code=Test_code&DiscountType_desc=Test+Description&DiscountType_code=

Does it make any difference that the primary key on the table is a varchar(20)?

If you want the rest of the code without the database, I can get it to you later in the day somehow.

Regards,
Paul
0
Atanas Korchev
Telerik team
answered on 02 Sep 2010, 02:00 PM
Hello Paul,

I order to test locally I need a working sample - you can use dummy data if you want to. There is no need to send the real database.

Regards,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Paul
Top achievements
Rank 1
answered on 02 Sep 2010, 04:19 PM
Hi Atanis,

I have stripped this Solution bare apart from the actual Grid and created a test database so this will still run as a standalone web app. The overall size of this is now 5.3Mb however I cannot access Dropbox at work and SkyDrive wont allow me to upload a folder .

Is there any other way I can get this to you? If need be you can have my work email to respond to and I will send it back to you that way

Regards,
Paul
0
Atanas Korchev
Telerik team
answered on 02 Sep 2010, 04:29 PM
Hello Paul,

Why don't you zip the folder and upload it to skydrive?

Regards,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Paul
Top achievements
Rank 1
answered on 02 Sep 2010, 04:52 PM
Hi Atanas,

Sorry, I must have mis-read, I assumed you didn't want it zipped up atall, it is now zipped up in the following location on SkyDrive:

http://cid-30c2073db88b27d9.office.live.com/browse.aspx/.Public?uc=1&lc=2057

Regards,
Paul
0
Atanas Korchev
Telerik team
answered on 03 Sep 2010, 08:44 AM
Hello Paul,

 The problem was that you have put the ScriptRegistrar at the top of your page. Unfortunately the grid cannot register with the script registrar due the lack of any lifecycle in ASP.NET MVC. To fix it move the ScriptRegistrar at the end of your master page - just before closing the <body> tag. I have attached the updated project.

Regards,
Atanas Korchev
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Paul
Top achievements
Rank 1
answered on 03 Sep 2010, 11:47 AM
Hi Atanas,

Thanks for that, I've move the ScriptRegister to the end and the Insert is working great, however the Update and Delete are still not working. Is this the only thing that you changed? I cannot get the files that you sent back to work, there seems to be some corrupted information there so have just moved the ScriptRegister in my own version.

When updating after editing I get the message: "Error! The requested URL returned 500 - Internal Server Error" and the Delete simply does nothing after the confirm delete message.

Thanks,
Paul
0
Paul
Top achievements
Rank 1
answered on 03 Sep 2010, 04:47 PM
Hi Atanas,

Do you know why I receive this error "Error! The requested URL returned 500 - Internal Server Error". I've looked in the forums which suggest circular dependancies but the stripped down version only has 1 table.

Thanks,
Paul

0
Bill
Top achievements
Rank 1
answered on 04 Sep 2010, 06:48 AM
Are you using Entity Framework for MVC2? If you are, I think you still have to bind the grid to a ViewModel instead of the entity model directly. At least that's what I had to do. It doesn't matter if it's 1 entity/table.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<LABunkerSMS_WebRole.ViewModels.BargeLogViewModel>>" %>
    
<% Html.Telerik().Grid(Model)
        .Name("frmBargeLog1")
           .DataKeys(keys =>
           {
               keys.Add<int>(c => c.BargeLogId);
           })
         .DataBinding(dataBinding => dataBinding
            .Ajax()
                .Select("AjaxSelectBargeLog", "BargeLog", new { BargeId = ViewData["BargeId"]})
                .Update("Edit_BargeLog", "BargeLog"))
                
        .Columns(col =>
        {
            col.Bound(bat => bat.BargeLogId).Visible(false);
            col.Bound(bat => bat.BargeActivityName)
                .Title("Activity")
                .Width(200);
            col.Bound(bat => bat.BargeActivityTypeId);
            col.Bound(bat => bat.BargeActivityDateTime)
                .Format("{0:MM/dd/yyyy hh:mm tt}")
                .Width(150)
                .Title("Date & Time");
            
            col.Command(cmd => cmd.Edit().ButtonType(GridButtonType.Text));            
        })
        .Editable(editing => editing.Mode(GridEditMode.InLine))      
        .Render();         
%>  
0
Paul
Top achievements
Rank 1
answered on 04 Sep 2010, 10:38 PM
Thanks, Yes I am using the latest version of MVC2, Telerik 825

I was reading about this last night and it seems the best way forward design wise is to specifically define the model rather than use the entity model, not only to enable this to work correctly but also for performance reasons as the entity model will capture all other tables where it has relations which isn't very useful if you only need to capture a few fields.

Anyway, I have done that, works like a dream in getting rid of my "500 Error" but now the insert, edit and delete do not work. The Insert will actually put a row in, but not into the database, which suggests it isn't saving the changes. Similar with Edit.

I used Firebug to check this and the information is being posted perfectly when I change something with edit, however the response comes back with the original data. Therefore, all I can think is that there is an error somewhere in the saving to the database which I haven't quite figured out yet

My repository code is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using CRPManager.Models.Database;
using CRPManager.Models.Entities;
 
namespace CRPManager.Models.DataAccess
{
    public class DiscountTypeRepository : IDiscountTypeRepository
    {
        private TestDataContext _db;
 
        public DiscountTypeRepository()
        {
            _db = new TestDataContext();
        }
 
        public IList<DiscountTypeModel> AllDiscountTypes()
        {
            //return _db.GetTable<DiscountType>().ToList<DiscountType>();
 
            IList<DiscountTypeModel> resultList = (IList<DiscountTypeModel>)HttpContext.Current.Session["DiscountType"];
 
            if (resultList == null)
            {
                HttpContext.Current.Session["DiscountType"] = resultList =
                   (from d in _db.GetTable<DiscountType>()
                    select new DiscountTypeModel
                    {
                        discountTypeCode = d.DiscountType_code,
                        discountTypeDescription = d.DiscountType_desc
                    }).ToList<DiscountTypeModel>();
            }
 
            return resultList;
 
        }
 
        public DiscountTypeModel GetDiscountType(string code)
        {
            //DiscountTypeModel discountTypeModel = new DiscountTypeModel();
            //return All().Where(predicate).FirstOrDefault();
 
            //return _db.DiscountTypes.SingleOrDefault(d => d.DiscountType_code == code);
 
            var discountType = (
                from d in _db.GetTable<DiscountType>()
                select new DiscountTypeModel
                {
                    discountTypeCode = d.DiscountType_code,
                    discountTypeDescription = d.DiscountType_desc
                }).FirstOrDefault<DiscountTypeModel>();
 
            return discountType;
        }
 
        public void Add(DiscountTypeModel discountType)
        {
            discountType.discountTypeCode = AllDiscountTypes().OrderByDescending(d => d.discountTypeCode).First().discountTypeCode + 1;
            AllDiscountTypes().Insert(0, discountType);
        }
 
        public void Delete(DiscountTypeModel discountType)
        {
            //_db.DiscountTypes.DeleteOnSubmit(discountType);
 
            if (discountType != null)
            {
                // Need to remove this from the Discount Model List [session it]
                AllDiscountTypes().Remove(discountType);
            }
 
        }
 
        public void Save()
        {
            this._db.SubmitChanges();
        }
    }
 
}

My updated Controller is :

using System;
using System.Linq;
using System.Web.Mvc;
using CRPManager.Models.DataAccess;
using CRPManager.Models.Entities;
using Telerik.Web.Mvc;
using Telerik.Web.Mvc.UI;
 
namespace CRPManager.Controllers
{
    public class DiscountTypeController : Controller
    {
        private DiscountTypeRepository _discountTypeRepository = new DiscountTypeRepository();
 
        #region Views
 
        public ActionResult Index()
        {
            return View(_discountTypeRepository.AllDiscountTypes());
        }
 
        [GridAction]
        public ActionResult SelectAjax()
        {
            return View(new GridModel(_discountTypeRepository.AllDiscountTypes()));
        }
 
        #endregion
 
 
        #region Posts
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult SaveAjax(string code)
        {
            // Find an existing DiscountType
            DiscountTypeModel discountType = _discountTypeRepository.GetDiscountType(code);
 
            // Update it and Save
            TryUpdateModel(discountType);
            _discountTypeRepository.Save();
 
            // Rebind the grid
            return View(new GridModel(_discountTypeRepository.AllDiscountTypes()));
        }
 
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult InsertAjax()
        {
            // Create a new instance of the DiscountTypeModel class.
            DiscountTypeModel discountType = new DiscountTypeModel();
 
            // Perform model binding (fill the product properties and validate it).
            if (discountType != null)
            {
                if (TryUpdateModel(discountType))
                {
                    //The model is valid - insert the product.
                    _discountTypeRepository.Add(discountType);
                }
            }
 
            _discountTypeRepository.Save();
 
            // Rebind the grid
            return View(new GridModel(_discountTypeRepository.AllDiscountTypes()));
        }
 
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult DeleteAjax(string code)
        {
            // Find a discountType with code equal to the value passed in
            DiscountTypeModel discountType = _discountTypeRepository.GetDiscountType(code);
 
            //Delete the record
            _discountTypeRepository.Delete(discountType);
            _discountTypeRepository.Save();
 
            //Rebind the grid
            return View(new GridModel(_discountTypeRepository.AllDiscountTypes()));
        }
 
        #endregion
    }
}

I'm still trying to work this one out but if anyone has had any similar problems and solved it I'd love to see some of my weekend

Thanks
0
Bill
Top achievements
Rank 1
answered on 05 Sep 2010, 07:44 AM
As far as the Insert and Update, have you tried using a prefix or tried the other overloads? It 

        TryUpdateModel(discountType, "discountType");
        _discountRepository.Save();

I actually had issues with this as well and due time constraints just manually updated my entity object. However,
it does sound like it's a ViewModel issue rather than a Grid issue. Let me know if you figured out the issue.
          [HttpPost]
          [GridAction]
        public ActionResult Edit_BargeLog(BargeLogViewModel blvm, int BargeId)
        {
            BargeLog obl = _db.BargeLogs.First(bl => bl.BargeLogId == blvm.BargeLogId);
            try
            {
                obl.ModifiedBy = CurrentUserName;
                obl.ModifiedOn = DateTime.Now;
                TryUpdateModel(obl,"");
                obl.BargeActivityDateTime = blvm.BargeActivityDateTime;
                _db.SaveChanges();
            }
            catch (Exception ex)
            {
            }
            IEnumerable<BargeLogViewModel> blvms = from bargelog in _db.BargeLogs.Where(bl => bl.BargeId == BargeId)
                                                   select (new BargeLogViewModel
                                                   {
                                                       BargeLogId = bargelog.BargeLogId,
                                                       BargeActivityDateTime = bargelog.BargeActivityDateTime,
                                                       BargeActivityName = bargelog.BargeActivityType.BargeActivityName,
                                                       BargeActivityTypeId = bargelog.BargeActivityTypeId
                                                   });
            ViewData.Model = new GridModel(blvms);
            ViewData["BargeId"] = BargeId;
            return PartialView("~/Views/FuelGrade/Index_FuelGradeVM.ascx");
        }


0
Paul
Top achievements
Rank 1
answered on 06 Sep 2010, 03:30 PM
I have sorted this problem out now with a clear head.

First thing was I needed to use the variable "id" for my string in my delete and update as this is what is posted, it didn't like the variable I was using.

Secondly, I got rid of all the cached session stuff from the Telerik example, not really worth it when you are trying to refresh your screen and is just as quick to read it back using a short LINQ query. Thirdly, this was the root of my problem, I got so wrapped up storing in the Session and writing data to there that I wasn't actually writing anything to the database. So I deleted all that and started with a blank piece of paper that I could understand.

Finally, I had that 500 server again... this was caused because I was also attempting to update a primary key in the database table.

I've listed my updated Respository and Controller below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using CRPManager.Models.Database;
using CRPManager.Models.Entities;
 
namespace CRPManager.Models.DataAccess
{
    public class DiscountTypeRepository : IDiscountTypeRepository
    {
        private CRPDataContext _db;
 
        public DiscountTypeRepository()
        {
            _db = new CRPDataContext();
        }
 
        public IQueryable<DiscountTypeModel> AllDiscountTypes()
        {
            // Retrieves all the discount types and stores them in the model
            var allDiscountTypes =
               (from d in _db.GetTable<DiscountType>()
                select new DiscountTypeModel
                {
                    discountTypeCode = d.DiscountType_code,
                    discountTypeDescription = d.DiscountType_desc
                });
             
            return allDiscountTypes;     
        }
 
        public DiscountTypeModel GetDiscountType(string code)
        {
            // Gets the discount type for the specified code and stores it in the model
            var discountType = (
                from d in _db.GetTable<DiscountType>()
                where d.DiscountType_code == code
                select new DiscountTypeModel
                {
                    discountTypeCode = d.DiscountType_code,
                    discountTypeDescription = d.DiscountType_desc
                }).FirstOrDefault<DiscountTypeModel>();
 
            return discountType;
        }
 
        public void Add(DiscountTypeModel discountType)
        {
            // Need to create a new instance of DiscountType to insert the new model added
            DiscountType discountTypeDB = new DiscountType();
            discountTypeDB.DiscountType_code = discountType.discountTypeCode;
            discountTypeDB.DiscountType_desc = discountType.discountTypeDescription;
 
            _db.DiscountTypes.InsertOnSubmit(discountTypeDB);
        }
 
 
        public void Update(DiscountTypeModel discountType, string code)
        {           
            // Retrieve the old database row that requires updating and update the description
            DiscountType discountTypeDB = Row(code);
            discountTypeDB.DiscountType_desc = discountType.discountTypeDescription;  
        }
 
        // Reads a Single Row rom the Database based on a code specified, used for update/delete
        public DiscountType Row(string code)
        {
            return _db.DiscountTypes.SingleOrDefault(d => d.DiscountType_code == code);           
        }
 
 
        public void Delete(DiscountTypeModel discountType)
        {
            // Retrieve the database row that requires deleting
            DiscountType discountTypeDB = Row(discountType.discountTypeCode);
 
            if (discountType != null)
            {             
                // Delete it from the database
                _db.DiscountTypes.DeleteOnSubmit(discountTypeDB);
            }
        }
 
        public void Save()
        {
            this._db.SubmitChanges();
        }
    }
 
}


using System;
using System.Linq;
using System.Web.Mvc;
using CRPManager.Models.DataAccess;
using CRPManager.Models.Entities;
using Telerik.Web.Mvc;
using Telerik.Web.Mvc.UI;
 
 
namespace CRPManager.Controllers
{
    public class DiscountTypeController : Controller
    {
        private DiscountTypeRepository _discountRepository = new DiscountTypeRepository();
 
        #region Views
 
        public ActionResult Index()
        {
            return View(_discountRepository.AllDiscountTypes());
        }
 
        [GridAction]
        public ActionResult SelectAjax()
        {
            return View(new GridModel(_discountRepository.AllDiscountTypes()));
        }
 
        #endregion
 
 
        #region Posts
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult UpdateAjax(string id)
        {
            try
            {
                // Find an existing DiscountType
                DiscountTypeModel discountType = _discountRepository.GetDiscountType(id);
 
                // Update it and Save
                TryUpdateModel(discountType);
                _discountRepository.Update(discountType, id);
                _discountRepository.Save();
 
            }
            catch (Exception ex)
            {
                throw new ApplicationException(ex.Message);
            }
 
            // Rebind the grid
            return View(new GridModel(_discountRepository.AllDiscountTypes()));
        }
 
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult InsertAjax()
        {
            // Create a new instance of the DiscountType class.
            DiscountTypeModel discountType = new DiscountTypeModel();
 
            // Perform model binding (fill the product properties and validate it).
            if (discountType != null)
            {
                if (TryUpdateModel(discountType))
                {
                    //The model is valid - insert the product.
                    _discountRepository.Add(discountType);
                }
            }
 
            _discountRepository.Save();
 
            // Rebind the grid
            return View(new GridModel(_discountRepository.AllDiscountTypes()));
        }
 
 
        [AcceptVerbs(HttpVerbs.Post)]
        [GridAction]
        public ActionResult DeleteAjax(string id)
        {
            // Find a discountType with code equal to the value passed in
            DiscountTypeModel discountType = _discountRepository.GetDiscountType(id);
 
            //Delete the record and Save it
            _discountRepository.Delete(discountType);
            _discountRepository.Save();
 
            //Rebind the grid
            return View(new GridModel(_discountRepository.AllDiscountTypes()));
        }
 
        #endregion
    }
}
0
Freddy
Top achievements
Rank 1
answered on 23 Jan 2013, 03:26 PM
Paul do you could post you code from IDiscountTypeRepository? 

Thanks for any help and apologies for my poor english!
Tags
Grid
Asked by
Paul
Top achievements
Rank 1
Answers by
Atanas Korchev
Telerik team
Paul
Top achievements
Rank 1
Bill
Top achievements
Rank 1
Freddy
Top achievements
Rank 1
Share this question
or