Службы расширения приложений

Название взято из локализованного MSDN. Признаться, я не большой любитель локализации профессиональных приложений и – особенно – терминов. В оригинале это называется Application Extension Service. Здесь же будем использовать простое слово “служба” (хотя на языке так и вертится “сервис”).

Как можно узнать из той же MSDN, есть два способа сделать что-то глобальное на уровне приложения: либо создав потомка Application, либо используя эти самые службы. Использование служб – на мой взгляд – более элегантное решение.

Итак, служба по своей сути класс, который реализует интерфейс IApplicationService (как минимум, как максимум – смотри ниже). Это два метода – StartService и StopService. Первый вызывается когда приложение запускается, второй – когда закрывается (грубо говоря). Объекты наших служб добавляем в коллекцию Application.ApplicationLifetimeObjects (программно или в XAML). Порядок имеет значение. Методы StartService вызываются в той последовательности, в которой наши объекты были добавлены (StopService в обратной).

Обычно службы выглядят так:

static public AppService Current { get; set; }

public void StartService(ApplicationServiceContext context)
{
    Current = this;
}

При запуске службы инициализируется статическое поле, куда записывается ссылка на текущий объект (который мы добавили в коллекцию ApplicationLifetimeObjects). Теперь к этой службе мы можем обращаться из любого места программы через статическое поле AppService.Current. В отличии от обычного класса со статической instance-переменной, у служб есть большой плюс: мы точно знаем когда и в каком порядке будут “запущены” и “остановлены” службы. Кроме того, существует интерфейс IApplicationLifetimeAware, который позволяет более детально отслеживать время жизни службы: Starting, Started. Exiting, Exit.

Порядок вызова и более детальное описание можно посмотреть в той же статье MSDN.

Открываем страницу из OOB Silverlight программно

OOB не позволяет открывать странички во внешнем браузере кроме как с помощью HyperlinkButton. Такова политика безопасности. Но что делать, если страничку ну очень надо окрыть программно? Например, проблема выбора открываемой страницы возложена на алгоритм, работу которого инициализирует нажатие обычной Button. Решение я подсмотрел тут и сделал вот такой helper:

static public class WebNavigateHelper
{
    private class Clicker : HyperlinkButton
    {
        public Clicker(Uri uri)
        {
            base.NavigateUri = uri;
        }

        public void DoClick()
        {
            base.OnClick();
        }
    }

    static public void Navigate(Uri uri)
    {
        var clicker = new Clicker(uri);
        clicker.DoClick();
    }
}

Использовать до безобразия просто:

WebNavigateHelper.Navigate(new Uri("http://www.albertakhmetov.ru"));

Тем не менее, все органичения политики безопасности в силе – работает только в результате действия инициализированного пользователем (клик по кнопке – да, срабатывание таймера – нет).

MVVM и диалоговые окна. Мысли вслух

Признаюсь честно, MVVM начал практиковать сравнительно недавно. Так что не стоит воспринимать мою писанину по этому поводу как истину последней в инстанции. Очень может быть это вообще полный бред Winking smile

Возникло желание делать View простым UserControl, а уже потом решать – отображать его в ChildWindow или, например, в PageFunction. Все что нам для этого нужно – понимать когда ViewModel хочет закрыть View.

Для начала определим интерфейс, который позволяет ViewModel сообщать о необходимости закрытия:

public interface IWindowViewModel
{
    event EventHandler Closed;
}

Следующий ход – само закрытие окна. Тут два пути. Можно либо в самом View реализовать логику подписки на событие Closed, смотреть условие Parent is ChildWindow и уже у него вызывать Close. А можно написать вот такой helper:

public class WindowModelHelper : DependencyObject
{
    static public DependencyProperty ChildWindowProperty = DependencyProperty.Register(
        "ChildWindow",
        typeof(ChildWindow),
        typeof(WindowModelHelper),
        null);

    static public DependencyProperty ViewModelProperty = DependencyProperty.Register(
        "ViewModel",
        typeof(IWindowViewModel),
        typeof(WindowModelHelper),
        new PropertyMetadata(new PropertyChangedCallback(ViewModelChanged)));

    static private void ViewModelChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var helper = obj as WindowModelHelper;

