Why can't I add attributes when serializing ObservableCollection<T>? - wpf

I'm trying to extend an ObservableCollection with a few custom properties and have it serialize. However, I can't seem to get it to serialize these properties. I'm using .NET 4.0 where they fixed the serialization issues of ObservableCollection, but am still having problems. My hunch is that GetObjectData is being called on the base class and not mine. Any ideas?
[Serializable]
[XmlRoot(ElementName = "MyCollection")]
public class MyCollection : ObservableCollection<MyItem>, ISerializable
{
private string name;
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Name", Name);
}
private MyCollection()
{
Name = string.Empty;
}
public MyCollection(string name)
{
Name = name;
}
public MyCollection(SerializationInfo info, StreamingContext context)
{
Name = (string)info.GetValue("Name", typeof(string));
}
[XmlAttribute]
public string Name
{
get { return name; }
protected set
{
string originalName = name;
name = value;
if (originalName != name)
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public void SaveToFile(string path)
{
string directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
XmlSerializer serializer = new XmlSerializer(typeof(MyCollection));
using (TextWriter textWriter = new StreamWriter(path))
{
serializer.Serialize(textWriter, this);
textWriter.Close();
}
}
public static MyCollection LoadFromFile(string path)
{
XmlSerializer deserializer = new XmlSerializer(typeof(MyCollection));
using (TextReader textReader = new StreamReader(path))
{
MyCollection myCollection = (MyCollection)deserializer.Deserialize(textReader);
textReader.Close();
return myCollection;
}
}
}

XML Serialization does not support this scenario. You simply cannot add anything to a class implementing ICollection.
If you require this, then you will have to implement IXmlSerializable and do the work yourself.
Note that you may be confusing XML Serialization with runtime serialization. XML Serialization doesn't care about the [Serializable] attribute or GetObjectData, etc.

Related

How to update a string property in the Model from an aggregated ViewModel

I'm working on a GUI application in WPF/MVVM. Let's say i have a Model class which is populated deserializing a (third-party) XML file
class Person
{
public string Name { get; set; }
public string Address { get; set; }
public string ReservationId { get; set; }
}
and a ViewModel which exposes to its View a lot of properties and commands to manipulate the Model, in this case strings (such as ReservationId):
class StringManipulatorViewModel
{
string modelString; //here's the problem
public StringManipulatorViewModel(string modelString)
{
this.modelString = modelString;
}
//Stuff to let the view manipulate the string
}
StringManipulatorViewModel is highly reusable and it is used by a lot of ViewModels, e.g.
class PersonViewModel
{
Person model;
public StringManipulatorViewModel ReservationManipulatorVM; //aggregated ViewModel
public StringManipulatorViewModel AddressManipulatorVM; //aggregated ViewModel
public PersonViewModel(Person model)
{
this.model = model;
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId); //here's the problem
AddressManipulatorVM = new StringManipulatorViewModel(model.Address); //here's the problem
}
}
Obviously passing the string as "model" to the ViewModel isn't effective, and C# doesn't seem to allow string references as fields.
What is the best/right way to let member ViewModels manipulate the Model when dealing with string types?
Thank you
Your problem is that you are trying to reference a property, not a string field.
But you can pass a delegate to the setter of the property.
If you also change the modelString field to a property, you can call this delegate automatically when the string is changed.
class StringManipulatorViewModel
{
private string modelString
{
get { return _modelString; }
set { _modelString = value; if (SetModelString != null) SetModelString(value); }
}
private string _modelString;
Action<string> SetModelString;
public StringManipulatorViewModel(string initialValue, Action<string> setModelString)
{
this.modelString = initialValue;
SetModelString = setModelString;
}
//Stuff to let the view manipulate the string
}
You initiate the StringManipulatorViewModel in PersonViewModel like this:
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId, value => model.ReservationId = value); //here's the problem
Here are some other ideas when you want to pass a property.
Passing properties by reference in C#
I can't think of a "right" way to manipulate the string as a reference inside the StringManipulatorViewModel, once you pass the string as a value it has nothing to do with the model.
But a way to legitimately change the model string value whenever the StringManipulatorViewModel manipulates it, is by raising an Event in the view model when it manipulates the string and then add an event handler to update the model with the new value:
class StringManipulatorViewModel
{
string modelString;
public event EventHandler<string> StringManipulated;
public StringManipulatorViewModel(string modelString)
{
this.modelString = modelString;
}
public ManipulateString()
{
// Manipulate the string
StringManipulated?.Invoke(this, modelString);
}
}
And in the PersonViewModel constructor:
class PersonViewModel
{
Person model;
public StringManipulatorViewModel ReservationManipulatorVM;
public StringManipulatorViewModel AddressManipulatorVM;
public PersonViewModel(Person model)
{
this.model = model;
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId);
AddressManipulatorVM = new StringManipulatorViewModel(model.Address);
ReservationManipulatorVM.StringManipulated += (sender, e) => model.ReservationId = e;
AddressManipulatorVM.StringManipulated += (sender, e) => model.Address = e;
}
}

