Пользовательские загрузчики содержимого

174

Система навигации Silverlight мощная, но не очень гибкая. В ней неявно заложены определенные предположения о способах обработки адресов URI (в частности, каждый URI должен предоставить имя страницы XAML, которая будет загружена во фрейм). Эта система неплохо работает в приложениях, в которых используется простая навигация, но с ее помощью тяжело решать более сложные задачи, например, когда есть URI, предлагающий приложению установить связь с удаленными службами или начать загрузку. Кроме того, эта система не позволяет вставить в процедуру навигации дополнительные процедуры, такие как аутентификация и авторизация.

Некоторые из этих средств могут появится в будущих версиях Silverlight. Многие из них будут предоставлены в отдельных библиотеках (платных или бесплатных), создаваемых сторонними разработчиками. Однако сейчас, пока их нет, можете самостоятельно расширить систему навигации с помощью пользовательских загрузчиков содержимого.

Главная идея пользовательских загрузчиков содержимого довольно простая. Нужно создать класс, реализующий интерфейс INavigationContentLoader. Этот класс получает URI и обрабатывает его соответствующим образом (например, предоставляет содержимое указанной страницы или запускает независимую задачу). После этого можно подключить загрузчик содержимого к фрейму в приложении. Можно даже выстроить цепочку загрузчиков содержимого, в которой URI передается последовательно от одного загрузчика к другому.

Однако, хотя главная идея простая, запрограммировать ее не так-то просто по двум причинам. Во-первых, интерфейс INavigationContentLoader разработан для асинхронной модели. Это значит, что вы обязательно должны реализовать такие методы, как BeginLoad(), EndLoad() и CancelLoad(), и применить объект IAcyncResult для координации взаимодействия фрейма с кодом загрузчика содержимого.

Во-вторых, в некоторых ситуациях может быть сгенерировано исключение или выполнена отмена навигации. Это особенно актуально, если навигация выполняется дольше нескольких миллисекунд. Если пользователь не получает мгновенного ответа, он может щелкнуть на ссылке несколько раз, запуская таким образом несколько запросов, каждый из которых будет отменен один за другим. Следовательно, создание пользовательского загрузчика содержимого — несложная задача, но обеспечение его устойчивости и надежности — довольно сложная.

Аутентификация и навигация

Если вы когда-либо программировали приложение ASP.NET, вам должна быть знакома процедура одновременного решения задач аутентификации, авторизации и навигации.

В традиционном веб-приложении некоторые страницы доступны всем пользователям, а другие защищены. Различия между этими страницами определяются правилами авторизации. Некоторые страницы должны быть доступны только для зарегистрированных пользователей, вошедших в систему, или специальных пользователей и групп. Когда анонимный пользователь пытается запросить страницу с ограниченным доступом, он перенаправляется на страницу входа в систему для аутентификации. Обычно на этой странице он должен ввести имя пользователя и пароль. После успешной аутентификации пользователь направляется на страницу с ограниченным доступом.

Точно такую же систему можно создать и с помощью пользовательского загрузчика содержимого. Загрузчик перехватывает каждый запрос к странице и проверяет, аутентифицирован ли пользователь. Если да, загрузчик направляет пользователя на страницу с ограниченным доступом. Но для этого приложение должно отличать открытые страницы от тех, для которых необходима аутентификация. В данном примере страницы, требующие аутентификации, расположены во вложенной папке проекта с именем SecurePages:

Доступ к защищенным страницам

На рисунке ниже показано простое приложение для проверки системы аутентификации. Слева показана начальная страница InitialPage.xaml. С нее пользователь может перейти на другую страницу, щелкнув на кнопке (которая запускает метод Navigate()) или на ссылке. Если приложение пытается перейти к странице с ограниченным доступом, расположенной в папке SecurePages, загрузчик содержимого направляет пользователя на страницу входа в систему:

Авторизация пользователей в Silverlight

На странице входа в систему у пользователя есть несколько вариантов дальнейших действий. Он может ввести правильные регистрационные данные и щелкнуть на кнопке Войти. Будет загружена запрошенная страница. Кроме того, пользователь может перейти к странице с открытым доступом (система навигации при этом работает как обычно) или к странице с ограниченным доступом (при этом он немедленно будет направлен обратно на страницу входа в систему).

Рассмотрим код, выполняющий аутентификацию и перенаправление пользователя.

Создание пользовательского загрузчика содержимого

Пользовательский загрузчик содержимого — это класс, реализующий интерфейс INavigationContentLoader. Этот интерфейс требует предоставить следующие методы: BeginLoad(), CanLoad(), CancelLoad() и EndLoad(). Правильная реализация этих методов — довольно коварная задача, но ее можно существенно упростить.

Для этого достаточно определить экземпляр класса PageResourceContentLoader как поле и вызывать его методы по мере прохода загрузчиком по разным стадиям процесса. В рассматриваемом пользовательском загрузчике содержимого класс PageResourceContentLoader используется для точного дублирования поведения стандартной системы навигации:

