2

So I'm building an application in WPF using MVVM and I want to save various object model data as XML. Using Serialize an object to XML I can correctly serialize objects to XML, but the issue I'm having is with the MVVM itself.
I can't directly access the Model of the object within the ICommand segments of the ViewModel code (E.G. When I hit save, that goes to an ICommand method inside the ViewModel).
I've made the Model is question serializable, I just have no way to pass it directly to my Serialize method (which is contained in it's own static Helper class) so even if I weren't getting "Unexpected type" spit back at me (since VieWModel is not serializable) I'd end up with a lot of excess garbage, not just the Model class being serialized.

I'm not sure if I'm just designing this incorrectly, or if there's a better way to do it or...?

P.S. All of these fields are just being written into TextBox controls that are bound appropriately. Right now I'm only trying to do the name fields to avoid any kind of issues with other data types not working right.

EDIT: As requested in a comment, the goal right now is just to be able to write some bits of text in a few text boxes (First, middle, last names), then save that to an XML file.

Summarized Model in question:

namespace XMLaw.Model
{
    [Serializable]
    public class ClientModel
    {
        private string firstName { get; set; }
        private string middleName { get; set; }
        private string lastName { get; set; }
        private DateTime dateOfBirth { get; set; }
        private string ssn { get; set; } //Format: AA ## ## ## A, spaces optional
        private string address { get; set; }
        private string phone { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #region Name Properties
        public string FirstName
        {
            get { return firstName; }
            set
            {
                if( firstName != value )
                {
                    firstName = value;
                    OnPropertyChanged("FirstName");
                }
            }
        }
        public string MiddleName
        {
            get { return middleName; }
            set
            {
                if (middleName != value)
                {
                    middleName = value;
                    OnPropertyChanged("MiddleName");
                }
            }
        }
        public string LastName
        {
            get { return lastName; }
            set
            {
                if (lastName != value)
                {
                    lastName = value;
                    OnPropertyChanged("LastName");
                }
            }
        }
        #endregion

        public DateTime DateOfBirth
        {
            get { return dateOfBirth; }
            set
            {
                if ( dateOfBirth != value )
                {
                    DateTime dt = Convert.ToDateTime(value); //This will probably need to revisited since DateTime objects are fucking stupid
                    dateOfBirth = dt.Date;
                    OnPropertyChanged("DateOfBirth");
                }
            }
        }

        public string SSN
        {
            get { return ssn; }
            set
            {
                if( ssn != value)
                {
                    ssn = value;
                    OnPropertyChanged("SSN");
                }
            }
        }

        public string Address
        {
            get { return address; }
            set
            {
                if( address != value)
                {
                    address = value;
                    OnPropertyChanged("Address");
                }
            }
        }

        public string Phone
        {
            get { return phone; }
            set
            {
                if( phone != value )
                {
                    phone = value;
                    OnPropertyChanged("Phone");
                }
            }
        }

    }
}

And the ViewModel in question (The Save command that calls the serialization is at the bottom)

namespace XMLaw.ViewModel
{
    public class ClientViewModel : INotifyPropertyChanged
    {
        private ClientModel client;
        private string displayMessage;
        private ICommand btnSave;

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public ClientViewModel()
        {
            client = new ClientModel();
        }

        public ClientModel ClientModel
        {
            get { return client; }
        }

        public string DisplayMessage
        {
            get { return displayMessage; }
            set
            {
                if( displayMessage != value)
                {
                    displayMessage = value;
                    OnPropertyChanged("DisplayMessage");
                }
            }
        }

        public ICommand SaveCommand
        {
            get
            {
                if (btnSave == null)
                    btnSave = new Save();
                return btnSave;
            }
            set { btnSave = value; }
        }

        protected class Save : ICommand
        {

            public bool CanExecute(object param) { return true; }
            public event EventHandler CanExecuteChanged; //Compiler yells at you if you don't implement this from inhereted ICommand
            public void Execute(object param)
            {
                ClientViewModel viewModel = (ClientViewModel)param;
                //TODO: Insert XML serialization and save to a file
                var xml = Helper.Serialize(param);


                //Placeholder to make sure the button works
                viewModel.DisplayMessage = "You clicked the button at " + DateTime.Now;
            }
        }


    }
}

And the Serailization method I shamelessly took from the above link

public static class Helper
{
    public static string Serialize<T>(this T value)
    {
        if (value == null)
        {
            return string.Empty;
        }
        try
        {
            var xmlserializer = new XmlSerializer(typeof(T));
            var stringWriter = new StringWriter();
            using (var writer = XmlWriter.Create(stringWriter))
            {
                xmlserializer.Serialize(writer, value);
                return stringWriter.ToString();
            }
        }
        catch (Exception ex)
        {
            throw new Exception("An error occurred", ex);
        }
    }
}
Koala Bear
  • 163
  • 3
  • 15
  • 2
    Dont use properties as backing fields for `Properties` (Did you really did that o.O). Make your private `Properties` fields please. Further more, your `PropertyChanged`-Event cannot be serialized. Flag it as `[NonSerialized]` – lokusking Jul 11 '16 at 19:12
  • Can you give me an example of what you mean by "Dont use properties as backing fields for Properties"? This is really my first foray into using MVVM and I've had a hell of a time finding a non-convoluted tutorial, and this was the only coherent way I found. – Koala Bear Jul 11 '16 at 19:13
  • 1
    @KoalaBear See where it says `private string firstName { get; set; }`? Just replace `{get; set; }` with a semicolon: `private string firstName;`. That turns a property into a field. A property has get/set accessors; a field is just a member variable. – 15ee8f99-57ff-4f92-890c-b56153 Jul 11 '16 at 19:24
  • Whatever you're trying to accomplish, this is not the way to do it. If you [edit] to describe your goals, we might be able to give you a much better and simpler solution. Also, xml serialization kinda sucks in the framework. I always use the NetDataContractSerializer or XamlServices.Save. They handle object graphs much better than the XmlSerializer. Both yield XML (the second is xaml-flavored xml). –  Jul 11 '16 at 19:27

1 Answers1

3

Change your Client-Model to this:

[Serializable]
    public class ClientModel
    {
        private string firstName;
        private string middleName;
        private string lastName;
        private DateTime dateOfBirth;
        private string ssn;
        private string address;
        private string phone;


        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #region Name Properties
        public string FirstName {
            get { return firstName; }
            set {
                if (firstName != value)
                {
                    firstName = value;
                    OnPropertyChanged("FirstName");
                }
            }
        }
        public string MiddleName {
            get { return middleName; }
            set {
                if (middleName != value)
                {
                    middleName = value;
                    OnPropertyChanged("MiddleName");
                }
            }
        }
        public string LastName {
            get { return lastName; }
            set {
                if (lastName != value)
                {
                    lastName = value;
                    OnPropertyChanged("LastName");
                }
            }
        }
        #endregion

