Total newby question here, I've been struggling with it for hours!
I'm trying to understand how to actually use, and create JSON data. I've been Googling all afternoon and trying to understand what I fine here http://james.newtonking.com/projects/json/help/ having downloaded the Newtonsoft DLLs.
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
using (JsonWriter jsonWriter = new JsonTextWriter(sw))
{
jsonWriter.Formatting = Formatting.Indented;
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("CPU");
jsonWriter.WriteValue("Intel");
jsonWriter.WritePropertyName("PSU");
jsonWriter.WriteValue("500W");
jsonWriter.WritePropertyName("Drives");
jsonWriter.WriteStartArray();
jsonWriter.WriteValue("DVD read/writer");
jsonWriter.WriteComment("(broken)");
jsonWriter.WriteValue("500 gigabyte hard drive");
jsonWriter.WriteValue("200 gigabype hard drive");
jsonWriter.WriteEnd();
jsonWriter.WriteEndObject();
}
Should create something that looks like:
{
"CPU": "Intel",
"PSU": "500W",
"Drives": [
"DVD read/writer"
/*(broken)*/,
"500 gigabyte hard drive",
"200 gigabype hard drive" ]
}
and I am sure it does... but how do I view it? How do I turn that into an object that the browser can output.
It seems to me that the first stage I need to resolve is "how to create" JSON files/strings, next stage will be how to actually use them. If it helps answer the question, what I'm aiming for initially is to be able to use AJAX Autocomplete from a search page generated from my MySQL database, I was hoping I could write a simple SQL query and have that returned using something similar to the above, but I'm clearly going about it all wrong!
BTW, the example above is in C#, I have successfully converted the process to VB, as that's what I am using, but any responses would be much appreciated as VB examples!
I came across this post about two years after it was posted, but I had the exact same question and noticed that the question wasn't really answered. To answer OP's question, this will get you the JSON string in his example.
sb.toString()
The upshot is that you need to get the JSON string back to the browser. You can either place it in a javascript variable (be sure to clean up line enders and single quotes if you do this) or pass it back as the result of an ajax query.
We actually use the built-in Javascript serializer since it has support both on the server and the client side and is quite easy to use. Assuming that you have an existing object, this code goes on the server side:
''' <summary>
''' This method safely serializes an object for JSON by removing all of the special characters (i.e. CRLFs, quotes, etc)
''' </summary>
''' <param name="oObject"></param>
''' <param name="fForScript">Set this to true when the JSON will be embedded directly in web page (as opposed to being passed through an ajax call)</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function SerializeObjectForJSON(ByVal oObject As Object, Optional ByVal fForScript As Boolean = False) As String
If oObject IsNot Nothing Then
Dim sValue As String
sValue = (New System.Web.Script.Serialization.JavaScriptSerializer).Serialize(oObject)
If fForScript Then
' If this serialized object is being placed directly on the page, then we need to ensure that its CRLFs are not interpreted literlally (i.e. as the actual JS values)
' If we don't do this, the script will not deserialize correctly if there are any embedded crlfs.
sValue = sValue.Replace("\r\n", "\\r\\n")
' Fix quote marks
Return CleanString(sValue)
Else
Return sValue
End If
Else
Return String.Empty
End If
End Function
On the client side, deserialization is trivial:
// The result should be a json-serialized record
oRecord = Sys.Serialization.JavaScriptSerializer.deserialize(result.value);
Once you have deserialized the object, you can use its properties directly in javascript:
alert('CPU = ' + oRecord.CPU);
In terms of generating the JSON try
public class HardwareInfo
{
[JsonProperty(PropertyName = "CPU")]
public string Cpu { get; set; }
[JsonProperty(PropertyName = "PSU")]
public string Psu { get; set; }
[JsonProperty]
public ICollection<string> Drives { get; set; }
}
public string SerializeHardwareInfo()
{
var info = new HardwareInfo
{
Cpu = "Intel",
Psu = "500W",
Drives = new List<string> { "DVD read/writer", "500 gigabyte hard drive", "200 gigabype hard drive" }
};
var json = JsonConvert.SerializeObject(info, Formatting.Indented);
// {
// "CPU": "Intel",
// "PSU": "500W",
// "Drives": [
// "DVD read/writer",
// "500 gigabyte hard drive",
// "200 gigabype hard drive"
// ]
// }
return json;
}
The formatting argument is optional. Best of luck.
Related
I need to read large csv files and to insert them into SQL, my idea was to use streamreader and read the file line by line because if I store the content in a variable, program crashes. So thats what i thought:
using FileStream fs
Dim list as String
Try
Dim MyFile as String = ("C:\\Test.txt")
Using fs as FileStream = File.Open(MyFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None) 'file is opened in a protected mode
firstline= fs.ReadLine 'treat the firstline as columnname
rest = fs.ReadLine 'the rest as rest
Do While (Not rest Is Nothing) 'read the complete file
list.Add(rest)
Filestream.TextFieldType = FileIO.FieldType.Delimited
Filestream.SetDelimiters(";")
Loop
End Using
Catch
ResultBlock.Text = "File not readable"
End Try
i wrote list.Add(rest) which is actually a bad idea because the content is stored in a variable then, but i need to read and insert line for line in a sql database which seems to be pretty complicated though, does anyone has an idea how i could handle that?
If you can't read the file into memory because it's too big then what you need is some sort of buffer that holds the records in memory and writes to the database when the list gets to a certain size.
If you really want to keep it manageable then the reader, the writer, and the buffer should all be completely separate from each other. That sounds like more work because it's more classes, but it's actually simpler because each class only does one thing.
I would create a class that represents the item that you're reading from the file, with properties for each record. Like if each line in the file represents a person with a name and employee number, create a class like
public class Person
{
public string FirstName {get;set;}
public string LastName {get;set;}
public string EmployeeNumber {get;set;}
}
You'll need a buffer. The job of the buffer is to have items put into it, and flush to a writer when it reaches its maximum size. Perhaps like this:
public interface IBuffer<T>
{
void AddItem(T item);
}
public interface IWriter<T>
{
void Write(IEnumerable<T> items);
}
public class WriterBuffer<T> : IBuffer<T>
{
private readonly IWriter<T> _writer;
private readonly int _maxSize;
private readonly List<T> _buffer;
public WriterBuffer(IWriter<T> writer, int maxSize)
{
_writer = writer;
_maxSize - maxSize;
}
public void AddItem(T item)
{
_buffer.Add(item);
if(_buffer.Count >= _maxSize)
{
_writer.Write(_buffer);
_buffer.Clear();
}
}
}
Then, your reader class doesn't know about the writer at all. All it knows is that it writes to the buffer.
public class PersonFileReader
{
private readonly string _filename;
private readonly IBuffer<Person> _buffer;
public PersonFileReader(string filename, IBuffer<Person> buffer)
{
_filename = filename;
_buffer = buffer;
}
public void ReadFile()
{
//Reads from file.
//Creates a new Person for each record
//Calls _buffer.Add(person) for each Person.
}
}
public class PersonSqlWriter : IWriter<Person>
{
private readonly string _connectionString;
public PersonSqlWriter(string connectionString)
{
_connectionString = connectionString;
}
public void Write(IEnumerable<Person> items)
{
//Writes the list of items to the database
//using _connectionString;
}
}
The result is that each of these classes does only one thing. You can use them separately from the others and test them separately from the others. That applies the Single Responsibility Principle. No one class is too complicated because each one has only one responsibility. It also applies the Dependency Inversion principle. The reader doesn't know what the buffer does. It just depends on the interface. The buffer doesn't know what the writer does. And the writer doesn't care where the data comes from.
Now the complexity is in creating the objects. You need a file name, a connection string, and a maximum buffer size. That means something like
var filename = "your file name";
var maxBufferSize = 50;
var connectionString = "your connection string"
var reader = new PersonFileReader(
filename,
new WriterBuffer<Person>(
new PersonSqlWriter(connectionString),
maxBufferSize));
Your classes are simpler, but wiring them all together has gotten a little more complicated. That's where dependency injection comes in. It manages this for you. I won't go into that yet because it might be information overload. But if you mention what sort of application this is - web, WCF service, etc., then I might be able to provide a concrete example of how a dependency injection container like Windsor, Autofac, or Unity can manage this for you.
This was all new to me several years ago. At first it just looked like more code. But it actually makes it easier to write small, simple classes, which in turn makes building complex applications much easier.
Have a look at below links:
BulkCopy How can I insert 10 million records in the shortest time possible?
This one contains code samples: http://www.sqlteam.com/article/use-sqlbulkcopy-to-quickly-load-data-from-your-client-to-sql-server
You can also use Import Wizard (https://msdn.microsoft.com/en-us/library/ms141209.aspx?f=255&MSPPError=-2147217396).
As part of a SSIS (2005) package I am calling a RESTful API through a script component (VB.NET). I am getting the records back OK. The problem I have though is processing the format it comes back in so I can output it (split into the separate output columns) within the data flow and write into a SQL table. Don't know where to start - anyone got any ideas? I have no control over the API so am stuck with this format.
Here is the schema as per the API documentation:
{StatusMessage : <string>, Unit : <string> [ { VehicleID : <int>, RegistrationNumber : <string>, DistanceTravelled : <double>} ] }
And here is a sample of the data returned (it comes back as a single line of text):
{"VehicleDistance":
[
{
"VehicleId":508767,
"RegistrationNumber":"BJ63 NYO",
"DistanceTravelled":0.09322560578584671
},
{
"VehicleId":508788,
"RegistrationNumber":"BJ63 NYL",
"DistanceTravelled":6.1591048240661621
},
{
"VehicleId":508977,
"RegistrationNumber":"PE12 LLC",
"DistanceTravelled":60.975761413574219
},
{
"VehicleId":510092,
"RegistrationNumber":"BJ64 FCY",
"DistanceTravelled":14.369173049926758
},
{
"VehicleId":510456,
"RegistrationNumber":"BJ63 NYY",
"DistanceTravelled":4.04599142074585
},
{
"VehicleId":513574,
"RegistrationNumber":"BL64 AEM",
"DistanceTravelled":302.150390625
}
],
"StatusMessage":null,
"Unit":"imperial",
"HttpStatus":200
}
This is Javscript Object Notation AKA JSON and you need to deserialise it. The problem is that using SSIS is tricky with third party tools that are popular (and fast) but VB.Net actually has a built in class to serialise and deserialise JSON called JavaScriptSerializer
First add a Reference to your project called System.Web.Extensions and then you can use the JavaScriptSerializer to deserialise your JSON.
I've put your JSON in a file for easier handling so first I have to...
Dim sJSON As String = ""
Using swReadFile As New System.IO.StreamReader("E:\JSON.txt")
sJSON = swReadFile.ReadToEnd()
End Using
The rest is the pertinent bit, so first add 2 Imports...
Imports System.Collections.Generic
Imports System.Web.Script.Serialization
Then for example we can...
Dim lvSerializer As JavaScriptSerializer = New JavaScriptSerializer()
lvSerializer.MaxJsonLength = 2147483644
Dim dictParsedJSONPairs As Dictionary(Of String, Object) = lvSerializer.Deserialize(Of Dictionary(Of String, Object))(sJSON)
If dictParsedJSONPairs.ContainsKey("VehicleDistance") AndAlso _
TypeOf dictParsedJSONPairs("VehicleDistance") Is ArrayList Then
Dim ArrayEntries As ArrayList = DirectCast(dictParsedJSONPairs("VehicleDistance"), ArrayList)
For Each ArrayEntry As Object In ArrayEntries
Dim DictEntry As Dictionary(Of String, Object) = DirectCast(ArrayEntry, Dictionary(Of String, Object))
If DictEntry.ContainsKey("VehicleId") Then Console.WriteLine("VehichleId:" & DictEntry("VehicleId"))
Next
End If
If dictParsedJSONPairs.ContainsKey("Unit") Then
Console.WriteLine("Unit is " & dictParsedJSONPairs.Item("Unit"))
End If
Clearly you should research JSON before launching into serious use. The object can be nested JSON (i.e. Dictionary(Of String, Object)), a number of some sort, a string or an ArrayList
It could be a bit late but you may want to have a look at json.net from Newtonsoft (website). The component provided contains .Net 2.0 version.
Using it in a SSIS script task is quite simple. I did it in SSIS2008 based on .Net 3.5 to parse JSON string like below. And according to the document, it should work for .Net 2.0 version as well.
//I assume you had obtained JSON in string format
string JasonBuffer;
//define Json object
JObject jObject = JObject.Parse(JasonBuffer);
//In my example the Json object starts with result
JArray Results = (JArray)jObject["result"];
//loop the result
foreach (JObject Result in Results)
{
//for simple object
JObject joCustomer = (JObject)Result["Customer"];
//do something...
//for complex object continue drill down
foreach (JObject joSection in Result["Sections"])
{
foreach (JObject joDepartment in joSection["Departments"])
{
foreach (JObject joItem in joDepartment["Items"])
{
}
You can find some actual code here: link
Here is the situation:
Silverlight 3 Application hits an asp.net hosted WCF service to get a list of items to display in a grid. Once the list is brought down to the client it is cached in IsolatedStorage. This is done by using the DataContractSerializer to serialize all of these objects to a stream which is then zipped and then encrypted. When the application is relaunched, it first loads from the cache (reversing the process above) and the deserializes the objects using the DataContractSerializer.ReadObject() method. All of this was working wonderfully under all scenarios until recently with the entire "load from cache" path (decrypt/unzip/deserialize) taking hundreds of milliseconds at most.
On some development machines but not all (all machines Windows 7) the deserialize process - that is the call to ReadObject(stream) takes several minutes an seems to lock up the entire machine BUT ONLY WHEN RUNNING IN THE DEBUGGER in VS2008. Running the Debug configuration code outside the debugger has no problem.
One thing that seems to look suspicious is that when you turn on stop on Exceptions, you can see that the ReadObject() throws many, many System.FormatException's indicating that a number was not in the correct format. When I turn off "Just My Code" thousands of these get dumped to the screen. None go unhandled. These occur both on the read back from the cache AND on a deserialization at the conclusion of a web service call to get the data from the WCF Service. HOWEVER, these same exceptions occur on my laptop development machine that does not experience the slowness at all. And FWIW, my laptop is really old and my desktop is a 4 core, 6GB RAM beast.
Again, no problems unless running under the debugger in VS2008. Anyone else seem this? Any thoughts?
Here is the bug report link: https://connect.microsoft.com/VisualStudio/feedback/details/539609/very-slow-performance-deserializing-using-datacontractserializer-in-a-silverlight-application-only-in-debugger
EDIT: I now know where the FormatExceptions are coming from. It seems that they are "by design" - they occur when when I have doubles being serialized that are double.NaN so that that xml looks like NaN...It seems that the DCS tries to parse the value as a number, that fails with an exception and then it looks for "NaN" et. al. and handles them. My problem is not that this does not work...it does...it is just that it completely cripples the debugger. Does anyone know how to configure the debugger/vs2008sp1 to handle this more efficiently.
cartden,
You may want to consider switching over to XMLSerializer instead. Here is what I have determined over time:
The XMLSerializer and DataContractSerializer classes provides a simple means of serializing and deserializing object graphs to and from XML.
The key differences are:
1.
XMLSerializer has much smaller payload than DCS if you use [XmlAttribute] instead of [XmlElement]
DCS always store values as elements
2.
DCS is "opt-in" rather than "opt-out"
With DCS you explicitly mark what you want to serialize with [DataMember]
With DCS you can serialize any field or property, even if they are marked protected or private
With DCS you can use [IgnoreDataMember] to have the serializer ignore certain properties
With XMLSerializer public properties are serialized, and need setters to be deserialized
With XmlSerializer you can use [XmlIgnore] to have the serializer ignore public properties
3.
BE AWARE! DCS.ReadObject DOES NOT call constructors during deserialization
If you need to perform initialization, DCS supports the following callback hooks:
[OnDeserializing], [OnDeserialized], [OnSerializing], [OnSerialized]
(also useful for handling versioning issues)
If you want the ability to switch between the two serializers, you can use both sets of attributes simultaneously, as in:
[DataContract]
[XmlRoot]
public class ProfilePerson : NotifyPropertyChanges
{
[XmlAttribute]
[DataMember]
public string FirstName { get { return m_FirstName; } set { SetProperty(ref m_FirstName, value); } }
private string m_FirstName;
[XmlElement]
[DataMember]
public PersonLocation Location { get { return m_Location; } set { SetProperty(ref m_Location, value); } }
private PersonLocation m_Location = new PersonLocation(); // Should change over time
[XmlIgnore]
[IgnoreDataMember]
public Profile ParentProfile { get { return m_ParentProfile; } set { SetProperty(ref m_ParentProfile, value); } }
private Profile m_ParentProfile = null;
public ProfilePerson()
{
}
}
Also, check out my Serializer class that can switch between the two:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ClassLibrary
{
// Instantiate this class to serialize objects using either XmlSerializer or DataContractSerializer
internal class Serializer
{
private readonly bool m_bDCS;
internal Serializer(bool bDCS)
{
m_bDCS = bDCS;
}
internal TT Deserialize<TT>(string input)
{
MemoryStream stream = new MemoryStream(input.ToByteArray());
if (m_bDCS)
{
DataContractSerializer dc = new DataContractSerializer(typeof(TT));
return (TT)dc.ReadObject(stream);
}
else
{
XmlSerializer xs = new XmlSerializer(typeof(TT));
return (TT)xs.Deserialize(stream);
}
}
internal string Serialize<TT>(object obj)
{
MemoryStream stream = new MemoryStream();
if (m_bDCS)
{
DataContractSerializer dc = new DataContractSerializer(typeof(TT));
dc.WriteObject(stream, obj);
}
else
{
XmlSerializer xs = new XmlSerializer(typeof(TT));
xs.Serialize(stream, obj);
}
// be aware that the Unicode Byte-Order Mark will be at the front of the string
return stream.ToArray().ToUtfString();
}
internal string SerializeToString<TT>(object obj)
{
StringBuilder builder = new StringBuilder();
XmlWriter xmlWriter = XmlWriter.Create(builder);
if (m_bDCS)
{
DataContractSerializer dc = new DataContractSerializer(typeof(TT));
dc.WriteObject(xmlWriter, obj);
}
else
{
XmlSerializer xs = new XmlSerializer(typeof(TT));
xs.Serialize(xmlWriter, obj);
}
string xml = builder.ToString();
xml = RegexHelper.ReplacePattern(xml, RegexHelper.WildcardToPattern("<?xml*>", WildcardSearch.Anywhere), string.Empty);
xml = RegexHelper.ReplacePattern(xml, RegexHelper.WildcardToPattern(" xmlns:*\"*\"", WildcardSearch.Anywhere), string.Empty);
xml = xml.Replace(Environment.NewLine + " ", string.Empty);
xml = xml.Replace(Environment.NewLine, string.Empty);
return xml;
}
}
}
This is a guess, but I think it is running slow in debug mode because for every exception, it is performing some actions to show the exception in the debug window, etc. If you are running in release mode, these extra steps are not taken.
I've never done this, so I really don't know id it would work, but have you tried just setting that one assembly to run in release mode while all others are set to debug? If I'm right, it may solve your problem. If I'm wrong, then you only waste 1 or 2 minutes.
About your debugging problem, have you tried to disable the exception assistant ? (Tools > Options > Debugging > Enable the exception assistant).
Another point should be the exception handling in Debug > Exceptions : you can disable the user-unhandled stuff for the CLR or only uncheck the System.FormatException exception.
Ok - I figured out the root issue. It was what I alluded to in the EDIT to the main question. The problem was that in the xml, it was correctly serializing doubles that had a value of double.NaN. I was using these values to indicate "na" for when the denominator was 0D. Example: ROE (Return on Equity = Net Income / Average Equity) when Average Equity is 0D would be serialized as:
<ROE>NaN</ROE>
When the DCS tried to de-serialize it, evidently it first tries to read the number and then catches the exception when that fails and then handles the NaN. The problem is that this seems to generate a lot of overhead when in DEBUG mode.
Solution: I changed the property to double? and set it to null instead of NaN. Everything now happens instantly in DEBUG mode now. Thanks to all for your help.
Try disabling some IE addons. In my case, the LastPass toolbar killed my Silverlight debugging. My computer would freeze for minutes each time I interacted with Visual Studio after a breakpoint.
I wonder if anyone can help me - I've not done much with reflection but understand the basic principles.
What I'm trying to do:
I'm in the process of developing a class that gathers a lot of information about the local system, network, etc... to be used for automated bug reporting. Instead of having to change my test harness every time I add a new property, I'd (ideally) like to be able to serialise the lot as an XML string and just display that in a textbox.
Unfortunately, the Framework won't use the default XML serializer on read-only properties (which almost all of mine are) as they wouldn't deserialize properly
[Not sure I agree with the assumption that anything serialized must be de-serializable - MS says this is a feature "by design" which I suppose I can understand - Perhaps a tag to indicate that it should be serialized anyway would be advantageous?]
The initial approach was to make properties gettable and settable (with a throw exception on the setter) but the amount of work tidying this up afterwards seems a little excessive and I would want the properties to be read-only in the final version.
What I need help with:
My current plan is to use reflection to recursively iterate through each (public) property of my topmost gathering class. The problem is, the samples I've seen don't handle things recursively. Additionally, I only want to inspect an object's properties if it's in one of my assemblies - Otherwise just call .ToString on it.
If I don't have the inspection limited to my assembly, I assume I'll get (say) a string which then contains a Length which in turn will have .Tostring method...
For the purposes of this project, I can almost guarantee no circular references within my code and as this will only be used as a development tool so I'm not too concerned about it running amok now and then.
I'd appreciate some examples/advice.
Many thanks in advance.
This will hopefully get you started. It prints a tree directly to the console so you'll need to adjust to output XML. Then change the IsMyOwnType method to filter out the assemblies you're interested in, right now it only cares about types in the same assembly as itself.
Shared Sub RecurseProperties(o As Object, level As Integer)
For Each pi As PropertyInfo In o.GetType().GetProperties()
If pi.GetIndexParameters().Length > 0 Then Continue For
Console.Write(New String(" "c, 2 * level))
Console.Write(pi.Name)
Console.Write(" = ")
Dim propValue As Object = pi.GetValue(o, Nothing)
If propValue Is Nothing Then
Console.WriteLine("<null>")
Else
If IsMyOwnType(pi.PropertyType) Then
Console.WriteLine("<object>")
RecurseProperties(propValue, level+1)
Else
Console.WriteLine(propValue.ToString())
End If
End If
Next
End Sub
Shared Function IsMyOwnType(t As Type) As Boolean
Return t.Assembly Is Assembly.GetExecutingAssembly()
End Function
you extension version on c# to use on any object
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace Extensions
{
public static class ObjectExtension
{
public static string ToStringProperties(this object o)
{
return o.ToStringProperties(0);
}
public static string ToStringProperties(this object o, int level)
{
StringBuilder sb = new StringBuilder();
string spacer = new String(' ', 2 * level);
if (level == 0) sb.Append(o.ToString());
sb.Append(spacer);
sb.Append("{\r\n");
foreach (PropertyInfo pi in o.GetType().GetProperties())
{
if (pi.GetIndexParameters().Length == 0)
{
sb.Append(spacer);
sb.Append(" ");
sb.Append(pi.Name);
sb.Append(" = ");
object propValue = pi.GetValue(o, null);
if (propValue == null)
{
sb.Append(" <null>");
} else {
if (IsMyOwnType(pi.PropertyType))
{
sb.Append("\r\n");
sb.Append(((object)propValue).ToStringProperties(level + 1));
} else{
sb.Append(propValue.ToString());
}
}
sb.Append("\r\n");
}
}
sb.Append(spacer);
sb.Append("}\r\n");
return sb.ToString();
}
private static bool IsMyOwnType(Type t)
{
return (t.Assembly == Assembly.GetExecutingAssembly());
}
}
}
I am having a minor problem with WCF service proxies where the message contains List<string> as a parameter.
I am using the 'Add Service reference' in Visual Studio to generate a reference to my service.
// portion of my web service message
public List<SubscribeInfo> Subscribe { get; set; }
public List<string> Unsubscribe { get; set; }
These are the generated properties on my MsgIn for one of my web methods.
You can see it used ArrayOfString when I am using List<string>, and the other takes List<SubscribeInfo> - which matches my original C# object above.
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false)]
public System.Collections.Generic.List<DataAccess.MailingListWSReference.SubscribeInfo> Subscribe {
get {
return this.SubscribeField;
}
set {
if ((object.ReferenceEquals(this.SubscribeField, value) != true)) {
this.SubscribeField = value;
this.RaisePropertyChanged("Subscribe");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false)]
publicDataAccess.MailingListWSReference.ArrayOfString Unsubscribe {
get {
return this.UnsubscribeField;
}
set {
if ((object.ReferenceEquals(this.UnsubscribeField, value) != true)) {
this.UnsubscribeField = value;
this.RaisePropertyChanged("Unsubscribe");
}
}
}
The ArrayOfString class generated looks like this. This is a class generated in my code - its not a .NET class. It actually generated me a class that inherits from List, but didn't have the 'decency' to create me any constructors.
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.CollectionDataContractAttribute(Name="ArrayOfString", Namespace="http://www.example.com/", ItemName="string")]
[System.SerializableAttribute()]
public class ArrayOfString : System.Collections.Generic.List<string> {
}
The problem is that I often create my message like this :
client.UpdateMailingList(new UpdateMailingListMsgIn()
{
Email = model.Email,
Name = model.Name,
Source = Request.Url.ToString(),
Subscribe = subscribeTo.ToList(),
Unsubscribe = unsubscribeFrom.ToList()
});
I really like the clean look this gives me.
Now for the actual problem :
I cant assign a List<string> to the Unsubscribe property which is an ArrayOfString - even though it inherits from List. In fact I cant seem to find ANY way to assign it without extra statements.
I've tried the following :
new ArrayOfString(unsubscribeFrom.ToList()) - this constructor doesn't exist :-(
changing the type of the array used by the code generator - doesn't work - it always gives me ArrayOfString (!?)
try to cast List<string> to ArrayOfString - fails with 'unable to cast', even though it compiles just fine
create new ArrayOfString() and then AddRange(unsubscribeFrom.ToList()) - works, but I cant do it all in one statement
create a conversion function ToArrayOfString(List<string>), which works but isn't as clean as I want.
Its only doing this for string, which is annoying.
Am i missing something? Is there a way to tell it not to generate ArrayOfString - or some other trick to assign it ?
Any .NET object that implements a method named "Add" can be initialized just like arrays or dictionaries.
As ArrayOfString does implement an "Add" method, you can initialize it like this:
var a = new ArrayOfString { "string one", "string two" };
But, if you really want to initialize it based on another collection, you can write a extension method for that:
public static class U
{
public static T To<T>(this IEnumerable<string> strings)
where T : IList<string>, new()
{
var newList = new T();
foreach (var s in strings)
newList.Add(s);
return newList;
}
}
Usage:
client.UpdateMailingList(new UpdateMailingListMsgIn()
{
Email = model.Email,
Name = model.Name,
Source = Request.Url.ToString(),
Subscribe = subscribeTo.ToList(),
Unsubscribe = unsubscribeFrom.To<ArrayOfString>()
});
I prefer not to return generic types across a service boundary in the first place. Instead return Unsubscribe as a string[], and SubscriptionInfo as SubscriptionInfo[]. If necessary, an array can easily be converted to a generic list on the client, as follows:
Unsubscribe = new List<string>(unsubscribeFrom);
Subscribe = new List<SubscriptionInfo>(subscribeTo);
Too late but can help people in the future...
Use the svcutil and explicitly inform the command line util that you want the proxy class to be serialized by the XmlSerializer and not the DataContractSerializer (default). Here's the sample:
svcutil /out:c:\Path\Proxy.cs /config:c:\Path\Proxy.config /async /serializer:XmlSerializer /namespace:*,YourNamespace http://www.domain.com/service/serviceURL.asmx
Note that the web service is an ASP.NET web service ok?!
If you are using VS 2008 to consume service then there is an easy solution.
Click on the "Advanced..." button on the proxy dialog that is displayed when you add a Service Reference. In the Collection Type drop down you can select System.Generic.List. The methods returning List should now work properly.
(Hope this is what you were asking for, I'm a little tired and the question was a tad difficult for me to read.)