Что такое эффективный и поддерживаемый метод создания навигационной системы в стиле мастера со сложными правилами в приложении ASP.NET? - PullRequest
2 голосов
/ 26 февраля 2012

Я работаю в команде, обновляю коммерческое веб-приложение на C #.Мы используем Radrikontrol от Telerik.Приложение направляет пользователя через набор задач в стиле мастера, но также позволяет пользователю вернуться к предыдущим шагам и внести изменения.Кроме того, некоторые шаги имеют сложные методы проверки.Например, после выполнения задачи на шаге 5 становятся доступны шаги 7, 8 и 9.Кроме того, если вы измените какие-либо настройки на шаге 2, все после этой точки необходимо будет выполнить заново, поэтому все шаги после шага 3 должны быть отключены.

  • С левой стороны расположена панель навигации.экрана (Telerik RadPanel), который перечисляет все шаги в последовательном порядке.В зависимости от того, где вы находитесь в процессе, некоторые шаги доступны для пользователя, а некоторые отключены.
  • В верхней части страницы есть раскрывающийся список (Telerik SplitButton на RadToolBar)он также содержит все шаги
  • Существуют кнопки «вперед» и «назад», позволяющие перейти к следующему или предыдущему шагу процесса.

Каждый из «шагов» имеет своисобственной страницы, поэтому, нажав на ссылку «Шаг 1», вы перейдете к шагу1.aspx

Код навигации разбросан по всей главной странице.Существуют методы, которые обрабатывают логику включения шагов, которые в основном представляют собой массивные операторы switch в выбранном индексе панели навигации.Я хочу переписать все эти функции и поместить их в один класс «NavigationManager», который имеет ссылку на все необходимые элементы управления (хотя я открыт для предложений по этому вопросу).Я ищу решение, которое будет:

  • Знать, где находится пользователь в процессе и куда ему разрешено идти, чтобы он мог включать и отключать элементы навигации при каждой загрузке страницы.Каждый шаг должен иметь своего рода процесс проверки, который позволяет ему решить, куда отправить пользователя.
  • Знать, как включать и отключать элементы навигации для каждого элемента управления.Как мне это связать?Индексы кнопок непредсказуемо отличаются для каждого элемента управления навигацией.
  • Узнайте, как связаны шаги, чтобы пользователь мог нажать кнопку «Далее» и перейти к следующему последовательному шагу.
  • Beремонтопригодность - текущая система настолько сложна, что исправление одной ошибки приводит к другим.
  • Быть эффективным - обработка каждой страницы не должна занимать много времени

Полагаю, чтоУ меня действительно возникают сложности с тем, как определить все эти шаги где-то один раз , чтобы система могла их использовать.Я думал об определении всех шагов в enum, но я предвижу множество операторов switch для включения кнопок шагов на элементах управления навигацией.

Я погуглил каждое связанное ключевое слово, о котором мог подуматьи я не могу найти ничего полезного.Я знаю , что это не единственная проблема.Я вижу много примеров веб-приложений с похожими навигационными системами, такими как TurboTax.

Итак, мои вопросы:

  • Как мне определить все мои шаги в одном месте, чтобычто вся программа знает, как с ними справиться?
  • Как использовать эти определения, чтобы определить, к каким шагам пользователь может получить доступ, а затем включить соответствующие элементы навигации?

Ответы [ 3 ]

2 голосов
/ 27 февраля 2012

Похоже, вам нужно знать несколько вещей в вашей системе.

  • Для начала вам нужен список шагов и задач для каждого шага.
  • Во-вторых, вам нужно знать состояние каждого задания каждого шага. Это может быть просто, например, битовый флаг, или перечисление с более сложными состояниями, такими как «Не начато», «Не завершено» и «Завершено». Каждая задача должна знать свой собственный статус и свои критерии для определения этого статуса. Как только все задачи в шаге выполнены, этот шаг автоматически считается завершенным. Вы можете связать URL-адрес страницы с каждым шагом и даже идентификатор элемента управления с каждой задачей.
  • Наконец, вам нужен глобальный контекст, который знает о состоянии всех задач при загрузке страницы и сохраняет сложные правила, которые вы упомянули. Один из способов сделать это - определить список задач и / или шагов, которые должны быть выполнены и использованы для установки свойства CanBeVisible на основе этого List.All (x x => true); это можно сделать в базе данных, XML, в зависимости от того, что загружено в контекст с обновленной информацией о состоянии для каждой задачи. Затем любой элемент управления навигацией может подключиться к этому глобальному контексту и точно знать, какие параметры отображать.