        public DateTime DateOfBirth {
            get { return dateOfBirth; }
            set {
                if (dateOfBirth != value)
                {
                    DateTime dt = Convert.ToDateTime(value); //This will probably need to revisited since DateTime objects are fucking stupid
                    dateOfBirth = dt.Date;
                    OnPropertyChanged("DateOfBirth");
                }
            }
        }

        public string SSN {
            get { return ssn; }
            set {
                if (ssn != value)
                {
                    ssn = value;
                    OnPropertyChanged("SSN");
                }
            }
        }

        public string Address {
            get { return address; }
            set {
                if (address != value)
                {
                    address = value;
                    OnPropertyChanged("Address");
                }
            }
        }

        public string Phone {
            get { return phone; }
            set {
                if (phone != value)
                {
                    phone = value;
                    OnPropertyChanged("Phone");
                }
            }
        }

    }

Usage:

 var xx = new ClientModel();
            xx.FirstName = "John";
            xx.LastName = "Smith";
            xx.DateOfBirth = DateTime.Now;

            var result = xx.Serialize();

Result:

http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"> John Smith 2016-07-11T00:00:00+02:00

EDIT:

This code:

public void Execute(object param)
            {
                ClientViewModel viewModel = (ClientViewModel)param;
                //TODO: Insert XML serialization and save to a file
                var xml = Helper.Serialize(param);


                //Placeholder to make sure the button works
                viewModel.DisplayMessage = "You clicked the button at " + DateTime.Now;
            }

Should be replaced by this:

public void Execute(object param)
            {
                ClientModel model= (ClientModel )param;
                //TODO: Insert XML serialization and save to a file
                var xml = Helper.Serialize(param);


                //Placeholder to make sure the button works
                viewModel.DisplayMessage = "You clicked the button at " + DateTime.Now;
            }

Make sure, your Param is of type ClientModel.

I also highly recommend you, to get into the basics of how DataBinding works

Edit 2 (The Command-Thingy):

class Save : ICommand
    {
        public ClientModel Model { get; set; }
        public bool CanExecute(object param) { return true; }
        public event EventHandler CanExecuteChanged; //Compiler yells at you if you don't implement this from inhereted ICommand
        public void Execute(object param)
        {
            //TODO: Insert XML serialization and save to a file
            var xml = Helper.Serialize(this.Model);


            //Placeholder to make sure the button works
            viewModel.DisplayMessage = "You clicked the button at " + DateTime.Now;
        }
    }

Usage:

public ICommand SaveCommand
        {
            get
            {
                if (btnSave == null)
                    btnSave = new Save();
btnSave.Model = this.ClientModel;

                return btnSave;
            }
            set { btnSave = value; }
        }
lokusking
  • 7,396
  • 13
  • 38
  • 57
  • Ah, yes. I see now how silly that was of me. Does not address being unable to serialize the object from inside the ViewModel class "save" command however. – Koala Bear Jul 11 '16 at 19:24
  • Post Edit: Unable to cast object of type 'XMLaw.ViewModel.ClientViewModel' to type 'XMLaw.Model.ClientModel'. – Koala Bear Jul 11 '16 at 19:26
  • 1
    First step: Use a [RelayCommand](http://www.c-sharpcorner.com/UploadFile/20c06b/icommand-and-relaycommand-in-wpf/). Alternative way, implement a property of type `ClientModel ` in your `Save`-Command and set use this for your purpose – lokusking Jul 11 '16 at 19:27
  • 1
    @KoalaBear I've made you a quick and dirty fix – lokusking Jul 11 '16 at 19:31
  • I edited the Save class to have a constructor that takes in a ClientModel, it wouldn't let me set the Model variable otherwise. I'm sure this is all 100% the wrong way to do anything but it does what I need to for right now, thanks for the help. I'll have to re-bury myself in other documentation to do this less poorly. – Koala Bear Jul 11 '16 at 19:39