How to generate WiX XML from a .reg file? - deployment

Is there a tool to generate WiX XML given a .reg file?
In 2.0, you were supposed to be able to run tallow to generate registry XML:
tallow -r my.reg
For what it's worth, the version of tallow I have is producing empty XML.
In 3.0, tallow has been replaced with heat, but I can't figure out how to get it to produce output from a .reg file.
Is there a way to do this in 3.0?

I couldn't find a tool, so I made one.
The source code may not be elegant, but it seems to work:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;
namespace Reg2Wix
{
class Program
{
static void PrintUsage()
{
Console.WriteLine("reg2wix <input file> <output file>");
}
/// <summary>
/// Parse the hive out of a registry key
/// </summary>
/// <param name="keyWithHive"></param>
/// <param name="hive"></param>
/// <param name="key"></param>
static void ParseKey(string keyWithHive, out string hive, out string key)
{
if (keyWithHive == null)
{
throw new ArgumentNullException("keyWithHive");
}
if (keyWithHive.StartsWith("HKEY_LOCAL_MACHINE\\"))
{
hive = "HKLM";
key = keyWithHive.Substring(19);
}
else if (keyWithHive.StartsWith("HKEY_CLASSES_ROOT\\"))
{
hive = "HKCR";
key = keyWithHive.Substring(18);
}
else if (keyWithHive.StartsWith("HKEY_USERS\\"))
{
hive = "HKU";
key = keyWithHive.Substring(11);
}
else if (keyWithHive.StartsWith("HKEY_CURRENT_USER\\"))
{
hive = "HKCU";
key = keyWithHive.Substring(18);
}
else
{
throw new ArgumentException();
}
}
/// <summary>
/// Write a WiX RegistryValue element for the specified key, name, and value
/// </summary>
/// <param name="writer"></param>
/// <param name="key"></param>
/// <param name="name"></param>
/// <param name="value"></param>
static void WriteRegistryValue(XmlWriter writer, string key, string name, string value)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
string hive;
string keyPart;
ParseKey(key, out hive, out keyPart);
writer.WriteStartElement("RegistryValue");
writer.WriteAttributeString("Root", hive);
writer.WriteAttributeString("Key", keyPart);
if (!String.IsNullOrEmpty(name))
{
writer.WriteAttributeString("Name", name);
}
writer.WriteAttributeString("Value", value);
writer.WriteAttributeString("Type", "string");
writer.WriteAttributeString("Action", "write");
writer.WriteEndElement();
}
/// <summary>
/// Convert a .reg file into an XML document
/// </summary>
/// <param name="inputReader"></param>
/// <param name="xml"></param>
static void RegistryFileToWix(TextReader inputReader, XmlWriter xml)
{
Regex regexKey = new Regex("^\\[([^\\]]+)\\]$");
Regex regexValue = new Regex("^\"([^\"]+)\"=\"([^\"]*)\"$");
Regex regexDefaultValue = new Regex("#=\"([^\"]+)\"$");
string currentKey = null;
string line;
while ((line = inputReader.ReadLine()) != null)
{
line = line.Trim();
Match match = regexKey.Match(line);
if (match.Success)
{
//key track of the current key
currentKey = match.Groups[1].Value;
}
else
{
//if we have a current key
if (currentKey != null)
{
//see if this is an acceptable name=value pair
match = regexValue.Match(line);
if (match.Success)
{
WriteRegistryValue(xml, currentKey, match.Groups[1].Value, match.Groups[2].Value);
}
else
{
//see if this is an acceptable default value (starts with #)
match = regexDefaultValue.Match(line);
if (match.Success)
{
WriteRegistryValue(xml, currentKey, (string)null, match.Groups[1].Value);
}
}
}
}
}
}
/// <summary>
/// Convert a .reg file into a .wsx file
/// </summary>
/// <param name="inputPath"></param>
/// <param name="outputPath"></param>
static void RegistryFileToWix(string inputPath, string outputPath)
{
using (StreamReader reader = new StreamReader(inputPath))
{
using (XmlTextWriter writer = new XmlTextWriter(outputPath, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
writer.Indentation = 3;
writer.IndentChar = ' ';
writer.WriteStartDocument();
writer.WriteStartElement("Component");
RegistryFileToWix(reader, writer);
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
}
static void Main(string[] args)
{
if (args.Length != 2)
{
PrintUsage();
return;
}
RegistryFileToWix(args[0], args[1]);
}
}
}

Here is the source code of the utility that generates Wix 3 markup (including binary, dword and multi-string registry values):
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
namespace AbsReg2Wix
{
public class Program
{
#region Constants
private const string NS_URI = "http://schemas.microsoft.com/wix/2006/wi";
private const string RegEditorVersionPattern = #"Windows\sRegistry\sEditor\sVersion\s(?<RegEditorVersion>.*)";
private const string RegKeyPattern = #"\[(?<RegistryHive>[^\\]*)\\(?<RegistryKey>.*)\]";
private const string RegNameValuePattern = "\\\"(?<Name>.*)\\\"=(?<Value>\\\"?[^\\\\\\\"]*)(?<MultiLine>\\\\?)";
private const RegexOptions DefaultRegexOptions = RegexOptions.Multiline |
RegexOptions.IgnorePatternWhitespace |
RegexOptions.CultureInvariant;
#endregion
#region Methods
/// <summary>
/// Main applciation entry point
/// </summary>
/// <param name="args">The args.</param>
private static void Main(string[] args)
{
if (args.Length != 4)
{
PrintUsageInstructions();
return;
}
if (File.Exists(args[1]))
{
ConvertRegistryFileToWix(args[1], args[3]);
Console.WriteLine("Successfully completed conversion.");
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
else
{
Console.WriteLine(#"Input file {0} not found.", args[1]);
}
}
/// <summary>
/// Prints the usage instructions.
/// </summary>
private static void PrintUsageInstructions()
{
Console.WriteLine("Syntax: AbsReg2Wix.exe /in <Input File (.reg)> /out <Output File>");
}
/// <summary>
/// Convert a .reg file into a .wsx file
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
private static void ConvertRegistryFileToWix(string inputPath, string outputPath)
{
try
{
using (var reader = new StreamReader(inputPath))
{
string regEditorVersion = string.Empty;
bool isRegEditorVersionFound = false;
// Initialize Regex
var regEditorVersionRegex = new Regex(RegEditorVersionPattern, DefaultRegexOptions);
var regKeyRegex = new Regex(RegKeyPattern, DefaultRegexOptions);
var regNameValueRegex = new Regex(RegNameValuePattern, DefaultRegexOptions);
// Create xml document for output
var xDoc = new XmlDocument();
xDoc.AppendChild(xDoc.CreateProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""));
xDoc.AppendChild(xDoc.CreateComment(
string.Format(
"{0}Following code was generated by AbsReg2Wix tool.{0}Tool Version: {1}{0}Date: {2}{0}Command Line: {3}\n",
"\n\t", Assembly.GetExecutingAssembly().GetName().Version,
DateTime.Now.ToString("F"),
Environment.CommandLine)));
XmlElement includeElement = xDoc.CreateElement("Include", NS_URI);
XmlElement componentElement = null,
regKeyElement = null,
registryValueElement = null;
bool multiLine = false;
var rawValueBuilder = new StringBuilder();
while (!reader.EndOfStream)
{
string regFileLine = reader.ReadLine().Trim();
if (!isRegEditorVersionFound)
{
var regEditorVersionMatch = regEditorVersionRegex.Match(regFileLine);
if (regEditorVersionMatch.Success)
{
regEditorVersion = regEditorVersionMatch.Groups["RegEditorVersion"].Value;
includeElement.AppendChild(
xDoc.CreateComment("Registry Editor Version: " + regEditorVersion));
isRegEditorVersionFound = true;
}
}
var regKeyMatch = regKeyRegex.Match(regFileLine);
// Registry Key line found
if (regKeyMatch.Success)
{
if (componentElement != null)
{
componentElement.AppendChild(regKeyElement);
includeElement.AppendChild(componentElement);
}
componentElement = xDoc.CreateElement("Component", NS_URI);
var idAttr = xDoc.CreateAttribute("Id");
idAttr.Value = "Comp_" + GetMD5HashForString(regFileLine);
componentElement.Attributes.Append(idAttr);
var guidAttr = xDoc.CreateAttribute("Guid");
guidAttr.Value = Guid.NewGuid().ToString();
componentElement.Attributes.Append(guidAttr);
regKeyElement = xDoc.CreateElement("RegistryKey", NS_URI);
var hiveAttr = xDoc.CreateAttribute("Root");
hiveAttr.Value = GetShortHiveName(regKeyMatch.Groups["RegistryHive"].Value);
regKeyElement.Attributes.Append(hiveAttr);
var keyAttr = xDoc.CreateAttribute("Key");
keyAttr.Value = regKeyMatch.Groups["RegistryKey"].Value;
regKeyElement.Attributes.Append(keyAttr);
var actionAttr = xDoc.CreateAttribute("Action");
actionAttr.Value = "createAndRemoveOnUninstall";
regKeyElement.Attributes.Append(actionAttr);
}
var regNameValueMatch = regNameValueRegex.Match(regFileLine);
// Registry Name/Value pair line found
if (regNameValueMatch.Success)
{
registryValueElement = xDoc.CreateElement("RegistryValue", NS_URI);
var nameAttr = xDoc.CreateAttribute("Name");
nameAttr.Value = regNameValueMatch.Groups["Name"].Value;
registryValueElement.Attributes.Append(nameAttr);
var actionAttr = xDoc.CreateAttribute("Action");
actionAttr.Value = "write";
registryValueElement.Attributes.Append(actionAttr);
if (string.IsNullOrEmpty(regNameValueMatch.Groups["MultiLine"].Value))
{
string valueType, actualValue;
ParseRegistryValue(regNameValueMatch.Groups["Value"].Value, out valueType,
out actualValue);
var typeAttr = xDoc.CreateAttribute("Type");
typeAttr.Value = valueType;
registryValueElement.Attributes.Append(typeAttr);
var valueAttr = xDoc.CreateAttribute("Value");
valueAttr.Value = actualValue;
registryValueElement.Attributes.Append(valueAttr);
regKeyElement.AppendChild(registryValueElement);
}
else
{
multiLine = true;
rawValueBuilder.Append(regNameValueMatch.Groups["Value"].Value
.Replace("\\", string.Empty));
}
}
else if (multiLine)
{
if (regFileLine.IndexOf("\\") != -1)
{
rawValueBuilder.Append(regFileLine.Replace("\\", string.Empty));
}
else
{
rawValueBuilder.Append(regFileLine);
string valueType, actualValue;
ParseRegistryValue(rawValueBuilder.ToString(), out valueType, out actualValue);
var typeAttr = xDoc.CreateAttribute("Type");
typeAttr.Value = valueType;
registryValueElement.Attributes.Append(typeAttr);
var valueAttr = xDoc.CreateAttribute("Value");
valueAttr.Value = actualValue;
registryValueElement.Attributes.Append(valueAttr);
regKeyElement.AppendChild(registryValueElement);
rawValueBuilder.Remove(0, rawValueBuilder.Length);
multiLine = false;
}
}
}
if (componentElement != null)
{
componentElement.AppendChild(regKeyElement);
includeElement.AppendChild(componentElement);
}
xDoc.AppendChild(includeElement);
xDoc.Save(outputPath);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/// <summary>
/// Parses the registry value.
/// </summary>
/// <param name="rawValue">The raw value.</param>
/// <param name="valueType">Type of the value.</param>
/// <param name="actualValue">The actual value.</param>
private static void ParseRegistryValue(string rawValue, out string valueType, out string actualValue)
{
if (rawValue.IndexOf("\"") != -1)
{
valueType = "string";
actualValue = rawValue.Substring(1, rawValue.Length - 2);
}
else if (rawValue.IndexOf("dword:") != -1)
{
valueType = "integer";
actualValue = rawValue.Replace("dword:", string.Empty);
}
else if (rawValue.IndexOf("hex:") != -1)
{
valueType = "binary";
actualValue = rawValue.Replace("hex:", string.Empty)
.Replace(",", string.Empty)
.ToUpper();
}
else if (rawValue.IndexOf("hex(7):") != -1)
{
valueType = "multiString";
string[] hexStrings = rawValue.Replace("hex(7):", string.Empty).Split(',');
var bytes = new byte[hexStrings.Length];
for (int i = 0; i < hexStrings.Length; i++)
{
bytes[i] = byte.Parse(hexStrings[i], NumberStyles.HexNumber);
}
actualValue = Encoding.Unicode.GetString(bytes).Replace("\0", "[~]");
}
else
{
valueType = "string";
actualValue = rawValue;
}
}
/// <summary>
/// Gets the short name of the registry hive.
/// </summary>
/// <param name="fullHiveName">Full name of the hive.</param>
/// <returns></returns>
private static string GetShortHiveName(string fullHiveName)
{
switch (fullHiveName)
{
case "HKEY_LOCAL_MACHINE":
return "HKLM";
case "HKEY_CLASSES_ROOT":
return "HKCR";
case "HKEY_USERS":
return "HKU";
case "HKEY_CURRENT_USER":
return "HKCU";
default:
throw new ArgumentException(string.Format("Registry Hive unsupported by Wix: {0}.",
fullHiveName));
}
}
/// <summary>
/// Gets the MD5 hash for string.
/// </summary>
/// <param name="inputString">The input string.</param>
/// <returns></returns>
private static string GetMD5HashForString(string inputString)
{
MD5 hashAlg = MD5.Create();
byte[] originalInBytes = Encoding.ASCII.GetBytes(inputString);
byte[] hashedOriginal = hashAlg.ComputeHash(originalInBytes);
String outputString = Convert.ToBase64String(hashedOriginal)
.Replace("/", "aa")
.Replace("+", "bb")
.Replace("=", "cc");
return outputString;
}
#endregion
}
}

Now it's built into Wix : Heat.exe - Harvesting tool.
https://stackoverflow.com/a/11988983/483588

I've tried tallow.exe (version 2.0.5805) from the latest stable Wix 2 release and it worked fine for me.
tallow -reg my.reg
This will generate the markup using Wix 2 "Registry" tag that was deprecated in Wix 3. Then you have to copy the output into a wix source file and execute WixCop utility to convert Wix 2 markup to Wix 3:
wixcop my.wxs -f

This code works well, but if you have a an empty string value in the registry file you are importing, an exception error gets thrown. You may want to update the ParseRegistryValue section accordingly.
if (rawValue.IndexOf("\"") != -1)
{
valueType = "string";
if (rawValue.Length > 1)
{
actualValue = rawValue.Substring(1, rawValue.Length - 2);
}
else
{
actualValue = "";
}
}

From Wix 4.0
C:\Program Files (x86)\WiX Toolset v4.0\bin>heat /? | find /i "reg"
reg harvest a .reg file
-sreg suppress registry harvesting
To use, where regfile.reg is the registry input file and fragment.xml is the output file to generate.
heat reg regfile.reg -o fragment.xml

Related

Open OSM pbf results in Protobuf exception

Using OSMSharp I am having trouble to open a stream for a file (which I can provide on demand)
The error occurs in PBFReader (line 104)
using (var tmp = new LimitedStream(_stream, length))
{
header = _runtimeTypeModel.Deserialize(tmp, null, _blockHeaderType) as BlobHeader;
}
and states: "ProtoBuf.ProtoException: 'Invalid field in source data: 0'" which might mean different things as I have read in this SO question.
The file opens and is visualized with QGis so is not corrupt in my opinion.
Can it be that the contracts do not match? Is OsmSharp/core updated to the latest .proto files for OSM from here (although not sure if this is the real original source for the definition files).
And what might make more sense, can it be that the file I attached is generated for v2 of OSM PBF specification?
In the code at the line of the exception I see the following comment which makes me wonder:
// TODO: remove some of the v1 specific code.
// TODO: this means also to use the built-in capped streams.
// code borrowed from: http://stackoverflow.com/questions/4663298/protobuf-net-deserialize-open-street-maps
// I'm just being lazy and re-using something "close enough" here
// note that v2 has a big-endian option, but Fixed32 assumes little-endian - we
// actually need the other way around (network byte order):
// length = IntLittleEndianToBigEndian((uint)length);
BlobHeader header;
// again, v2 has capped-streams built in, but I'm deliberately
// limiting myself to v1 features
So this makes me wonder if OSM Sharp is (still) up-to-date.
My sandbox code looks like this:
using OsmSharp.Streams;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OsmSharp.Tags;
namespace OsmSharp
{
class Program
{
private const string Path = #"C:\Users\Bernoulli IT\Documents\Applications\Argaleo\Test\";
private const string FileNameAntarctica = "antarctica-latest.osm";
private const string FileNameOSPbf = "OSPbf";
private const Boolean useRegisterSource = false;
private static KeyValuePair<string, string> KeyValuePair = new KeyValuePair<string, string>("joep", "monita");
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
//string fileName = $#"{Path}\{FileNameAntarctica}.pbf";
string fileName = $#"{Path}\{FileNameOSPbf}.pbf";
string newFileName = $"{fileName.Replace(".pbf", string.Empty)}-{Guid.NewGuid().ToString().Substring(0, 4)}.pbf";
Console.WriteLine("*** Complete");
string fileNameOutput = CompleteFlow(fileName, newFileName);
Console.WriteLine("");
Console.WriteLine("*** Display");
DisplayFlow(fileNameOutput);
Console.ReadLine();
}
private static string CompleteFlow(string fileName, string newFileName)
{
// 1. Open file and convert to bytes
byte[] fileBytes = FileToBytes(fileName);
// 2. Bytes to OSM stream source (pbf)
PBFOsmStreamSource osmStreamSource;
osmStreamSource = BytesToOsmStreamSource(fileBytes);
osmStreamSource.MoveNext();
if (osmStreamSource.Current() == null)
{
osmStreamSource = FileToOsmStreamSource(fileName);
osmStreamSource.MoveNext();
if (osmStreamSource.Current() == null)
{
throw new Exception("No current in stream.");
}
}
// 3. Add custom tag
AddTag(osmStreamSource);
// 4. OSM stream source to bytes
//byte[] osmStreamSourceBytes = OsmStreamSourceToBytes(osmStreamSource);
// 5. Bytes to file
//string fileNameOutput = BytesToFile(osmStreamSourceBytes, newFileName);
OsmStreamSourceToFile(osmStreamSource, newFileName);
Console.WriteLine(newFileName);
return newFileName;
}
private static void DisplayFlow(string fileName)
{
// 1. Open file and convert to bytes
byte[] fileBytes = FileToBytes(fileName);
// 2. Bytes to OSM stream source (pbf)
BytesToOsmStreamSource(fileBytes);
}
private static byte[] FileToBytes(string fileName)
{
Console.WriteLine(fileName);
return File.ReadAllBytes(fileName);
}
private static PBFOsmStreamSource BytesToOsmStreamSource(byte[] bytes)
{
MemoryStream memoryStream = new MemoryStream(bytes);
memoryStream.Position = 0;
PBFOsmStreamSource osmStreamSource = new PBFOsmStreamSource(memoryStream);
foreach (OsmGeo element in osmStreamSource.Where(osmGeo => osmGeo.Tags.Any(tag => tag.Key.StartsWith(KeyValuePair.Key))))
{
foreach (Tag elementTag in element.Tags.Where(tag => tag.Key.StartsWith(KeyValuePair.Key)))
{
Console.WriteLine("!!!!!!!!!!!!!! Tag found while reading !!!!!!!!!!!!!!!!!!".ToUpper());
}
}
return osmStreamSource;
}
private static PBFOsmStreamSource FileToOsmStreamSource(string fileName)
{
using (FileStream fileStream = new FileInfo(fileName).OpenRead())
{
PBFOsmStreamSource osmStreamSource = new PBFOsmStreamSource(fileStream);
return osmStreamSource;
}
}
private static void AddTag(PBFOsmStreamSource osmStreamSource)
{
osmStreamSource.Reset();
OsmGeo osmGeo = null;
while (osmGeo == null)
{
osmStreamSource.MoveNext();
osmGeo = osmStreamSource.Current();
if(osmGeo?.Tags == null)
{
osmGeo = null;
}
}
osmGeo.Tags.Add("joep", "monita");
Console.WriteLine($"{osmGeo.Tags.FirstOrDefault(tag => tag.Key.StartsWith(KeyValuePair.Key)).Key} - {osmGeo.Tags.FirstOrDefault(tag => tag.Key.StartsWith(KeyValuePair.Key)).Value}");
}
private static byte[] OsmStreamSourceToBytes(PBFOsmStreamSource osmStreamSource)
{
MemoryStream memoryStream = new MemoryStream();
PBFOsmStreamTarget target = new PBFOsmStreamTarget(memoryStream, true);
osmStreamSource.Reset();
target.Initialize();
UpdateTarget(osmStreamSource, target);
target.Flush();
target.Close();
return memoryStream.ToArray();
}
private static string BytesToFile(byte[] bytes, string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
fs.Write(bytes, 0, bytes.Length);
}
return fileName;
}
private static void OsmStreamSourceToFile(PBFOsmStreamSource osmStreamSource, string fileName)
{
using (FileStream fileStream = new FileInfo(fileName).OpenWrite())
{
PBFOsmStreamTarget target = new PBFOsmStreamTarget(fileStream, true);
osmStreamSource.Reset();
target.Initialize();
UpdateTarget(osmStreamSource, target);
target.Flush();
target.Close();
}
}
private static void UpdateTarget(OsmStreamSource osmStreamSource, OsmStreamTarget osmStreamTarget)
{
if (useRegisterSource)
{
osmStreamTarget.RegisterSource(osmStreamSource, osmGeo => true);
osmStreamTarget.Pull();
}
else
{
bool isFirst = true;
foreach (OsmGeo osmGeo in osmStreamSource)
{
Tag? tag = osmGeo.Tags?.FirstOrDefault(t => t.Key == KeyValuePair.Key);
switch (osmGeo.Type)
{
case OsmGeoType.Node:
if (isFirst)
{
for (int indexer = 0; indexer < 1; indexer++)
{
(osmGeo as Node).Tags.Add(new Tag(KeyValuePair.Key + Guid.NewGuid(), KeyValuePair.Value));
}
isFirst = false;
}
osmStreamTarget.AddNode(osmGeo as Node);
break;
case OsmGeoType.Way:
osmStreamTarget.AddWay(osmGeo as Way);
break;
case OsmGeoType.Relation:
osmStreamTarget.AddRelation(osmGeo as Relation);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
}
}
Already I posted this question on the GITHube page of OSMSharp as is linked here. Any help would be very appreciated.

How do you cast a Unity.Object into a generic class member?

I am a decades-old C programmer. Unity is my first foray into modern C and I find myself pleasantly surprised - but some of the subtleties of the language have escaped me.
First a technical question: What is the right way to store a Unity Object in a generic class? In the example below I get the following error:
Assets/scripts/MediaTest.cs(49,44): error CS0030: Cannot convert type `UnityEngine.Texture2D' to `T'
Second the real question: What is a better approach to loading a set of textures?
Thanks in advance for your help
/*
* MediaTest.cs
*
* Pared down generic class test
*
*/
using System.Collections; // Load IEnumerable
using System.Collections.Generic; // Load Dictionary
using System.Text.RegularExpressions; // Load Regex
using UnityEngine; // Load UnityEngine.Object
public class MediaTest<T> : IEnumerable where T : UnityEngine.Object {
private Dictionary<string, MediaContent> _dict =
new Dictionary<string, MediaContent>();
private class MediaContent {
public string path { get; set; }
public T item { get; set; }
public MediaContent(string path, T item) {
this.path = path;
this.item = item;
}
}
// Indexer to return a UnityEngine.Object by filename
public T this[string name] {
get { return (T)_dict[name].item; }
}
// Convert a path to just the filename
public string Basename(string path) {
return new Regex(#"^.*/").Replace(path, "");
}
// Iterate through the filenames (keys) to the stored objects
public IEnumerator GetEnumerator() {
foreach (string name in _dict.Keys) {
yield return name;
}
}
// Read in the Resource at the specified path into a UnityEngine.Object
public void Load(string path, bool load=false) {
string name = Basename(path);
if (this.GetType() == typeof(Media<Texture2D>) && IsStill(name)) {
T item = (load) ? (T)Resources.Load<Texture2D>(path) : null;
_dict[name] = new MediaContent(path, item);
return;
}
if (this.GetType() == typeof(Media<AudioClip>) && IsAudio(name)) {
T item = (load) ? (T)Resources.Load<AudioClip>(path) : null;
_dict[name] = new MediaContent(path, item);
return;
}
}
// The real code uses Regex.Match on the file extension for supported types
public bool IsStill(string name) { return true; }
public bool IsAudio(string name) { return true; }
}
Here is the working code with updates from the comments. Thanks derHugo!
public void Load(string path, bool load=false) {
// Translate the filesystem path to a Resources relative path by removing both
// The */Resources prefix and the filename extention
Regex re_path = new Regex(#".*/Resources/");
Regex re_ext = new Regex(#"\.\w+$");
string relpath = re_ext.Replace(re_path.Replace(path, ""), "");
// Create an asset name from the path
string name = System.IO.Path.GetFileName(relpath);
// Skip this file if it doesn't match our type
if ( (typeof(T) == typeof(Texture2D) && IsStill(path)) ||
// (typeof(T) == typeof(Video) && IsVideo(path)) ||
(typeof(T) == typeof(AudioClip) && IsAudio(name))) {
T item = (load) ? Resources.Load(relpath) as T : null;
_dict[name] = new MediaContent(path, item);
}
}

System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification calling Autorest generated code

I have a unit test, calling a service that makes use of Autorest generated code to call my Api.
I want my unit test to display the error that my Api is throwing, but there seems to be an error in the service's error handling.
I am using the following command to generate code to consume my api.
autorest --input-file=https://mywebsite.com.au:4433/myapi/api-docs/v1/swagger.json --output-folder=generated --csharp --namespace=MyConnector
The generated "client code" contains
/// <param name='request'>
/// </param>
/// <param name='customHeaders'>
/// Headers that will be added to request.
/// </param>
/// <param name='cancellationToken'>
/// The cancellation token.
/// </param>
/// <exception cref="HttpOperationException">
/// Thrown when the operation returned an invalid status code
/// </exception>
/// <exception cref="SerializationException">
/// Thrown when unable to deserialize the response
/// </exception>
/// <return>
/// A response object containing the response body and response headers.
/// </return>
public async Task<HttpOperationResponse<GetAvailableCarriersResponse>> GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(GetAvailableCarriersRequest request = default(GetAvailableCarriersRequest), Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
// Tracing
bool _shouldTrace = ServiceClientTracing.IsEnabled;
string _invocationId = null;
if (_shouldTrace)
{
_invocationId = ServiceClientTracing.NextInvocationId.ToString();
Dictionary<string, object> tracingParameters = new Dictionary<string, object>();
tracingParameters.Add("request", request);
tracingParameters.Add("cancellationToken", cancellationToken);
ServiceClientTracing.Enter(_invocationId, this, "GetAvailableCarriersByJobHeaderId", tracingParameters);
}
// Construct URL
var _baseUrl = BaseUri.AbsoluteUri;
var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "api/shipping-management/Get-Available-Carriers").ToString();
// Create HTTP transport objects
var _httpRequest = new HttpRequestMessage();
HttpResponseMessage _httpResponse = null;
_httpRequest.Method = new HttpMethod("POST");
_httpRequest.RequestUri = new System.Uri(_url);
// Set Headers
if (customHeaders != null)
{
foreach(var _header in customHeaders)
{
if (_httpRequest.Headers.Contains(_header.Key))
{
_httpRequest.Headers.Remove(_header.Key);
}
_httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value);
}
}
// Serialize Request
string _requestContent = null;
if(request != null)
{
_requestContent = SafeJsonConvert.SerializeObject(request, SerializationSettings);
_httpRequest.Content = new StringContent(_requestContent, System.Text.Encoding.UTF8);
_httpRequest.Content.Headers.ContentType =System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json-patch+json; charset=utf-8");
}
// Send Request
if (_shouldTrace)
{
ServiceClientTracing.SendRequest(_invocationId, _httpRequest);
}
cancellationToken.ThrowIfCancellationRequested();
_httpResponse = await HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
if (_shouldTrace)
{
ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse);
}
HttpStatusCode _statusCode = _httpResponse.StatusCode;
cancellationToken.ThrowIfCancellationRequested();
string _responseContent = null;
if ((int)_statusCode != 200)
{
var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode));
if (_httpResponse.Content != null) {
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else {
_responseContent = string.Empty;
}
ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent);
ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent);
if (_shouldTrace)
{
ServiceClientTracing.Error(_invocationId, ex);
}
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw ex;
}
// Create Result
var _result = new HttpOperationResponse<GetAvailableCarriersResponse>();
_result.Request = _httpRequest;
_result.Response = _httpResponse;
// Deserialize Response
if ((int)_statusCode == 200)
{
_responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
_result.Body = SafeJsonConvert.DeserializeObject<GetAvailableCarriersResponse>(_responseContent, DeserializationSettings);
}
catch (JsonException ex)
{
_httpRequest.Dispose();
if (_httpResponse != null)
{
_httpResponse.Dispose();
}
throw new SerializationException("Unable to deserialize the response.", _responseContent, ex);
}
}
if (_shouldTrace)
{
ServiceClientTracing.Exit(_invocationId, _result);
}
return _result;
}
I have a unit test to call the generated code using
var api = MakeApi();
var task=api.GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(req);
var carriers = task.Result.Body.Carriers;
where
private static MyApiService MakeApi()
{
var setting = new MyAPISettings(false);
var api = new MyApiService(setting);
return api;
}
and MyApiService contains (with altered namespaces)
public Task<HttpOperationResponse<GetAvailableCarriersResponse>> GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(
GetAvailableCarriersRequest request = default(GetAvailableCarriersRequest), Dictionary<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default(CancellationToken))
{
return ApiCaller.ExecuteAsync(
async headers => await API.GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync(request, headers, cancellationToken),
async () => await GetTokenHeadersAsync(customHeaders));
}
where apicaller is
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyServices
{
public static class ApiCaller
{
private static Dictionary<string, List<string>> Headers { get; set; }
private static string GetHeadersMessage()
{
string ret = "";
if (Headers != null)
{
foreach (string key in Headers.Keys)
{
if (Headers[key] != null)
{
foreach (string value in Headers[key])
{
ret = $"{key}-{value}\n";
}
}
}
}
return ret;
}
public async static Task<T> ExecuteAsync<T>(Func<Dictionary<string, List<string>>, Task<T>> f,
Func<Task<Dictionary<string, List<string>>>> getHeaders)
{
T ret = default(T);
try
{
try
{
if (getHeaders != null && Headers == null)
{
Headers = await getHeaders();
}
ret = await f(Headers);
}
catch (Microsoft.Rest.HttpOperationException ex1)
{
if (ex1.Response?.StatusCode == System.Net.HttpStatusCode.Unauthorized && getHeaders != null)
{
Headers = await getHeaders();
ret = await f(Headers);
}
else
{
throw;
}
}
}
catch (Exception ex)
{
//Log.Error(ex, $"... API CALL ERROR ...\nHEADERS:{GetHeadersMessage()}");
throw new Exception($"Error calling the API. {ex.Message}", ex);
}
return ret;
}
}
}
My Api throws an InternalServerError
However when I run the unit test, I get an error in the client code.
The error occurs at
// Create Result
var _result = new HttpOperationResponse<GetAvailableCarriersResponse>();
And is
System.Exception: Error calling the API. Operation returned an invalid status code 'InternalServerError' ---> Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError'
at MyConnector.MyApi.<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>d__49.MoveNext()
How can I work around this?
I note that the code for HttpOperationResponse is
namespace Microsoft.Rest
{
/// <summary>
/// Represents the base return type of all ServiceClient REST operations.
/// </summary>
public class HttpOperationResponse<T> : HttpOperationResponse, IHttpOperationResponse<T>, IHttpOperationResponse
{
/// <summary>Gets or sets the response object.</summary>
public T Body { get; set; }
}
}
Here is the structure for GetAvailableCarriersResponse
using Newtonsoft.Json;
using System.Collections.Generic;
public partial class GetAvailableCarriersResponse
{
public GetAvailableCarriersResponse()
{
CustomInit();
}
public GetAvailableCarriersResponse(IList<DeliverBy> carriers = default(IList<DeliverBy>))
{
Carriers = carriers;
CustomInit();
}
partial void CustomInit();
[JsonProperty(PropertyName = "carriers")]
public IList<DeliverBy> Carriers { get; set; }
}
[Update]
In ApiCaller ExecuteAsync the following executes.
throw;
If I catch the error at this point, it's (edited) ToString() returns
"Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError' at MyAPI.
<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>d__49.MoveNext() in
MyConnector\\generated\\MyAPI.cs:line 4018
End of stack trace from previous location where exception was thrown at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.
HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyApiService.<>c__DisplayClass39_0.<<GetAvailableCarriersByJobHeaderIdWithHttpMessagesAsync>b__0>d.MoveNext()
in MyApiService.cs:line 339
End of stack trace from previous location where exception was thrown
at System.Runtime.ExceptionServices.ExceptionDispatch
Info.Throw() at System.Runtime.CompilerServices.TaskAwaiter.
HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyServices.ApiCaller.<ExecuteAsync>d__5`1.MoveNext()
in ApiCaller.cs:line 50"
I edited some of the names in the above code to simplify and obfuscate.
[Update]
The problem seems to be to do with the getHeaders parameter to ApiCaller.ExecuteAsync
[Update]
If I examine ex1 thrown in ExecuteAsync, I can get my Api Error type using
ex1.Response.StatusCode
but how do I get the error description?
What I did to get error description is to cast it to one of the error types generated by Autorest.
if (myRawResponseObject is My422Response my422Response)
{
// Response has HTTP Status Code 422
Console.WriteLine($"Error Response Type: {my422Response.ToString()}");
}
If you OpenAPI document defines error properties for a 422 response, then you will find them on the My422Response object.

How to transfer Datagridview edit control to the next cell of the selected row when we press Enter Key

I am using this Code in dataGridView1_SelectionChanged and dataGridView1_CellEndEdit event. It works properly but when I press the enter Key firstly the edit control focus jumps on new row of selected column and then automatically goes back to the upper row and select the next cell. But I want to transfer directly to the next cell of the selected row.
For more details I attached a picture.
Please help me. I am trying a lot of logic for this.
Here my code
try
{
if (MouseButtons != 0) return;
if (celWasEndEdit != null && dataGridView1.CurrentCell != null)
{
// if we are currently in the next line of last edit cell
if (dataGridView1.CurrentCell.RowIndex == celWasEndEdit.RowIndex + 1 &&
dataGridView1.CurrentCell.ColumnIndex == celWasEndEdit.ColumnIndex)
{
int iColNew;
int iRowNew = 0;
if (celWasEndEdit.ColumnIndex >= dataGridView1.ColumnCount - 1)
{
iColNew = 0;
iRowNew = dataGridView1.CurrentCell.RowIndex;
}
//if we Edit the cell and press Enter the focus on the next cell
else
{
iColNew = celWasEndEdit.ColumnIndex + 1;
iRowNew = celWasEndEdit.RowIndex;
}
dataGridView1.CurrentCell = dataGridView1[iColNew, iRowNew];
}
}
celWasEndEdit = null;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
Create an extended control derived from the DataGridView and override the ProcessDialogKey() method.
For additional information see here.
Test sources
User control:
using System.Windows.Forms;
namespace DGNextEdit
{
public class UpdatedDataGridView : DataGridView
{
protected override bool ProcessDialogKey(Keys keyData)
{
if (keyData == Keys.Enter)
{
var col = CurrentCell.ColumnIndex;
var row = CurrentCell.RowIndex;
if (col >= ColumnCount - 1 && row < RowCount - 1)
row++;
CurrentCell = this[col < ColumnCount - 1 ? col + 1 : 0, row];
return true;
}
return base.ProcessDialogKey(keyData);
}
}
}
Test form:
using System.Windows.Forms;
namespace DGNextEdit
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}
Test form code-behind:
namespace DGNextEdit
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.dataGridView1 = new DGNextEdit.UpdatedDataGridView();
this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Column3 = new System.Windows.Forms.DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
this.SuspendLayout();
//
// dataGridView1
//
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.Column1,
this.Column2,
this.Column3});
this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill;
this.dataGridView1.Location = new System.Drawing.Point(0, 0);
this.dataGridView1.Name = "dataGridView1";
this.dataGridView1.Size = new System.Drawing.Size(465, 261);
this.dataGridView1.TabIndex = 0;
//
// Column1
//
this.Column1.HeaderText = "Column1";
this.Column1.Name = "Column1";
//
// Column2
//
this.Column2.HeaderText = "Column2";
this.Column2.Name = "Column2";
//
// Column3
//
this.Column3.HeaderText = "Column3";
this.Column3.Name = "Column3";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(465, 261);
this.Controls.Add(this.dataGridView1);
this.Name = "Form1";
this.Text = "Form1";
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
this.ResumeLayout(false);
}
#endregion
private UpdatedDataGridView dataGridView1;
private System.Windows.Forms.DataGridViewTextBoxColumn Column1;
private System.Windows.Forms.DataGridViewTextBoxColumn Column2;
private System.Windows.Forms.DataGridViewTextBoxColumn Column3;
}
}

When working with Entity Framework, is it possible to force generated entity classes to be Pascal case?

The database I'm working against has table names such as "table_name". That's fine, but I'd like to generate classes in the format "TableName" to work with in C#, Pascal style.
Is this possible?
Update: For use with EF6, see additional answer on this page.
Thanks to Alex's answer, I've now extended this code to a full working solution that solves this problem. Since it's taken me most of the day, I'm posting here to help others facing the same challenge. This includes a complete class for manipulating the edmx file (no points for pretty code here) which passes fairly verbose unit tests on varying input strings:
A few examples:
some_class > SomeClass
_some_class_ > SomeClass
some_1_said > Some1Said
I had some additional issues to deal with:
Firstly optionally not replacing underscores while still changing the string to pascal case (due to column names like "test_string" and "teststring", which both resolved to "TestString" otherwise, causing collisions).
Secondly, my code here takes an input parameter that modifies the object context class name that is generated.
Lastly, I wasn't initially sure how to get the designer file to update after modifiying the edmx, so I've included exact steps. I would recommend creating a pre build step in Visual Studio, which saves some of the remaining effort in updating from a modified database schema.
Updating an EDMX File to Reflect Database Changes
Double click the edmx file to show the design surface.
Right click the design surface and select "update model from database".
Select "yes include sensitive information in connection string".
Uncheck "save entity connection settings in App Config" (we already have these).
Select the appropriate database. In Add screen select Tables, Views etc.
Leave pluralise and foreign key options as checked. If creating the edmx file from fresh, you
are given the option to enter a model name. You can change or leave this.
Click finish.
Run the code below on the edmx file.
Depending on the state of the application, you may see errors here until the next step.
Right click the edmx file that you are updating, and select "Run custom tool" in Visual Studio. This will cuse
the designer.cs (C#) file to be updated.
Run build to check there are no compiler errors.
Run tests to ensure application is functioning correctly.
Any application issues following this should be expected according to the application
changes that have been made.
Replacing an edmx file in it's entirety.
Delete the edmx file, taking the designer file with it.
Right click the entities folder
From the create file dialogue, select ADO.NET Entity Data Model. Name it accroding to the class name you would like for your object context(IMPORTANT).
This value is referenced in the connection string, so take a look at your app config in case of issues.
In choose model contents, select generate from database.
Follow above instructions from step 3.
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace EdmxStringFormatter
{
public class Program
{
static void Main(string[] args)
{
if (args.Length < 1) return;
string filePath = args[0];
string entityName = null;
// Optionally do not replace underscores which
// helps with naming collisions with siimilarly named
// columns on some database tables.
bool replaceUnderscores = true;
// Allow for the replacement of the object context class name, which is useful
// where multiple databases have edmx files.
bool doEntityNameReplace = false;
if (args.Length > 1)
{
entityName = args[1];
doEntityNameReplace = true;
}
if (args.Length > 2)
{
replaceUnderscores = args[2] != "0";
}
if (!File.Exists(filePath))
{
StopWithMessage("Could not find specified file.");
return;
}
if (Path.GetExtension(filePath) != ".edmx")
{
StopWithMessage("This works only on EDMX files.");
return;
}
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2008/09/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2008/10/edmx";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
XElement csdl = xdoc.Descendants(XName.Get("Schema", CSDLNamespace)).First();
XElement msl = xdoc.Descendants(XName.Get("Mapping", MSLNamespace)).First();
XElement designerDiagram = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).First();
//modifications for renaming everything, not just table names:
#region CSDL2
Console.WriteLine("Modifying CSDL...");
Console.WriteLine(" - modifying entity sets...");
foreach (var entitySet in csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("EntitySet", CSDLNamespace)))
{
entitySet.Attribute("Name").Value = FormatString(entitySet.Attribute("Name").Value, replaceUnderscores);
entitySet.Attribute("EntityType").Value = FormatString(entitySet.Attribute("EntityType").Value, replaceUnderscores);
}
Console.WriteLine(" - modifying association sets...");
foreach (var associationSet in csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("AssociationSet", CSDLNamespace)))
{
foreach (var end in associationSet.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("EntitySet").Value = FormatString(end.Attribute("EntitySet").Value, replaceUnderscores);
}
}
Console.WriteLine(" - modifying entity types...");
foreach (var entityType in csdl.Elements(XName.Get("EntityType", CSDLNamespace)))
{
entityType.Attribute("Name").Value = FormatString(entityType.Attribute("Name").Value, replaceUnderscores);
foreach (var key in entityType.Elements(XName.Get("Key", CSDLNamespace)))
{
foreach (var propertyRef in key.Elements(XName.Get("PropertyRef", CSDLNamespace)))
{
propertyRef.Attribute("Name").Value = FormatString(propertyRef.Attribute("Name").Value, replaceUnderscores);
}
}
foreach (var property in entityType.Elements(XName.Get("Property", CSDLNamespace)))
{
property.Attribute("Name").Value = FormatString(property.Attribute("Name").Value, replaceUnderscores);
}
foreach (var navigationProperty in entityType.Elements(XName.Get("NavigationProperty", CSDLNamespace)))
{
navigationProperty.Attribute("Name").Value = FormatString(navigationProperty.Attribute("Name").Value, replaceUnderscores);
}
}
Console.WriteLine(" - modifying associations...");
foreach (var association in csdl.Elements(XName.Get("Association", CSDLNamespace)))
{
foreach (var end in association.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("Type").Value = FormatString(end.Attribute("Type").Value, replaceUnderscores);
}
foreach (var propref in association.Descendants(XName.Get("PropertyRef", CSDLNamespace)))
{
//propertyrefs are contained in constraints
propref.Attribute("Name").Value = FormatString(propref.Attribute("Name").Value, replaceUnderscores);
}
}
#endregion
#region MSL2
Console.WriteLine("Modifying MSL...");
Console.WriteLine(" - modifying entity set mappings...");
foreach (var entitySetMapping in msl.Element(XName.Get("EntityContainerMapping", MSLNamespace)).Elements(XName.Get("EntitySetMapping", MSLNamespace)))
{
entitySetMapping.Attribute("Name").Value = FormatString(entitySetMapping.Attribute("Name").Value, replaceUnderscores);
foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", MSLNamespace)))
{
entityTypeMapping.Attribute("TypeName").Value = FormatString(entityTypeMapping.Attribute("TypeName").Value, replaceUnderscores);
foreach
(var scalarProperty in
(entityTypeMapping.Element(XName.Get("MappingFragment", MSLNamespace))).Elements(XName.Get("ScalarProperty", MSLNamespace))
)
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value, replaceUnderscores);
}
}
}
Console.WriteLine(" - modifying association set mappings...");
foreach (var associationSetMapping in msl.Element(XName.Get("EntityContainerMapping", MSLNamespace)).Elements(XName.Get("AssociationSetMapping", MSLNamespace)))
{
foreach (var endProperty in associationSetMapping.Elements(XName.Get("EndProperty", MSLNamespace)))
{
foreach (var scalarProperty in endProperty.Elements(XName.Get("ScalarProperty", MSLNamespace)))
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value, replaceUnderscores);
}
}
}
#endregion
#region Designer
Console.WriteLine("Modifying designer content...");
foreach (var item in designerDiagram.Elements(XName.Get("EntityTypeShape", DiagramNamespace)))
{
item.Attribute("EntityType").Value = FormatString(item.Attribute("EntityType").Value, replaceUnderscores);
}
#endregion
// Optionally replace the entity name in case the default of "Entity" is not
// sufficient for your needs.
if (doEntityNameReplace)
{
Console.WriteLine("Modifying entity name refs...");
// CSDL
xdoc.Descendants(XName.Get("EntityContainer", CSDLNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
xdoc.Descendants(XName.Get("EntityContainerMapping", CSNameSpace)).First().Attribute("CdmEntityContainer").Value = entityName;
}
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
/// <summary>
/// Formats the string to pascal case, additionally checking for a period
/// in the string (in which case it skips past the period, which indicates
/// the use of namespace in a string.
/// </summary>
/// <param name="str"></param>
/// <param name="replaceUnderscores"></param>
/// <returns></returns>
private static string FormatString(string str, bool replaceUnderscores = true)
{
char[] chars = str.ToCharArray();
var sb = new StringBuilder();
bool previousCharWasUpper = false;
bool lastOperationWasToLower = false;
int startPos = 0;
if (str.Contains("."))
{
if (str.IndexOf(".") < (str.Length - 1))
{
startPos = str.IndexOf(".") + 1;
}
sb.Append(str.Substring(0, startPos));
}
for (int i = startPos; i < chars.Length; i++)
{
char character = chars[i];
if (Char.IsLetter(character))
{
if (Char.IsLower(character))
{
bool toUpper = false;
if (i > 0)
{
// Look at the previous char to see if not a letter
if (!Char.IsLetter(chars[i - 1]))
{
toUpper = true;
}
}
if (i == 0 || toUpper)
{
character = Char.ToUpper(character);
lastOperationWasToLower = false;
}
}
else // IsUpper = true
{
if (previousCharWasUpper || lastOperationWasToLower)
{
character = Char.ToLower(character);
lastOperationWasToLower = true;
}
}
previousCharWasUpper = Char.IsUpper(character);
sb.Append(character);
}
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}
}
return sb.ToString();
}
private static void StopWithMessage(string str)
{
Console.WriteLine(str);
Console.ReadLine();
throw new InvalidOperationException("Cannot continue.");
}
}
}
Edit by Chris
Adapting to Visual Studio 2013 & EF6
The code namespaces need a little tweak in order to make it work with EF6:
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2009/11/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
plus you need to take care of designerDiagram(in my case, it wasn't found, so just replaced First() with FirstOrDefault() and added simple null check).
Update based on Chris' Edit To Original Answer
This is the full C# .edmx modification code for use in an Entity Framework 6 context (original answer was for EF4).
(Additional answer as opposed to edit due to character limit per answer)
Thank you Chris for your input. I have recently revisited this as a project using this tool has been upgraded to EF6. The full code for use with EF6 is copied below.
Note that this program code now operates on two files - the .edmx and the .edmx.diagram file. Visual Studio 2013 splits the diagram out into a separate file, and this needs editing otherwise the table / entity representations will not show up on the .edmx designer surface. The file path for the designer file is accepted as the second argument.
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace EdmxStringFormatter
{
public class Program
{
static void Main(string[] args)
{
if (args.Length < 1) return;
string filePath = args[0];
string designerFilePath = null;
string entityName = null;
// Optionally do not replace underscores which
// helps with naming collisions with siimilarly named
// columns on some database tables.
bool replaceUnderscores = true;
// Allow for the replacement of the object context class name, which is useful
// where multiple databases have edmx files.
bool doEntityNameReplace = false;
if (args.Length > 1)
{
designerFilePath = args[1];
}
if (args.Length > 2)
{
entityName = args[2];
doEntityNameReplace = true;
}
if (args.Length > 3)
{
replaceUnderscores = args[3] != "0";
}
if (!File.Exists(filePath))
{
StopWithMessage("Could not find specified file.");
return;
}
if (Path.GetExtension(filePath) != ".edmx")
{
StopWithMessage("This works only on EDMX files.");
return;
}
TransformEdmx(filePath, replaceUnderscores, doEntityNameReplace, entityName);
TransformEdmxDiagram(designerFilePath, replaceUnderscores);
}
private static void TransformEdmx(string filePath, bool replaceUnderscores, bool doEntityNameReplace, string entityName)
{
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
//const string CSDLNamespace = "http://schemas.microsoft.com/ado/2008/09/edm";
//const string MSLNamespace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
//const string DiagramNamespace = "http://schemas.microsoft.com/ado/2008/10/edmx";
//const string CSNameSpace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2009/11/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
XElement csdl = xdoc.Descendants(XName.Get("Schema", CSDLNamespace)).First();
XElement msl = xdoc.Descendants(XName.Get("Mapping", MSLNamespace)).First();
//modifications for renaming everything, not just table names:
#region CSDL2
Console.WriteLine("Modifying CSDL...");
Console.WriteLine(" - modifying entity sets...");
foreach (
var entitySet in
csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("EntitySet", CSDLNamespace)))
{
entitySet.Attribute("Name").Value = FormatString(entitySet.Attribute("Name").Value, replaceUnderscores);
entitySet.Attribute("EntityType").Value = FormatString(entitySet.Attribute("EntityType").Value,
replaceUnderscores);
}
Console.WriteLine(" - modifying association sets...");
foreach (
var associationSet in
csdl.Element(XName.Get("EntityContainer", CSDLNamespace))
.Elements(XName.Get("AssociationSet", CSDLNamespace)))
{
foreach (var end in associationSet.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("EntitySet").Value = FormatString(end.Attribute("EntitySet").Value, replaceUnderscores);
}
}
Console.WriteLine(" - modifying entity types...");
foreach (var entityType in csdl.Elements(XName.Get("EntityType", CSDLNamespace)))
{
entityType.Attribute("Name").Value = FormatString(entityType.Attribute("Name").Value, replaceUnderscores);
foreach (var key in entityType.Elements(XName.Get("Key", CSDLNamespace)))
{
foreach (var propertyRef in key.Elements(XName.Get("PropertyRef", CSDLNamespace)))
{
propertyRef.Attribute("Name").Value = FormatString(propertyRef.Attribute("Name").Value,
replaceUnderscores);
}
}
foreach (var property in entityType.Elements(XName.Get("Property", CSDLNamespace)))
{
property.Attribute("Name").Value = FormatString(property.Attribute("Name").Value, replaceUnderscores);
}
foreach (var navigationProperty in entityType.Elements(XName.Get("NavigationProperty", CSDLNamespace)))
{
navigationProperty.Attribute("Name").Value = FormatString(navigationProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
Console.WriteLine(" - modifying associations...");
foreach (var association in csdl.Elements(XName.Get("Association", CSDLNamespace)))
{
foreach (var end in association.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("Type").Value = FormatString(end.Attribute("Type").Value, replaceUnderscores);
}
foreach (var propref in association.Descendants(XName.Get("PropertyRef", CSDLNamespace)))
{
//propertyrefs are contained in constraints
propref.Attribute("Name").Value = FormatString(propref.Attribute("Name").Value, replaceUnderscores);
}
}
#endregion
#region MSL2
Console.WriteLine("Modifying MSL...");
Console.WriteLine(" - modifying entity set mappings...");
foreach (
var entitySetMapping in
msl.Element(XName.Get("EntityContainerMapping", MSLNamespace))
.Elements(XName.Get("EntitySetMapping", MSLNamespace)))
{
entitySetMapping.Attribute("Name").Value = FormatString(entitySetMapping.Attribute("Name").Value,
replaceUnderscores);
foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", MSLNamespace)))
{
entityTypeMapping.Attribute("TypeName").Value = FormatString(entityTypeMapping.Attribute("TypeName").Value,
replaceUnderscores);
foreach
(var scalarProperty in
(entityTypeMapping.Element(XName.Get("MappingFragment", MSLNamespace))).Elements(
XName.Get("ScalarProperty", MSLNamespace))
)
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
}
Console.WriteLine(" - modifying association set mappings...");
foreach (
var associationSetMapping in
msl.Element(XName.Get("EntityContainerMapping", MSLNamespace))
.Elements(XName.Get("AssociationSetMapping", MSLNamespace)))
{
foreach (var endProperty in associationSetMapping.Elements(XName.Get("EndProperty", MSLNamespace)))
{
foreach (var scalarProperty in endProperty.Elements(XName.Get("ScalarProperty", MSLNamespace)))
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
}
#endregion
// Optionally replace the entity name in case the default of "Entity" is not
// sufficient for your needs.
if (doEntityNameReplace)
{
Console.WriteLine("Modifying entity name refs...");
// CSDL
xdoc.Descendants(XName.Get("EntityContainer", CSDLNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
var diagramDescendants = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).FirstOrDefault();
if (diagramDescendants != null)
{
diagramDescendants.Attribute("Name").Value = entityName;
}
// Diagram
xdoc.Descendants(XName.Get("EntityContainerMapping", CSNameSpace)).First().Attribute("CdmEntityContainer").Value
= entityName;
}
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
private static void TransformEdmxDiagram(string filePath, bool replaceUnderscores)
{
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
XElement designerDiagram = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).FirstOrDefault();
#region Designer
Console.WriteLine("Modifying designer content...");
if (designerDiagram != null)
{
foreach (var item in designerDiagram.Elements(XName.Get("EntityTypeShape", DiagramNamespace)))
{
item.Attribute("EntityType").Value = FormatString(item.Attribute("EntityType").Value, replaceUnderscores);
}
}
#endregion
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
/// <summary>
/// Formats the string to pascal case, additionally checking for a period
/// in the string (in which case it skips past the period, which indicates
/// the use of namespace in a string.
/// </summary>
/// <param name="str"></param>
/// <param name="replaceUnderscores"></param>
/// <returns></returns>
private static string FormatString(string str, bool replaceUnderscores = true)
{
char[] chars = str.ToCharArray();
var sb = new StringBuilder();
bool previousCharWasUpper = false;
bool lastOperationWasToLower = false;
int startPos = 0;
if (str.Contains("."))
{
if (str.IndexOf(".") < (str.Length - 1))
{
startPos = str.IndexOf(".") + 1;
}
sb.Append(str.Substring(0, startPos));
}
for (int i = startPos; i < chars.Length; i++)
{
char character = chars[i];
if (Char.IsLetter(character))
{
if (Char.IsLower(character))
{
bool toUpper = false;
if (i > 0)
{
// Look at the previous char to see if not a letter
if (!Char.IsLetter(chars[i - 1]))
{
toUpper = true;
}
}
if (i == 0 || toUpper)
{
character = Char.ToUpper(character);
lastOperationWasToLower = false;
}
}
else // IsUpper = true
{
if (previousCharWasUpper || lastOperationWasToLower)
{
character = Char.ToLower(character);
lastOperationWasToLower = true;
}
}
previousCharWasUpper = Char.IsUpper(character);
sb.Append(character);
}
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if (!replaceUnderscores)
{
if (character == '_')
{
sb.Append(character);
}
}
}
}
return sb.ToString();
}
private static void StopWithMessage(string str)
{
Console.WriteLine(str);
Console.ReadLine();
throw new InvalidOperationException("Cannot continue.");
}
}
}
some_class > SomeClass
didn't work for me, instead I got:
some_class > Someclass
To fix it I moved
previousCharWasUpper = false;
a few lines up. It's near the end of the old code block.
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}
Changed to:
else
{
previousCharWasUpper = false;
if (Char.IsDigit(character))
{
sb.Append(character);
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}