Exposing custom properties using UI Automation Framework

Given a very basic WinForms custom/user control, using System.Windows.Automation it is possible to manipulate built in properties for the custom control.
This is done like this:
public object GetPropertyValue(int propertyId)
{
if (propertyId == AutomationElementIdentifiers.NameProperty.Id)
{
return "Hello World!";
}
}
What I would like to do is expose custom properties to ui automation such as ReadyState, LastAccessed, Etc.
Is this possible?
No, you can't extend the list of properties, and this is complicated by the fact you use Winforms that has a poor UI Automation support (it uses IAccessible with bridges etc.).
What you can do though is add some fake objects to the automation tree, for example, here is a sample Winforms UserControl that does it:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
Button button = new Button();
button.Location = new Point(32, 28);
button.Size = new Size(75, 23);
button.Text = "MyButton";
Controls.Add(button);
Label label = new Label();
label.Location = new Point(49, 80);
label.Size = new Size(35, 13);
label.Text = "MyLabel";
Controls.Add(label);
MyCustomProp = "MyCustomValue";
}
public string MyCustomProp { get; set; }
protected override AccessibleObject CreateAccessibilityInstance()
{
return new UserControl1AccessibleObject(this);
}
protected class UserControl1AccessibleObject : ControlAccessibleObject
{
public UserControl1AccessibleObject(UserControl1 ownerControl)
: base(ownerControl)
{
}
public new UserControl1 Owner
{
get
{
return (UserControl1)base.Owner;
}
}
public override int GetChildCount()
{
return 1;
}
public override AccessibleObject GetChild(int index)
{
if (index == 0)
return new ValueAccessibleObject("MyCustomProp", Owner.MyCustomProp);
return base.GetChild(index);
}
}
}
public class ValueAccessibleObject : AccessibleObject
{
private string _name;
private string _value;
public ValueAccessibleObject(string name, string value)
{
_name = name;
_value = value;
}
public override AccessibleRole Role
{
get
{
return AccessibleRole.Text; // activate Value pattern
}
}
// note you need to override with member values, base value cannot always store something
public override string Value { get { return _value; } set { _value = value; } }
public override string Name { get { return _name; } }
}
And this is how it appears in the automation tree (using the inspect.exe tool):
Note this technique also supports writing back to the property because it's based on the ValuePattern.

Access Arrays inside Java Objects

