XmlSerializer, XmlArray with dynamic content... how? - c#-4.0

To start: This is also for REST deserialiaztion, so a custom XmlSerializer is out of the question.
I have a hjierarchy of classes that need to be serializable and deserializable from an "Envelope". It has an arrayelement named "Items" that can contain subclasses of the abstract "Item".
[XmlArray("Items")]
public Item [] Items { get; set; }
Now I need to add XmlArrayItem, but the number is not "fixed". We use so far reflection to find all subclasses with a KnownTypeProvider so it is easy to extend the assembly with new subtypes. I dont really want to hardcode all items here.
The class is defined accordingly:
[XmlRoot]
[KnownType("GetKnownTypes")]
public class Envelope {
but it does not help.
Changing Items to:
[XmlArray("Items")]
[XmlArrayItem(typeof(Item))]
public Item [] Items { get; set; }
results in:
{"The type
xxx.Adjustment
was not expected. Use the XmlInclude
or SoapInclude attribute to specify
types that are not known statically."}
when tyrying to serialize.
Anyone an idea how I can use XmlInclude to point to a known type provider?

The KnownTypesAttribute does not work for XmlSerializer. It's only used by a DataContractSerializer. I'm quite sure that you can exchange the serializer in WCF, because I have done that for the DataContractSerializer. But if that's not an option, you have to implement IXmlSerializable yourself and handle type lookup there.
Before disqualifying this solution: You just have to implement IXmlSerializable just for a special class which replaces Item[]. Everything else can be handled by the default serializer.

According to: http://social.msdn.microsoft.com/Forums/en-US/asmxandxml/thread/83181d16-a048-44e5-b675-a0e8ef82f5b7/
you can use different XmlSerializer constructor:
new XmlSerializer(typeof(Base), new Type[] { typeof(Derived1), ..});
Instead of enumerating all derived classes in the base definition like this:
[System.Xml.Serialization.XmlInclude(typeof(Derived1))]
[System.Xml.Serialization.XmlInclude(typeof(Derived2))]
[System.Xml.Serialization.XmlInclude(typeof(DerivedN))]
I think you should be able to use your KnownTypeProvider to fill the array in the XmlSerializer's constructor.

Related

How to decorate the final class DocumentGenerator

I am having problems to decorate the final class "DocumentGenerator" (in vendor/shopware/core/Checkout/Document/Service/DocumentGenerator.php) and overwrite the "generate" function inside of it.
I tried to decorate it the usual way, but an error is thrown because the "DocumentController" class excepts the original class and not my decorated one?
Argument 2 passed to Shopware\Core\Checkout\Document\DocumentGeneratorController::__construct() must be an instance of Shopware\Core\Checkout\Document\Service\DocumentGenerator
Its also not possible to extend from the class in my decorated class, because the "DocumentGenerator" is a final class.
My goal is to execute additional code, after an order document is generated. Previously I successfully used to decorate the "DocumentService" Class, but its marked as deprecated and shouldnt be used anymore. Also the "DocumentGenerator" class is used for the new "bulkedit" function for documents as of Version 6.4.14.0
I'm grateful for every tip.
As #j_elfering already wrote it's by design that you should not extend that class and therefore also shouldn't decorate it.
To offer a potential alternative:
Depending on what you want to do after a document has been generated it might be enough to add a subscriber to listen to document.written, check if it was a new document created and then work with the data from the payload for fetching/persisting data depending on that.
public static function getSubscribedEvents()
{
return [
'document.written' => 'onDocumentWritten',
];
}
public function onDocumentWritten(EntityWrittenEvent $event): void
{
foreach ($event->getWriteResults() as $result) {
if ($result->getOperation() !== EntityWriteResult::OPERATION_INSERT) {
// skip if the it's not a new document created
continue;
}
$payload = $result->getPayload();
// do something with the payload
}
}
Probably not what you want to hear but: The service is final in purpose as it is not intended to be decorated.
So the simple answer is you can't. Depending on your use case there may be other ways that don't rely on decoration.

Domain Modelling: Neither an Entity nor a Value Object

In DDD, the domain model consists of entities and value objects, but what do we do when we need something in the model which is neither of these?
For example, I have introduced the following ScheduledItems<T> implementation in order to encapsulate scheduling specifics:
public class ScheduledItems<T>
{
private SortedDictionary<DateTime, T> scheduledItems;
public ScheduledItems()
{
scheduledItems = new SortedDictionary<DateTime, T>();
}
public void ScheduleItem(DateTime scheduledDate, T item)
{
scheduledItems.Add(scheduledDate, item);
}
public void RemoveItem(T item)
{
scheduledItems
.Where(x => x.Value.Equals(item))
.Select(x => x.Key)
.ToList()
.ForEach(k => scheduledItems.Remove(k));
}
}
This class will be used by a couple of entities for scheduling purposes.
At this point, this is neither an Entity (it has no identity) nor a Value Object (it is not immutable).
One solution is to turn it into a Value Object by making it immutable ('adding' or 'removing' items would return a new instance of ScheduledItems).
But is this really necessary for something which is not really associated to the domain? This class could be just like any other .NET collection.
That class looks like a repository for ScheduledItems. So ScheduledItem is the Entity and ScheduledItems is the the Repository with Add(), Remove() methods.
I guess it depends on why the items are sorted.
If they need to be sorted because of certain business rules then this should be part of your domain.
If they need to be sorted to be properly shown in the UI, then this most likely is just a bit of view logic that should not be part of the domain.
If none of the above, I would consider this a collection-like helper class that could be in a part of the infrastructure layer that could be used across the other layers.

class properties are not available in generic method c#

I am calling a generic method with two different classes as below:
FillDataPointsInOrder<Metrics>(dataPoints.Where(O => O.SortOrder != null).OrderBy(O => O.SortOrder));
FillDataPointsInOrder<Metric>(angieStatsCmp.GetDataColumns());
private void FillDataPointsInOrder<T>(IEnumerable<T> dataPoints)
{
foreach (T dpoint in dataPoints)
{
if (!dpoint.IsPhone)
FillDrp(this.EmailDrp, dpoint.Name, dpoint.MetricId.ToString(), dpoint.VName);
if (dpoint.IsPhone && this.IsPhoneShop)
FillDrp(this.PhoneDrp, dpoint.Name, dpoint.MetricId.ToString(), dpoint.VName);
}
}
in "FillDataPointsInOrder" method I am getting compile errors :
'T' does not contain a definition for 'IsPhone' and no extension method 'IsPhone' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)
Same errors for Name , MetricId and VName properties.
Not sure why T is not able to access properties of Metrics and Metric.
If I remove the code from generic method and write it directly in foreach over dataPoints it is working fine.
Can somebody advise what is wrong here?
FillDataPointsInOrder only knows it will be called with a T. T could actually be string, int or anything.
If you want to call properties on T, you will have to use a where constraint.
But in this case it looks like your method does not even need to be generic.
If both Metric and Metrics share a base class or an interface that has the properties you need:
interface IMetric {
bool IsPhone {get; }
}
you could just have:
private void FillDataPointsInOrder(IEnumerable<IMetric> dataPoints)
Note that IEnumerable is covariant, so if Metric is a IMetric, IENumerable<Metric> is a IEnumerable<IMetric>
You need to at least tell the compiler something about T if you want to do that. Do you have an interface that has members like IsPhone, Name, MetricId, etc. that your classes implement?
If so you can add a 'where' constraint to your class definition:
public class Something<T> where T : ISomethingElse
...where ISomethingElse is the interface that implements IsPhone.

ektorp / CouchDB mix HashMap and Annotations

In jcouchdb I used to extend BaseDocument and then, in a transparent manner, mix Annotations and not declared fields.
Example:
import org.jcouchdb.document.BaseDocument;
public class SiteDocument extends BaseDocument {
private String site;
#org.svenson.JSONProperty(value = "site", ignoreIfNull = true)
public String getSite() {
return site;
}
public void setSite(String name) {
site = name;
}
}
and then use it:
// Create a SiteDocument
SiteDocument site2 = new SiteDocument();
site2.setProperty("site", "http://www.starckoverflow.com/index.html");
// Set value using setSite
site2.setSite("www.stackoverflow.com");
// and using setProperty
site2.setProperty("description", "Questions & Answers");
db.createOrUpdateDocument(site2);
Where I use both a document field (site) that is defined via annotation and a property field (description) not defined, both get serialized when I save document.
This is convenient for me since I can work with semi-structured documents.
When I try to do the same with Ektorp I have documents using annotations and Documents using HashMap BUT I couldn't find an easy way of getting the mix of both (I've tried using my own serializers but this seems to much work for something that I get for free in jcouchdb). Also tried to annotate a HashMap field but then is serialized as an object and I get the fields automatically saved BUT inside an object with the name of the HashMap field.
Is it possible to do (easily/for free) using Ektorp?
It is definitely possible. You have two options:
Base your class on org.ektorp.support.OpenCouchDbDocument
Annotate the you class with #JsonAnySetter and #JsonAnyGetter. Red more here: http://wiki.fasterxml.com/JacksonFeatureAnyGetter

