在ASP.NET应用程序中创建具有复杂规则的向导式导航系统的有效且可维护的方法是什么?

时间:2012-02-26 19:47:54

标签: c# asp.net navigation wizard rad-controls

我正在开发团队,用C#更新商业网络应用程序。我们正在使用Telerik的RadControls。该应用程序以类似向导的方式引导用户完成一组任务,但也允许用户跳回到先前的步骤并进行修改。另外,一些步骤具有复杂的验证方法。例如,在步骤5完成任务后,步骤7,8和9变为可用。此外,如果您在步骤2中更改了任何设置,则必须重新执行该点之后的所有设置,因此必须禁用步骤3之后的所有步骤。

  • 屏幕左侧有一个导航栏(Telerik RadPanel)按顺序列出所有步骤。根据 在这个过程中您所处的位置,可以使用一些步骤 用户,有些是禁用的。
  • 页面顶部有一个下拉框(Telerik RadToolBar上的SplitButton也包含所有步骤
  • 有前进和后退按钮,可让您移动到下一个按钮 或过程中的上一步。

每个“步骤”都有自己的页面,因此点击指向“步骤1”的链接将转到步骤1.aspx

导航代码散布在整个母版页上。有一些方法可以处理步骤启用逻辑,这些逻辑基本上是导航面板所选索引上的大量切换语句。我想重新编写所有这些功能并将其放入一个“NavigationManager”类中,该类引用了所有必要的控件(尽管我对此提出了建议)。我正在寻找一个解决方案:

  • 了解用户在此过程中的位置,以及允许用户访问的位置,以便在每次加载页面时启用和禁用导航项。每个步骤都应该有一种验证过程,使其能够决定将用户发送到何处。
  • 了解如何为每个控件启用和禁用导航项。我该如何链接?每个导航控件的按钮索引都是不可预测的。
  • 了解步骤的相关性,以便用户点击“下一步”按钮并进入下一个连续步骤。
  • 可维护 - 当前的系统非常复杂,以至于修复一个错误会导致其他错误。
  • 高效 - 每页加载时不应花费太多时间

我认为我真正遇到的困难是如何在一次的某个地方定义所有这些步骤,以便系统可以利用它们。我考虑过定义enum中的所有步骤,但我预见到很多用于启用导航控件上的步骤按钮的switch语句。

我用Google搜索了我能想到的每个相关关键字,但我找不到任何有用的信息。我知道这不是一个独特的问题。我看到很多具有类似导航系统的Web应用程序示例,例如TurboTax。

所以,我的问题是:

  • 如何在一个位置定义所有步骤,以便整个程序知道如何处理它们?
  • 如何使用这些定义来确定用户可以访问哪些步骤,然后启用相应的导航项?

3 个答案:

答案 0 :(得分:2)

看起来您需要了解系统中的一些内容。

  • 首先,您需要每个步骤的步骤和任务列表。
  • 其次,您需要知道每个步骤的每个任务的状态。这可能很简单,例如位标志,也可能是具有更复杂状态的枚举,例如未启动,不完整和完整。每个任务都应该知道自己的状态,以及确定该状态的自己标准。完成步骤中的所有任务后,该步骤将自动视为完成。您可以选择将页面URL与每个步骤相关联,甚至是每个任务的控件ID。
  • 最后,您需要一个全局上下文,它可以了解页面加载时所有任务的状态,并保留您提到的复杂规则。一种方法是定义必须完成的任务和/或步骤列表,并用于根据此List.All设置属性CanBeVisible(x x => true);只要将映射加载到具有每个任务的更新状态信息的上下文中,就可以在数据库XML中执行此操作。然后,任何导航控件都可以使用此全局上下文,并准确了解要呈现的可选选项。

我们的想法是将可见性依赖关系的映射封装到中心位置中每个任务的状态,然后任何其他UI元素(如导航面板)都可以使用此信息仅渲染满足任何可见性标准的控件。给出一步和一组任务。

来自CKirb250的回复: 抱歉,我必须在此处发表评论,因为评论框中的格式很糟糕。

  • 是的,我确实需要一系列步骤。我应该把它们放在哪里?他们应该是一个简单的enum吗?它们是否应该以XML格式布局,以便我可以使用键值引用它们,应该向用户显示它们的名称,以及哪个aspx页面对应于该步骤?

  • 这是在数据库中跟踪的。为了知道每个步骤的状态,查询数据库。

  • 如何定义复杂的规则?我想象一个巨大的转换语句,上面写着if (currentStep == steps.Step1) { if (page.IsFilledOut) { enableSteps(1, 2, 3, 4, 5); } }

这是一种在XML中设置它的方法 - 每个元素都应该被定义为一个类,如果在数据库中跟踪(如推荐的那样),那么你有一些表(Step,Task,VisibilityDependency,至少。)您可以从DB生成XML(或直接从DB填充类)。我使用XML作为一个简单的例子,只是想象我想到的是什么:

<WizardSchema>
  <Steps>
    <Step>
      <StepID>1</StepID>
      <StepOrder>1</StepOrder>
      <StepTitle>First Step</StepTitle>
      <StepUrl>~/step1.aspx</StepUrl>
      <Tasks>
        <Task>
          <TaskID>1</TaskID>
          <TaskOrder>1</TaskOrder>
          <TaskPrompt>Enter your first name:</TaskPrompt>
          <TaskControlID>FirstNameTextBox</TaskControlID>
          <VisibilityDependencyList></VisibilityDependencyList>
          <IsCompleted>True</IsCompleted>
        </Task>
        <Task>
          <TaskID>2</TaskID>
          <TaskOrder>2</TaskOrder>
          <TaskPrompt>Enter your last name:</TaskPrompt>
          <TaskControlID>LastNameTextBox</TaskControlID>
          <VisibilityDependencyList>
            <VisibilityDependency StepID="1" TaskID="1" />
          </VisibilityDependencyList>
          <IsCompleted>False</IsCompleted>
        </Task>
      </Tasks>
    </Step>
    <Step>
      <StepID>2</StepID>
      <StepOrder>2</StepOrder>
      <StepTitle>Second Step</StepTitle>
      <StepUrl>~/step2.aspx</StepUrl>
      <Tasks>
        <Task>
          <TaskID>3</TaskID>
          <TaskOrder>1</TaskOrder>
          <TaskPrompt>Enter your phone number type:</TaskPrompt>
          <TaskControlID>PhoneNumberTypeDropDown</TaskControlID>
          <VisibilityDependencyList>
            <VisibilityDependency StepID="1" /> 
            <!-- Not setting a TaskID attribute here means ALL tasks should be complete in Step 1 for this dependency to return true -->
          </VisibilityDependencyList>
          <IsCompleted>False</IsCompleted>
        </Task>
        <Task>
          <TaskID>4</TaskID>
          <TaskOrder>2</TaskOrder>
          <TaskPrompt>Enter your phone number:</TaskPrompt>
          <TaskControlID>PhoneNumberTextBox</TaskControlID>
          <VisibilityDependencyList>
            <VisibilityDependency StepID="1" />
            <VisibilityDependency StepID="2" TaskID="1" />
          </VisibilityDependencyList>
          <IsCompleted>False</IsCompleted>
        </Task>
      </Tasks>
    </Step>
  </Steps>
</WizardSchema>

现在我在想的是你的导航控件会轮询你的全局上下文,为此编写一些代码,只是为了让你知道我将如何做到这一点。

以下是一些代表此架构的代码以及一种返回可在页面上显示的所有任务的方法;没有转换声明!

using System;
using System.Linq;
using System.Collections.Generic;

namespace Stackoverflow.Answers.WizardSchema
{

    // Classes to represent your schema
    public class VisibilityDependency
    {
        public int StepID { get; set; }
        public int? TaskID { get; set; } // nullable to denote lack of presense
    }

    public class Task
    {
        public int TaskID { get; set; }
        public int TaskOrder { get; set; }
        public string TaskControlID { get; set; }
        public bool IsComplete { get; set; }
        public List<VisibilityDependency> VisibilityDependencyList { get; set; }
    }

    public class Step
    {
        // properties in XML

        public int StepID { get; set; }
        public string StepTitle { get; set; }
        public List<Task> Tasks { get; set; }
    }

    // Class to act as a global context
    public class WizardSchemaProvider
    {
        /// <summary>
        /// Global variable to keep state of all steps (which contani all tasks)
        /// </summary>
        public List<Step> Steps { get; set; }

        /// <summary>
        /// Current step, determined by URL or some other means
        /// </summary>
        public Step CurrentStep { get { return null; /* add some logic to determine current step from URL */ } }

        /// <summary>
        /// Default Constructor; can get data here to populate Steps property
        /// </summary>
        public WizardSchemaProvider()
        {
            // Init; get your data from DB
        }

        /// <summary>
        /// Utility method - returns all tasks that match visibility dependency for the current page;
        /// Designed to be called from a navigation control;
        /// </summary>
        /// <param name="step"></param>
        /// <returns></returns>
        private IEnumerable<Task> GetAllTasksToDisplay(Step step)
        {

            // Let's break down the visibility dependency for each one by encapsulating into a delegate;
            Func<VisibilityDependency, bool> isVisibilityDependencyMet = v =>
            {
                // Get the step in the visibility dependency
                var stepToCheck = Steps.First(s => s.StepID == v.StepID);

                if (null == v.TaskID)
                {
                    // If the task is null, we want all tasks for the step to be completed
                    return stepToCheck
                        .Tasks // Look at the List<Task> for the step in question
                        .All(t => t.IsComplete); // make sure all steps are complete

                    // if the above is all true, then the current task being checked can be displayed
                }

                // If the task ID is not null, we only want the specific task (not the whole step)
                return stepToCheck
                    .Tasks
                    .First(t => t.TaskID == v.TaskID) // get the task to check
                    .IsComplete;
            };

            // This Func just runs throgh the list of dependencies for each task to return whether they are met or not; all must be met
            var tasksThatCanBeVisible = step
                .Tasks
                .Where(t => t.VisibilityDependencyList
                    .All(v => isVisibilityDependencyMet(v)
                ));

            return tasksThatCanBeVisible;    
        }

        public List<string> GetControlIDListForTasksToDisplay(Step step)
        {
            return this.GetAllTasksToDisplay(this.CurrentStep).Select(t => t.TaskControlID).ToList();
        }

    }

}

让我知道这是否足以将您自己的想法转化为重构代码的简洁方法。我已经开发并研究了许多向导式系统,我亲眼看到了你所描述的内容;也就是说,如果从一开始就没有很好的架构,它会变得非常快。祝你好运!

答案 1 :(得分:0)

你看过asp:Wizard对照了吗?这里有一些使用示例:The ASP.NET 2.0 Wizard Control和文档msdn.microsoft

策略可以是为每个步骤创建自定义控件,然后将它们添加到向导中。这样,每个步骤的代码都可以隔离,包含向导的单页面可以使用这些控件的界面来处理每一步的更改!

答案 2 :(得分:0)

我最近设计了自己的“向导”控件;授予它适用于WinForms,但我认为我的方法有一些优点,因为它与许多其他似乎非常普遍的向导不同。

我将流程从“基于步骤”的方法转变为“基于任务”的方法。也就是说,没有任何步骤知道任何其他步骤或它们如何相关,并且任务知道所有步骤。当步骤“完成”时,控制返回到任务,然后该任务收集任何OUTPUT状态并将其作为INPUT状态馈送到下一步骤。因为任务知道步骤的每个个人类型(通过静态类型),所以它可以以类型安全的方式执行任何适当的操作。 (任务和任务UI应该是分开的,但是相关的组件:任务完成工作,并与UI一起工作以处理用户导航。)

然后使用一致的辅助方法/类来推进微不足道的情况中的步骤,只需要最少的布线来进行“自定义工作”。 (我更喜欢将逻辑保留在“代码中”,因为它经常可以快速逃脱标记语言/“规则对象”的界限。)

现在,真正不同的是UI的处理方式;-)我建议保持UI 显式(在ASCX或模板中或其他),而不是尝试动态生成它。 ..除非确实有充分的理由。

希望这能为您的设计提供一些见解!