So basically I have an object with a few properties in it:

public MyObject
public string Name {get;set;}
public bool Complete {get; set;}

List<MyObject> myList = new List<MyObject>();

myList.Add(new MyObject("Page1", true));
myList.Add(new MyObject("Page2", false));
myList.Add(new MyObject("Page3", false));

Imagine a webpage. Load the list of pages above from the database and then render a link to each one.

The pages must be completed in order. So if a page is not complete then the next page cannot be edited.

So in this case page 1 is already complete. Page2 can be edited/completed. Page3 cannot be edited until Page2 has been completed.

I'm trying to decide the best way to implement - I have 3 options I am trying to decide between:

1) Change database query - The current query contains the name and the complete flag. I was thinking to just add a new flag which will pull the value from the previous row, like so:

SELECT Name, Complete, LAG(Complete) OVER (ORDER BY 1) as Editable

But that is bordering on business logic in the database. I'm told that is bad.

2) Change the webpage View (MVC page)

Simplest way. When looping through the list, check the previous row.

But I'm also told business logic in the view is bad.

3) Some sort of new view model. But since the property is dependant on the previous object in a list I'm trying to find a 'nice' way of doing it.
Best I managed so far...

public class MyViewModel
    public List<MyObject> _list;

    public bool IsEditable(int index)
        if (index > 0)
            if (this._list[index-1].Complete)
                return true;
            return false;
            return true;

All 3 of those will work.
But recently I've been making more of an effort to do things 'properly' rather than just hacking together the quickest or the first thing i think of.

- Or am I better scrapping the whole object and starting from scratch?
I already have the list of pages taken from the database which was fine when we wanted to show everything and treat them equally. Now it's all screwed up..

2 Answers 2


The way this is traditionally done is actually very simple:

  • The menu system has a link for accessing Page1, but does not offer a direct link to Page2 or Page3
  • Page1 has a link to Page2
  • Page2 has a link to Page3

Ta da. No need to boil the ocean.

Now if you need to enforce the ordering rigidly, you can do the following:

  1. Add code to Page2 to ensure Page1 has been submitted. If it hasn't, redirect to Page1.
  2. Add code to Page3 to ensure Page2 has been submitted. If it hasn't, redirect to Page2.

Again, not exactly rocket science.

I can't think of any reason why you'd want to make this "generic" and use a List of pages with flags, unless your workflow is very long, maybe more than 10 pages, or if the page flow is supposed to be configurable at run-time, or if the flow is constantly changing with every release. Most of the time, a multi-page workflow doesn't have any of those properties, and the additional complexity isn't worth it and is likely to confuse future developers.

  • The list of pages wants to be on screen all the time and will be 'fixed', so it will always show entries for page1, page2, page3, etc. It's just a case of whether the links to edit them are active or not. Since the pages will always be the same (page1-10) and will never change - a list seemed like the obvious choice - maximum code re-use and all that..
    – jb.
    Commented May 30, 2017 at 19:30

If your workflow is really that complicated, and you need to be able to display a list of all pages and enable only those that are currently available, I would suggest an approach like this.

  1. Create a single model that represents the workflow. Think of it as a very long form that a user must fill out.
  2. Create ten views that all use the same model. Each view corresponds to a page on the very long form.
  3. Each view should update the model with any fields that it can, i.e. fields entered on the current view.
  4. Each view should also pass through any fields that it can't update (e.g. fields from previous pages). If your site is stateless/sessionless, these would be held in hidden variables, but you could also use TempData or even Session if you need to.
  5. Each page has preconditions. For example, perhaps Page2 requires five fields on Page1 to be fully filled out. All preconditions should be evaluated when the action is invoked, and if any of the preconditions aren't met, the browser should be redirected to the previous page.
  6. On the "master" page that lists all of the 10 workflow steps, add code to check preconditions for each page and determine whether to enable or disable each link.
  7. You can implement the above with one action per workflow step, or you could use a single action with a parameter that indicates the workflow step. But best of all would be to have a single action and no parameter at all; instead, the action should check all preconditions and figure out what the next step is based on the missing information. This allows your sequential coupling to be enforced endogenously rather than building something special that you would have to maintain. You can add this logic as a helper function to your model, e.g.:

    class WorkflowModel
        public string NextView()
            if (string.IsNullOrEmpty(this.FirstName)) return "Page1";
            if (string.IsNullOrEmpty(this.Address)) return "Page2";
            if (string.IsNullOrEmpty(this.CardNumber)) return "Page3";
            return "Submit";

Then in your action method:

    public ActionResult NextStep(WorkflowModel model)
         var nextView = model.NextView;
         return View(nextView, model);

And on your "master" page:

    @{ var nextView = model.NextView; }
    <A HREF="Page1" @{ (nextView < "Page1") ? "Disabled" : ""}>Page One</A>
    <A HREF="Page2" @{ (nextView < "Page2") ? "Disabled" : ""}>Page Two</A>
    <A HREF="Page3" @{ (nextView < "Page3") ? "Disabled" : ""}>Page Three</A>

If you really want, you could improve on the above with a simple loop of course.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.