public class AuthenticatingContentLoader : INavigationContentLoader
{        
        public string LoginPage
        {
            get;
            set;
        }

        public string SecuredFolder
        {
            get;
            set;
        }
               
        private PageResourceContentLoader loader = new PageResourceContentLoader();

        public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState)
        {
            if (!App.UserIsAuthenticated)
            {
                if ((System.IO.Path.GetDirectoryName(targetUri.ToString()).Trim('\\') == SecuredFolder) &&
                    (targetUri.ToString() != LoginPage))
                {
                    // Перенаправление на страницу входа в систему
                    targetUri = new Uri(LoginPage, UriKind.Relative);
                }
            }
            return loader.BeginLoad(targetUri, currentUri, userCallback, asyncState);
        }

        public bool CanLoad(Uri targetUri, Uri currentUri)
        {
            return loader.CanLoad(targetUri, currentUri);
        }

        public void CancelLoad(IAsyncResult asyncResult)
        {
            loader.CancelLoad(asyncResult);
        }

        public LoadResult EndLoad(IAsyncResult asyncResult)
        {
            return loader.EndLoad(asyncResult);            
        }
}

Методы CanLoad(), CancelLoad() и EndLoad() используют базовые методы через экземпляр класса PageResourceContentLoader, метод BeginLoad() немного откорректирован. В нем выясняется, вошел ли пользователь в систему. Если пользователь не в системе и запросил страницу с ограниченным доступом, код должен перенаправить его на страницу входа в систему. В коде загрузчика также добавлены два открытых свойства: LoginPage - ссылка на страницу входа в систему, SecuredFolder - папка со страницами, к которым не должен получить доступ неавторизированный пользователь.

Чтобы отследить, вошел ли пользователь в систему, в класс App нужно добавить логическое свойство:

public static bool UserIsAuthenticated
{
            get;
            set;
}

Теперь загрузчик содержимого готов к установке в приложение.

Применение пользовательского загрузчика содержимого

Пользовательский загрузчик применяется так же, как и любой другой пользовательский компонент в XAML. Первый шаг — объявление пространства имен XML в пространстве имен проекта, чтобы загрузчик был доступен в проекте. В данном примере проект называется CustomContentLoader:

xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:local="clr-namespace:CustomContentLoader"

Затем нужно присвоить свойству Frame.ContentLoader экземпляр пользовательского загрузчика содержимого. Одновременно можно установить свойства загрузчика:

<UserControl x:Class="CustomContentLoader.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    xmlns:tonavigation="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
    xmlns:local="clr-namespace:CustomContentLoader">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.Resources>
            <tonavigation:UriMapper x:Key="UriMapper">
                <tonavigation:UriMapping Uri="" MappedUri="/InitialPage.xaml"/>
            </tonavigation:UriMapper>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>

        <Border Margin="10" Padding="10" BorderBrush="DarkOrange" BorderThickness="2"
                CornerRadius="4">
            <navigation:Frame x:Name="mainFrame" UriMapper="{StaticResource UriMapper}">
                <navigation:Frame.ContentLoader>
                    <local:AuthenticatingContentLoader LoginPage="/LoginPage.xaml" SecuredFolder="SecurePages"></local:AuthenticatingContentLoader>
                </navigation:Frame.ContentLoader>
            </navigation:Frame>
        </Border>
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
            <HyperlinkButton Margin="5" NavigateUri="/SecurePages/Page1.xaml" Content="SecurePages/Page1.xaml" VerticalAlignment="Center"></HyperlinkButton>
            <HyperlinkButton Margin="5" NavigateUri="/SecurePages/Page2.xaml" Content="SecurePages/Page2.xaml" VerticalAlignment="Center"></HyperlinkButton>
            <HyperlinkButton Margin="5" NavigateUri="/InitialPage.xaml" Content="InitialPage.xaml" VerticalAlignment="Center"></HyperlinkButton>
        </StackPanel>
    </Grid>
</UserControl>

После этого остается только написать код страницы входа в систему LoginPage.xaml. В данном примере она всего лишь проверяет жестко закодированный пароль. В реальном приложении проверка пароля, конечно, должна выполняться иначе. Например, можно закодировать проверку хеша пароля или вызвать веб-службу, которая аутентифицирует пользователя на веб-сервере.

Когда пользователь аутентифицирован, свойству UserIsAuthenticated должно быть присвоено значение true, а приложение должно вызвать метод NavigationService.Refresh(), который повторит всю процедуру навигации с начала. На этот раз, поскольку пользователь аутентифицирован, перенаправление не выполняется и пользователь попадает на запрошенную страницу:

// LoginPage.xaml.cs используется жестко закодированный пароль
private void cmdLogin_Click(object sender, RoutedEventArgs e)
{
       if (txtPassword.Text == "12345")
       {
            App.UserIsAuthenticated = true;
            this.NavigationService.Refresh();                
       }
}

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

Пройди тесты
Лучший чат для C# программистов