Highrise Api, Subject data not posting with RestSharp - c#

I am trying to use the Highrise Api with the .NET 4.5 wrapper by scottschluer in order to post a new Person to our Highrise account along with any custom data that they enter.
The issue that I am running into is with the custom data. The person object gets posted just fine, but the subject data fields are not being included with the post.
I did come across this post: Highrise Custom Fields. It looked like the thing that was missing was the type="array" attribute from the subject_datas field. I tested this hypothesis by manually creating a request using the serialized value of the object I was trying to post. The result of this test, was a successful post to the server with all custom data fields filled.
I've tried extending the classes from the wrapper assembly to add that missing attribute to the list, but that still didn't seem to work. The Person object has a property for a List of SubjectData objects, I overwrote that property in a child class to use a custom class instead. This way I could add a property to use as the attribute. This custom class still didn't seem to work.
After looking at the code for RestSharp's XmlSerializer, it appears that it will only add a list of items when that object implements IList. That wasn't an issue, i was able to get that working, but the code does not seem to allow for adding attributes to the list element. It only looks at the children of the list class and ignores any other properties on the object.
So my question is this:
Is it possible to apply attributes to a list property in RestSharp, or is there another way to add the type="array" attribute to the data_subjects xml node before the request is sent?

I eventually solved the problem myself by creating a new request class that would create a RestRequest using a custom XmlSerializer.
I then extended the Person class and hid the property behind a custom list object property
Before:
[SerializeAs(Name = "subject_datas")]
public List<SubjectData> SubjectDatas { get; set; }
After:
[SerializeAs(Name = "subject_datas")]
public new SubjectDataList SubjectDatas { get; set; }
The SubjectDataList class is just a wrapper for List<SubjectData>.
SubjectDataList implements an interface called ISerializeList<SubjectData>
which is defined as:
interface ISerializeList : IEnumerable {}
interface ISerializeList<T> :IEnumerable<T>, ISerializeList {}
SubjectDataList also has a type property to render the type attribute onto the subjectdatas node of the rest request.
[SerializeAs(Name = "type", Attribute = true)]
public string Type { get; set; } = "array";
I then made a class called XmlListSerializer which implements ISerializer. I copied the implementation of XmlSerializer, but i made a few modifications. In the Map method, there is a part that checks if the variable rawValue is an IList. I changed this part slightly, and added a clause for my XmlListSerializer class.
So it now looks like this:
if (propType.IsPrimitive || propType.IsValueType || propType == typeof(string)) {
//...
} else if (rawValue is IList) {
ProcessIList((IList) rawValue, element);
} else if (rawValue is ISerializeList) {
ProcessISerializeList((ISerializeList) rawValue, element);
} else {
Map(element, rawValue);
}
Where ProcessIList and ProcessISerializeList are defined as:
private void ProcessIList(IList list, XElement element) {
ProcessIEnumerable(list, element);
}
private void ProcessISerializeList(ISerializeList list, XElement element) {
ProcessIEnumerable(list, element);
Map(element, list);
}
private void ProcessIEnumerable(IEnumerable list, XElement element) {
var itemTypeName = "";
foreach (var item in list) {
if (itemTypeName == "") {
var type = item.GetType();
var setting = type.GetAttribute<SerializeAsAttribute>();
itemTypeName = setting != null && setting.Name.HasValue() ? setting.Name : type.Name;
}
var instance = new XElement(itemTypeName.AsNamespaced(Namespace));
Map(instance, item);
element.Add(instance);
}
}
I hope this answer will be able to help anyone else having issues with this problem.

Related

Is it possible to define one attribute that applies other attributes in C#? [duplicate]

