Anyway to use ko.wrap instead of ko.mapping

Oct 11, 2012 at 10:07 PM

Hi,

We are having performance issues with MVC3controltoolkit on IE8. MVC3controltoolkit internally uses ko.mapping. Could you point me in right direction how can I use ko.wrap instead of ko.mapping with MVC3toolkit. 

Thanks

dealsboy

Coordinator
Oct 12, 2012 at 12:05 PM

It is true that ko.mapping is slow but I dont think it is the bottlenek. If you load hundreds of items an then  you use templating to put them on the screen, the time waste ko to build the templates and to parse their bindings is probably higher then the ko.mapping time. 

How many objects are you putting on the screen? I was able to build grids of about 1000 rows with acceptable performance. However, if you use IE8 from the VS 2010 in debug mode...it is very slow. Try launching IEA selecting "run without debug" from the debug menu, or better install the application on IIS and then acces the IIS app from IE8.

Another tip with IE8 is to avoid big tables. IE is ivery slow in creating big tables, You can "simulate" a table with floating and divs of predefined width, or you can make each row  with a li and then put inside each li a table with just one row. if all columns have a fixed width, and if the li is styled adequately you have the same effect of a table. However, as I said I think the bottleneck is the templating engine.

 

Pls pls give me more infos on your app, so I can do some performance measurement to see if it is possible to improve the overall ise of ko with the Mvc Controls Toolkit. If I see that if ko.wrap ACTUALLY allows a sensible increase in performance of the whole chain from transformind data to putting them on the screen I will provide a way to use it instead of ko.mapping ...If possible send me a selfcontained app showing the performance issue, you can use the contact form of my blog:

http://www.dotnet-programming.com/contact.aspx

Oct 12, 2012 at 7:20 PM

Thank you for quick reply.

We are using IE in "run without debug" mode. Main issue with out project is we are converting the whole server side view model to client side. We are loading 11 records but there are several child collections in this viewmodel. We can fix this issue by doing manual mapping. 

