Статьи

Windows Phone — быстрое и грязное тестирование, если ваша ViewModel является сериализуемой

При написании приложений WP7 (и новых приложений Metro UI в Windows 8) вам придется иметь дело с такими понятиями, как «Tombstoning» или «Suspension»; Если вы следуете шаблону MVVM, очень вероятно, что статус страницы вашего приложения представлен самой ViewModel. Таким образом, быстрый и грязный способ обработки этих приостановленных состояний состоит в том, чтобы «сохранить» ViewModel (или его часть, имеющую фактическое значение) в состоянии приложения или в файловом хранилище и извлечь его позднее, когда приложение будет повторно активировано. ,

Если вы поместите что-то, что не является примитивным типом, в словари «Состояние приложения» или «Состояние страницы», оно будет сериализовано с использованием стандартного DataContractSerializer (по вопросу сериализации и десериализации в WP7 вы можете прочесть мои предыдущие посты: WP7. Понимание сериализации. и http://www.primordialcode.com/blog/post/wp7-datacontractserializer-bug ).

Хороший способ избежать головной боли при использовании этого подхода для обработки захоронения / приостановки — это проверить не только поведение ваших ViewModels, но также и то, являются ли ваши ViewModels сериализуемыми; Вы можете сделать это, используя вспомогательный класс, подобный этому:

public static class DataContractSerializerHelpers
{
    public static string ToXml(object obj)
    {
        return ToXml(obj, null);
    }

    public static string ToXml(object obj, IEnumerable<Type> knownTypes)
    {
        Type objType = obj.GetType();
        DataContractSerializer ser = new DataContractSerializer(objType, knownTypes);
        {
            using (StringWriter sw = new StringWriter())
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.OmitXmlDeclaration = true;
                settings.Indent = true;
                //settings.NewLineOnAttributes = true;
                using (XmlWriter writer = XmlWriter.Create(sw, settings))
                {
                    ser.WriteObject(writer, obj);
                }
                return sw.ToString();
            }
        }
    }

    public static object FromXml(string data, Type type)
    {
        return FromXml(data, type, null);
    }

    public static object FromXml(string data, Type type, IEnumerable<Type> knownTypes)
    {
        using (StringReader sr = new StringReader(data))
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            using (XmlReader reader = XmlReader.Create(sr, settings))
            {
                DataContractSerializer ser = new DataContractSerializer(type, knownTypes);
                return ser.ReadObject(reader);
            }
        }
    }

    public static T FromXml<T>(string data) where T : class
    {
        return FromXml(data, typeof(T), null) as T;
    }

    public static T FromXml<T>(string data, IEnumerable<Type> extraTypes) where T : class
    {
        return FromXml(data, typeof(T), extraTypes) as T;
    }
}

По сути, это простая оболочка для функции DataContractSerializer, которая позволяет вызывать ее удобным способом, который имитирует работу инфраструктуры.

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

[TestClass]
public class SerializableTypes : SilverlightTest
{
    // Test if we can save the VM to the state dictionaries
	[TestMethod]
    public void ViewModel_Serialize()
    {
		// todo: add proper members initialization
	    MainViewModel vm = new MainViewModel();
        DataContractSerializerHelpers.ToXml(vm);
    }

	// Test if we can retrieve the VM from the state dictionaries
    [TestMethod]
    public void ViewModel_DeSerialize()
    {
		// todo: add proper members initialization
	    MainViewModel vm = new MainViewModel();
        var data = DataContractSerializerHelpers.ToXml(vm);

        // test
        var loaded = DataContractSerializerHelpers.FromXml<MainViewModel>(data);
        Assert.IsNotNull(loaded);
		// todo: add proper testing on members values
    }
}

При таком подходе код, который вы должны написать в представлениях для обработки надгробий, станет тривиальным; в ваших взглядах у вас будет нечто похожее на это:

...

private SingleFeedViewModel Vm
{
    get { return (SingleFeedViewModel)DataContext; }
    set { DataContext = value; }
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    // if we came up after a dormant state we do not need to do anything at this stage
    // I also have to check if the Vm is unassigned (we can come here from a normal navigation, after a resume)
    if (ApplicationHelper.IsApplicationInstancePreserved && Vm != null)
        return;

    // recover from tombstoning
    if (State.ContainsKey(StateKeys.SingleFeedVm))
	{
		//clear prev value
		SingleFeedViewModel itm = State[StateKeys.SingleFeedVm] as SingleFeedViewModel;
		if (itm != null)
		    Vm = itm;
	}
    // nothing was retrieved from the state dictionary, create an instance
	// of the ViewModel
    if (Vm == null)
    {
        Vm = new SingleFeedViewModel();
        Vm.LoadData();
    }
}

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);
    // save state to handle tombstoning
    if (State.ContainsKey(StateKeys.SingleFeedVm))
    {
        //clear previous value
        State.Remove(StateKeys.SingleFeedVm);
    }
    State.Add(StateKeys.SingleFeedVm, Vm);
}

...


ПРЕДУПРЕЖДЕНИЕ!
Я собирался забыть важный момент: события не могут быть сериализованы (еще раз ссылка на другой мой старый пост: исключение сериализации: PropertyChangedEventManager не сериализуем , он связан с двоичной сериализацией или объектами, но концепция та же), поэтому не забудьте пометить события с помощью DataMemberAttribute или, если ваш ViewModel имеет все общедоступное, и вы не использовали DataContractAttribute для выбора того, что вы хотите сериализовать, лучше пометить события с помощью [field: IgnoreDataMember] для исключить их из процесса сериализации.

Источник:
http://www.primordialcode.com/blog/post/wp7-test-if-your-viewmodel-is-serializable