Is it possible to include a web.config or app.config file in the azure functions folder structure to allow assembly binding redirects?
Assuming you are using the latest (June'17) Visual Studio 2017 Function Tooling, I derived a somewhat-reasonable config-based solution for this following a snippet of code posted by npiasecki over on Issue #992.
It would be ideal if this were managed through the framework, but at least being configuration-driven you have a bit more change isolation. I suppose you could also use some pre-build steps or T4 templating that reconciles the versions of the nugets in the project (and their dependencies) before writing out this config or generating code.
So the downside..
.. becomes having to remember to update the BindingRedirects config when you update the NuGet package (this is often a problem in app.configs anyway). You may also have an issue with the config-driven solution if you need to redirect Newtonsoft.
In our case, we were using the new Azure Fluent NuGet that had a dependency on an older version of Microsoft.IdentityModel.Clients.ActiveDirectory than the version of the normal ARM management libraries which are used side-by-side in a particular Function.
local.settings.json
{
"IsEncrypted": false,
"Values": {
"BindingRedirects": "[ { \"ShortName\": \"Microsoft.IdentityModel.Clients.ActiveDirectory\", \"RedirectToVersion\": \"3.13.9.1126\", \"PublicKeyToken\": \"31bf3856ad364e35\" } ]"
}
}
FunctionUtilities.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
namespace Utilities.AzureFunctions
{
public static class FunctionUtilities
{
public class BindingRedirect
{
public string ShortName { get; set; }
public string PublicKeyToken { get; set; }
public string RedirectToVersion { get; set; }
}
public static void ConfigureBindingRedirects()
{
var config = Environment.GetEnvironmentVariable("BindingRedirects");
var redirects = JsonConvert.DeserializeObject<List<BindingRedirect>>(config);
redirects.ForEach(RedirectAssembly);
}
public static void RedirectAssembly(BindingRedirect bindingRedirect)
{
ResolveEventHandler handler = null;
handler = (sender, args) =>
{
var requestedAssembly = new AssemblyName(args.Name);
if (requestedAssembly.Name != bindingRedirect.ShortName)
{
return null;
}
var targetPublicKeyToken = new AssemblyName("x, PublicKeyToken=" + bindingRedirect.PublicKeyToken)
.GetPublicKeyToken();
requestedAssembly.Version = new Version(bindingRedirect.RedirectToVersion);
requestedAssembly.SetPublicKeyToken(targetPublicKeyToken);
requestedAssembly.CultureInfo = CultureInfo.InvariantCulture;
AppDomain.CurrentDomain.AssemblyResolve -= handler;
return Assembly.Load(requestedAssembly);
};
AppDomain.CurrentDomain.AssemblyResolve += handler;
}
}
}
Just posted a new blog post explaining how to fix the problem, have a look:
https://codopia.wordpress.com/2017/07/21/how-to-fix-the-assembly-binding-redirect-problem-in-azure-functions/
It's actually a tweaked version of the JoeBrockhaus's code, that works well even for Newtonsoft.Json.dll
Inspired by the accepted answer I figured I'd do a more generic one which takes into account upgrades as well.
It fetches all assemblies, orders them descending to get the newest version on top, then returns the newest version on resolve. I call this in a static constructor myself.
public static void RedirectAssembly()
{
var list = AppDomain.CurrentDomain.GetAssemblies()
.Select(a => a.GetName())
.OrderByDescending(a => a.Name)
.ThenByDescending(a => a.Version)
.Select(a => a.FullName)
.ToList();
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var requestedAssembly = new AssemblyName(args.Name);
foreach (string asmName in list)
{
if (asmName.StartsWith(requestedAssembly.Name + ","))
{
return Assembly.Load(asmName);
}
}
return null;
};
}
It is not directly possible today, but we are thinking about ways to achieve this. Can you please open an issue on https://github.com/Azure/azure-webjobs-sdk-script/issues to make sure your specific scenario is looked at? Thanks!
First SO post, so apologies if formatting's a bit off.
We've hit this issue a couple of times and managed to find a better way of getting the required redirects by forcing MSBUILD to generate a binding redirects file and then parsing that to be used with the previously suggested answer.
Modify the project settings and add in a couple of targets:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
...
<AutoGenerateBindingRedirects>True</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
...
</PropertyGroup>
</Project>
These classes apply the binding redirects using the same idea that was posted earlier (link) except instead of using the host.json file it reads from the generated binding redirects file. The filename to use is from reflection using the ExecutingAssembly.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
public static class AssemblyBindingRedirectHelper
{
private static FunctionRedirectBindings _redirects;
public static void ConfigureBindingRedirects()
{
// Only load the binding redirects once
if (_redirects != null)
return;
_redirects = new FunctionRedirectBindings();
foreach (var redirect in _redirects.BindingRedirects)
{
RedirectAssembly(redirect);
}
}
public static void RedirectAssembly(BindingRedirect bindingRedirect)
{
ResolveEventHandler handler = null;
handler = (sender, args) =>
{
var requestedAssembly = new AssemblyName(args.Name);
if (requestedAssembly.Name != bindingRedirect.ShortName)
{
return null;
}
var targetPublicKeyToken = new AssemblyName("x, PublicKeyToken=" + bindingRedirect.PublicKeyToken).GetPublicKeyToken();
requestedAssembly.Version = new Version(bindingRedirect.RedirectToVersion);
requestedAssembly.SetPublicKeyToken(targetPublicKeyToken);
requestedAssembly.CultureInfo = CultureInfo.InvariantCulture;
AppDomain.CurrentDomain.AssemblyResolve -= handler;
return Assembly.Load(requestedAssembly);
};
AppDomain.CurrentDomain.AssemblyResolve += handler;
}
}
public class FunctionRedirectBindings
{
public HashSet<BindingRedirect> BindingRedirects { get; } = new HashSet<BindingRedirect>();
public FunctionRedirectBindings()
{
var assm = Assembly.GetExecutingAssembly();
var bindingRedirectFileName = $"{assm.GetName().Name}.dll.config";
var dir = Path.Combine(Environment.GetEnvironmentVariable("HOME"), #"site\wwwroot");
var fullPath = Path.Combine(dir, bindingRedirectFileName);
if(!File.Exists(fullPath))
throw new ArgumentException($"Could not find binding redirect file. Path:{fullPath}");
var xml = ReadFile<configuration>(fullPath);
TransformData(xml);
}
private T ReadFile<T>(string path)
{
using (StreamReader reader = new StreamReader(path))
{
var serializer = new XmlSerializer(typeof(T));
var obj = (T)serializer.Deserialize(reader);
reader.Close();
return obj;
}
}
private void TransformData(configuration xml)
{
foreach(var item in xml.runtime)
{
var br = new BindingRedirect
{
ShortName = item.dependentAssembly.assemblyIdentity.name,
PublicKeyToken = item.dependentAssembly.assemblyIdentity.publicKeyToken,
RedirectToVersion = item.dependentAssembly.bindingRedirect.newVersion
};
BindingRedirects.Add(br);
}
}
}
public class BindingRedirect
{
public string ShortName { get; set; }
public string PublicKeyToken { get; set; }
public string RedirectToVersion { get; set; }
}
Xml classes to use to deserialise the generated binding redirect file into something easier to use. These were generated from the binding redirects file by using VS2017 "paste special -> paste xml as classes" so feel free to roll your own if needed.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
// NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0.
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class configuration
{
[System.Xml.Serialization.XmlArrayItemAttribute("assemblyBinding", Namespace = "urn:schemas-microsoft-com:asm.v1", IsNullable = false)]
public assemblyBinding[] runtime { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "urn:schemas-microsoft-com:asm.v1", IsNullable = false)]
public partial class assemblyBinding
{
public assemblyBindingDependentAssembly dependentAssembly { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")]
public partial class assemblyBindingDependentAssembly
{
public assemblyBindingDependentAssemblyAssemblyIdentity assemblyIdentity { get; set; }
public assemblyBindingDependentAssemblyBindingRedirect bindingRedirect { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")]
public partial class assemblyBindingDependentAssemblyAssemblyIdentity
{
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string publicKeyToken { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string culture { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")]
public partial class assemblyBindingDependentAssemblyBindingRedirect
{
[System.Xml.Serialization.XmlAttributeAttribute()]
public string oldVersion { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string newVersion { get; set; }
}
Here's an alternate solution for when you want the exact version of a particular assembly. With this code, you can easily deploy the assemblies that are missing:
public static class AssemblyHelper
{
//--------------------------------------------------------------------------------
/// <summary>
/// Redirection hack because Azure functions don't support it.
/// How to use:
/// If you get an error that a certain version of a dll can't be found:
/// 1) deploy that particular dll in any project subfolder
/// 2) In your azure function static constructor, Call
/// AssemblyHelper.IncludeSupplementalDllsWhenBinding()
///
/// This will hook the binding calls and look for a matching dll anywhere
/// in the $HOME folder tree.
/// </summary>
//--------------------------------------------------------------------------------
public static void IncludeSupplementalDllsWhenBinding()
{
var searching = false;
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
// This prevents a stack overflow
if(searching) return null;
var requestedAssembly = new AssemblyName(args.Name);
searching = true;
Assembly foundAssembly = null;
try
{
foundAssembly = Assembly.Load(requestedAssembly);
}
catch(Exception e)
{
Debug.WriteLine($"Could not load assembly: {args.Name} because {e.Message}");
}
searching = false;
if(foundAssembly == null)
{
var home = Environment.GetEnvironmentVariable("HOME") ?? ".";
var possibleFiles = Directory.GetFiles(home, requestedAssembly.Name + ".dll", SearchOption.AllDirectories);
foreach (var file in possibleFiles)
{
var possibleAssembly = AssemblyName.GetAssemblyName(file);
if (possibleAssembly.Version == requestedAssembly.Version)
{
foundAssembly = Assembly.Load(possibleAssembly);
break;
}
}
}
return foundAssembly;
};
}
}
Related
I am having a bad time trying to consume an XML service.
This is my code to consume the service (it is a public service so you can consume it):
using( var client = new HttpClient() )
{
client.DefaultRequestHeaders
.Accept
.Add( new MediaTypeWithQualityHeaderValue( "text/xml" ) );
var request = new HttpRequestMessage( HttpMethod.Get, "https://gee.bccr.fi.cr/Indicadores/Suscripciones/WS/wsindicadoreseconomicos.asmx/ObtenerIndicadoresEconomicosXML?Indicador=317&FechaInicio=20%2F04%2F2022&FechaFinal=20%2F04%2F2022&Nombre=Jos%C3%A9+Miguel+Torres+G%C3%B3mez&SubNiveles=N&CorreoElectronico=josetorres%40outlook.com&Token=2LOEU2EM8O" );
var returnedXml = client.SendAsync( request ).Result.Content.ReadAsStringAsync().Result;
Console.WriteLine( returnedXml );
}
The response I get from there is the following, adding the console screenshot due to it returns special scape chars:
<string xmlns="http://ws.sdde.bccr.fi.cr">
<Datos_de_INGC011_CAT_INDICADORECONOMIC>
<INGC011_CAT_INDICADORECONOMIC>
<COD_INDICADORINTERNO>317</COD_INDICADORINTERNO>
<DES_FECHA>2022-04-20T00:00:00-06:00</DES_FECHA>
<NUM_VALOR>650.10000000</NUM_VALOR>
</INGC011_CAT_INDICADORECONOMIC>
</Datos_de_INGC011_CAT_INDICADORECONOMIC>
</string>
This is the class I use to try to deserialize:
public class Datos_de_INGC011_CAT_INDICADORECONOMIC
{
public INGC011_CAT_INDICADORECONOMIC INGC011_CAT_INDICADORECONOMIC { get; set; }
}
public class INGC011_CAT_INDICADORECONOMIC
{
public int COD_INDICADORINTERNO { get; set; }
public DateTime DES_FECHA { get; set; }
public double NUM_VALOR { get; set; }
}
But when I try to Deserialize the string I get the error: There is an error in XML document. Data at the root level is invalid. And due to I don't use to work with XML data I am stocked here.
The problem is that the "outer" XML is a wrapper for the "inner" one, which is encoded as text node for the first. In other words, that's NOT a single document, rather two different documents nested.
My guess is about the outer one, which looks like a descriptor for what itself contains.
That being said, you can extract what you need in a two-step job:
using System;
using System.Xml.Linq;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using( var client = new HttpClient() )
{
client.DefaultRequestHeaders
.Accept
.Add( new MediaTypeWithQualityHeaderValue( "text/xml" ) );
var request = new HttpRequestMessage( HttpMethod.Get, " ... " );
var returnedXml = client.SendAsync( request ).Result.Content.ReadAsStringAsync().Result;
Console.WriteLine( returnedXml );
//create a XML DOM from the returned payload
XDocument xdoc_outer = XDocument.Parse(returnedXml );
//extract the inner document as text (being a true-text node)
string inner_text = (string)xdoc_outer.Root;
//deserialize the text straight to a POCO shaped accordingly
var serializer = new XmlSerializer(typeof(Datos_de_INGC011_CAT_INDICADORECONOMIC));
using (var reader = XmlReader.Create(new StringReader(inner_text)))
{
//'result' contains the deserialized POCO, unless something went wrong!
var result = (Datos_de_INGC011_CAT_INDICADORECONOMIC)serializer.Deserialize(reader);
}
}
Probably there are better yet compact ways to achieve the same result, but I think that's pretty clear.
As final note, I reccomend to use async/await than accessing the data through the Result property.
Looks like the problem you are having is because of the outer element you have in your xml (string)
You can use one of the ways mentioned here (https://stackoverflow.com/a/19613934/3377344) in this post to create your class.
I used the paste special method shown there and here is the class I was able to generate:
// NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0.
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://ws.sdde.bccr.fi.cr")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://ws.sdde.bccr.fi.cr", IsNullable = false)]
public partial class #string
{
private stringDatos_de_INGC011_CAT_INDICADORECONOMIC datos_de_INGC011_CAT_INDICADORECONOMICField;
/// <remarks/>
public stringDatos_de_INGC011_CAT_INDICADORECONOMIC Datos_de_INGC011_CAT_INDICADORECONOMIC
{
get
{
return this.datos_de_INGC011_CAT_INDICADORECONOMICField;
}
set
{
this.datos_de_INGC011_CAT_INDICADORECONOMICField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://ws.sdde.bccr.fi.cr")]
public partial class stringDatos_de_INGC011_CAT_INDICADORECONOMIC
{
private stringDatos_de_INGC011_CAT_INDICADORECONOMICINGC011_CAT_INDICADORECONOMIC iNGC011_CAT_INDICADORECONOMICField;
/// <remarks/>
public stringDatos_de_INGC011_CAT_INDICADORECONOMICINGC011_CAT_INDICADORECONOMIC INGC011_CAT_INDICADORECONOMIC
{
get
{
return this.iNGC011_CAT_INDICADORECONOMICField;
}
set
{
this.iNGC011_CAT_INDICADORECONOMICField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://ws.sdde.bccr.fi.cr")]
public partial class stringDatos_de_INGC011_CAT_INDICADORECONOMICINGC011_CAT_INDICADORECONOMIC
{
private ushort cOD_INDICADORINTERNOField;
private System.DateTime dES_FECHAField;
private decimal nUM_VALORField;
/// <remarks/>
public ushort COD_INDICADORINTERNO
{
get
{
return this.cOD_INDICADORINTERNOField;
}
set
{
this.cOD_INDICADORINTERNOField = value;
}
}
/// <remarks/>
public System.DateTime DES_FECHA
{
get
{
return this.dES_FECHAField;
}
set
{
this.dES_FECHAField = value;
}
}
/// <remarks/>
public decimal NUM_VALOR
{
get
{
return this.nUM_VALORField;
}
set
{
this.nUM_VALORField = value;
}
}
}
Once you have that class ready, you should be able to get the deserialize to work.
I used something like this:
class Program
{
static void Main(string[] args)
{
string myxml = $#"
<string xmlns = ""http://ws.sdde.bccr.fi.cr"">
<Datos_de_INGC011_CAT_INDICADORECONOMIC>
<INGC011_CAT_INDICADORECONOMIC>
<COD_INDICADORINTERNO>317</COD_INDICADORINTERNO>
<DES_FECHA>2022-04-20T00:00:00-06:00</DES_FECHA>
<NUM_VALOR>650.10000000</NUM_VALOR>
</INGC011_CAT_INDICADORECONOMIC>
</Datos_de_INGC011_CAT_INDICADORECONOMIC>
</string>
";
var res = XMLToObject(myxml, typeof(#string));
}
public static object XMLToObject(string xml, Type type, string xmlnamespace = "http://ws.sdde.bccr.fi.cr")
{
object result = null;
using (var stream = new StringReader(xml))
{
using(var reader = new XmlTextReader(stream))
{
var xmlRootAttr = new XmlRootAttribute();
xmlRootAttr.Namespace = xmlnamespace;
xmlRootAttr.ElementName = type.Name;
var serializer = new XmlSerializer(type, xmlRootAttr);
result = serializer.Deserialize(reader);
}
}
return result;
}
}
I'm trying to compile the following code using the .NET 4.5 Framework:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Discussion.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
class CodeReview
{
public List<CodeReviewComment> GetCodeReviewComments(int workItemId)
{
List<CodeReviewComment> comments = new List<CodeReviewComment>();
Uri tfsuri = new Uri(MYURL);
TeamFoundationDiscussionService service = new TeamFoundationDiscussionService();
service.Initialize(new Microsoft.TeamFoundation.Client.TfsTeamProjectCollection(tfsuri));
IDiscussionManager discussionManager = service.CreateDiscussionManager();
IAsyncResult result = discussionManager.BeginQueryByCodeReviewRequest(workItemId, QueryStoreOptions.ServerAndLocal, new AsyncCallback(CallCompletedCallback), null);
var output = discussionManager.EndQueryByCodeReviewRequest(result);
foreach (DiscussionThread thread in output)
{
if (thread.RootComment != null)
{
CodeReviewComment comment = new CodeReviewComment();
comment.Author = thread.RootComment.Author.DisplayName;
comment.Comment = thread.RootComment.Content;
comment.PublishDate = thread.RootComment.PublishedDate.ToShortDateString();
comment.ItemName = thread.ItemPath;
comments.Add(comment);
}
}
return comments;
}
static void CallCompletedCallback(IAsyncResult result)
{
// Handle error conditions here
}
public class CodeReviewComment
{
public string Author { get; set; }
public string Comment { get; set; }
public string PublishDate { get; set; }
public string ItemName { get; set; }
}
}
The Visual Studio compiler is complaining that "the type or namespace 'Uri' could not be found" and when I expand the System namespace in the Object Browser it doesnt list any of the Uri classes. I've tried a few other versions of .NET and still have the same problem although it does appear if I select the .NET 4.0 Client Profile but then none of the TeamFoundation classes are present in the Microsoft namespace.
I solved my own problem but I don't understand why it worked. In desperation I created another new Project using .NET 4.5 and copied and pasted the code over. It compiled and Uri shows up as a Class in the Object Browser. I have no idea what the difference between the 2 projects is.
I got a issue with XML, and write some information on a XML-file.
I got several xsd-files describing my XML. I created one big .cs-file with "xsd/c testfile1.xsd testFile2.xsd..." etc. And everything went nice and looks good.
But if I take one created class i.e. testfile1.xsd, it looks like
"<xs:complexType name="Header">" and inside that one there is this some ordinary xs:element and stuff, but also this: "<xs:attribute name="version" default="1.0.0.0"/>". This is translated to:
"public Header() {
this.versionField = "1.0.0.0";}"
in the generated class 'Header'. And it got as well this field: private string versionField;
. (There is of course a couple of other private fields as well, but those works good.). So I create instances of all classes, fill them with data and write it as an XML-file with this:
- XmlSerializer XmlSerRoot = new XmlSerializer(typeof(RootInformation))
(the root of my xml!)
- StringWriterWithEncoding strWriter = new StringWriterWithEncoding(Encoding.GetEncoding("iso-8859-1"));
- XmlDocument documentInXML = new XmlDocument();
- XmlSerRoot.Serialize(strWriter, rootInformation); (Here is the XML, filled with values, and the Header.version got the value 1.0.0.0)
- string XmlString;
- XmlString = strWriter.ToString(); (Here I can look at the created xml when debugging in VS and now the version-information is gone)
- documentInXML.LoadXml(XmlString);
- documentInXML.Save(myPath);
But when I look at the xml-file this <Header> is not have anything like version. I want i to look like: <Header version="1.0.0.0">
I even tried to do it in code like:
Header header = new Header();
header.version = header.version;
and
with header.version = "1.0.0.0";
But still it's no version-text in the tag. All other tags got their value. Just this <Header> loose this extra information.
Does someone got a tip? There is a lot of places I need this to work. Everything else is just working fine.
Regards, /E
Here is an piece of example-code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
namespace TestAppXML
{
class Program
{
static void Main(string[] args)
{
RootInfo rootInfo = new RootInfo();
rootInfo.RootText = "This is the Root!";
Header header = new Header();
header.TestHeader = "This is HeaderText!";
rootInfo.Header = header;
XmlSerializer XmlSerRoot = new XmlSerializer(typeof(RootInfo));
StringWriterWithEncoding strWriter = new StringWriterWithEncoding(Encoding.GetEncoding("iso-8859-1"));
XmlDocument documentInXML = new XmlDocument();
XmlSerRoot.Serialize(strWriter, rootInfo);
string XmlString;
XmlString = strWriter.ToString();
documentInXML.LoadXml(XmlString);
strWriter.Close();
documentInXML.Save(#"C:\TestXml.xml");
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://acme.com")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://acme.com", IsNullable = false)]
public partial class RootInfo
{
private Header headerField;
private string rootTextField;
public Header Header
{
get { return this.headerField; }
set { this.headerField = value; }
}
[System.Xml.Serialization.XmlElementAttribute(DataType = "normalizedString")]
public string RootText
{
get { return this.rootTextField; }
set { this.rootTextField = value; }
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://acme.com")]
public partial class Header
{
private string testHeaderField;
private string versionField;
public Header()
{
this.versionField = "1.0.0.0";
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(DataType = "normalizedString")]
public string TestHeader
{
get { return this.testHeaderField; }
set { this.testHeaderField = value; }
}
[System.Xml.Serialization.XmlAttributeAttribute()]
[System.ComponentModel.DefaultValueAttribute("1.0.0.0")]
public string version
{
get { return this.versionField; }
set { this.versionField = value; }
}
}
class StringWriterWithEncoding : StringWriter
{
private Encoding MyEncoding;
public StringWriterWithEncoding(Encoding encoding)
: base()
{MyEncoding = encoding;}
public override Encoding Encoding
{
get{return MyEncoding;}
}
}
}
Nevermind, I think I knov the issue. It's because this xsd.exe creates 'DefaultValueAttribute' for those fields. That prevent this field to be in the xml. Maybe some xsd-switch could have done that, I dunno...
I'm developing an Azure Worker Role that will need to periodically perform actions based on work pulled from a WCF service. Due to the nature of Azure deployments, a decision was made to implement a plugin framework in Azure to allow quick updates to our product while minimizing the downtime by removing the need for full azure deployments, as well as supporting multiple versions of various assemblies.
A plugin is downloaded from Cloud storage and loaded into a class that contains all relevant info needed to load the assembly. (See PluginAssemblyInfo below)
In other development work I've developed the below ReflectionHelper class. This class works in other applications and I've verified it works in a console application.
However when running in the Azure emulator I get the following error: (I've added a comment on the line the exception is being thrown on.)
Type is not resolved for member 'MyCompanyName.ReflectionHelper,MyCompanyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
The ReflectionHelper class is added as a source file to the same assembly that is calling it. (I did edit the name of the assembly/namespaces for anonymity reasons)
Given the knowledge that this class works in other cases, my only guess is that it may be a trust issue, but I've not found much info online relating to this. Any help would be greatly appreciated.
This is the code for the ReflectionHelper class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Serialization;
using System.Web;
namespace MyCompanyName
{
[Serializable]
public class ReflectionHelper
{
public AppDomain appDomain { get; set; }
public byte[] PluginBytes { get; set; }
public string PluginClassName { get; set; }
public string PluginAssemblyName { get; set; }
public Dictionary<string, byte[]> AssemblyArchive { get; set; }
public object GetInstance(AppDomain NewDomain, byte[] PluginBytes, string PluginClassName, string PluginAssemblyName, Dictionary<string, byte[]> AssemblyArchive)
{
try
{
this.appDomain = NewDomain;
this.PluginBytes = PluginBytes;
this.PluginClassName = PluginClassName;
this.AssemblyArchive = AssemblyArchive;
this.PluginAssemblyName = PluginAssemblyName;
//this is the line that throws the serializationexception
appDomain.AssemblyResolve += new ResolveEventHandler((sender, args) =>
{
AssemblyName x = new AssemblyName(args.Name);
string Name = x.Name;
if (System.IO.Path.GetExtension(x.Name) != ".dll")
Name += ".dll";
Assembly Ret = appDomain.Load(this.AssemblyArchive[Name]);
return Ret;
});
appDomain.DoCallBack(LoaderCallBack);
return appDomain.GetData("Plugin");
}
catch (Exception ex)
{
throw ex;
}
}
public void LoaderCallBack()
{
ObjectHandle PluginObject = appDomain.CreateInstance(string.Format(this.PluginAssemblyName), PluginClassName);
appDomain.SetData("Plugin", PluginObject.Unwrap());
}
}
}
This is the code snippet that invokes it
AppDomain currentDomain = AppDomain.CurrentDomain;
AppDomainSetup domainSetup = new AppDomainSetup() { };
AppDomain BrokerJobDomain = AppDomain.CreateDomain("WorkerPluginDomain" + Guid.NewGuid().ToString());//, null, new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase });
ReflectionHelper helper = new ReflectionHelper();
object Instance = helper.GetInstance(BrokerJobDomain, pluginAssembly.PluginBytes, pluginAssembly.ClassName, pluginAssembly.AssemblyName, pluginAssembly.AssemblyArchive);
IWorkPlugin executor = (IWorkPlugin)Instance;
This is the assembly info class that is populated when the plugin is downloaded from cloud storage
public class PluginAssemblyInfo
{
public string ClassName { get; set; }
public byte[] PluginBytes { get; set; }
public Dictionary<string, byte[]> AssemblyArchive { get; set; }
public string AssemblyName { get; set; }
public DateTime LoadDate { get; set; }
}
The issue seems to be related to the RelativeSearchPath not being set up on the child domain. Ensuring the child domain had the same setup credentials seemed to fix this.
AppDomain currentDomain = AppDomain.CurrentDomain;
AppDomain BrokerJobDomain = AppDomain.CreateDomain("WorkerPluginDomain" + Guid.NewGuid().ToString(), null, AppDomain.CurrentDomain.SetupInformation );
Tried the code in Visual Studio on Windows to be sure.
Mono framework does not appear to honor the EmitDefaultValue argument of the DataMemberAttribute. Using the following code:
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
namespace MyApp
{
class MainClass
{
public static void Main (string[] args)
{
Cereal specialK = new Cereal();
specialK.TheValue="This is a what?";
var ser = new DataContractJsonSerializer(typeof(Cereal));
MemoryStream stm = new MemoryStream();
ser.WriteObject(stm, specialK);
string json = System.Text.Encoding.UTF8.GetString(stm.ToArray());
Console.WriteLine(json);
Console.ReadLine();
}
}
[DataContract]
class Cereal
{
[DataMember(Name="set_on_serialize")]
private string _setOnSerialize = string.Empty;
[DataMember(Name = "default_export", EmitDefaultValue = false)]
private string _default_null;
public Cereal() { }
[DataMember(Name = "out_value")]
public string TheValue
{
get;
set;
}
[OnSerializing]
void OnSerializing(StreamingContext content)
{
this._setOnSerialize = "A brick!";
}
}
}
The output in Mono results in:
{"default_export":null,"out_value":"This is a what?","set_on_serialize":""}
The default_export property is being exported as null but should not be output as it is the default value of type string.
Correct output from VS on Windows is:
{"out_value":"This is a what?","set_on_serialize":"A brick!"}
Is this a bug in Mono or am I missing something?
Apparently this feature is not yet implemented in mono. See the FIXME comment in the mono source code (line 197).