Is there anyway we can suppress the conversion by using data attributes (like "Not Serializable)? 

If you look at below profile data, visitPropertiesOrArrayEntries called 3675 times and it took 7064 milliseconds and arrayIndexOf called 30k times and it took 6396 milliseconds.

We are using Telerik grid to bind the data.

visitPropertiesOrArrayEntries 3,675 7,064.71 39.00 http://localhost:35790/Scripts/knockout.mapping-1.0.debug.js 375

arrayIndexOf 30,390 6,396.64 6,396.64 http://localhost:35790/Scripts/knockout.debug.js 63 \

get 14,806 5,309.53 34.00 http://localhost:35790/Scripts/knockout.mapping-1.0.debug.js 462

JScript - window script block 1 4,835.48 2.00 http://localhost:35790/DependentManager/ContinueToNext 289

updateViewModel 5,881 4,832.48 109.01 http://localhost:35790/Scripts/knockout.mapping-1.0.debug.js 156

JScript - window script block 5,757 4,832.48 31.00 http://localhost:35790/Scripts/knockout.mapping-1.0.debug.js 211

fromJS 1 4,832.48 0.00 http://localhost:35790/Scripts/knockout.mapping-1.0.debug.js 41

As you are aware of slow performance with ko.Mapping, giving ability to swap with ko.wrap is a good idea.

 

Thanks
dealsboy

 

 

Coordinator
Oct 12, 2012 at 10:17 PM

Yes you can suppress conversion with the NotSerializable attribute. I think this is the right way to proceed. However may question is why the ServerSide viemodel contains data that are not used in the web page?

First step should be to extract a ServerSide ViewModel from the business model.  You can use tools like automapper to do this if your models are very complex.

 

Further improvement can be reached by using the last release of knockout.mapping (2.3.1) that solved the main issue that caused the slow down, that is a look up table based on a simple list instead of an hash table. Download it from Github and build a new knockout.all-x.x.x.min.js substituting thie new release of the mapping plugin as explained here: http://mvccontrolstoolkit.codeplex.com/wikipage?title=Which%20Javascript%20file%20to%20include

"knockout.all-x.x.x.min.js includes,  knockout-2.0.0.jsknockout.mapping-2.0.0.js, and MvcControlToolkit.Bindings-x.x.x.js minimized with YUI compressor. If you would like to upgrade to a newer version of the knockout you can build yourself this file."

Telerick grid for sure is fater than using ko templating because it doesnt use templates....however you pay this with flexibility ...it has just predefined row templates.  However, since you just show 11 record...your problem is not the grid rendering for sure.If you need rendering several thousands objects you may consider using the SlickGrid that is quite fast on big amounts of data.

 

Last recommendation. If you DONT use knockout bindings and observables and you just need a normal javascript model be transferred on the client side...you can avoid knockout.mapping processes at all your model by turning off the processing of the model for adding it observables and observable arrays. You can do this by using a lower level API instead of ClientBlocks. see here: http://mvccontrolstoolkit.codeplex.com/wikipage?title=Client-Side%20ViewModel%20%26%20Bindings.

Namely use ClientViewModel  helper with applyDependencies set to false.

 

Anyway in the nex release I will give the possibility to use knockout.wrap. 

Oct 24, 2012 at 8:26 PM
Edited Oct 24, 2012 at 10:23 PM

I updated Nu Get package to use latest MVC3toolkit and we ran into an issue.


If you see the below code, we are changing the form action method to point to a different action and pass the viewModel to the new action. ViewModel is null in the new action method.


On the page load we are pointing to a different action and on click of a button we are changing the action method.

We are using ExDefaultBinder as our default model binder.

It used to work and now it is not.

Do you think it is a bug ? If not, could you point me into right direction.

Thanks

dealsboy


$("form").first().attr('action', utilities.GetUrl());

clientViewModel.saveAndSubmit();


Coordinator
Oct 25, 2012 at 12:11 AM
Edited Oct 25, 2012 at 2:46 PM

First  of all from several version you dont need anymore to use SaveAndSubmit() it is enough to trigger the submit of the form the ViewModel was rendered in. That is $("form").first().submit() is enough to submit the ViewModel.

Second, we were forced to modify the ExDefaultModelBinder because of incompatibilties with the Mvc4 model binder (that has some strange behaviour...ie bugs) so it is possible that in some extreme situation it behaves differentlty. I dont thingk the problem is the fact that you changed the action method...you can do this if the new action method has a ViewModel that is compatible with the original ViewModel.  Moreover we added more options to some ClientViewModelRendering functions...so maybe we introduced incompatibilities in some expreme situations

In particular you might experience problems with model binding based on the names of the parameters of the action method. However this problems doesnt depend on us but  it depends in turn on modifications done in the Mvc4 release of the DefaultModelBinder. Anyway these problems can be fixed easily.

Could you please post:

1) The View and the ViewModel used to render the View

2) The receiving action method

3) Are you working with Mvc 3 or Mvc 4?

Oct 25, 2012 at 9:35 PM

We are using MVC 3.0. I am trying to create a sample with the issue but it is working fine in this sample.  Below sample is very similar to what we are doing. 

 

 

public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            ViewBag.Message = "Welcome to ASP.NET MVC!";
            var customer = new Customer();
            customer.Firstname = "deals";
            customer.Lastname = "boy";
            return View(customer);
        }

        [HttpPost]
        public ActionResult Edit(Customer customer)
        {
            return View("Index", customer);
        }

        [HttpPost]
        public ActionResult Save(Customer customer)
        {
            return View("Index", customer);
        }
    }

 

 

