High memory usage of a windows forms method - c#

I have a c# windows forms application which uses too much memory. The peace of code, that is the problem is this
private void mainTimer_Tick(object sender, EventArgs e)
{
try
{
if (DateTime.Now.DayOfWeek == DayOfWeek.Saturday)
{
if (File.Exists(Globals.pathNotifFile + "1"))
{
File.Delete(Globals.pathNotifFile + "1");
File.Move(Globals.pathNotifFile, Globals.pathNotifFile + "1");
}
File.Move(Globals.pathNotifFile, Globals.pathNotifFile + "1");
}
if (DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
{
return;
}
if (Globals.shotsFired != true)
{
CreateDLclient();
Globals.shotsFired = true;
}
if (Globals.pathNotifFile == null)
{
return;
}
var data = Deserialize();
foreach (var notifyData in data.#new)
{
if (notifyData.Status == "1" || notifyData.Status == string.Empty)
{
if (DateTime.Now >= Convert.ToDateTime(notifyData.DateTime))
{
if (notifyData.Message != string.Empty)
{
notifyData.Status = SendMessageToUser(notifyData.Message, notifyData.Company, notifyData.EmojiCode);
Serialize(data);
}
else
{
notifyData.Status = "3";
Serialize(data);
}
}
else if (DateTime.Now >= Convert.ToDateTime(notifyData.DateTime).AddMinutes(5))
{
if (notifyData.Message != string.Empty)
{
notifyData.Status = SendMessageToUser(notifyData.Message, notifyData.Company, notifyData.EmojiCode);
Serialize(data);
}
else
{
notifyData.Status = "3";
Serialize(data);
}
}
}
}
}
It causes a huge problem and the application crashes with 'out of memory' Can somebody give me an advice how can I reduce the memory usage of that. I've tried to invoke the GC /I know it is not a good idea/, but it didn't help.
Thank you in advance

You have not provided any info on which serializer you used in your program but I am quite inclined to think it is XMLSerializer because it is prone to memory leak and you said in your comment that the program is crashing after working more then 10-12 hours.
XmlSerializer uses assembly generation, and assemblies cannot be collected. As far as I know It does some caching for re-use but only for simple cases.
So if you have code like the following,which is called pretty often→
XmlSerializer xml = new XmlSerializer(typeof(MyObject), ....
Then you will get out of memory exception sooner or later.
Edit: How to avoid Memory Leak from XMLSerializer:
Please have a look at the Topic
Dynamically Generated Assemblies in the following link→MSDN Link
If I just summarize what is written in there is that, you have a couple of ways.
1) You can use the following constructors to avoid dynamic assembly
XmlSerializer.XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type, String)
2) Using a dictionary or hashtable, create your own caching
private Dictionary<Tuple<Type, XmlRootAttribute>, XmlSerializer> cacheSerializer = new Dictionary<Tuple<Type, XmlRootAttribute>, XmlSerializer>();
public XmlSerializer GetXmlSerializer(Type type, XmlRootAttribute root) {
var key = Tuple.Create(type, root);
XmlSerializer xmlSerializer;
if (cacheSerializer.TryGetValue(key, out xmlSerializer)) {
return xmlSerializer;
}
xmlSerializer = new XmlSerializer(type, root);
cacheSerializer.Add(key,xmlSerializer);
return xmlSerializer;
}

Related

Microsoft Bond deserialization without knowing underlying schema

I am looking at some network requests that are happening on my machine, and I recognize some as using the Microsoft Bond data serialization format. I want to deserialize the content of the request, but I do not have the schema that was used to create its content.
I know that with the ProtoBuf compiler, there is a way to output the content of a ProtoBuf-based binary file without the schema with something like:
protoc --decode_raw < data.proto
Is something similar available for Microsoft Bond? I am happy to write C#/C++ code to get that done, but curious if that is even possible.
For reference, the protocol is Compact Binary.
Thanks to some insights from Christopher Warrington, I was able to piece together the methodology through which a Bond-encoded Compact Binary content piece can be "unpacked" into its component pieces:
var ib = new Bond.IO.Unsafe.InputBuffer(File.ReadAllBytes("response_data.bin"));
var cbr = new CompactBinaryReader<Bond.IO.Unsafe.InputBuffer>(ib, 2);
cbr.ReadStructBegin();
BondDataType dt = BondDataType.BT_BOOL;
ushort id = 0;
while (dt != BondDataType.BT_STOP)
{
cbr.ReadFieldBegin(out dt, out id);
Console.WriteLine(dt + " " + id);
if (dt == BondDataType.BT_STRING)
{
var stringValue = cbr.ReadString();
Console.WriteLine(stringValue);
}
else if (dt == BondDataType.BT_LIST)
{
BondDataType listContent = BondDataType.BT_BOOL;
int counter = 0;
cbr.ReadContainerBegin(out counter, out listContent);
Console.WriteLine("Inside container: " + listContent);
if (listContent == BondDataType.BT_STRUCT)
{
BondDataType structDt = BondDataType.BT_BOOL;
cbr.ReadStructBegin();
while(structDt != BondDataType.BT_STOP)
{
cbr.ReadFieldBegin(out structDt, out id);
Console.WriteLine(structDt + " " + id);
if (structDt == BondDataType.BT_STRING)
{
var stringValue = cbr.ReadString();
Console.WriteLine(stringValue);
}
else
{
if (structDt != BondDataType.BT_STOP)
{
cbr.Skip(structDt);
}
}
}
cbr.ReadStructEnd();
}
cbr.ReadContainerEnd();
}
else
{
if (dt != BondDataType.BT_STOP)
{
cbr.Skip(dt);
}
}
cbr.ReadFieldEnd();
}
This is non-production code (you can spot many issues and lack of nested parsing) but it shows the approach through which one can get the contents.

Can we deserialize InstallState file?

I am trying to pass the information in Install state file to the installer class which will then uninstall.
But before passing it I need to convert the info to System.Collections.IDictionary savedState.
For this, is it possible to deserialize install state file?
Screenshot of the Installstate file
If you use the AssemblyInstaller class, it appears (although this doesn't seem to be documented) that it will, in general, ignore any passed savedState parameter and will instead deal with the INSTALLSTATE file instead (writing it during install, reading it during uninstall).
If you're unable to use it, for some reason, you can probably use a disassembly tool to extract the necessary code from its Uninstall method to perform the deserialization (I believe, and it appears so, that the specific serialization methods used vary between .NET versions, so I'd recommend using the one appropriate to whichever .NET version you're currently working with).
This is the Uninstall method, decompiled from System.Configuration.Install (File Version 4.6.1590.0):
public override void Uninstall(IDictionary savedState)
{
this.PrintStartText(Res.GetString("InstallActivityUninstalling"));
if (!this.initialized)
{
this.InitializeFromAssembly();
}
string installStatePath = this.GetInstallStatePath(this.Path);
if ((installStatePath != null) && File.Exists(installStatePath))
{
FileStream input = new FileStream(installStatePath, FileMode.Open, FileAccess.Read);
XmlReaderSettings settings = new XmlReaderSettings {
CheckCharacters = false,
CloseInput = false
};
XmlReader reader = null;
if (input != null)
{
reader = XmlReader.Create(input, settings);
}
try
{
if (reader != null)
{
NetDataContractSerializer serializer = new NetDataContractSerializer();
savedState = (Hashtable) serializer.ReadObject(reader);
}
goto Label_00C6;
}
catch
{
object[] args = new object[] { this.Path, installStatePath };
base.Context.LogMessage(Res.GetString("InstallSavedStateFileCorruptedWarning", args));
savedState = null;
goto Label_00C6;
}
finally
{
if (reader != null)
{
reader.Close();
}
if (input != null)
{
input.Close();
}
}
}
savedState = null;
Label_00C6:
base.Uninstall(savedState);
if ((installStatePath != null) && (installStatePath.Length != 0))
{
try
{
File.Delete(installStatePath);
}
catch
{
object[] objArray2 = new object[] { installStatePath };
throw new InvalidOperationException(Res.GetString("InstallUnableDeleteFile", objArray2));
}
}
}
You'll notice that it doesn't use whatever was passed to it as savedSate - by the time it uses that variable for anything (here, passing it to its base class), it's either over-written it from the INSTALLSTATE file or it's assigned null to it.

Reading text files with 64bit process very slow

I'm merging text files (.itf) with some logic which are located in a folder. When I compile it to 32bit (console application, .Net 4.6) everything works fine except that I get outofmemory exceptions if there are lots of data in the folders. Compiling it to 64bit would solve that problem but it is running super slow compared to the 32bit process (more than 15 times slower).
I tried it with BufferedStream and ReadAllLines, but both are performing very poorly. The profiler tells me that these methods use 99% of the time. I don't know were the problem is...
Here's the code:
private static void readData(Dictionary<string, Topic> topics)
{
foreach (string file in Directory.EnumerateFiles(Path, "*.itf"))
{
Topic currentTopic = null;
Table currentTable = null;
Object currentObject = null;
using (var fs = File.Open(file, FileMode.Open))
{
using (var bs = new BufferedStream(fs))
{
using (var sr = new StreamReader(bs, Encoding.Default))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (line.IndexOf("ETOP") > -1)
{
currentTopic = null;
}
else if (line.IndexOf("ETAB") > -1)
{
currentTable = null;
}
else if (line.IndexOf("ELIN") > -1)
{
currentObject = null;
}
else if (line.IndexOf("MTID") > -1)
{
MTID = line.Replace("MTID ", "");
}
else if (line.IndexOf("MODL") > -1)
{
MODL = line.Replace("MODL ", "");
}
else if (line.IndexOf("TOPI") > -1)
{
var name = line.Replace("TOPI ", "");
if (topics.ContainsKey(name))
{
currentTopic = topics[name];
}
else
{
var topic = new Topic(name);
currentTopic = topic;
topics.Add(name, topic);
}
}
else if (line.IndexOf("TABL") > -1)
{
var name = line.Replace("TABL ", "");
if (currentTopic.Tables.ContainsKey(name))
{
currentTable = currentTopic.Tables[name];
}
else
{
var table = new Table(name);
currentTable = table;
currentTopic.Tables.Add(name, table);
}
}
else if (line.IndexOf("OBJE") > -1)
{
if (currentTable.Name != "Metadata" || currentTable.Objects.Count == 0)
{
var shortLine = line.Replace("OBJE ", "");
var obje = new Object(shortLine.Substring(shortLine.IndexOf(" ")));
currentObject = obje;
currentTable.Objects.Add(obje);
}
}
else if (currentTopic != null && currentTable != null && currentObject != null)
{
currentObject.Data.Add(line);
}
}
}
}
}
}
}
The biggest problem with your program is that, when you let it run in 64-bit mode, then it can read a lot more files. Which is nice, a 64-bit process has a thousand times more address space than a 32-bit process, running out of it is excessively unlikely.
But you do not get a thousand times more RAM.
The universal principle of "there is no free lunch" at work. Having enough RAM matters a great deal in a program like this. First and foremost, it is used by the file system cache. That magical operating system feature that makes it look like reading files from a disk is very cheap. It is not at all, one of the slowest things you can do in a program, but it is very good at hiding it. You'll invoke it when you run your program more than once. The second, and subsequent, times you won't read from the disk at all. That's a pretty dangerous feature and very hard to avoid when you test your program, you get very unrealistic assumptions about how efficient it is.
The problem with a 64-bit process is that it easily makes the file system cache ineffective. Since you can read a lot more files, thus overwhelming the cache. And getting old file data removed. Now the second time you run your program it will not be fast anymore. The files you read will not be in the cache anymore but must be read from the disk. You'll now see the real perf of your program, the way it will behave in production. That's a good thing, even though you don't like it very much :)
Secondary problem with RAM is the lesser one, if you allocate a lot of memory to store the file data then you'll force the operating system to find the RAM to store it. That can cause a lot of hard page faults, incurred when it must unmap memory used by another process, or yours, to free up the RAM that you need. A generic problem called "thrashing". Page faults is something you can see in Task Manager, use View > Select Columns to add it.
Given that the file system cache is the most likely source of the slow-down, a simple test you can do is rebooting your machine, which ensures that the cache cannot have any of the file data, then run the 32-bit version. With the prediction that it will also be slow and that BufferedStream and ReadAllLines are the bottlenecks. Like they should be.
One final note, even though your program doesn't match the pattern, you cannot make strong assumptions about .NET 4.6 perf problems yet. Not until this very nasty bug gets fixed.
A few tips:
Why do you use File.Open, then BufferedStream then StreamReader when
you can do the job with just a StreamReader, which is buffered?
You should reorder your conditions with the one that happen the more often in first.
Consider read all lines then use Parallel.ForEach
I could solve it. Seems that there is a bug in .Net compiler. Removing the code optimization checkbox in VS2015 lead to a huge performance increase. Now, it is running with a similar performance as the 32 bit version. My final version with some optimizations:
private static void readData(ref Dictionary<string, Topic> topics)
{
Regex rgxOBJE = new Regex("OBJE [0-9]+ ", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Regex rgxTABL = new Regex("TABL ", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Regex rgxTOPI = new Regex("TOPI ", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Regex rgxMTID = new Regex("MTID ", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Regex rgxMODL = new Regex("MODL ", RegexOptions.IgnoreCase | RegexOptions.Compiled);
foreach (string file in Directory.EnumerateFiles(Path, "*.itf"))
{
if (file.IndexOf("itf_merger_result") == -1)
{
Topic currentTopic = null;
Table currentTable = null;
Object currentObject = null;
using (var sr = new StreamReader(file, Encoding.Default))
{
Stopwatch sw = new Stopwatch();
sw.Start();
Console.WriteLine(file + " read, parsing ...");
string line;
while ((line = sr.ReadLine()) != null)
{
if (line.IndexOf("OBJE") > -1)
{
if (currentTable.Name != "Metadata" || currentTable.Objects.Count == 0)
{
var obje = new Object(rgxOBJE.Replace(line, ""));
currentObject = obje;
currentTable.Objects.Add(obje);
}
}
else if (line.IndexOf("TABL") > -1)
{
var name = rgxTABL.Replace(line, "");
if (currentTopic.Tables.ContainsKey(name))
{
currentTable = currentTopic.Tables[name];
}
else
{
var table = new Table(name);
currentTable = table;
currentTopic.Tables.Add(name, table);
}
}
else if (line.IndexOf("TOPI") > -1)
{
var name = rgxTOPI.Replace(line, "");
if (topics.ContainsKey(name))
{
currentTopic = topics[name];
}
else
{
var topic = new Topic(name);
currentTopic = topic;
topics.Add(name, topic);
}
}
else if (line.IndexOf("ETOP") > -1)
{
currentTopic = null;
}
else if (line.IndexOf("ETAB") > -1)
{
currentTable = null;
}
else if (line.IndexOf("ELIN") > -1)
{
currentObject = null;
}
else if (currentTopic != null && currentTable != null && currentObject != null)
{
currentObject.Data.Add(line);
}
else if (line.IndexOf("MTID") > -1)
{
MTID = rgxMTID.Replace(line, "");
}
else if (line.IndexOf("MODL") > -1)
{
MODL = rgxMODL.Replace(line, "");
}
}
sw.Stop();
Console.WriteLine(file + " parsed in {0}s", sw.ElapsedMilliseconds / 1000.0);
}
}
}
}
Removing the code optimization checkbox should typically result in performance slowdowns, not speedups. There may be an issue in the VS 2015 product. Please provide a stand-alone repro case with an input set to your program that demonstrate the performance problem and report at: http://connect.microsoft.com/

WPF: How do I validate that a number from memory is not duplicated in db table?

I'm having trouble figuring out how to determine if a number is duplicated.
Right now, the process is when the user clicks on a button to browse for an xml file, the xml file gets deserialized and stored into db and the data gets shown on a DataGrid on the view.
So, I added a confirmation dialog so when the user clicks on browse, the code checks to see if the lot_number being deserialized is a duplicate or not from inside a column from a table in database. I only want the user to be able to add lot numbers to db that are not duplicates.
Here's my code so far:
public void DeSerializationStream(string filePath)
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "lot_information";
xRoot.IsNullable = false;
// Create an instance of lotinformation class.
var lot = new LotInformation();
// Create an instance of stream writer.
TextReader txtReader = new StreamReader(filePath);
// Create and instance of XmlSerializer class.
XmlSerializer xmlSerializer = new XmlSerializer(typeof(LotInformation), xRoot);
// DeSerialize from the StreamReader
lot = (LotInformation)xmlSerializer.Deserialize(txtReader);
// Close the stream reader
txtReader.Close();
}
public void ReadLot(LotInformation lot)
{
try
{
using (var db = new DDataContext())
{
var lotNumDb = db.LotInformation.FirstOrDefault(r => r.lot_number.Equals(r.lot_number));
if (lotNumDb != null || lotNumDb.lot_number.ToString().Equals(lot.lot_number))
{
confirmationWindow.Message = LanguageResources.Resource.Sample_Exists_Already;
dialogService.ShowDialog(LanguageResources.Resource.Error, confirmationWindow);
}
else {
Console.WriteLine("lot does not exist. yay");
}
DateTime ExpirationDate = lot.exp_date;
if (ExpirationDate != null)
{
if (DateTime.Compare(ExpirationDate, DateTime.Now) > 0)
{
try
{
LotInformation lotInfo = db.LotInformation.FirstOrDefault(r => r.lot_number.Equals(lotNumber));
}
catch (InvalidOperationException e)
{
//TODO: Add a Dialog Here
}
}
else
{
Console.WriteLine(ExpirationDate);
errorWindow.Message = LanguageResources.Resource.Lot_Expired;
dialogService.ShowDialog(LanguageResources.Resource.Error, errorWindow);
}
}
else
{
errorWindow.Message = LanguageResources.Resource.Lot_Not_In_Database;
dialogService.ShowDialog(LanguageResources.Resource.Error, errorWindow);
}
}
}
catch
{
errorWindow.Message = LanguageResources.Resource.Database_Error;
dialogService.ShowDialog(LanguageResources.Resource.Error, errorWindow);
logger.writeErrLog(LanguageResources.Resource.Database_Error);
}
}
I think I'm just having problems with when to grab the lot_number in this process.
This part below gives me problems. It keeps showing the Sample Exists already message for unique lot numbers that I'm uploading and I'm not sure why. I think it's a problem with my LINQ query but I'm not sure how to fix it. Any ideas?
var lotNumDb = db.LotInformation.FirstOrDefault(r => r.lot_number.Equals(r.lot_number));
if (lotNumDb != null || lotNumDb.lot_number.ToString().Equals(lot.lot_number))
{
confirmationWindow.Message = LanguageResources.Resource.Sample_Exists_Already;
dialogService.ShowDialog(LanguageResources.Resource.Error, confirmationWindow);
}
else {
Console.WriteLine("lot does not exist. yay");
}
you can't use like this:
db.LotInformation.FirstOrDefault(r => r.lot_number.Equals(r.lot_number))
may be :
db.LotInformation.FirstOrDefault(r => r.lot_number.Equals(lot.lot_number))
or
db.LotInformation.FirstOrDefault(r => r.lot_number.Equals(a string))

XML Schemas -- List allowed attributes/tags at position in XML

Is there a way to query an XmlSchema or XmlSchemaSet for a list of available tags/attributes at a certain point in the XML? So say my cursor is between <b> and </b> and my schema only allows for a <c/> element there, can I figure that out using anything built in to C#?
<tagset>
<a></a>
<b><!-- CURSOR IS HERE --></b>
</tagset>
There is a way, but the Xml Schema specification is complex so it will take some effort and a few hundred lines of code.
The GetExpectedParticles method of the .NET XmlSchemaValidator class is the key part to a solution. This uses the XmlSchemaSet, passed as an argument, to return a set of XmlSchemaObject instances.
Before you can call this method you need to build a node path to your cursor location which must include ancestor elements and their preceding siblings and also the preceding siblings at the current nesting level. This node path is used to set the context for the schema validator.
After GetExpectedParticles has been called you need to process the particles. For instance, check if each the expected particle is a member of a substitution group, and check whether the expected particle is a restricted simple type that's an enumeration.
It's probably best to separate out code that fetches expected elements and attributes respectively.
The following incomplete code snippet includes the GetExpectedParticles method call, this only caters for element tag content, not attributes:
public static List<XmlSchemaObject> XsdExpectedElements(XmlSchemaSet schemaSet,
List<NodeDescriptor> nodePath)
{
List<XmlSchemaObject> elementNames = new List<XmlSchemaObject>();
NameTable nt = new NameTable();
XmlNamespaceManager manager = new XmlNamespaceManager(nt);
XmlSchemaValidator validator = new XmlSchemaValidator(nt, schemaSet, manager, XmlSchemaValidationFlags.None);
// event handler sets validationErrorFound local field
validator.ValidationEventHandler += new ValidationEventHandler(validator_ValidationEventHandler);
validator.Initialize();
XmlSchemaInfo xsInfo = new XmlSchemaInfo();
int i = 0;
foreach (nodeDescriptor nameUri in nodePath)
{
validator.ValidateElement(nameUri.LocalName, nameUri.NamespaceUri, xsInfo);
if ((i >= siblingPosition && siblingPosition > -1) || nameUri.Closed)
{
validator.SkipToEndElement(null);
}
else
{
validator.ValidateEndOfAttributes(null);
}
i++;
}
XmlSchemaParticle[] parts = validator.GetExpectedParticles();
if (parts.Length == 0)
{
bool hasElements = true;
bool elementClosed = nodePath[nodePath.Count - 1].Closed;
if (elementClosed) // we're outside the element tags
{
hasElements = true;
}
else if (xsInfo.SchemaType is XmlSchemaSimpleType)
{
hasElements = false;
}
else
{
XmlSchemaComplexType xsCt = xsInfo.SchemaType as XmlSchemaComplexType;
XmlSchemaContentType xsContent = (XmlSchemaContentType)xsCt.ContentType;
if (xsContent == XmlSchemaContentType.TextOnly)
{
hasElements = false;
}
}
if (!hasElements)
{
expectedType = XmlEditor.expectedListType.elementValue;
if (xsInfo.SchemaElement != null)
{
elementNames.Add(xsInfo.SchemaElement);
}
}
return elementNames;
}
foreach (XmlSchemaObject xso in parts)
{
if (xso is XmlSchemaElement)
{
XmlSchemaElement xse = (XmlSchemaElement)xso;
if (subGroupList.ContainsKey(xse.QualifiedName))
{
List<XmlSchemaElement> xses = subGroupList[xse.QualifiedName];
foreach (XmlSchemaElement xseInstance in xses)
{
elementNames.Add(xseInstance);
}
}
else
{
elementNames.Add(xse);
}
}
else if (xso is XmlSchemaAny)
{
XmlSchemaAny xsa = (XmlSchemaAny)xso;
foreach (XmlSchema xs in schemaSet.Schemas())
{
if (xs.TargetNamespace == xsa.Namespace)
{
foreach (XmlSchemaElement xseAny in xs.Elements)
{
elementNames.Add(xseAny);
}
}
}
}
}
}
The following (incomplete) code snippet shows how to get expected enumerated values from a particle:
private List<string> ExpectedEnumValues(XmlSchemaObject xsso)
{
XmlSchemaSimpleType xst = null;
XmlSchemaComplexType xsCt = null;
List<string> values = new List<string>();
if (xsso == null)
{
return values;
}
if (xsso is XmlSchemaAttribute)
{
XmlSchemaAttribute xsa = (XmlSchemaAttribute)xsso;
xst = xsa.AttributeSchemaType;
}
else
{
XmlSchemaElement xse = (XmlSchemaElement)xsso;
XmlSchemaType gxst = xse.ElementSchemaType;
if (gxst is XmlSchemaSimpleType)
{
xst = (XmlSchemaSimpleType)gxst;
}
else if (gxst is XmlSchemaComplexType)
{
xsCt = (XmlSchemaComplexType)gxst;
}
else
{
return values;
}
}
if(xst != null)
{
if (xst.TypeCode == XmlTypeCode.Boolean)
{
values.Add("true");
values.Add("false");
}
else
{
ProcessXmlSimpleType(xst, values);
}
}
else if (xsCt != null)
{
XmlSchemaContentType xsContent = (XmlSchemaContentType) xsCt.ContentType;
XmlSchemaContentModel xsModel = (XmlSchemaContentModel)xsCt.ContentModel;
if (xsModel is XmlSchemaSimpleContent)
{
XmlSchemaSimpleContent xsSC = (XmlSchemaSimpleContent)xsModel;
XmlSchemaContent xsRE = xsSC.Content;
if (xsRE != null)
{
if (xsRE is XmlSchemaSimpleContentRestriction)
{
XmlSchemaSimpleContentRestriction xsCCR = (XmlSchemaSimpleContentRestriction)xsRE;
foreach (XmlSchemaObject xso in xsCCR.Facets)
{
if (xso is XmlSchemaEnumerationFacet)
{
XmlSchemaEnumerationFacet xsef = (XmlSchemaEnumerationFacet)xso;
values.Add(xsef.Value);
}
}
}
}
}
else
{
XmlSchemaComplexContent xsCC = (XmlSchemaComplexContent)xsModel;
XmlSchemaContent xsRE = xsCC.Content;
if (xsRE != null)
{
if (xsRE is XmlSchemaComplexContentRestriction)
{
XmlSchemaComplexContentRestriction xsR = (XmlSchemaComplexContentRestriction)xsRE;
}
else if (xsRE is XmlSchemaComplexContentExtension)
{
XmlSchemaComplexContentExtension xsE = (XmlSchemaComplexContentExtension)xsRE;
}
}
}
}
return values;
}
And to process a simple type:
private static void ProcessXmlSimpleType(XmlSchemaSimpleType xst, List<string> values)
{
if (xst == null)
{
return;
}
XmlSchemaSimpleTypeContent xsstc = xst.Content;
if (xsstc is XmlSchemaSimpleTypeRestriction)
{
XmlSchemaSimpleTypeRestriction xsr = (XmlSchemaSimpleTypeRestriction)xsstc;
XmlSchemaObjectCollection xsoc = xsr.Facets;
XmlSchemaSimpleType bastTypeOfRestiction = xsr.BaseType;
foreach (XmlSchemaObject xso in xsoc)
{
if (xso is XmlSchemaEnumerationFacet)
{
XmlSchemaEnumerationFacet xsef = (XmlSchemaEnumerationFacet)xso;
values.Add(xsef.Value);
}
}
}
else if (xsstc is XmlSchemaSimpleTypeList)
{
XmlSchemaSimpleTypeList xsstL = (XmlSchemaSimpleTypeList)xsstc;
XmlSchemaSimpleType xstL = xsstL.BaseItemType;
ProcessXmlSimpleType(xstL, values); // recursive
}
else if (xsstc is XmlSchemaSimpleTypeUnion)
{
XmlSchemaSimpleTypeUnion xstU = (XmlSchemaSimpleTypeUnion)xsstc;
XmlSchemaSimpleType[] xsstArray = xstU.BaseMemberTypes;
foreach (XmlSchemaSimpleType xsstA in xsstArray)
{
ProcessXmlSimpleType(xsstA, values); // recursive
}
}
}
The above code snippets probably address 20% of what's needed, but hopefully give you some idea of what you will be dealing with. .NET provides a very powerful set of classes for analysing the Schema Object Model, but you will need detailed knowledge of the XML Schema specification to get usable results.
XML editors should still provide auto-completion help when the XML is not valid, this adds an extra dimension to the problem because there may be ambiguities if there's limited validation context and the schema design is more 'russian-doll' than 'salami sliced'.
Summary
Getting a list of expected XML schema particles for a given context within an XML instance using .NET is possible but relatively complex. In view of this, it would be worthwhile to first check if libraries from existing .NET XML editors provide the functionality you need.
For a working implementation under LGPL have a look at SharpDevelops XmlEditor part.
You get the code completion for xml in one dll, namely the XmlEditor.dll in the AddIns/DisplayBindings directory.

Categories

Resources