views:

129

answers:

2

Here's a simple problem: users want to edit products in grid-like manner: select and click add, select and click add... and they see updated products list... then click "Finish" and order should be saved.

However, each "Add" have to go to server, because it involves server-side validation. Moreover, the validation is inside domain entity (say, Order) - that is, for validation to happen I need to call order.Add(product) and then order decides if it can add the product.

The problem is, if I add products to order, it persists changes so even if users do not click "Finish" the changes will still be there!

OK, I probably shouldn't modify the order until users click Finish. However, how do I validate the product then? This should be done by the order entity - if product is already added, if product does not conflict with other products, etc.

Another problem, is that I have to add product to order and "rebuild view/HTML" based on its new state (as it can greatly change). But if I don't persist order changes, the next Add will start from the same order each time, not from the updated one. That is, I need to track changes to the order somehow.

I see several solutions:

  1. Each time the user click Add, retrieve order from database, and add all new products (from the page), but do not persist it, just return View(order). The problem is I cannot redirect from POST /Edit to GET /Edit - because all the data only exists in the POST data, and GET lose it. This means that Refresh page doesn't work in a nice way (F5 and you get duplicated request, not to mention the browser's dialog box)).
    • Hm, I thought I can do redirect to GET using TempData (and MvcContrib helper). So after POST to /Edit I process business logic, gets new data for view, and do RedirectToAction<>(data) from MvcContrib that passes data via TempData. But since TempDate is... temp... after F5 all the data is lost. Doesn't work. The damn data should be stored somewhere, this way or another.
  2. Store "edit object" in Session with the POST data (order, new products info). This can also be database. Kind of "current item - per page type". So page will get order ID and currently added products from this storage. But editing from multiple pages is problematic. And I don't like storing temp/current objects in Session.
  3. Marking products as "confirmed" - if we do /order/show, we first cleanup all non-confirmed products from the order. Ugly and messy logic.
  4. Make a copy of the order - a temporary one - and make /Edit work with it. Confirm will move changes from temp order to persisted. A lot of ugly work.
  5. Maybe some AJAX magic? I.e. "Add" button won't reload page but will just send new + already added products to server, server will validate as order.Add(products + newproduct) but will not persist changes, will just return updated order information to re-build the grid. But Refresh/F5 will kill all user-entered info.
  6. What else?

Is this problem common? How do you solve similar ones? What's the best practices?

A: 

It depends a lot on how you implement your objects/validation, but your option number 5 is probably the best idea. If AJAX isn't your thing, you can accomplish the same thing by writing the relevant data of already-added-but-not-saved entries to hidden fields.

In other words, the flow ends up something like this:

  1. User enters an item.

  2. Item is sent to the server and validated. The view is returned with the data entered by the user in hidden fields.

  3. User enters a second item.

  4. Item is sent to the server, and both items are validated. The view is returned with the data for both items in hidden fields.

  5. etc.

So far as F5/Refresh killing entered data... In my experience this isn't too much of a problem. A more pressing concern is the back/forward buttons, which need to be managed with something like Really Simple History.

If you DO want to make the page continue to work after a refresh, you need to do one of the following:

  1. Persist the records to the database, associated with the current user in some way.
  2. Persist the records to session.
  3. Persist the records to the query string.

These are the only storage locations available that persist through both redirection and refreshes.

AaronSieb
Ajax is my thing, and I don't need hidden - I actually need to display the data to the user (i.e. user entered Product1, I need to display also price AND relations to other products in the order). Well, maybe F5 is not a problem, I'll need to discuss it with customers. But in general this would be irritating (to me at least). I'll updated question with the way you suggested (persisting).
queen3
Also, I'm a little confused by your references to not being able to click the refresh button being annoying. Do you regularly refresh pages when you are half-way through filling out a form?
AaronSieb
Sorry I must be missing some point about those hidden fields... using fields to store data is option #1 that I listed. The problem is that F5 sends last request again to the server which will result in duplicated data. Hm, I think I can workaround with TempData (see update). As for refreshing, imagine you pressed this accidentally and lost all entered data (with AJAX); or you use it to "reset" form - as I often do - but instead get dialog box and duplicated POST to the server - annoying.
queen3
Hm, TempData obviously won't work. Well, I hoped to see a common solution to a common problem, but seems it is not common...
queen3
For the hidden fields, I've expunged my comment. Sounds like you knew what I was talking about, but I misinterpreted your comment.
AaronSieb
Regarding F5 -- Hitting it accidentally is annoying, but there are a lot of buttons in the browser (like home, go, back, forward, favorites, etc.) that can have destructive properties. I think you would be better off implementing a warning message like this site does (by handling the OnUnload event) than trying to circumvent the default behavior of the buttons.
AaronSieb
After all, if you use session, database, viewstate, tempdata, etc. you can't use F5 to "reset" the form anymore :)
AaronSieb
This page deals with creating a confirmation prior to allowing a page to unload: http://www.openjs.com/scripts/events/exit_confirmation.php
AaronSieb
A: 

If I were you, I would come up with something which resembles option 5. And since you say that you are comfortable with Ajax you can try this. But before you do this, you should move your validation logic outside the Order.Add() method. Maybe you can move it to another public function called Validate() which returns a bool. And you can still call the same Validate() in the Add() method, thereby doing the necessary validation before you add the order.

  1. Try to do the validation on the client side. If you are using jQuery, you can use the jquery validate plugin. But, if this is not possible for some reason (such as when you need to validate stuff against a database). You should do your validation on the server side and just return a JSON object with the 'success' boolean flag and an optional message, just a way to mark that the data is valid. You would allow the user to add a new product only if the previous Order was valid.
  2. And when the user hits finish send the product to the server and do the validation again, but persist the order in this round-trip.

Now, If I had a complete say in this, I wouldn't even go to the extent of doing validation whenever a product is added/edited. I would just do the validation whenever the customer hits finish. That would be the simplest solution. But, maybe I am missing something.

Khaja Minhajuddin
It's not just validation, it's entity changes... say, when you add product, other (added) products have their "buddy" count recalculated... there should be a server call for this as it's not nice to rewrite business rules in client-side scripts.
queen3