我正在开发团队,用C#更新商业网络应用程序。我们正在使用Telerik的RadControls。该应用程序以类似向导的方式引导用户完成一组任务,但也允许用户跳回到先前的步骤并进行修改。另外,一些步骤具有复杂的验证方法。例如,在步骤5完成任务后,步骤7,8和9变为可用。此外,如果您在步骤2中更改了任何设置,则必须重新执行该点之后的所有设置,因此必须禁用步骤3之后的所有步骤。
每个“步骤”都有自己的页面,因此点击指向“步骤1”的链接将转到步骤1.aspx
导航代码散布在整个母版页上。有一些方法可以处理步骤启用逻辑,这些逻辑基本上是导航面板所选索引上的大量切换语句。我想重新编写所有这些功能并将其放入一个“NavigationManager”类中,该类引用了所有必要的控件(尽管我对此提出了建议)。我正在寻找一个解决方案:
我认为我真正遇到的困难是如何在一次的某个地方定义所有这些步骤,以便系统可以利用它们。我考虑过定义enum
中的所有步骤,但我预见到很多用于启用导航控件上的步骤按钮的switch语句。
我用Google搜索了我能想到的每个相关关键字,但我找不到任何有用的信息。我知道这不是一个独特的问题。我看到很多具有类似导航系统的Web应用程序示例,例如TurboTax。
所以,我的问题是:
答案 0 :(得分:2)
看起来您需要了解系统中的一些内容。
我们的想法是将可见性依赖关系的映射封装到中心位置中每个任务的状态,然后任何其他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或模板中或其他),而不是尝试动态生成它。 ..除非确实有充分的理由。
希望这能为您的设计提供一些见解!