On a control I am using multiple attribute properties:
[Browsable(false)]
[Bindable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Obsolete("", true)]
public new Boolean AllowDrop;
I am using those properties on a lot of the other control properties as well.
I am wondering if there is a way to reduce the amount of code to write each time.
It would be nice if I could combine multiple attributes like this:
[Hidden(true)]
public new Boolean AllowDrop;
Where the Hidden Property would include all the attributes above. So there is only 1 single line of code.
Maybe there is also a way to combine the attributes in a macro or something?
I am aware that there are other ways of hiding properties but I chose the way of using attributes.
Thanks
It depends to the framework which is using the attribute.
Combining attributes can be meaningful in order to the context which uses and interprets attributes. For example for those contexts which use .Net Type Description mechanisms you can customize the type description which .Net returns to consumers.
It's possible to provide custom metadata for types using the standard .Net mechanism for that purpose, registering a custom type descriptor for your object.
The idea will work this way, you create a custom type descriptor for your type. In the custom type descriptor, you return custom property descriptors for the properties of your type and in the property descriptor, you return a custom set of attributes for the property.
The approach requires more code, but it's really interesting and shares some good idea about how to provide custom metadata for your types:
IMetedataAttribute Interface
The usage is providing an standard way to create MetaDataAttributes. Each attribute which implements this interface will be used as metadata and instead of the attribute, those one which it returns in Process method will be used:
public interface IMetadatAttribute
{
Attribute[] Process();
}
Sample MetadataAttribute
It's a sample metadata attribute which returns some attribute instead when processing the attribute:
public class MySampleMetadataAttribute : Attribute, IMetadatAttribute
{
public Attribute[] Process()
{
var attributes = new Attribute[]{
new BrowsableAttribute(false),
new EditorBrowsableAttribute(EditorBrowsableState.Never),
new BindableAttribute(false),
new DesignerSerializationVisibilityAttribute(
DesignerSerializationVisibility.Hidden),
new ObsoleteAttribute("", true)
};
return attributes;
}
}
Property Descriptor
This class will be used by the custom type descriptor to provide a custom list of attributes for the property:
public class MyPropertyDescriptor : PropertyDescriptor
{
PropertyDescriptor original;
public MyPropertyDescriptor(PropertyDescriptor originalProperty)
: base(originalProperty) { original = originalProperty;}
public override AttributeCollection Attributes
{
get
{
var attributes = base.Attributes.Cast<Attribute>();
var result = new List<Attribute>();
foreach (var item in attributes)
{
if(item is IMetadatAttribute)
{
var attrs = ((IMetadatAttribute)item).Process();
if(attrs !=null )
{
foreach (var a in attrs)
result.Add(a);
}
}
else
result.Add(item);
}
return new AttributeCollection(result.ToArray());
}
}
// Implement other properties and methods simply using return original
// The implementation is trivial like this one:
// public override Type ComponentType
// {
// get { return original.ComponentType; }
// }
}
Type Descriptor
This is the type descriptor which provides a custom description for your type. In this example it uses custom property descriptors to provide custom attributes set for the properties of your class:
public class MyTypeDescriptor : CustomTypeDescriptor
{
ICustomTypeDescriptor original;
public MyTypeDescriptor(ICustomTypeDescriptor originalDescriptor)
: base(originalDescriptor)
{
original = originalDescriptor;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>()
.Select(p => new MyPropertyDescriptor(p))
.ToArray();
return new PropertyDescriptorCollection(properties);
}
}
Typedescriptor Provider
This class will be used in the attribute above your type to introduce the custom type descriptor which we created as the metadata engine for the type:
public class MyTypeDescriptionProvider : TypeDescriptionProvider
{
public MyTypeDescriptionProvider()
: base(TypeDescriptor.GetProvider(typeof(object))) { }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance)
{
ICustomTypeDescriptor baseDescriptor = base.GetTypeDescriptor(objectType, instance);
return new MyTypeDescriptor(baseDescriptor);
}
}
Sample Class
Here is my sample class which its Name property is decorated using MySampleMetadataAttribute and the class itself is registered to use our custom type descriptor provider:
[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]
public class MySampleClass
{
public int Id { get; set; }
[MySampleMetadataAttribue]
[DisplayName("My Name")]
public string Name { get; set; }
}
To see the result it's enough to create an instance of the class and see the result in PropertyGrid:
var o = new MySampleClass();
this.propertyGrid1.SelectedObject = o;
Some notes on answer
Probably it's not as simple as you expected for such task. But it's working.
It's a lengthy answer, but contains a complete working example of how you can apply type descriptors to your types to provide custom metadata.
The approach will not work for engines which use reflection instead of type description. But it's completely working with for example PropertyGrid control which works with type description.
The best way for me to do this, is by using Metalama (modern rewrite of PostSharp for new .NET releases).
It is absolutely the best framework for doing AOP in .NET from the same guys that did PostSharp. It is still in preview, but Metalama 1.0 will be released in a week or 2, and in next year, it will probably get most of features found in PostSharp... And it has a nice community on Slack and the authors of this Metalama framework are super supportive, they helped me with each question I had, and I had a lot of them already :D
And so this library is perfect for creating custom aspects, but could easily be used for this merging of attributes :) It will be even better then the approach above, because once you see transformed file (using Metalama Diff Preview - you gotta install Metalama extension to VS), then you will actually see all those original attributes there, in a transformed file :)
And so this is how easily I will merge 3 attributes into 1 with Metalama:
Metalama easy merging of attributes 1
(This would be for aspect attributes created by Metalama)
Or, for other attributes (from other libraries), that don't need to do the aspect work, it would by like this: (And this is probably what you want to use, not the first example...):