How do I obtain values of an array that is located inside a java object in a jsp page?
I have set an object attribute so that in the jsp page I can call the object like so
${obj.property}
My question is how would I obtain property String [] example from Object obj?
<c:forEach var="prop" items="${obj.example}">
<td>${prop}</td>
</c:forEach>
I get Errors that tell me the class obj.Obj does not have the property property 'example'
and obviously I don't get the data out.
Actual errors:
org.apache.jasper.JasperException: javax.el.PropertyNotFoundException: The class 'roommate.Roommate' does not have the property 'favProfessors'.
javax.el.PropertyNotFoundException: The class 'roommate.Roommate' does not have the property 'favProfessors'
And my actual class:
package roommate;
public class Roommate{
public String firstname;
public String lastname;
public String gender;
public String place;
public String[] favProfessors;
public Roommate(String fname, String lname, String roommateGender, String hangout,String[] professors) {
firstname= fname;
lastname= lname;
gender= roommateGender;
place= hangout;
favProfessors= professors;
}
public String getFirstname()
{
return firstname;
}
public void setFirstname(String newFirstname)
{
this.firstname = newFirstname;
}
public String getLastname()
{
return lastname;
}
public void setLastname(String newLastname)
{
this.lastname = newLastname;
}
public String getGender()
{
return gender;
}
public void setGender(String newGender)
{
this.gender = newGender;
}
public String getHangout()
{
return place;
}
public void setHangout(String newPlace)
{
this.place = newPlace;
}
public String[] getProfessors()
{
return favProfessors;
}
public void setProfessors(final String[] newfavProfessors)
{
this.favProfessors = newfavProfessors;
}
public void addRoommate(String fname, String lname, String roommateGender, String hangout,String[] professors)
{
}
}
I create the object in my servlet as well ass the Atrribute
String [] profArray = request.getParameterValues("professor");
Roommate roommate= new Roommate(
session.getAttribute("fname").toString(),
session.getAttribute("lname").toString(),
session.getAttribute("gender").toString(),
session.getAttribute("hangout").toString(),
profArray);
session.setAttribute("roommate",roommate);
I asked this earlier but did not receive a clear answer. I think my issue is in pulling the data out in the jsp alone in my forEach that I mentioned at the top
javax.el.PropertyNotFoundException: The class 'roommate.Roommate' does not have the property 'favProfessors'
Java is right. You do not have a getFavProfessors() method in that class. It's instead the following:
public String[] getProfessors()
{
return favProfessors;
}
You have 2 options: use ${roommate.professors} instead, or fix the getter method name to be getFavProfessors().
In contrary to what most starters think, EL does not access private properties directly. EL just calls the public getter/setter methods according the Javabeans specification. The real private property behind it can have a completely different name or even not exist at all.

Passing a list or array to RESTeasy using get

