Associate XML Attribute with Class Property - c#

I am building an MVC App in C# and am currently working on a method to load an xml document into a file stream and iterate through the nodes and save the data into my model objects.
It is all working fine but I am hard coding the relationship between the object property and the xml attribute name. I was wondering if there is a smart way to associated the two so I can run it all through a for each loop.
This is the code I have currently and it works, but I would like to make it more generic
OLD CODE
var xmlDoc = LoadXmlFileIntoStream("WSAPayCode.xml");
var elementCollection = ExtractDescendants(xmlDoc, "WSAPayCode");
foreach (var element in elementCollection)
{
var abbreviationChar = element.Attribute("AbbreviationChar");
var payCode = new PayCode
{
Name = element.Attribute("Name").Value,
AutoResolved = element.Attribute("AutoResolved").Value.IsBool(),
EditExcuseAbsn = element.Attribute("EditExcuseAbsn").Value.IsBool(),
PersistPceSw = element.Attribute("PersistPceSw").Value.IsBool(),
AbbreviationChar = (string)element.Attribute("AbbreviationChar"),
EditCntToCdotSw = element.Attribute("EditCntToCdotSw").Value.IsBool(),
EditAffShfTotal = element.Attribute("EditAffShfTotal").Value.IsBool(),
EditCntToOt = element.Attribute("EditCntToOt").Value.IsBool(),
PayUsingWeightedAverageRate = element.Attribute("PayUsingWeightedAverageRate").Value.IsBool(),
RequiresMgrApproval = element.Attribute("RequiresMgrApproval").Value.IsBool(),
WeightedAverageRateIsComputedDaily =
element.Attribute("WeightedAverageRateIsComputedDaily").Value.IsBool(),
JustAutoResExpAsWorked = element.Attribute("JustAutoResExpAsWorked").Value.IsBool(),
AssociatedDurationPayCodeName = element.Attribute("AssociatedDurationPayCodeName").Value,
WeightedAverageRateContributionsUseAnAdjustedRate =
element.Attribute("WeightedAverageRateContributionsUseAnAdjustedRate").Value.IsBool(),
ScheduleHoursType = element.Attribute("ScheduleHoursType").Value,
CheckAvlbltySw = element.Attribute("CheckAvlbltySw").Value.IsBool(),
WageAddition = (string)element.Attribute("WageAddition"),
VisibleInMainArea = element.Attribute("VisibleInMainArea").Value.IsBool(),
IsMoneyCategory = element.Attribute("IsMoneyCategory").Value.IsBool(),
AmountType = element.Attribute("AmountType").Value,
VisibleInReport = element.Attribute("VisibleInReport").Value.IsBool(),
ContributesToWeightedAverageRates =
element.Attribute("ContributesToWeightedAverageRates").Value.IsBool(),
UnjustAutoResExpAsWorked = (bool)element.Attribute("UnjustAutoResExpAsWorked"),
WageMultiply = (string)element.Attribute("WageMultiply"),
Type = (string)element.Attribute("Type"),
VisibleToUser = (bool)element.Attribute("VisibleToUser"),
CustomerId = _customerId,
};
_db.PayCodes.Add(payCode);
_db.SaveChanges();
}
New Code
I have written some code to interate through the xml file and pull out the names of the attributes - Code Below (Also works)
var xmlDoc = LoadXmlFileIntoStream("WSAPayCode.xml");
var elementCollection = ExtractDescendants(xmlDoc, "WSAPayCode");
var nodeAttributes = xmlDoc.Descendants("WSAPayCode").Select(x => x.Attributes());
foreach (var attrs in nodeAttributes)
{
var _attribute = "";
foreach (var attr in attrs)
{
// This successfully reads through each attribute and pulls out the name of the attribut
_attribute = attr.Name.ToString();
}
}
Problem I would like to solve
What I would like to do now is instantiate an object and iterate through the attribute names and save the values to the corresponding property in the object. i.e. replace the OLD code with something that dynamically assigns the values to the object properties.

