I feel like I am fighting against the current when I develop ASP.NET Webform apps. I frequently run into the same problems, and while I eventually find some kind of workaround i'm never fully satisfied with the results.
Here is an example of a typical problem:
The design requires a grid or grid-like result set. This result set is pulled from a database, however, there are additional controls on each row that are not data bound, but their contents are used to insert data into other records.
A good example of this would be displaying a list of products, then adding selected products to a shopping cart based on values entered into quantity fields, and options selected per product. Throw into the mix that you have to allow multiple lines to be added to the cart at the same time, and it starts to get more complex.
Let's add to that mix that you can't select certain products together (mutually exclusive), that you can only select a certain number of one product, but not another, that prices may change while the user is selecting their items, that you you get an overall discount per item based on quantity purchased (both per item and overall order), that you are using a line of credit and cannot exceed the line of credit, nor can you buy more of a given item than an arbitrary amount set in your account or in the product by your account representitive (think certain over the counter medications that the government limits how many you can buy), etc.. etc.. etc..
What starts out as a simple grid with an add to cart becomes a hopeless mess of business logic, which then of course requires validation and notification to the user of various errors in their choices.
How does one deal with very complex data entry schemes in asp.net? How do you even begin to design a piece of software to do all this?
EDIT:
Please don't suggest changing the interface, as the interface is not the problem. Users are fine with it, and they demand that it function the way it does. I'm looking for help on how to design and solve the problem of implementing it.
Don't put anything other than the basic validation in your code behind. The code behind should just take what the user entered, build a business object (or collection of business objects) and let those business objects validate themselves.
Each business rule should be a single function call on the business object that deals with just the one rule and nothing else. You then simply call them one after another and keep track of which ones pass and which ones fail.
When a validation fails, the business objects can provide the code behind enough information that it can display the correct errors and highlight the fields that have errors.
Some options to consider:
Separate out your validation logic so you can call it from both client-side and server-side code (enables next point).
Use AJAX to perform server-side validation and provide immediate feedback as the user performs various tasks, ie increases quantity.
Provide abundant and clear instructions/feedback to the user both before and after any actions are performed. Before: warn that product X can't be bought with product Y (especially if Y is already in the cart). After: explain the problem exactly and suggest ways to correct, eg "why don't you remove product Y?")
Fail gracefully, ie if only one product fails validation then ensure all other products are added.
Simplify the entry process, eg only allow one product to be added to the cart at a time.
The last point is important. A complicated data entry process can confuse users before they start, and makes trying to understand the numerous validation errors difficult. And this is even before you start coding the validation logic.
Related
What would be considered best practice for implementing a persistent shopping cart in an ASP.net Web Forms(*) based application? The only built-in way seems to be involving the Session state, which is not ideal because once you close the browser... it's gone. One way seems to be involving the localStorage via Javascript, but that creates awkward client/server mixups, as the data processing is meant to be done server side.
(* please pay attention to that part - MVC or Blazor based solutions will not work for this particular case)
Well, I can't imagine any practical application that involves database operations having ANY kind of suggesting that some kind of session, or view state will have ANY kind of relevance to database operations?
Be it a person filling out an invoice, or making orders or anything?
Not at all different then if you were writing a desktop application.
If a user is building up a list of items or "things" for/in an order?
Then as they build up such an order, then a database "order" record and structure will be built up. No different than say even building an invoice system say for the desktop with ms-access.
The only "use" of say session in most cases allows you to pass information from one page to another. (In place of say using parameters in the URL).
So, person might have a shopping basket of items (in your database, right!!!!).
They browse/search for more things to add to that shopping cart.
So say while on some items page, they choose to buy/select. When they make that choice, then you might say shove the ProductID into session, and then jump back into the current basket of items page, check session for a new product, and then insert/add that new item to the basket (which was being saved in the database).
If they log off, then fine. When they log back on, then you can load the current basket of items for display, and then allow them to continue shopping.
the only thing you going to use session for (or viewstate) is to allow the person to jump around looking at items, and then upon selection, you get/grab that particular product ID into session, jump back to the basket page, and add this new item from session, and then display their list of selected items.
So, in your database you have their current active order/basket, and thus when the user logs on, you can jump to that most recent or only "active order" page, and pull that order from the database. And then display all the details etc.
No session or viewstate would be required nor used at that point in time.
Not really any different than building up a invoice in a accounting package, then closing the program, and then next day, you launch the accounting package, load up the invoice, and thus can continue working on that invoice until you are done.
So, they look at the order, and go, "hum", I want to browse some more. So, they go look at/buy/choose/select another product. When they do, then you can now jump back to the current orders/basket page, and check session for the value just passed, add to order/basket, and now display the items in the basket, and that one "product id" in session can be pulled out, and is no longer required.
so session is not some kind of "magic" database system but is really only a feature to allow you to persist some values for a very short time, and only a simple "id" or simple variable in code for a rather short time. So session is ideal for passing a few values for use in code behind variables from one page to another.
Thus things like an address, name, products selected? That goes in the database, and not session.
For the most part, such software really not much different then desktop software.
You save and build up the list of items attached to a given order, and that order going to exist in the database.
So no real development approach here suggests saving their name, billing address, their past orders, the current new basket/order they have? That all going to be saved in a database.
So, session() is only relevant in most cases to "pass" some values, or "hold" some values for code behind.
So, if they select some product from a grid, you would shove productID into session, jump to the basket, add the new item in code behind to that order, and then let the page pull the information from the database to display items in that order.
So "session" persisting of data? That really only for a few variables that your code might need for some data operations, but your data for that order is not persisted in session - it goes into the database.
Session has VERY little to do with using a database system to "manage" and "hold" and "have" the order saved in the database system.
Session is not some kind of database - only a "active" storage of some variables you might need during program operation.
Easy, store it in database for logged in user.
use local storage or cookies for guess.
Is it possible to assign a person or a group of people as reviewers in a certain state of a workflow in Plone?
I have been looking at AutoRole en the IRolesplugin, but do not seem to find what I need?
In our case, users need a multiple review step workflow, yet the first reviewer should have control over which reviewers come afterwards...
Workflows can trigger scripts. Scripts can do things like grant roles to users. You'd have to come up with an approach to letting your first specify additional users. There are probably multiple ways to do it, but I could imagine using archetypes.schemextender or a custom content type to provide a field for choosing additional reviewers, only visible to initial reviewers. Then use those values in the workflow script. http://plone.org/documentation/kb/creating-workflows-in-plone/tutorial-all-pages provides a good overview of how DCWorkflow works.
We are employing BDD and using SpecFlow to drive our development (ATDD).
Our QA team would like to define their own 'end-to-end regression tests (in Gherkin/SpecFlow) and re-use the Steps we have already defined.
(Please note - I know that this is not a great example but it should provide enough details)
A test may include..
Log in
Search for a product
Select a product to buy
Create an order
Select delivery option.
Submit the order.
Cancel the order.
This would suggests a scenario like..
Given I am logged in
When I Search for a product
And I Select a product to buy
And I Create an order
And I Select delivery option
And I Submit the order
And I Cancel the order
Then ??!!
Which is clearly wrong as we are not checking the output at each step.
So this may be resolved as a sequence of scenarios:
Scenario 1:
Given I am logged in
When I Search for a product
Then I see a list of products
Scenario 2:
When I select a product to buy
Then I can create an order
Scenario 3:
When I create the order
And I Select delivery option
Then I can submit the order
etc etc
The main issue with this is that there seems no way to specify the order/sequence that the scenarios are run in (a characteristic of nUnit?). Because there are dependencies between scenarios (they are not set to a know starting point) they must be run in sequence.
My questions are:
a) Are we trying to fit a square peg in a round hole?!
b) Does anyone know if there is a way to use SpecFlow/Gherkin in this way?
c) Or does anyone know what alternatives there are?
Many thanks!
I would say that you are writing your scenarios on the wrong abstraction level. But that depends on what you want to use them for;
If you want to write test-scripts then you are on the right track... but it will be a nightmare to maintain as it, in the first case (long script) will be very brittle and the second case (several scenarios) need to ensure a certain execution order. Both of them are discouraged and considered anti-patterns.
I would suggest that you merge the ATDD-tests you are writing and talk to the test department to get their view on the matter and include the test-cases they need to ensure that the system is thoroughly tested. Who know? You might even learn something from each other :P
And when you write those "specifications" (as I rather call them) you write them on a higher level. So instead of writing:
Given I am logged in
When I Search for a product
And I Select a product to buy
And I Create an order
And I Select delivery option
And I Submit the order
you write something like
When I submit an order for product 'Canned beans'
In the step-definitions behind that step you perform all that automation (login, browsing to the product page, select the delivery options, submit the order).
All of this can be read about in these great articles on how to write maintainable UI Automation tests:
http://gojko.net/2010/04/13/how-to-implement-ui-testing-without-shooting-yourself-in-the-foot-2/
http://elabs.se/blog/15-you-re-cuking-it-wrong
http://www.marcusoft.net/2011/04/clean-up-your-stepsuse-page-objects-in.html
http://dhemery.com/pdf/writing_maintainable_automated_acceptance_tests.pdf
http://gojko.net/2010/01/05/bdd-in-net-with-cucumber-part-3-scenario-outlines-and-tabular-templates/
http://chrismdp.github.com/2011/09/layers-of-abstraction-writing-great-cucumber-code/
http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html
http://mislav.uniqpath.com/2010/09/cuking-it-right/
I hope this helps
When and why would it be a good choice to keep the view of two different sets of information on the same page, and just change what's visible depending on different parameters?
For example, an application I'm working on has three levels of users: Admin, Director and Project Manager. We have a Managers.aspx page which follows this flow of control:
If user is admin, load gridview list of directors with several simple CRUD-like properties. This includes a link for each director that, when clicked, will load a new gridview with several properties of all of the Project Managers belonging to the selected director.
If user is director, load straight to the list of Project Managers that belong to this director, in the exact fashion as described in the second half of the "if user is admin" clause".
This is all done on one Managers.aspx page. Why? What advantage does this hold? When else might this type of situation arise? Personally, this seems like a job for two separate pages. One listing the directors, and another listing the Project Managers.
Thanks in advance :)
As you pointed out, most of the logic for those two requirements is the same: get a list of users, present them in a gridview, apply simple CRUD functions. Doing this as two separate pages would require all that code to be repeated twice, and then kept in sync.
The only real difference between the two screens is which users it shws by default... That's one if-statement of difference in a pageful of code..
As stobor indicates - when you don't want to write the same code twice.
Using the one page solution, the decision about which view to display is made on that one page. If you separate the different views out to different pages, you have to make this decision every time you need to display this data. So rather than one single if/else in Managers.aspx to determine the view, you have multiple if/else statements on multiple pages which are intended to decide whether to load AdministratorManagers.aspx or DirectorManagers.aspx.
Provided your application is robust enough, and you're not doing something stupid like determining which page to show based on a querystring value, there is no real problem using one single page to display different views. In fact, many websites do this. What you don't want to do is use seperate but identical controls for each view, because then you're just going to end up with messy code. It may make sense to create custom controls called 'AdminView' and 'DirectorView' to at least allow you to think of the two views as separate entities, but you'll have to decide for yourself whether this will be a headache-saver or a waste of time.
I have a web application at work that is similar to a ticket working system. Some users enter new issues. Other workers choose and resolve issues. All of the data is maintained in MS SQL server 2005.
The users working to resolve issues go to a page where they can view open issues. Because up to twenty people can be looking at this page at the same time, one potential problem I had to address was what happens if someone picks an issue that someone else picked just after their page loaded.
To address this, I did two things. First, the gridview displaying the issues to select uses an AJAX timer to update every second. Once an issue has been selected, it disappears one second later at most. In case they select one within this second, they get a message asking them to choose another.
The problem is that the AJAX part of this is sending too many updates (this is what I am assuming) and it is affecting the performance of the page and database. In addition, the updates are not performing every second. I find the timer to be unreliable when working to trigger stored procedures.
There has to be a better way, but I can't seem to find one. Does anyone have experience with a situation like this or have suggestions to keep multiple users from selecting the same record to maintain? I really do not want to disable the AJAX part entirely because I feel the message alone would make the application frustrating to use.
Thanks,
Put a lock timestamp field on the row in the database. Write a stored proc that returns true or false if the expiration timsetamp is older than a specific time. Set your sessions on your web app to expire in the same time, a minute or two. When a user select a row they hit the stored proc which helps the app to decide if it should let the user to modify it.
Hope that makes sense....
Two things can help mitigate your problem.
First, after-selection notification that the case has been taken is needed regardless of your ajax update time frame. Even checking every second doesn't mean two people cannot click the same case at what they perceive to be the same time. In such cases, one of the users needs to be notified that their selection is invalid even though it appeared valid when selected. This notification doesn't need to be elaborate; keeping a light, helpful tone can improve user perception even in the light of disappointment. And if you identify the user who selected that record already, that will not only help your users coordinate in future but also divert attention from your program to the user who snaked the juicy case. (indeed, management may like giving your users the occasional collision as it will motivate them to select cases faster)
Second, a small tweak to how you display your cases can reduce selection collisions. Adding a random element to display order and/or filtering out every other case on display will help your users select different cases naturally. Human pattern recognition and task selection isn't really random so small changes to presentation can equal big changes to selection behavior. Reductions in collision chance keeps your collision notifications rare (and thus less frustrating to your users). This is even better if your users can be separated into classifications that can help determine useful case ordering/filtering.
Okay, a third thing that will help you over time is if you keep a log of when collisions occur (with helpful meta data about the collision—like who was involved and selection timing). Armed with solid collision data, you can find what works and what doesn't. Over time, you can hone your application to your actual use cases as well as identify potential problems early. Nothing reassures your users more than being on top of a problem (and able to explain your plans to solve it) before they're even aware it exists.
With these mitigating patterns, you'll probably find you can safely reduce your ajax query timeframe without affecting user experience. And with useful logging, you'll have the assurance that any tweaks you put in place are actually working (or not—which is maybe even more useful to know).
I did something similar where once a user opened a ticket (row) it assigned that ticket to that user and set a value on that record, like and FK to that particular user, so if anyone else tried to open that ticket (row) it would let them know it has already been assigned to someone else.
If possible limit the system so that they just get the next open issue off the work queue as opposed having them be able choose from all open issues.
If that isn't possible, I suppose you could check upon the choosing of an issue to see if it is still available. If it's not available, then make it disappear after the user clicks on it. This way you are only requesting when they actually click on something as opposed to constant polling of the data.
Have you tried increasing the time between refreshes. I would expect that once per 30 seconds would be sufficient. 40 requests/minute is a lot less load than 1200/minute. Your users may not even notice the difference.
If they do, how about providing a refresh button on the page so the users can manually refresh the list just prior to selecting an item to avoid the annoying message if they choose.
I'm missing to see the issue, specially after you mentioned you are already flagging tickets as in progress/being maintained and have a timestamp/version of the item.
Isn't the following enough:
User browses the tickets and sees a list of available tickets i.e. this excludes ones that are in the db as in progress. If you want the users to also see tickets in progress, you indicate it clearly in the ticket status and disable the option to take it.
User either flags a ticket as in progress explicitly or implicitly by opening the ticket (depends on the user experience / how its presented to the users).
User explicitly moves the ticket to a different status i.e. completed, invalid, awaiting for feedback, etc.
When the items are retrieved at 1, you include a timestamp/version. When 2 happens, you use a optimistic concurrency approach to make sure that if 2 persons try to update the take the ticket at the same time only the first one will be successful.
What will happen is that for the second person, the update ... where ... timestamp = #timestamp will not find any records to update and you will report back that the ticket was already taken.
If you want, you can build on top of the above to update the UI as tickets are grabbed. This could be by just doing a full refresh of the current page of tickets after x time (maybe alerting/prompting the user), or even by retrieving a list of tickets changed for the page of tickets being showed with ajax. You still have the earlier steps in place, as this modification its just a convenience for the users.