I've seen this kind of thing described in various examples showing how to create a REST service which takes arrays or a list of objects as part of the URL.
My question is, how to implement this using RESTeasy?
Something like the following would be how i would assume this to work.
#GET
#Path("/stuff/")
#Produces("application/json")
public StuffResponse getStuffByThings(
#QueryParam("things") List<Thing> things);
Create a StringConverter and a use a wrapper object. Here is a quick and dirty example:
public class QueryParamAsListTest {
public static class Thing {
String value;
Thing(String value){ this.value = value; }
}
public static class ManyThings {
List<Thing> things = new ArrayList<Thing>();
ManyThings(String values){
for(String value : values.split(",")){
things.add(new Thing(value));
}
}
}
static class Converter implements StringConverter<ManyThings> {
public ManyThings fromString(String str) {
return new ManyThings(str);
}
public String toString(ManyThings value) {
//TODO: implement
return value.toString();
}
}
#Path("/")
public static class Service {
#GET
#Path("/stuff/")
public int getStuffByThings(
#QueryParam("things") ManyThings things){
return things.things.size();
}
}
#Test
public void test() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getProviderFactory().addStringConverter(new Converter());
dispatcher.getRegistry().addSingletonResource(new Service());
MockHttpRequest request = MockHttpRequest.get("/stuff?things=a,b,c");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
Assert.assertEquals("3", response.getContentAsString());
}
}
I think you can also use a StringParamUnmarshaller
I had some luck with this, using Collection rather than List. I was unable to make a StringConverter for List work.
#Provider
public class CollectionConverter implements StringConverter<Collection<String>> {
public Collection<String> fromString(String string) {
if (string == null) {
return Collections.emptyList();
}
return Arrays.asList(string.split(","));
}
public String toString(Collection<String> values) {
final StringBuilder sb = new StringBuilder();
boolean first = true;
for (String value : values) {
if (first) {
first = false;
} else {
sb.append(",");
}
sb.append(value);
}
return sb.toString();
}
}
I did the toString from my head. Be sure to write unit tests for it to verify. But of course, everything is easier and clearer when you use Guava. Can use Joiner and Splitter. Really handy.
Just use a wrapper on its own, no need for anything else.
In your endpoint
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Path("/find")
#GET
MyResponse find(#QueryParam("ids") Wrapper ids);
And you wrapper looks like this :
public class Wrapper implements Serializable {
private List<BigInteger> ids = Collections.emptyList();
public String toString() {
return Joiner.on(",")
.join(ids);
}
public List<BigInteger> get() {
return ids;
}
public Wrapper(String s) {
if (s == null) {
ids = Collections.emptyList();
}
Iterable<String> splitted = Splitter.on(',')
.split(s);
Iterable<BigInteger> ids = Iterables.transform(splitted, Functionz.stringToBigInteger);
this.ids = Lists.newArrayList(ids);
}
public Wrapper(List<BigInteger> ids) {
this.ids = ids;
}
}

property names are different from original Object in the silverlight

Following is part of service layer which is provided by WCF service :
[Serializable]
public class WaitInfo
{
private string roomName;
private string pName;
private string tagNo;
public string RoomName
{ get { return roomName; } set { this.roomName = value; } }
public string PName
{ get { return pName; } set { this.pName = value; } }
public string TagNo
{ get { return tagNo; } set { this.tagNo = value; } }
}
public class Service1 : IService1
{
public List<WaitInfo> GetWaitingList()
{
MyDBDataContext db = new MyDBDataContext();
var query = from w in db.WAIT_INFOs
select new WaitInfo
{
TagNo = w.PATIENT_INFO.TAG_NO,
RoomName= w.ROOM_INFO.ROOM_NAME,
PName= w.PATIENT_INFO.P_NAME
};
List<WaitInfo> result = query.ToList();
return result;
}
And following is codebehind part of UI layer which is provided by Silverlight
public MainPage()
{
InitializeComponent();
Service1Client s = new Service1Client();
s.GetWaitingListCompleted +=
new EventHandler<GetWaitingListByCompletedEventArgs>( s_GetWaitingListCompleted);
s.GetWaitingListAsync();
}
void s_GetWaitingListCompleted(object sender,
RadControlsSilverlightApplication1.ServiceReference2.GetWaitingListByCompletedEventArgs e)
{
GridDataGrid.ItemsSource = e.Result;
}
And following is xaml code in Silverlight page
<Grid x:Name="LayoutRoot">
<data:DataGrid x:Name="GridDataGrid"></data:DataGrid>
</Grid>
It is very simple code, however what I am thinking weird is property name of object at "e.Result" in the code behind page.
In the service layer, although properties' names are surely "RoomName, PName, TagNo", in the silverlight properties' names are "roomName, pName, tagNo" which are private variable name of the WaitingList Object.
Did I something wrong?
Thanks in advance.
Unless you specifically decorate your class with the DataContract attribute (which you should, instead of Serializable) then a default DataContract will be inferred. For normal Serializable types, this means the fields will be serialized as opposed to the properties.
You can markup your class in either of the following two ways. The latter will use the property accessors when serializing/deserializing your object which may be very useful or be a hassle depending on your circumstances.
[DataContract]
public class WaitInfo
{
[DataMember(Name="RoomName")]
private string roomName;
[DataMember(Name="PName")]
private string pName;
[DataMember(Name="TagNo")]
private string tagNo;
public string RoomName
{ get { return roomName; } set { this.roomName = value; } }
public string PName
{ get { return pName; } set { this.pName = value; } }
public string TagNo
{ get { return tagNo; } set { this.tagNo = value; } }
}
The method I prefer:
[DataContract]
public class WaitInfo
{
private string roomName;
private string pName;
private string tagNo;
[DataMember]
public string RoomName
{ get { return roomName; } set { this.roomName = value; } }
[DataMember]
public string PName
{ get { return pName; } set { this.pName = value; } }
[DataMember]
public string TagNo
{ get { return tagNo; } set { this.tagNo = value; } }
}

Resources