How to load related objects through MVC4 result

I get a simple DTO object loaded into my view model, which is happily viewed through Knockoutjs.

My DTO A contains List objects. Therefore, I can provide elements inside A.

again:

class A { someprop; List<B> childB; } Class B { somepropB; } 

So far so good. I can iterate over data without any problems. But if I change "someprop" inside the instance of A and SaveAll, the server will not respond at all. The updateData controlle method is not even called. If I cleared childB.Clear () before passing it to the client, everything is fine.

It seems that it was not possible to update objects with collections?

+1
asp.net-mvc-4 upshot
source share
1 answer

There is little work if you want this scenario to work. The result only rotates the parent objects into observable elements. Thus, only the javascript view of class A is an observable knockout, the javascript view of class B is not. Therefore, Upshot is not aware of any changes to related objects.

The solution is to display objects manually. To make my life easier, I used the code from my sample DeliveryTracker application in the code snippets below. In my blog article, you can see an example of manual matching: http://bartjolling.blogspot.com/2012/04/building-single-page-apps-with-aspnet.html , so my examples below work on β€œdelivery” and "customer" objects.

Server Side Domain Model

 namespace StackOverflow.q9888839.UploadRelatedEntities.Models { public class Customer { [Key] public int CustomerId { get; set; } public string Name { get; set; } public string Address { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public virtual ICollection<Delivery> Deliveries { get; set; } } public class Delivery { [Key] public int DeliveryId { get; set; } public string Description { get; set; } public bool IsDelivered { get; set; } [IgnoreDataMember] //needed to break cyclic reference public virtual Customer Customer { get; set; } public virtual int CustomerId { get; set; } } public class AppDbContext : DbContext { public DbSet<Customer> Customers { get; set; } public DbSet<Delivery> Deliveries { get; set; } } } 

Data service controller

The data service controller provides data that conforms to OData standards to "HTTP: // local: [yourport] / api / DataService / GetCustomers". To be able to update both customers and deliveries, you need to define the UpdateCustomer AND UpdateDelivery function

 namespace StackOverflow.q9888839.UploadRelatedEntities.Controllers { public class DataServiceController : DbDataController<AppDbContext> { //Service interface for Customer public IQueryable<Customer> GetCustomers() { return DbContext.Customers.Include("Deliveries").OrderBy(x => x.CustomerId); } public void InsertCustomer(Customer customer) { InsertEntity(customer); } public void UpdateCustomer(Customer customer) { UpdateEntity(customer); } public void DeleteCustomer(Customer customer) { DeleteEntity(customer); } //Service interface for Deliveries public void InsertDelivery(Delivery delivery) { InsertEntity(delivery); } public void UpdateDelivery(Delivery delivery) { UpdateEntity(delivery); } public void DeleteDelivery(Delivery delivery) { DeleteEntity(delivery); } } } 

Client Side Model

Add a new javascript file containing your client model. Here, I explicitly turn each property into an observable knockout. The key to solving your problem is the line inside the constructor of the Customer object, where I map the incoming deliveries to the observed array

 /// <reference path="_references.js" /> (function (window, undefined) { var deliveryTracker = window["deliveryTracker"] = {}; //clear namespace deliveryTracker.DeliveriesViewModel = function () { // Private var self = this; self.dataSource = upshot.dataSources.Customers; self.dataSource.refresh(); self.customers = self.dataSource.getEntities(); }; deliveryTracker.Customer = function (data) { var self = this; self.CustomerId = ko.observable(data.CustomerId); self.Name = ko.observable(data.Name); self.Address = ko.observable(data.Address); self.Latitude = ko.observable(data.Latitude); self.Longitude = ko.observable(data.Longitude); self.Deliveries = ko.observableArray(ko.utils.arrayMap(data.Deliveries, function (item) { return new deliveryTracker.Delivery(item); })); upshot.addEntityProperties(self, "Customer:#StackOverflow.q9888839.UploadRelatedEntities.Models"); }; deliveryTracker.Delivery = function (data) { var self = this; self.DeliveryId = ko.observable(data.DeliveryId); self.CustomerId = ko.observable(data.CustomerId); self.Customer = ko.observable(data.Customer ? new deliveryTracker.Customer(data.Customer) : null); self.Description = ko.observable(data.Description); self.IsDelivered = ko.observable(data.IsDelivered); upshot.addEntityProperties(self, "Delivery:#StackOverflow.q9888839.UploadRelatedEntities.Models"); }; //Expose deliveryTracker to global window["deliveryTracker"] = deliveryTracker; })(window); 

View

In index.cshtml, you initialize Upshot, set up custom client mapping, and bind viewmodel

 @(Html.UpshotContext(bufferChanges: false) .DataSource<StackOverflow.q9888839.UploadRelatedEntities.Controllers.DataServiceController>(x => x.GetCustomers()) .ClientMapping<StackOverflow.q9888839.UploadRelatedEntities.Models.Customer>("deliveryTracker.Customer") .ClientMapping<StackOverflow.q9888839.UploadRelatedEntities.Models.Delivery>("deliveryTracker.Delivery") ) <script type="text/javascript"> $(function () { var model = new deliveryTracker.DeliveriesViewModel(); ko.applyBindings(model); }); </script> <section> <h3>Customers</h3> <ol data-bind="foreach: customers"> <input data-bind="value: Name" /> <ol data-bind="foreach: Deliveries"> <li> <input type="checkbox" data-bind="checked: IsDelivered" > <span data-bind="text: Description" /> </input> </li> </ol> </ol> </section> 

results

When you go to the index page, the list of customers and related deliveries will be loaded asynchronously. All deliveries are grouped by customer and pre-installed using the flag associated with the IsDelivered delivery property. The client name is also being edited because it is associated with the INPUT element

I don't have enough reputation to post a screenshot, so you have to do without it

When you check or uncheck IsDelivered Upshot will detect the change and send it to the DataService controller

 [{"Id":"0", "Operation":2, "Entity":{ "__type":"Delivery:#StackOverflow.q9888839.UploadRelatedEntities.Models", "CustomerId":1, "DeliveryId":1, "Description":"NanoCircuit Analyzer", "IsDelivered":true }, "OriginalEntity":{ "__type":"Delivery:#StackOverflow.q9888839.UploadRelatedEntities.Models", "CustomerId":1, "DeliveryId":1, "Description":"NanoCircuit Analyzer", "IsDelivered":false } }] 

When changing the client name, Upshot will send the changes when the input field loses focus

 [{ "Id": "0", "Operation": 2, "Entity": { "__type": "Customer:#StackOverflow.q9888839.UploadRelatedEntities.Models", "Address": "Address 2", "CustomerId": 2, "Latitude": 51.229248, "Longitude": 4.404831, "Name": "Richie Rich" }, "OriginalEntity": { "__type": "Customer:#StackOverflow.q9888839.UploadRelatedEntities.Models", "Address": "Address 2", "CustomerId": 2, "Latitude": 51.229248, "Longitude": 4.404831, "Name": "Rich Feynmann" } }] 

So, if you follow the above procedure, Upshot will update the parent and child objects for you.

+3
source share

All Articles