I would check into using the XML Serializers / Deserializers. On your class, you have provide attributes as to whether the property is an element or attribute, and then let it handle the rest of it for you.
You could set the attributes to match what the XML document is providing, such as if the name is an element, or attribute, etc.
For example:
[XmlRoot("PayCode")]
public class PayCode
{
[XmlElement("Name")
public string Name { get; set;}
....
}
Then, to deserialize to your object:
XmlSerializer serializer = new
XmlSerializer(typeof(PayCode));
FileStream fs = new FileStream(filename, FileMode.Open);
XmlReader reader = XmlReader.Create(fs);
PayCode payCode = (PayCode)serializer.Deserialize(reader);

Related

accessing json property with given values

var orderJson = JsonConvert.DeserializeObject<dynamic>(httpResultStr);
var orderidCount = orderJson.data.orderUuids.Count;
for (int i = 0; i <= orderidCount; i++)
{
var orderId = orderJson.data.orderUuids[i]; // my fail attempt. Didnt work
var map = orderJson.data.ordersMap;
foreach (var d in map)
{
var receipt = d.fareInfo.totalPrice;
Console.WriteLine(receipt);
}
}
Im trying to access the ordersMap members with the given values in orderUuids object. Inside the ordersMap Ids contain the fareInfo.totalPrice property that I'm trying to access. How would I go about achieving this?
[![json tree with ordersMap. Trying to access its members with the given values in orderUuids object.][1]][1]
You can make a partial/full map using the JSON file and use JsonConvert.DeserializeObject<>(json).
Other solution could be create a partial map using an anonymous type. Here is a code snip.
var anonymousTypeObject = new
{
status = "",
data = new
{
ordersMap = new Dictionary<string, JToken>(),
orderUuids = new string[0]
}
};
var obj = JsonConvert.DeserializeAnonymousType(json, anonymousTypeObject);
foreach (var kvp in obj.data.ordersMap)
{
var totalPrice = kvp.Value["fareInfo"]?["totalPrice"]?.ToString();
Debug.WriteLine($"{kvp.Key} -> {totalPrice}");
}
EDIT If you don't want any map use this solution.
var jObj = JsonConvert.DeserializeObject<JObject>(json);
var orderUuids = jObj.SelectToken("data.orderUuids")?.Values<string>();
foreach (var orderUuid in orderUuids)
{
var totalPrice = jObj.SelectToken($"data.ordersMap.{orderUuid}.fareInfo.totalPrice")?.Value<double>();
Debug.WriteLine($"{orderUuid} -> {totalPrice}");
}

ASP.NET MVC - Xml Export - formatting the file name according to class attributes

I have this class, which takes the selected file in the grid
[HttpPost]
public ActionResult ExportXml(string apontamentos)
{
try
{
string[] arrApontamentos = apontamentos.Split(';');
var listApontamentoId = arrApontamentos.Select(x => new Guid(x)).ToList();
var apontamentosViewModel = this._apontamentoAppService.ObterTodos(listApontamentoId);
List<ApontamentoExportarViewModel> listXml = new List<ApontamentoExportarViewModel>();
int item = 1;
foreach (var informacaoApontamentoVM in apontamentosViewModel)
{
listXml.Add(new ApontamentoExportarViewModel
{
Item = item,
Equipamento = informacaoApontamentoVM.Barco.SapId,
Atendimento = informacaoApontamentoVM.Atendimento,
Escala = informacaoApontamentoVM.LocalDaOperacao.Abreviacao,
DescricaoDaOperacao = informacaoApontamentoVM.CodigosDeOperacao.Descricao,
//GrupoDeCodigo = "xxx",
CodigoOperacao = informacaoApontamentoVM.CodigosDeOperacao.Codigo,
DataInicial = string.Format("{0:dd.MM.yyyy}", informacaoApontamentoVM.DataInicio),
HoraInicial = string.Format("{0:HH.mm.ss}", informacaoApontamentoVM.DataInicio),
DataFinal = string.Format("{0:dd:MM:yyyy}", informacaoApontamentoVM.DataTermino),
HoraFinal = string.Format("{0:HH:mm:ss}", informacaoApontamentoVM.DataTermino),
Observacoes = informacaoApontamentoVM.Observacao
});
item++;
}
var status = this._apontamentoAppService.ObterDescricaoStatusApontamento(Domain.Apontamentos.StatusApontamento.Exportado);
this._apontamentoAppService.AtualizarStatus(apontamentosViewModel.Select(x => x.Id).ToList(), status);
return new XmlActionResult<ApontamentoExportarViewModel>(listXml);
}
catch (Exception ex)
{
throw ex;
}
}
And a i have that other one, which does export and xml format and filename
public class XmlActionResult<T> : ActionResult
{
public XmlActionResult(List<T> data)
{
Data = data;
}
public List<T> Data { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "text/xml";
// TODO: Use your preferred xml serializer
// to serialize the model to the response stream :
// context.HttpContext.Response.OutputStream
ApontamentoExportarViewModel apontamentoExportarViewModel = new ApontamentoExportarViewModel();
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = string.Format("MA_"+ "Equipamento" + "_{0:dd-MM-yyyy_HH-mm-ss}.xml", DateTime.Now),
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
var root = new XmlRootAttribute("meadinkent");
XmlSerializer x = new XmlSerializer(Data.GetType(), root);
context.HttpContext.Response.AppendHeader("Content-Disposition", cd.ToString());
x.Serialize(context.HttpContext.Response.OutputStream, Data);
}
}
}
Basically, I need to get the "Equipamento" attribute and insert it into the file name.
The information from the "ApontamentoExportarViewModel" class is coming from the "Data" attribute, but how do you find the information inside that list? Remembering that I only need the information of the attribute "Equipment"
How would I bring the value of this attribute to the XmlActionResult class?
Well, you've got it in Data in the XmlActionResult, but because it's a List<T>, it could be anything. In this case, it's your ApontamentoExportarViewModel view model.
Should this XmlActionResult method be able to work with various kinds of objects, or only with ApontamentoExportarViewModel? If with various types, then not every one of those types will have a Equipamento property. In that case, you would have to do something like:
var fileName = "default";
if(Data is List<ApontamentoExportarViewModel>)
{
var record = (Data as List<ApontamentoExportarViewModel>).FirstOrDefault(); // what should happen if there's more than one?
if (record != null)
fileName = record.Equipamento;
}
and then later:
FileName = string.Format("MA_"+ fileName + "_{0:dd-MM-yyyy_HH-mm-ss}.xml", DateTime.Now);
Or something like that.
If you are sure that every object that comes in to your method will have a Equipamento property, then you could create a base class from which you derive your view model classes that has Equipamento, or an interface, and then alter your method to not accept any type (<T>), but rather only classes that derive from the base class / implement the interface.