@using BfMall.Web.Mvc.Knockout
@using ExModelBinder.Controllers
@using MVCControlsToolkit.Controls.Bindings
@using Microsoft.Web.Mvc
@model ExModelBinder.Models.Customer
@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>
@using (Html.BeginForm("Save", "Home"))
{
    var clientViewModel = Html.ClientViewModelWithTracking("customer", m => m);
    
    <p>
         @Html.TextBoxFor(vm => vm.Firstname,
                            new
                            {
                                data_bind = clientViewModel.ValueWithUpdate(vm => vm.Firstname, "change").Get()
                            })
         @Html.TextBoxFor(vm => vm.Lastname,
                            new
                            {
                                data_bind = clientViewModel.ValueWithUpdate(vm => vm.Lastname, "change").Get()
                            })
        <button id="editButton" onclick="edit()">Edit</button>
    
    </p>
}
<script type="text/javascript">

    function edit() {
            $("form").first().attr('action', "\\home\\Edit");
            clientViewModel.saveAndSubmit();

        }
    
   
   
</script>

public static IBindingsBuilder<T> ClientViewModelWithTracking<VM, T>(this HtmlHelper<VM> htmlHelper,
            string uniqueName, Expression<Func<VM, T>> expression,
            string htmlElementId = null,
            bool initialSave = true,
            bool applyBindings = true,
            bool applyDependencies = true)
            where T : class, new()
            where VM : class, new()
        {
            //If no view model was passed to the view, we use the default constructor, 
            //this is required so that the knockoutJs framework has an object to serialize as a
            //client side view model.
            if (htmlHelper.ViewData.Model == null)
            {
                var viewModel = Activator.CreateInstance(typeof(VM)) as VM;
                htmlHelper.ViewData.Model = viewModel;
            }
            htmlHelper.ViewContext.Writer.WriteChangeTrackingScript(uniqueName);
            //htmlHelper.WriteWorkflowTrackingScript();

            return htmlHelper.ClientViewModel<VM, T>(uniqueName, expression,
                    htmlElementId, initialSave, applyBindings, applyDependencies);
        }

 

 

Coordinator
Oct 25, 2012 at 10:40 PM

I dont think the problem is connected in any way to the fact that you change the action of the form...but for sure with something else that is in your application. Do you have several ClientViewModels in the same form, in the original application (I suspect...yes)?

This is not allowed if the two ClientViewModels have the same field prefix (you can specify the field prefix when creating the ClientViewModel) because it creates potential name conflicts in the model binder. This is a general rule of Mvc, that si not specific for the knockout extensions: if you have data from several objects they must have different field prefixes to avoid name conflicts.

If this is the case, please, show me the two client view model creaction instruction and I will say how to fix.

In any case case putting in a form fields that NEED NOT BE SUBMITTED is not a good practice(waste of bandwidth) so if you have alternative data to send to the server it is better to put them in different forms someway....There are several way to fix this by creating a new form before submission and copynG there only the fileds you are interested in. However this can be accomplished EASILY just if all fiElds have different prefixes, for instance: 

 

OfficeAdress.Street, OfficeAdress.City and Home.Street Home.Adress. This way it is easy to copy for instance only the OfficeAdress fields in a new dynamic form created just to submit data with jquery selectors that selects just all fields prefixed with "OfficeAdress"

Oct 26, 2012 at 10:34 PM
Edited Oct 26, 2012 at 10:55 PM

We are using a single view model. This is how we get client side view model.

var clientViewModel = Html.ClientViewModelWithTracking("customer", m => m);

In the client side view model, we have code that does change tracking. The way we implemented is to we find all the observables and recursively find properties and subscribe to them. When they change we set the "State" to modified. We use this state on server side to map to our domain models. Since we are using recursion we are having performance issues on IE8.

 

http://nuget.org/packages/Knockout.ChangeTracker

Above plugin will give me the capability of dirty flag but I need every property that got changed. I see some change tracking capability in your tool kit. Does it give me every single property that got changed ?

Coordinator
Oct 27, 2012 at 1:11 AM