Идея состоит в том, чтобы инкапсулировать отображение зависимостей видимости в состояние каждой задачи в центральном местоположении, и тогда любой другой элемент пользовательского интерфейса, такой как панель навигации, может использовать эту информацию для отображения только тех элементов управления, которые удовлетворяют критериям видимости для любого данный шаг и набор задач.

Ответ от CKirb250: Извините, пришлось разместить мой комментарий здесь, потому что форматирование в поле для комментариев отстой.

  • Да, мне нужен список шагов. Куда мне их положить? Должны ли они быть простыми enum? Должны ли они быть размещены в формате XML где-нибудь, чтобы я мог ссылаться на них со значением ключа, их именем, которое должно быть показано пользователю, и какая страница aspx соответствует этому шагу?

  • Отслеживается в базе данных. Чтобы узнать состояние каждого шага, запрашивается база данных.

  • Как можно определить сложные правила? Я представляю себе гигантское заявление о переключении, которое гласит if (currentStep == steps.Step1) { if (page.IsFilledOut) { enableSteps(1, 2, 3, 4, 5); } }.

Вот способ настроить это в XML - каждый элемент должен быть определен как класс, и если он отслеживается в базе данных (как рекомендуется), то у вас есть несколько таблиц (как минимум, Step, Task, VisibilityDependency.) Вы можете генерировать XML из БД (или просто заполнять классы напрямую из БД). Я использовал 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();
        }

    }

}

Дайте мне знать, достаточно ли этого, чтобы раскрутить ваши собственные идеи для чистого способа рефакторинга вашего кода. Я разработал и работал над многими системами стилей волшебников и из первых рук увидел то, что вы описываете; а именно, что это становится беспорядком очень быстро, если не спроектировано хорошо с самого начала. Удачи!

0 голосов
/ 27 февраля 2012

Я недавно разработал свой собственный элемент управления "Wizard";Конечно, это было для WinForms, но я думаю, что мой подход имеет некоторые достоинства, так как он отличался от ряда других мастеров, которые кажутся настолько общими для всех.

Я перевернул процесс из «пошагового» подхода к«основанный на задачах» подход.То есть ни один из шагов не знает ни о каких других шагах, ни о том, как они связаны , а задача знает обо всех шагах.Когда шаг был «завершен», элемент управления возвращается в задачу, которая затем собирает любое состояние ВЫХОДА и передает его как состояние ВХОДА на следующий шаг (и).Поскольку задача знает о каждом отдельном виде шага (посредством статической типизации), она может выполнять любые соответствующие операции безопасным для типов образом.(Задача и пользовательский интерфейс задачи должны быть отдельными, но связанными компонентами: задача выполняет свою работу и работает вместе с пользовательским интерфейсом для обработки пользовательской навигации.)

Затем для продвижения используются согласованные вспомогательные методы / классы.По шагам в тривиальных случаях требуется только минимальная разводка для выполнения «нестандартной работы».(Я предпочитаю хранить логику «в коде», потому что очень часто она может быстро выходить за пределы языка разметки / «объекта правила».)

Теперь, где она действительно отличается, - как обрабатывается пользовательский интерфейс;) Я бы порекомендовал оставить UI явным (либо в ASCX, либо в шаблоне, либо еще чем-нибудь) и не пытаться генерировать его динамически ..., если только на самом деле нет веской причины.дает некоторые идеи для вашего дизайна!

0 голосов
/ 26 февраля 2012

Вы смотрели на asp:Wizard элемент управления? Вот несколько примеров использования: Элемент управления ASP.NET 2.0 и документ msdn.microsoft

Стратегия может заключаться в создании настраиваемого элемента управления для каждого шага и последующем добавлении их в мастер. Таким образом, ваш код для каждого шага может быть изолирован, и одна страница, содержащая мастер, может использовать интерфейс с этими элементами управления для обработки изменений на каждом шаге!

...