XML with elements of the same name

How do I edit the data in each Address Line, since they all have the same name?
<StructuredAddress>
<AddressLine></AddressLine>
<AddressLine></AddressLine>
<AddressLine></AddressLine>
</StructuredAddress>
My code so far, which doesn't work, is this. How do I isolate each AddressLine indivisually and insert the appropriate data?
XElement StructuredAddress = PatientAddress.Descendants("StructuredAddress").First();
StructuredAddress.Element("AddresLine").Value = cc.address1;
StructuredAddress.Element("AddresLine").Value = cc.address2;
StructuredAddress.Element("AddresLine").Value = cc.address3;
You may want to access each <AddressLine> using it's index :
XElement StructuredAddress = PatientAddress.Descendants("StructuredAddress").First();
var address = StructuredAddress.Elements("AddresLine").ToList();
address[0].Value = cc.address1;
address[1].Value = cc.address2;
address[2].Value = cc.address3;
You could iterate them which will present them in ordinal order:
foreach (var addressLine in StructuredAddress.Elements("AddressLine"))
{
addressLine.Value = ...
}
Or by index;
var lines = StructuredAddress.Elements("AddressLine").ToList();
lines[0].Value = "...";

Can I add a prefix to root element when I have no control over the serialized .NET type?

I have a library that contains a .NET type. The library has its own configuration (annotations etc) for serializing the type to XML, and I do not have the source code.
No matter what I do, I could not manage to add the prefix I want to add to the root element of the output XML. Using XmlSerializerNamespaces made no difference. Here is a snippet that shows my code at the moment:
var comp = row[0] as LibraryType;
var ser = new XmlSerializer(comp.GetType());
var strWriter = new StringWriter();
var xmlWriter = XmlWriter.Create(strWriter);
ser.Serialize(xmlWriter, comp);
string serXml = strWriter.ToString();
Is there a way in configure XMLSerializer to create an xlm output for root such as
<lt:LibraryType ....
instead of the current
<LibraryType .....
I'm getting ?
Lets say that your library type looks like
public class Foo
{
public int i { get; set; }
}
public class Bar
{
public Foo Foo { get; set; }
}
then serialization should look like
var comp = new Bar {Foo = new Foo()};
var overrides = new XmlAttributeOverrides();
overrides.Add(typeof (Bar), new XmlAttributes {XmlRoot = new XmlRootAttribute {Namespace = "http://tempuri.org"}});
// in case you want to remove prefix from members
var emptyNsAttribute = new XmlAttributes();
emptyNsAttribute.XmlElements.Add(new XmlElementAttribute { Namespace = "" });
overrides.Add(typeof(Bar), "Foo", emptyNsAttribute);
// if you actual library type contains more members, then you have to list all of them
var ser = new XmlSerializer(comp.GetType(), overrides);
var strWriter = new StringWriter();
var xmlWriter = XmlWriter.Create(strWriter);
var ns = new XmlSerializerNamespaces();
ns.Add("lt", "http://tempuri.org");
ser.Serialize(xmlWriter, comp, ns);
string serXml = strWriter.ToString();
and the output will be
<?xml version="1.0" encoding="utf-16"?><lt:Bar xmlns:lt="http://tempuri.org"><Foo><i>0</i></Foo></lt:Bar>