which spring ws jaxb annotation to change xml element name

I am using a sping ws endpoint with jaxb marshalling/unmarshalling to proudce a list of Organisation objects (our local type). The endpoint is SOAP 1.1, no parameters supplied on the request message.
I understand JAXB doesn't handle lists very well, so I use a wrapper class.
#XmlRootElement(name="orgResponse", namespace=....)
public class OrganisationListWrapper {
private ArrayList<Organisation> organisationList;
public getOrganisationList() {
return organisationList;
}
public setOrganisationList(ArrayList<Organisation> organisationList) {
this.organisationList = organisationList;
}
}
The endpoint....
#PayloadRoot(localPart=.... namespace=....)
#ResponsePayload
public OrganisationListWrapper getOrganisations() {
OrganisationListWrapper wrapper = new OrganisationListWrapper();
wrapper.setOrganisationList(.... call service layer get list ....);
return wrapper;
}
This works fine and I get a SOAP payload with
<orgResponse>
<organisationList>
... contents of organisation 1
</organisationList>
<organisationList>
... comtents of organisation 2
</organisationList>
.... etc ....
</orgResponse>
The Organisation class is not JAXB annotated. It is part of a large list of pre-existing classes that are being exposed through web services for the first time. Trying to get by without going in and annotating them all by hand.
I was able to override the name OrganisationWrapper with orgResponse in the XmlRootElement annotation. I would like to override the organisationList name in the child element with organisation but haven't been able to find an annotation that does this.
I can replace the array list name with organisation and it will work fine, but our coding standard here required us to put List on the end of our list names. I would like to try and stick to that. I have tried XmlElement, but that produced a jaxb exception.
Any suggestions would be appreciated.
Because JAXB default the access type to PUBLIC_MEMBER, make sure you annotate the property (getter) and not the field:
#XmlElement(name="organisation")
public getOrganisationList() {
return organisationList;
}
If you want to annotate the field then add the following annotation to your class:
#XmlAccessorType(XmlAccessType.FIELD)

Resources