Deserializing XML when the root type is the same but the inner XML can represent different types

I have to work on a application when sends XML back. Because the root is always the "Reply", but the property's differ, deserializing can't be based on 1 object type.
I've now written code which loads the Xml first in a new XmlDocument, reads a Name attribute and based on the attribute, I try to deserialize it. Are there
better ways?
Example xml I can expect:
<Reply Name="GetModulesList" Result="yes"><ModuleName="xxxxxx.exe" Path="\Debug\xxxxxxx.exe" Order="1"/></Reply>
<Reply Name="OpenRecipe" Result="yes"/>
How whould you solve this?
XmlDocument doc = new XmlDocument();
doc.LoadXml(trimmedPart);
if (doc.DocumentElement?.Attributes != null)
{
XmlAttribute name = doc.DocumentElement.Attributes.Cast<XmlAttribute>().SingleOrDefault(a => String.Compare(a.Name, "name", StringComparison.OrdinalIgnoreCase) == 0);
replyName = name?.Value;
}
if (!string.IsNullOrEmpty(replyName))
{
XmlSerializer serializer = new XmlSerializer(GetSerializerObjectType(replyName));
using (StringReader reader = new StringReader(trimmedPart))
{
object obj = serializer.Deserialize(reader);
if (obj != null) returnList.Add(obj);
}
}
I recently dealt with a similar scenario. Here's a very rough outline, adapted from what I was working with to your case as specifically as I can.
My assumption is that you're going to do different things with different types. If it's an OpenRecipe you're going to do one thing, if it's GetModuleList you're going to do something else.
The biggest difference is that each "handler" I worked with for different types might handle completely different types of data - XML, JSON, even Excel, so the handlers received content in the form of a byte array and were responsible for deserializing it. (It wasn't my idea to write an in-house version of Biztalk.)
In this case, if it's always XML, you can
Determine the type of the inner XML from the Name attribute
Deserialize the inner XML to that type
Pass it off to a strongly-typed class
This class is concerned with deserializing the Reply (except for the unknown inner content) and passing it to something that will handle that inner content in a more strongly typed way:
public class XmlReplyRouter
{
private readonly IReplyTypeMapper _replyTypeMapper;
private readonly IHandlerFactory _handlerFactory;
private readonly XmlSerializer _serializer = new XmlSerializer(typeof(Reply));
public XmlReplyRouter(
IReplyTypeMapper replyTypeMapper,
IHandlerFactory handlerFactory)
{
_replyTypeMapper = replyTypeMapper;
_handlerFactory = handlerFactory;
}
public void RouteReply(string replyXml)
{
using (var reader = new StringReader(replyXml))
{
var reply = (Reply)_serializer.Deserialize(reader);
var replyType = _replyTypeMapper.GetReplyType(reply.Name);
var handler = _handlerFactory.GetHandler(replyType);
handler.HandleReply(reply);
}
}
}
public class Reply
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Result { get; set; }
[XmlAnyElement]
public XmlNode InnerXml { get; set; }
public string XmlContent => InnerXml?.OuterXml;
}
That's where all the ugliness goes.
The XmlAnyElement attribute allows us to deserialize without knowing what to make of that inner content. Once we know what type to use we can separately deserialize that.
The implementations of IReplyTypeMapper and IHandlerFactory could be anything. In my case I couldn't use a DI container so it contained a Dictionary<string, IReplyHandler> and selected the correct one based on the name. (The terminology I worked with was different, but same concept.)
The first determines the type (from the "name" attribute) and the second returns a handler for that type.
public interface IReplyTypeMapper
{
Type GetReplyType(string replyName);
}
public interface IHandlerFactory
{
IReplyHandler GetHandler(Type contentType);
}
Finally, here's the interface and a base class for the reply handlers.
As you can see, the base class doesn't really do anything. It just bridges the gap between object and generic types so that we can write strongly-typed handlers.
public interface IReplyHandler
{
void HandleReply(object content);
}
public abstract class BaseHandler<T> : IReplyHandler
{
private readonly XmlSerializer _serializer = new XmlSerializer(typeof(T));
public void HandleReply(object content)
{
HandleReplyContent((T)content);
}
protected abstract void HandleReplyContent(T content);
}
As you can see it's very similar to what you're doing. The objective, though, is that once I've gotten this minimal amount of initial code out of the way, everything else is a strongly-typed class like this:
public class SomeSpecificReplyHandler : BaseHandler<SomeSpecificReply>
{
// Maybe these have dependencies of their own. That's easiest if
// everything gets resolved from an IoC container.
protected override void HandleReplyContent(SomeSpecificReply content)
{
// do whatever
}
}
I don't know if this might be overkill for your needs. My intent was to start from an entry point where my data could be just about anything and quickly get out of there to a place where all of my code was strongly-typed and easy to test. All of these classes are testable (I wrote a few while typing this up) the individual handlers would also be easy to test.
This post contains some other details about implementing the factories without an IoC container. In my case I couldn't work with an IoC container, so I had to "manually" compose all of my classes. The inner implementation of the factory was just a dictionary. That way if I had the option of switching to an IoC container later I could replace the whole thing with DI registrations.