I have the updatesManager that tracks collections of objects and it is able to send to the server all objects that changed that is the list of all deleted items, the list of all newly created itemsm and the list of all modified items. However, it doesnt track each single property

You can send two models on the client one that you modify and another "reference" to compare. Then you submit both of them and compare each property on the server to discover the changed properties. In the "data moving plugin" I have an helper that do this job automatically on any Mvc ViewModel. However it will be commercialized at end of november, and this plugin hase also knockout extensions to undo and redo observables changes. When you create a viewmodel you just specify you want undo-redo capabilities...and done. 

However sincerely I dont understand the very nature of your issue...subscriptions should not cause such performance issues if well done.  Maybe your subscription code ...doesnt works well and do sevral llops on each change...that is why you have the issue.

In any case a better approach is to dirty yourself each property by creating an observable extension that add a dirty flag to the observablewhen it changes(or store the initial property. Then before submitting the model you process it on the client to create the list of all changed properties. You can also send to the server a list of objects containing: property changed name old value, new value. It is better to do this processing just before submitting the model once and for all instead triggering subscriptions each time something changes.

 

So were you able to solve the original issue? That is the viewmodel returning null? If you dont use two viewmodels and if the simplified code you showed me is working...I think there is something wrong in your code...Try to add always more features to the simplified code you showed me till you trandorm it in the actual code you need. Doing this you will discover for sure what is causing the problem

Nov 6, 2012 at 5:03 PM
Edited Nov 6, 2012 at 5:03 PM

I downloaded your source code and debugged it. One of the line in "ExportToModel" method is commented out. After uncommenting the line it is started working. It seems JavaScriptSerializer not able to serialize JSON DateTime's. Once the serialization fails you are returning null.

 

public object ExportToModel(Type TargetType, params object[] context)
        {
            if (string.IsNullOrWhiteSpace(JSonModel)) return null;
            if (!TargetType.IsAssignableFrom(typeof(T)))
                throw (new NotSupportedException(string.Format(ControlsResources.NotCompatibleTypes, typeof(T).FullName, TargetType.FullName)));
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            //JSonModel = dateRewriteOut.Replace(JSonModel, "\"\\/Date($1)\\/\"");
            T result = serializer.Deserialize<T>(JSonModel);
            Validate(result, null, null, prefix);
            return result;
        }

It seems there are several performance improvements in KO 2.1. Is there any plan to update to latest framework ? We tried to update on our side and we are getting errors. It seems there are breaking changes with new KO framework.

Coordinator
Nov 6, 2012 at 6:53 PM

Hi @dealsboy,

That line is commented out because now the job done by that line is done on the client side, but you are right there is a bug on the client side. I explain better While the save function of the viewmodel works this way:

viewModel.save = function () {        document.getElementById(jsonHiddenId).value = mvcct.utils.stringify(ko.mapping.toJS(this));    };

that is it calls an internal mvc controls toolkit stringify function that do the job of processind dates in the right way, the validateAndSave function use the standard browser stringify : ko.mapping.toJSON(this);

In fact the  ko.mapping.toJSON function just call the standard browser stringify.

 

I advice to comment out the line there again and do one of the following:

1) dont use validateAndSave that is obsolete. just submit the form the model is in when the model is rendered it hooks the submit event and save automatically the viemodel applying just the save function that works correctly, because whe you submit the form validation is performed automatically.

2) correct the saveAndSubmit by sumstituting ko.mapping.toJSON(this); with mvcct.utils.stringify(ko.mapping.toJS(this)); 

This is the correct way, otherwise that line that you uncommented might create problems in other situations!

 

In order to build a new knockout-all file you must include in the order

1) the ko js file minimized (just copy the minimized version)

2) the ko.mapping js file minimized (just copy the minimized version)

3) the MvcControlsToolKit.Controls.Bindings file THAT YOU MUST MINIMIZE with the yohoo minimizer here: 

http://www.refresh-sf.com/yui/

 