        if (helper != null)
        {
            helper.UnSubscribe(e.OldValue as IWindowViewModel);
            helper.Subscribe(e.NewValue as IWindowViewModel);
        }
    }

    public WindowModelHelper()
    {
    }

    public ChildWindow ChildWindow
    {
        get { return (ChildWindow)GetValue(ChildWindowProperty); }
        set { SetValue(ChildWindowProperty, value); }
    }

    public IWindowViewModel ViewModel
    {
        get { return (IWindowViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }

    private void Subscribe(IWindowViewModel viewModel)
    {
        if (viewModel != null)
            viewModel.Closed += new EventHandler(OnClosed);
    }

    private void UnSubscribe(IWindowViewModel viewModel)
    {
        if (viewModel != null)
            viewModel.Closed -= new EventHandler(OnClosed);
    }

    private void OnClosed(object sender, EventArgs e)
    {
        if (ChildWindow != null)
            ChildWindow.Close();
    }
}

Думаю, коментарии по реализации излишни. Использовать его можно вот так:

<helpers:WindowModelHelper x:Key="windowModelHelper"
    ChildWindow="{Binding RelativeSource={RelativeSource AncestorType=sdk:ChildWindow}}"
    ViewModel="{StaticResource aboutViewModel}"/>

Как видно, если наш View не содержится в ChildWindow – ничего не произойдет (будет null).

p. s. Эта штука еще не испытана в product коде. Не исключено, что в чем-то ошибся

Особенность именования сборок с ресурсами в Silverlight

Возникло у меня непреодолимое желание хранить ресурсы во внешней dll. Вполне логично для этого создать проект xxx.Resources, добавить туда ресурсы и подключить их в ResourceDictionary основного проекта. Например, вот так:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="/xxx.Resources;component/Styles/DesignDictionary.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Но при запуске приложения нас ожидает сюрприз в виде XamlParseException: Failed to assign to property ‘System.Windows.ResourceDictionary.Source’. Руководствуясь принципом “если что-то не работает – ошибка скорее всего у тебя” я принялся изучать код. Время было потрачено с пользой – освежил в памяти Uri, внешние ресурсы и все такое в этом духе. Однако, как ни странно, в данном случае оказалось, что сие поведение есть не что иное как “особенность” Silverlight. Если название сборки заканчивается на Resources, то появляется это исключение. Изменение названия (например, на ResourcesLib) исправляет ситуацию. Глюк это или так задумано – не понятно. Но тем не менее лучше сборки с ресурсами так не называть Smile

How-to: убираем надпись Microsoft с заставки Visual Studio

На сплеш-скрине Visual Studio 2010, установленной на Windows 7 x64 можно наблюдать такую картинку:

logo-before

Кроме того, при создании проекта в AssemblyInfo.cs атрибуты AssemblyCompany и AssemblyCopyright будут иметь значение “Microsoft”. Что характерно, на 32-битных системах такой надписи не наблюдается.

Весь фокус состоит в том, что Visual Studio – 32-битная программа. Соответственно, к ней применяется вся сила WoW64. Таким образом, при установке информация о пользователе берется из раздела HKEY_LOCAL_MACHINE\ SOFTWARE\ Wow6432Node\Microsoft\Windows NT\CurrentVersion.

Если зайти туда, можно увидеть RegisteredOrganization и RegisteredOwner с заветной надписью Microsoft. Надпись на сплеше и атрибутах AssemblyInfo берется из RegisteredOrganization. Можно оставить их пустыми или же написать свои личные реквизиты.

Далее, чтобы изменения вступили  в силу надо обновить установочную информацию. Делается это при запуске IDE с ключом /setup. Самы прострой способ это проделать – запустить cmd.exe с правами администратора, перейти в каталог C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE и выполнить команду devenv /setup. Для Express-выпусков надо запускать отдельно каждое установленное IDE – vwdexpress /setup, vcsexpress /setup и так далее. Никаких сообщений выведено не будет.

Теперь при запуске мы видим такую картинку:

logo-after

Соответственно, все атрибуты будут заполнены значениями, которые мы указали в параметре реестра RegisteredOrganization.

How-to: настраиваем IIS Express для работы в локальной сети

IIS Express – облегченная версия IIS из Windows Server 2008 R2. Его можно установить на любой компьютер с операционной системой Windows XP и более поздней. Поддерживаются даже домашние редакции. Можно ставить вместе с полноценным IIS. Более подробно – тут.

При запуске IIS Express с правами пользователя, доступ к нему будет возможен только с локальной машины. Если нужен доступ с другого компьютера, то IIS Express нужно запускать с правами администратора. Это обязательное, но не достаточное условие. Кроме того надо отредактировать настройки веб-сайта, открыв доступ к нему из вне. Для этого откроем в текстовом редакторе файл C:\Users\[user name]\Documents\IISExpress\config\applicationhost.config, найдем там наш сайт и приведем его bindings приблизительно к такому виду:

<bindings>
<binding protocol=”http” bindingInformation=”*:8080:” />
</bindings>

, где 8080 – порт сайта.

Далее, заходим в настройки нашего файрвола и открываем доступ “из вне” на порт 8080. Запускаем наш IIS Express с правами администратора и пробуем открыть страничку с другого компьютера.

В свете интеграции IIS Express в Visual Studio 2010 SP1 данный подход мне видится крайне правильным. При разработке и тестировании запускаем IDE с правами пользователя и открываем наш сайт только с локальной машины. Как только назревает необходимость открыть сайт с удаленного компьютера – перезапускаем IDE с правами администратора и доступ автоматически открываетсяОднако перед запуском IDE надо все же изменить настройки config-файла: для user-mode с localhost, для admin – без