How to unset a property on a Delta object

I am using Azure Mobile App Services and in the PATCH method I receive as body an object of type Delta (see MSDN).
I receive some fields with null values that I would like to remove from the Delta input object: how can I do this action?
e.g. I have a JSON input like
{
"Content":"test",
"CreatedAt":null
...
}
this maps an Entity that inherits from Microsoft.Azure.Mobile.Server.EntityData
e.g.
public class MyBean : EntityData
{
public MyBean() { }
public string Content { get; set; }
}
I would like to remove the field "CreatedAt", which by the way is declared in the EntityData parent object, part of the Microsoft library (therefore I don't have direct access to it).
I don't think you should try to remove the CreatedAt, but instead, take the incoming Delta and create a new one. You could either include the fields you wanted or exclude the ones you don't want.
var newDelta = new Delta<MyBean>();
foreach(var fieldName in patchDelta.GetChangedPropertyNames()){
if(fieldName != "CreatedAt"){
if(patchDelta.TryGetPropertyValue(fieldName, out object fieldValue)){
newDelta.TrySetPropertyValue(fieldNAme,fieldValue);
}
}
}
If you are using the Newtonsoft.Json to serialize the Entity then you can use conditional serialization of a property.
To conditionally serialize a property, add a method that returns
boolean with the same name as the property and then prefix the method
name with ShouldSerialize. The result of the method determines whether
the property is serialized. If the method returns true then the
property will be serialized, if it returns false then the property
will be skipped.
public class MyBean : EntityData
{
public MyBean() { }
public string Content { get; set; }
public bool ShouldSerializeCreatedAt()
{
return false;
// Or you can add some condition to whether serialize the property or not on runtime
}
}

Solution for CA2227 or better approach?

I'm only using Code Analysis for cleaning, organizing and ensuring these changes are globally performed for all instances of a particular warning. I'm down to the final, and it's CA2227.
CA2227 Collection properties should be read only Change '' to be
read-only by removing the property setter.
Note this is for mapping of EDI documents. These classes are to represent a whole or part of an EDI document.
public class PO1Loop
{
public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; set; }
public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }
/* Max Use: 8 */
public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; set; }
}
You can see all of the Collection properties will give me this warning, and there are hundreds of them. When using the above class I instantiate it without any data. Then externally I add the data and set each individual variable through its public accessor. I do not instantiate this class with all the data prepared and passed using a constructor method (IMO for the size these can reach it can easily wreak havoc on the eyes). When complete and all properties are assigned the class as a whole is then used to generate that part of a document it represents.
My question is, for the usage described above, what would be a better approach for setting this up correctly? Do I keep the public accessors and suppress this warning entirely, or is there a entirely different solution that would work?
Here's what MSDN says about the error, and also how you can avoid it.
Here's my take on the issue.
Consider, the following class:
class BigDataClass
{
public List<string> Data { get; set; }
}
This class will throw that exact same issue. Why? Because Collections do not need a setter. Now, we can do anything with that object: assign Data to an arbitrary List<string>, add elements to Data, remove elements from Data, etc. If we remove the setter, we only lose the ability to directly assign to that property.
Consider the following code:
class BigDataClass
{
private List<string> data = new List<string>();
public List<string> Data { get { return data; } } // note, we removed the setter
}
var bigData = new BigDataClass();
bigData.Data.Add("Some String");
This code is perfectly valid and in fact the recommended way to do things. Why? Because the List<string> is a reference to a memory location, that contains the remainder of the data.
Now, the only thing you cannot now do with this, is directly set the Data property. I.e. the following is invalid:
var bigData = new BigDataClass();
bigData.Data = new List<string>();
This is not necessarily a bad thing. You'll notice that on many .NET types this model is used. It's the basics of immutability. You usually do not want direct access to the mutability of Collections, as this can cause some accidental behavior that has strange issues. This is why Microsoft recommends you omit setters.
Example:
var bigData = new BigDataClass();
bigData.Data.Add("Some String");
var l2 = new List<string>();
l2.Add("String 1");
l2.Add("String 2");
bigData.Data = l2;
Console.WriteLine(bigData.Data[0]);
We might be expecting Some String, but we'll get String 1. This also means that you cannot reliably attach events to the Collection in question, so you cannot reliably determine if new values are added or values are removed.
A writable collection property allows a user to replace the collection with a completely different collection.
Essentially, if you only ever need to run the constructor, or assignment, once, then omit the set modifier. You won't need it, direct assignment of collections is against best-practices.
Now, I'm not saying never use a setter on a Collection, sometimes you may need one, but in general you should not use them.
You can always use .AddRange, .Clone, etc. on the Collections, you only lose the ability of direct assignment.
Serialization
Lastly, what do we do if we wish to Serialize or Deserialize a class that contains our Collection without a set? Well, there is always more than one way to do it, the simplest (in my opinion) is to create a property that represents the serialized collection.
Take our BigDataClass for example. If we wished to Serialize, and then Deserialize this class with the following code, the Data property would have no elements.
JavaScriptSerializer jss = new JavaScriptSerializer();
BigDataClass bdc = new BigDataClass();
bdc.Data.Add("Test String");
string serd = jss.Serialize(bdc);
Console.WriteLine(serd);
BigDataClass bdc2 = jss.Deserialize<BigDataClass>(serd);
So, to fix this, we can simply modify our BigDataClass a bit to make it use a new string property for Serialization purposes.
public class BigDataClass
{
private List<string> data = new List<string>();
[ScriptIgnore]
public List<string> Data { get { return data; } } // note, we removed the setter
public string SerializedData { get { JavaScriptSerializer jss = new JavaScriptSerializer(); return jss.Serialize(data); } set { JavaScriptSerializer jss = new JavaScriptSerializer(); data = jss.Deserialize<List<string>>(value); } }
}
Another option is always the DataContractSerializer (which is really a better option, in general.) You can find information about it on this StackOverflow question.
With current VS2019 we can simply do this:
public List<string> Data { get; } = new List<string>();
This satisfies CA2227 and can be serialized/deserialized.
The deserialization works because List<> has an "Add" method, and the serializer knows how to handle a read-only collection property with an Add method (the property is read-only but not the elements) (I use Json.Net, other serializers may behave differently).
Edit:
As pointed out it should be "=" and not "=>" (compiler will prevent you using "=>"). If we used "public List Data => new List();" then it would create a new list every time the property was accessed which is not what we want either.
Edit:
Note that this will NOT work if the type of the property is an interface, such as IList
Edit:
I think the handling of interfaces is determined by the serializer used. The following works perfectly. I'm sure all common serializers know how to handle ICollection. And if you have some custom interface that does not implement ICollection then you should be able to configure the serializer to handle it, but in that case CA2227 probably won't be triggered making it irrelevant here. (As it is a read-only property you have to assign a concrete value within the class so it should always be serializing and de-serializing a non-null value)
public class CA2227TestClass
{
public IList Data { get; } = new List<string>();
}
[TestMethod]
public void CA2227_Serialization()
{
var test = new CA2227TestClass()
{
Data = { "One", "Two", "Three" }
};
var json = JsonConvert.SerializeObject(test);
Assert.AreEqual("{\"Data\":[\"One\",\"Two\",\"Three\"]}", json);
var jsonObject = JsonConvert.DeserializeObject(json, typeof(CA2227TestClass)) as CA2227TestClass;
Assert.IsNotNull(jsonObject);
Assert.AreEqual(3, jsonObject.Data.Count);
Assert.AreEqual("One", jsonObject.Data[0]);
Assert.AreEqual("Two", jsonObject.Data[1]);
Assert.AreEqual("Three", jsonObject.Data[2]);
Assert.AreEqual(typeof(List<string>), jsonObject.Data.GetType());
}
💡 Alternative Solution 💡
In my situation, making the property read-only was not viable because the whole list (as a reference) could change to a new list.
I was able to resolve this warning by changing the properties' setter scope to be internal.
public List<Batch> Batches
{
get { return _Batches; }
internal set { _Batches = value; OnPropertyChanged(nameof(Batches)); }
}
Note one could also use private set...
The hint's (achilleas heal) of this warning seems really pointed to libraries for the documentation says (Bolding mine):
An externally visible writable property is a type that implements
System.Collections.ICollection.
For me it was, "Ok, I won't make it viewable externally...." and internal was fine for the app.
Thanks to #Matthew, #CraigW and #EBrown for helping me understanding the solution for this warning.
public class PO1Loop
{
public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; private set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; private set; }
public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }
/* Max Use: 8 */
public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; private set; }
public PO1Loop()
{
PIDRepeat1 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID1>();
PIDRepeat2 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID2>();
ACKRepeat = new Collection<SegmentTypes.PO1LoopSegmentTypes.ACK>();
}
}
When wanting to assign data to the collection types use AddRange, Clear or any other variation of method for modifying a collection.
Only while binding DTO, you need to suppress warnings.
otherwise a custom ModelBinder is required custom ModelBinder to bind collections.
quoting the rule documentation:
When to suppress warnings
You can suppress the warning if the property is part of a Data Transfer Object (DTO) class.
Otherwise, do not suppress warnings from this rule.
https://learn.microsoft.com/pt-br/visualstudio/code-quality/ca2227?view=vs-2019
DTOs often require serialization and deserialization. Thus, they are required to be mutable.
Having to create an alternate backing property is a pain.
Simply change the property type from List<string> to IReadOnlyList<string> then this works as expected without CA2227.
The collection is set via the property but you can also cast to List<string> if you wish to append or delete items.
class Holder
{
public IReadOnlyList<string> Col { get; set; } = new List<string>();
}
var list = new List<string> { "One", "Two" };
var holder = new Holder() { Col = list } ;
var json = JsonConvert.SerializeObject(holder);
// output json {"Col":["One","Two"]}
var deserializedHolder = JsonConvert.DeserializeObject<Holder>(json);
I had to fix some of the CA2227 violations, so i had to add the "readonly" keyword to the collection field and then of course, had to remove the setter property. Some code that have used the setter, just created a new collection object which initially was empty. This code sure did not compile so i had to add a SetXxx() method in order to realize the missing setter's functionality. I did it like this:
public void SetXxx(List<string> list)
{
this.theList.Clear();
this.theList.AddRange(list);
}
The code of callers using the setter has been replaced with a call to the method SetXxx().
Instead of creating a complete new list, the existing list now will be cleared and filled with new items from another list, passed in as a parameter. The original list, due to the fact it is readonly and created only once, will always remain.
I believe this is also a good way to avoid that the garbagae collector has to delete old objects that got out of scope and second, to create new collection objects although there is already one.
As an addition to Der Kommissar's excellent answer.
Starting with .NET 5 (C# 9.0) there are init-only properties. These properties are only settable under specific circumstances, see here for reference.
The following example should not raise a warning CA2227, yet still allow for the collection being set during object initialization.
using System.Collections.Generic;
namespace BookStore
{
public class BookModel
{
public ICollection<string> Chapters { get; init; }
}
}
Note that the current version of the .NET SDK still raises a warning when using the built-in analyzer (not the NuGet package). This is a known bug and should be fixed in the future.
To cover all the possible scenarios to resolve CA2227 error:
This covers the Entity relationship mapping when we use Entity Framework.
class Program
{
static void Main(string[] args)
{
ParentClass obj = new ParentClass();
obj.ChildDetails.Clear();
obj.ChildDetails.AddRange();
obj.LstNames.Clear();
obj.LstNames.AddRange();
}
}
public class ChildClass
{ }
public class ParentClass
{
private readonly ICollection<ChildClass> _ChildClass;
public ParentClass()
{
_ChildClass = new HashSet<ChildClass>();
}
public virtual ICollection<ChildClass> ChildDetails => _ChildClass;
public IList<string> LstNames => new List<string>();
}

How to control node names in xml using Request.CreateResponse?

I am using asp.net web api and eventhough I personally like to handle my data in json by using HttpResponseMessage and Request.CreateResponse I can let the client decide what they rather work in.
However when looking at the xml data I see the node names are horrible. I am wondering can they be changed other than changing file names? I tried xmlRoot for instance but that did nothing.
For instance I do a return like this
return Request.CreateResponse<ResponseResult<List<PersonSearchDto>>>(HttpStatusCode.OK, p);
I get a root node named like this
ResponseResultOfArrayOfPersonSearchK0AojvId
Would be nice if I could rename to something like Persons
The right way to solve your problem is to use the CollectionDataContractAttribute:
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.collectiondatacontractattribute.aspx
So in your case, you could have a type like this:
[CollectionDataContract(Name = "Persons")]
public class PersonResults : List<PersonSearchDto>
{
}
Add this line in your Global.asax.cs:
GlobalConfiguration.Configuration.Formatters.XmlDataContractSerializer = true;
And then decorate your classes and properties using [DataMember] and [DataContract] as explained in this MSDN article:
namespace MyTypes
{
[DataContract]
public class PurchaseOrder
{
private int poId_value;
// Apply the DataMemberAttribute to the property.
[DataMember]
public int PurchaseOrderId
{
get { return poId_value; }
set { poId_value = value; }
}
}
}

Categories