The next release of the MvcControlsToolkit will include the last releases of the knockout engine. I already have everything ready for the next release but I am waiting to finish the debug of the "DataMoving plugin" because some problems with this plugin might force me to apply changes also to the  mvc controls toolkit.

Anyway if you pack youself the new files as I explained everything should work also with the new version of ko. (I already tested it)

Nov 6, 2012 at 7:12 PM
Edited Nov 6, 2012 at 7:53 PM

 

function MvcControlsToolkit_ClientViewModel_Init(viewModel, jsonHiddenId, validationType) {
    viewModel.save = function () {
        document.getElementById(jsonHiddenId).value = mvcct.utils.stringify(ko.mapping.toJS(this));
    };
    viewModel.validateAndSave = function () {
        if (MvcControlsToolkit_FormIsValid(jsonHiddenId, validationType)) {
            document.getElementById(jsonHiddenId).value = ko.mapping.toJSON(this);
            return true;
        }
        return false;
    }
    viewModel.saveAndSubmit = function () {
        if (this.validateAndSave()) {
            $('#' + jsonHiddenId).parents('form').submit();
        }
    };
    viewModel.saveAndSubmitAlone = function (formId) {
        if (MvcControlsToolkit_FormIsValid(formId, validationType)) {
            this.save();
            $('#' + jsonHiddenId).parents('form').submit();
        }
    }
    $(document).ready(function () {
        $('#' + jsonHiddenId).parents('form').submit(function () {
            viewModel.save();
            return true;
        });
    });
}

In the above saveAndSubmit, there is no ko.mapping.toJSON(this). It is in validateAndSave. We use saveAndSubmit(). I am using  MvcControlToolkit.Bindings-2.0.0.js file.

 

After following your suggestion we are getting this error on one our page.  (Only on that page in IE8 browser. Not happened on Firefox.)


  Unexpected call to method or property access.  knockout.all-latest.min.js, line 1 character 24596

Coordinator
Nov 6, 2012 at 9:29 PM

Substitution is to be done in validateAndSave that in turn is called by your function. 

Now I ma using knockout 2.2 ...I dont know if latest....is reliable and knockout.mapping 2.3.2.

Try with the above files and try also to simply submit the form without calling at all saveAndSubmit, maybe the problem arise because of code that is executed twice when you call saveAndSubmit, since on submit the submit handler save again the viewmodel because of the hook that is in

MvcControlsToolkit_ClientViewModel_Init: 

:

$(document).ready(function () {
        $('#' + jsonHiddenId).parents('form').submit(function () {
            viewModel.save();
            return true;
        });
    });

Nov 7, 2012 at 7:38 PM

I am using   $("form").submit(); and I am still getting JSON error.  These are the changes I made. I am not using saveAndSubmit function.

 

function MvcControlsToolkit_ClientViewModel_Init(viewModel, jsonHiddenId, validationType) {
    viewModel.save = function () {
        document.getElementById(jsonHiddenId).value = mvcct.utils.stringify(ko.mapping.toJS(this));
    };
    viewModel.validateAndSave = function () {
        if (MvcControlsToolkit_FormIsValid(jsonHiddenId, validationType)) {
            document.getElementById(jsonHiddenId).value = mvcct.utils.stringify(ko.mapping.toJS(this));
            return true;
        }
        return false;
    }
    viewModel.saveAndSubmit = function () {
        if (this.validateAndSave()) {
            $('#' + jsonHiddenId).parents('form').submit();
        }
    };
    viewModel.saveAndSubmitAlone = function (formId) {
        if (MvcControlsToolkit_FormIsValid(formId, validationType)) {
            this.save();
            $('#' + jsonHiddenId).parents('form').submit();
        }
    }
    $(document).ready(function () {
        $('#' + jsonHiddenId).parents('form').submit(function () {
            viewModel.save();
            return true;
        });
    });
}

