This program is supposed to take every embedded XML Schema file in an assembly and generate classes in the csOutPath. The names of the classes and files should match the stored schema and be put in the Target Assembly's "GeneratedClasses" folder.
Only requirement is that classes must have their namespace and folder location be:
{targetAssembly}.GeneratedClasses.{SubFolderTakenFromEmbeddedResource}.{ResourceNameStrippedOfIllegalChars}
I would prefer to not create local temp files as I'm having problems with file locking, but I'm using the XSD.exe tool. I also have several parallel processes to speed it up but it is still a bit slow (this program runs on every build of the other project).
internal class Program { #region Constants private static readonly Assembly assembly = Assembly.Load("AssemblyName"); private const string basepath = "TMP_SCHEMA_OUTPUT_PATH"; private const string csOutPath = "D:\\_Workspace\\Integrations\\XmlHelper\\GeneratedClasses"; private static readonly string warningMessage = "// DO NOT MODIFY THIS FILE. Instead modify how it\'s build " + $"in {typeof(Program).Assembly.GetName().Name}.\r\n" + "// Any modifications are overwritten in the pre-build event."; #endregion private static void Main() { /* ----------------------- Build Schemas ----------------------- */ var schemasBuilt = BuildSchemas(); /* ------------------ Run the XSD.exe process ------------------ */ Parallel.ForEach(schemasBuilt, schema => { var name = Path.GetFileNameWithoutExtension(schema); var baseName = assembly.GetName().Name; /* ------------------- Build namespaces -------------------- */ var process = new Process { StartInfo = { WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, WorkingDirectory = Directory.GetCurrentDirectory(), CreateNoWindow = true, FileName = "xsd.exe", Arguments = "/order " + $"{schema} " + "/c " + $"/out:{basepath} " + $"/namespace:{baseName}.GeneratedClasses.{name}" }, EnableRaisingEvents = true }; process.Start(); Console.WriteLine(process.StandardOutput.ReadToEnd()); Console.WriteLine(process.StandardError.ReadToEnd()); }); /* --------------------------------------------------------------*/ /* ---------------------- Write the files ---------------------- */ BuildCsFiles(new DirectoryInfo(basepath) .EnumerateFiles() .Where(x => x.Extension.Contains("cs")) .Select(y => y.FullName).ToList()); /* -------------------------- Cleanup -------------------------- */ Directory.Delete(basepath, true); /* --------------------------------------------------------------*/ } private static void BuildCsFiles(IEnumerable<string> generatedCsFiles) { foreach (var generatedPath in generatedCsFiles) { var firstLine = true; var name = Path.GetFileName(generatedPath); var file = Path.Combine(csOutPath, name.Replace("_", "\\")); Directory.CreateDirectory(Path.GetDirectoryName(file)); /* ---------------- Write and update files ----------------- */ using (var reader = new StreamReader(generatedPath)) using (var fileStream = new FileStream(file, FileMode.Create)) using (var writer = new StreamWriter(fileStream)) { string tmpLine; while ((tmpLine = reader.ReadLine()) != null) { /* ----------- Any class changes go here ----------- */ writer.WriteLine(tmpLine); if (!firstLine) continue; writer.WriteLine(warningMessage); firstLine = false; } writer.Flush(); } } } /// <summary> /// Builds Schemas. /// </summary> /// <returns>A list of schemas by their names.</returns> private static IEnumerable<string> BuildSchemas() { var schemas = new ConcurrentBag<string>(); var relevantResources = assembly.GetManifestResourceNames() .Where(x => x.Contains(".xsd")); /* ---------------------- Write the files ---------------------- */ Parallel.ForEach(relevantResources, resource => { Directory.CreateDirectory(basepath); // Schemas are stored in NameSpace.Schemas.*.<schemaName> string key = Regex.Replace(resource, "(.*Schemas\\.|\\(.*)", String.Empty); var tmpOutPath = Path.Combine(basepath, key + ".xsd"); using (var s = assembly.GetManifestResourceStream(resource)) using (var fileStream = WaitForFile(tmpOutPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Write)) { if (fileStream == null) { Console.WriteLine($"Unable to unlock {tmpOutPath} after 10 attempts. Exiting..."); throw new IOException($"Unable to unlock {tmpOutPath} after 10 attempts."); } s?.Seek(0, SeekOrigin.Begin); s?.CopyTo(fileStream); schemas.Add(tmpOutPath); } }); return schemas; } /// <summary> /// Waits for the file to be available 10 times before throwing an error /// </summary> /// <returns></returns> static FileStream WaitForFile(string fullPath, FileMode mode, FileAccess access, FileShare share) { for (var numTries = 0; numTries < 10; numTries++) { FileStream fs = null; try { fs = new FileStream(fullPath, mode, access, share); return fs; } catch (IOException) { fs?.Dispose(); Thread.Sleep(50); } } return null; } }