LINQ to XML to POCO object

I've got an XML file that I want to turn in to a list of POCO objects.
I have the following working code to read the XML and create objects from it. I just want to check this is a good way to do this and I'm not missing any tricks. In particular with regards to the nested Linq query.
XDocument xmlDoc = XDocument.Load(path);
var q = from file in xmlDoc.Descendants("File")
select new ImportDefinition()
{
Name = file.Attribute("Name").Value,
TypeName = file.Attribute("TypeName").Value,
ColumnMappings =
(
from map in file.Descendants("ColumnMap")
select new ColumnMap()
{
DatabaseColumn = new Column()
{
Name = map.Element("DatabaseColumn").Attribute("Name").Value
}
}
).ToList<ColumnMap>()
};
List<ImportDefinition> def = q.ToList<ImportDefinition>();
Thanks
Maybe try an explicit conversion
public class ColumnMap
{
public static explicit operator ColumnMap(XElement xElem)
{
return new ColumnMap()
{
DatabaseColumn = new Column()
{
Name = xElem.Element("DatabaseColumn").Attribute("Name").Value
}
};
}
}
public class ImportDefinition
{
public static explicit operator ImportDefinition(XElement xElem)
{
return new ImportDefinition()
{
Name = (string)xElem.Attribute("Name"),
TypeName = (string)xElem.Attribute("TypeName"),
Size = (int)xElem.Attribute("Size"),
LastModified = (DateTime?)xElem.Attribute("LastModified"),
ColumnMappings = xElem.Descendants("ColumnMap").Select(xelem => (ColumnMap)xelem).ToList()
};
}
}
Then use it like so:
XDocument xmlDoc = XDocument.Load(path);
List<ImportDefinition> importDefinitions = xmlDoc.Descendants("File").Select(xElem => (ImportDefinition)xElem).ToList()
In case your POCO objects do not only have string properties, XElement and XAttribute provide a wide selection of conversion operators to other types, including nullables in case the element/attribute doesn't exist.
Example:
XDocument xmlDoc = XDocument.Load(path);
var q = from file in xmlDoc.Descendants("File")
select new ImportDefinition()
{
Name = (string)file.Attribute("Name"),
TypeName = (string)file.Attribute("TypeName"),
Size = (int)file.Attribute("Size"),
LastModified = (DateTime?)file.Attribute("LastModified"),
// ...
};

Categories