Coordinator
Nov 7, 2012 at 8:19 PM

You have done the right correction, my only doubt is the version of ko, since 

So the problem is just on IE8 right? IE9 is ok or not? 

If you use the version of  ko that ships with Mvc Controls Toolkit is everything ok or not ? 

At moment I am using IE9, Chrome, Firefox, Safari and the toolkit appears to work with the 2.2 release of ko, but I have no IE8 machine. 

Can you send me a self contained project showing the problem at francesco@dotnet-programming.com

Nov 7, 2012 at 8:41 PM
Edited Nov 7, 2012 at 9:32 PM

I am using all the defaults we get from latest MVC3ToolKit Nuget 2.3 package except ko.mapping.2.3.2.

Coordinator
Nov 7, 2012 at 10:07 PM

How can send you the knockout.all-2.4.0.min.js file that is working for me? It contains the last bits of knockout, knockout.mapping and mvc controls toolkit. 

Nov 7, 2012 at 10:15 PM

Please send it to dealsboy@gmail.com

Nov 8, 2012 at 5:19 PM
Edited Nov 8, 2012 at 8:53 PM

After using the file you sent, we are getting below errors.

Same error as before. (All browsers)

{"\\/Date(-62135575200000)\\/ is not a valid value for DateTime."}

 

Getting below error on one our page. Only on IE8. This issue is most probably a KO issue.

Unexpected call to method or property access.  knockout.all-2.4.0.min.js, line 163 character 23

 

UPDATE

 

I was able to fix "Unexpected call to method or property access" issue by changing from text binding to value binding for datetime values.

 @Html.HiddenFor(emp => e.DateOfDeath,

   new {data_bind = "value: $.telerik.formatString('{0:d}', new Date(e.DateOfDeath()))"})

Coordinator
Nov 8, 2012 at 9:34 PM

The file that I sent doesnt contain the fix for the saveAndSubmit method so you have to submit the form as I already explained!!!

The issue on the text binding doent depend on my toolkit...I dont touch the text binding.  I redefined the value binding...instead to empower it in different ways...maybe this is the reason why it works :) Anyway, to show the date in the correct format you can suse the _D helper. This helper allow you specify the format string into attributes. However it works just if you use the Client Blocks feature of the Mvc Controls Toolkit.

 

Anyway maybe also the text binding works if you use a function in the binding:

text: function(){ return $.telerik.formatString('{0:d}', new Date(e.DateOfDeath()));}

Using directly instruction in the bindings often creates problems I always use either simple expressions like adress.street or I enclose everything into a function

Nov 8, 2012 at 9:45 PM

I am using $("form").submit(); to submit my form.

Coordinator
Nov 9, 2012 at 1:27 PM

VERY STRANGE, because the version with the file that you packaged with ko 2.2 ecc worked for you,...so why thi error is appearing again with the file that I sent...that should be...sunstantially the same. Moreover, it works for me also on projects that use dates...so it is very strange! I sent to your emai a project containing dates that is working with the same file I sent you!.

 

I suspect that it doesnt work because the dates received by the server is out of the range of valid dates. This might in turn depend by the fact that some ko binding put in the model some "wrong date". Now since you put on the screen dates with the value binding...this might depend on this! The value binding you used uses a custom function instead of passing date as it is for ko, so ko is unable to pass a valid date to your model(the binding works in a direction only)...and this causes in turn all problems.

The value binding of has been improved by the Mvc Controls Toolkit, so it is able to understand all .net types date included. Moreover you can format date by simply using data annotations on the property without formatting them in the binding! This way the binding is fulli bi-directions. In order to specify the date format you can use the FormatAttribute. However, properly handling date formats reuires dates localization in the culture being used so you might need to install some js globalization library: http://mvccontrolstoolkit.codeplex.com/wikipage?title=Globalization

Anyway try also using Text binding in the way I suggested(with everything enclosed in a function), and give a look to the project I sent you,.