2

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

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

每个“步骤”都有自己的页面,因此单击“步骤 1”的链接将带您进入 step1.aspx

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

  • 了解用户在流程中的位置,以及允许用户去的位置,以便它可以在每次页面加载时启用和禁用导航项。每个步骤都应该有一种验证过程,使其能够决定将用户发送到哪里。
  • 了解如何启用和禁用每个控件的导航项。我如何将其链接起来?每个导航控件的按钮索引都出人意料地不同。
  • 了解这些步骤是如何相关的,以便用户可以单击“下一步”按钮并进入下一个连续步骤。
  • 可维护 - 当前系统非常复杂,修复一个错误会导致其他错误。
  • 高效 - 每次页面加载都不需要太多时间来处理

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

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

所以,我的问题是:

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

3 回答 3

2

看起来您需要了解系统中的几件事。

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

这个想法是将可见性依赖关系与每个任务的状态的映射封装在一个中心位置,然后任何其他 UI 元素(例如导航面板)可以使用此信息仅呈现满足任何给定步骤的可见性标准的那些控件,并且一组任务。

来自 CKirb250 的回复:对不起,我不得不在这里发表评论,因为评论框中的格式很糟糕。

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

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

  • 如何定义复杂的规则?我在想象一个巨大的 switch 语句,上面写着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>

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

这里有一些代码来表示这个模式和一个返回所有可以在页面上显示的任务的方法;没有 switch 语句!

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();
        }

    }

}

让我知道这是否足以激发您自己的想法,以一种干净的方式重构您的代码。我已经开发和工作过许多向导风格的系统,并且我亲眼目睹了您所描述的内容;也就是说,如果从一开始就没有很好地架构,它会很快变得一团糟。祝你好运!

于 2012-02-26T20:23:39.957 回答
0

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

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

于 2012-02-26T19:52:05.107 回答
0

我最近设计了自己的“向导”控件;当然它是针对 WinForms 的,但我认为我的方法有一些优点,因为它与许多其他看起来如此普遍的向导不同。

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

然后使用一致的辅助方法/类来推进琐碎案例中的步骤,只需要最少的布线来完成“定制工作”。(我更喜欢将逻辑保留在“代码中”,因为它经常可以快速逃离标记语言/“规则对象”的界限。)

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

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

于 2012-02-26T21:48:12.553 回答