UDP Packet to Class - c#

I'm making a simple program that receive UDP Packets from another program and I want to cast this packet to a class.
I have a class :
public class Packet
{
public string MyFirstProperty {get; set;}
public string MySecondProperty {get; set;}
public string MyThirdProperty {get; set;}
public OtherObject[] ObjectArray {get; set}
}
The packet that I receive are bytes array. How can I transform the packet to a class. I've heard of marshalling but I'm not experienced enough to fully understand it.
What should I do.
Thank you.

To send an object from client to server (utilizing Json.Net) ; assuming you have already done your own research and have a working UDP client/server
Client site:
c1) Packet p = new Packet();
c2) //fill the properties
c3) string json = JsonConvert.SerializeObject(p);
c4) byte[] buf = Encoding.UTF8.GetBytes(json);
c5) Send *buf* to server
Server site:
s1) Receive data from client( call it *buf*)
s2) string json = Encoding.UTF8.GetString(buf); (must be equal to json at client site)
s3) Packet p = JsonConvert.DeserializeObject<Packet>(json);
s4) Tada........
Now you can use the same algorithm to send object from server to client
PS: As long as you can send and then receive the same byte array using UDP(c5 => s1), you can get the original object back.

You need to use Serialization and De-Serialization to convert back class objects from and to Byte Array.
Here is a sample example.
public class MyClass {
public int Id { get; set; }
public string Name { get; set; }
public byte[] Serialize() {
using (MemoryStream m = new MemoryStream()) {
using (BinaryWriter writer = new BinaryWriter(m)) {
writer.Write(Id);
writer.Write(Name);
}
return m.ToArray();
}
}
public static MyClass Desserialize(byte[] data) {
MyClass result = new MyClass();
using (MemoryStream m = new MemoryStream(data)) {
using (BinaryReader reader = new BinaryReader(m)) {
result.Id = reader.ReadInt32();
result.Name = reader.ReadString();
}
}
return result;
}
}
Link to MSDN for more on serialization

Related

protobuf-net cannot deserialize from google protobuf if there is an abstract class

I have .Net Client and .Net Server and they are using protobuf-net for communication. Now I have to create a Java Client to communicate with .Net Server and to do that I have to use google protobuf but I have an issue:
protobuf-net are failing during deserialization of an object which is sent from Java Client via google protobuf, protobuf-net shows the next error: 'No parameterless constructor found for ProtobufClasses.BasePage' but if I send the same object from .Net Client via protobuf-net then everything works. The reason of failure: it's happening because of abstract type.
Please note: I cannot touch server's code at all
Here is a small example of whole structure:
Server classes:
[ProtoContract]
public class LoginPage : BasePage
{
[ProtoMember(2)]
public sting Name { get; set; }
}
[ProtoContract]
[ProtoInclude(100, typeof(LoginPage))]
public abstract class BasePage
{
[ProtoMember(1)]
public int Id { get; set; }
}
Example of .proto file:
message LoginPage
{
string Name = 2;
}
message BasePage
{
oneof subtype
{
LoginPage LoginPage = 100;
}
int32 Id = 1;
}
Scenario N1 (protobuf-net -> protobuf-net)
var obj = new LoginPage();
byte[] array = null;
//Serializing to array
using (var memoryStream = new MemoryStream())
{
Serializer.Serialize(memoryStream, obj);
array = memoryStream.ToArray();
}
LoginPage obj2;
//Deserializing from array
using (var stream = new MemoryStream(array))
{
obj2 = Serializer.Deserialize<LoginPage>(stream);
}
Everhithing is working
Scenario N2 (protobuf-net -> google protobuf -> protobuf-net)
//xxxNN - means [protobuf-net] object
//xxxGG - means [google protobuf] object
var objNN = new LoginPage();
byte[] arrayNN = null;
//Serializing via [protobuf-net] to array
using (var memoryStream = new MemoryStream())
{
Serializer.Serialize(memoryStream, objNN);
arrayNN = memoryStream.ToArray();
}
Protobuf.BasePage objGG = null;
//Deserializing to [google protobuf] from [protobuf-net] array
using (var stream = new MemoryStream(arrayNN))
{
objGG = Protobuf.BasePage.Parser.ParseFrom(stream);
}
byte[] arrayGG = null;
//Serializing via [google protobuf] to array
using (var stream = new MemoryStream())
{
objGG.WriteTo(stream);
arrayGG = stream.ToArray();
}
LoginPage objN;
//Deserializing to [protobuf-net] from [google protobuf] array
using (var stream = new MemoryStream(arrayGG))
{
objN = Serializer.Deserialize<LoginPage>(stream); // Exception will be shown
}
Exception is present: 'No parameterless constructor found for Protobuf.BasePage'
How it possible that it works with protobuf-net but doesn't work with google protobuf? And how to avoid it? Please note that I cannot change server's code.

Object de-serializing from base64 in C#

I have a class as so
[Serializable]
public class ExternalAccount
{
public string Name { get;set;}
}
I have converted this to JSON like so
{\"Name\":\"XYZ\"}
I have then base64 encoded the JSON string
I then send across the wire to a web api service
I receive the base64 encoded string and now need to de-serialize it back to the original object as above (ExternalAccount) so i firstly do a
byte[] byteArray = Convert.FromBase64String(base64EncodedExternalAccount);
What is the next step?
I have tried the below but this returns null...
using (MemoryStream memoryStream = new MemoryStream(byteArrayToConvert))
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
// set memory stream position to starting point
memoryStream.Position = 0;
// Deserializes a stream into an object graph and return as a object.
return binaryFormatter.Deserialize(memoryStream) as ExternalAccount;
}
Any pointers/tips greatly appreciated.
You can try converting the byte array back to string (it will be the same JSON you sent), then deserialize to the ExternalAccount object. Using the Newtonsoft JSON library the following sample correctly displays "Someone" on the console:
class Program
{
static void Main(string[] args)
{
var account = new ExternalAccount() { Name = "Someone" };
string json = JsonConvert.SerializeObject(account);
string base64EncodedExternalAccount = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
byte[] byteArray = Convert.FromBase64String(base64EncodedExternalAccount);
string jsonBack = Encoding.UTF8.GetString(byteArray);
var accountBack = JsonConvert.DeserializeObject<ExternalAccount>(jsonBack);
Console.WriteLine(accountBack.Name);
Console.ReadLine();
}
}
[Serializable]
public class ExternalAccount
{
public string Name { get; set; }
}
you need to extract string from the bytes you recieve.
byte[] byteArray = Convert.FromBase64String(base64EncodedExternalAccount);
string AccountInfo = System.Text.Encoding.UTF8.GetString(byteArray );
As expected, you will get {\"Name\":\"XYZ\"} in your AccountInfo string. Now you need to Deserialize. you can use the same model, ExternalAccount. you may do something like:
ExnternalAccount model = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<ExnternalAccount>(AccountInfo );

XmlSerialize a class and send it from client to server

I have 2 clases:
public class products
{
public string category;
public string name;
public double price;
public string desc;
public string version;
public string logoURL;
public string imgURL;
public string prod;
public string Category
{
set { categorie = value; }
get { return category; }
}
and:
[Serializable()]
public class groupProducts
{
public products[] produse;
}
I want to XmlSerialize the groupProducts class and send the data from a server via TCP conection to a client!
I've tried something like:
groupProducts gp = new groupProducts();
XmlSerializer xmlSel = new XmlSerializer(typeof(groupProducts));
TextWriter txtStream = new StreamWriter("xmlStreamFile.xml");
xmlSel.Serialize(txtStream, gp);
txtStream.Close();
try
{
Stream inputStream = File.OpenRead("xmlStreamFile.xml");
// declaring the size of the byte array to the length of the xmlfile
msg = new byte[inputStream.Length];
//storing the xml file in the byte array
inputStream.Read(msg, 0, (int)inputStream.Length);
//reading the byte array
communicator[i].Send(msg);
}
but it when I Deserialize it on the client side - the XML file has some weird data in it!
Do you have any idea what could it be? What am I doing wrong?
1- To be safe I would use an encoding while opening StreamWriter
2- In inputStream.Read(msg, 0, (int)inputStream.Length); Read does't guarantee that you will get inputStream.Length bytes from stream. You have to check the returned value.
3- You don't need a temp file. Use MemoryStream
XmlSerializer xmlSel = new XmlSerializer(typeof(groupProducts));
MemoryStream m = new MemoryStream();
xmlSel.Serialize(m);
communicator[i].Send(m.ToArray());

Serializing object ready to send over TCPClient Stream

I've got a server and client set up using TcpListener and TcpClient.
I want to send an object to my server application for processing.
I've discovered the using System.Runtime.Serialization and the following documentation, but I didn't want to faff around to find that I'm doing it in long winded way.
The question: What is the best way to process and send an object over the TCP stream?
Sending and receiving.
Here's an example of my object:
// Create a new house to send
house newHouse = new house();
// Set variables
newHouse.street = "Mill Lane";
newHouse.postcode = "LO1 BT5";
newHouse.house_number = 11;
newHouse.house_id = 1;
newHouse.house_town = "London";
Assuming you have a class House (available on both sides of your connection) looking like this:
[Serializable]
public class House
{
public string Street { get; set; }
public string ZipCode { get; set; }
public int Number { get; set; }
public int Id { get; set; }
public string Town { get; set; }
}
You can serialize the class into a MemoryStream. You can then use in your TcpClient connection like this:
// Create a new house to send house and set values.
var newHouse = new House
{
Street = "Mill Lane",
ZipCode = "LO1 BT5",
Number = 11,
Id = 1,
Town = "London"
};
var xmlSerializer = new XmlSerializer(typeof(House));
var networkStream = tcpClient.GetStream();
if (networkStream.CanWrite)
{
xmlSerializer.Serialize(networkStream, newHouse);
}
Of course you have to do a little more investigation to make the program running without exception. (e.g. Check memoryStream.Length not to be greater than an int, a.s.o.), but I hope I gave you the right suggestions to help you on your way ;-)
First create a empty ServerApplication and ClientApplication as Console Application to simplify the example.
Then, put the definition for the serializable object into a separate assembly and then add a reference to the shared assembly to each project (server and client). Is necesary share the same object, not just an identical class copy.
To Generate DLL >
Right clic in Solution 'ServerApplication' in the Solution Explorer > Add New Project... -> select Class Library
(e.g. name this project MySharedHouse)
Rename the default Class1 to House and complete it
[Serializable]
public class House
{
public string Street { get; set; }
public string ZipCode { get; set; }
public int Number { get; set; }
public int Id { get; set; }
public string Town { get; set; }
}
Right clic in MySharedHouse and Build.
Now the dll is build and we need to add it in Server Project and Client Project.
Right clic in ServerApplication > Add Reference > Browse and find the dll, for this example
Projects\ServerApplication\MySharedHouse\bin\Debug\MySharedHouse.dll
Repeat the process in ClientApplication using the same dll (same path).
Now you can use instances of House class in ServerApplication and ClientApplication as a single object, simply adding the sentence "using MySharedHouse" at the top.
SERVER CODE
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using MySharedHouse;
namespace ServerApplication
{
class Program
{
static void Main(string[] args)
{
MessageServer s = new MessageServer(515);
s.Start();
}
}
public class MessageServer
{
private int _port;
private TcpListener _tcpListener;
private bool _running;
private TcpClient connectedTcpClient;
private BinaryFormatter _bFormatter;
private Thread _connectionThread;
public MessageServer(int port)
{
this._port = port;
this._tcpListener = new TcpListener(IPAddress.Loopback, port);
this._bFormatter = new BinaryFormatter();
}
public void Start()
{
if (!_running)
{
this._tcpListener.Start();
Console.WriteLine("Waiting for a connection... ");
this._running = true;
this._connectionThread = new Thread
(new ThreadStart(ListenForClientConnections));
this._connectionThread.Start();
}
}
public void Stop()
{
if (this._running)
{
this._tcpListener.Stop();
this._running = false;
}
}
private void ListenForClientConnections()
{
while (this._running)
{
this.connectedTcpClient = this._tcpListener.AcceptTcpClient();
Console.WriteLine("Connected!");
House house = new House();
house.Street = "Evergreen Terrace";
house.ZipCode = "71474";
house.Number = 742;
house.Id = 34527;
house.Town = "Springfield";
_bFormatter.Serialize(this.connectedTcpClient.GetStream(), house);
Console.WriteLine("send House!");
}
}
}
}
CLIENT CODE
using System;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using MySharedHouse;
namespace ClientApplication
{
class Program
{
static void Main(string[] args)
{
MessageClient client = new MessageClient(515);
client.StartListening();
}
}
public class MessageClient
{
private int _port;
private TcpClient _tcpClient;
private BinaryFormatter _bFormatter;
private Thread _listenThread;
private bool _running;
private House house;
public MessageClient(int port)
{
this._port = port;
this._tcpClient = new TcpClient("127.0.0.1", port);
this._bFormatter = new BinaryFormatter();
this._running = false;
}
public void StartListening()
{
lock (this)
{
if (!_running)
{
this._running = true;
this._listenThread = new Thread
(new ThreadStart(ListenForMessage));
this._listenThread.Start();
}
else
{
this._running = true;
this._listenThread = new Thread
(new ThreadStart(ListenForMessage));
this._listenThread.Start();
}
}
}
private void ListenForMessage()
{
Console.WriteLine("Reading...");
try
{
while (this._running)
{
this.house = (House)this._bFormatter.Deserialize(this._tcpClient.GetStream());
Console.WriteLine(this.house.Street);
Console.WriteLine(this.house.ZipCode);
Console.WriteLine(this.house.Number);
Console.WriteLine(this.house.Id);
Console.WriteLine(this.house.Town);
}
}
catch (Exception e)
{
Console.WriteLine(e);
Console.ReadLine();
}
}
}
}
Wooala! the first house to be sent over TCP/IP
You can simply decorate your House class with the [Serializable] attribute. (You do not need to define all the other stuff as posted in the other answer)
You can then send this object on the wire by serializing it using the BinaryFormatter class.
Have you considered setting up a WCF service instead of using TcpListener and TcpClient ? Makes life a lot easier.
For instance you could define a service that returned a house
[ServiceContract]
public interface IService
{
[OperationContract]
House GetHouse(int houseId);
}
See this real world example.
Your answer implies the following object (it is common practice to name classes using PascalCase):
[Serializable]
class House:ISerializable
{
public string Street {get; set;}
public string PostalCode {get; set;}
public int HouseNumber {get; set;}
public int HouseID {get; set;}
public string City {get; set;}
public House() { }
protected House(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new System.ArgumentNullException("info");
Street = (string)info.GetValue("Street ", typeof(string));
PostalCode = (string)info.GetValue("PostalCode", typeof(string));
HouseNumber = (int)info.GetValue("HouseNumber", typeof(int));
HouseID = (int)info.GetValue("HouseID", typeof(int));
City = (string)info.GetValue("City", typeof(string));
}
[SecurityPermissionAttribute(SecurityAction.LinkDemand,
Flags=SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new System.ArgumentNullException("info");
info.AddValue("Street ", Street);
info.AddValue("PostalCode", PostalCode);
info.AddValue("HouseNumber", HouseNumber);
info.AddValue("HouseID", HouseID );
info.AddValue("City", City);
}
}
Now you can serialize your objects:
void Send(Stream stream)
{
BinaryFormatter binaryFmt = new BinaryFormatter();
House h = new House()
{
Street = "Mill Lane",
PostalCode = "LO1 BT5",
HouseNumber = 11,
HouseID = 1,
City = "London"
};
binaryFmt.Serialize(stream, h);
}
How would you deserialize the xml House stream back to a House object on the receiving end?
I'm refering to the solution given in Fischermaen's answer.
On my recieving end I can see a string representation in my Output window by using the following:
ASCIIEncoding encoder = new ASCIIEncoding();
System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead));
Thank you in advance.
EDIT *
Ok well this solution has worked for me. Might need some tidying up.
Here's a method to deserialize a string:
public static T DeserializeFromXml<T>(string xml)
{
T result;
XmlSerializer ser = new XmlSerializer(typeof(T));
using (TextReader tr = new StringReader(xml))
{
result = (T)ser.Deserialize(tr);
}
return result;
}
Then from my TPC/IP Recieving end I call the method like so:
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
byte[] message = new byte[4096];
int bytesRead;
while (true)
{
bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = clientStream.Read(message, 0, 4096);
}
catch
{
//a socket error has occured
break;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
break;
}
//message has successfully been received
ASCIIEncoding encoder = new ASCIIEncoding();
System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead));
House house = DeserializeFromXml<House>(encoder.GetString(message, 0, bytesRead));
//Send Message Back
byte[] buffer = encoder.GetBytes("Hello Client - " + DateTime.Now.ToLongTimeString());
clientStream.Write(buffer, 0, buffer.Length);
clientStream.Flush();
}
tcpClient.Close();
}

How can I send multiple types of objects across Protobuf?

I'm implementing a client-server application, and am looking into various ways to serialize and transmit data. I began working with Xml Serializers, which worked rather well, but generate data slowly, and make large objects, especially when they need to be sent over the net. So I started looking into Protobuf, and protobuf-net.
My problem lies in the fact that protobuf doesn't sent type information with it. With Xml Serializers, I was able to build a wrapper which would send and receive any various (serializable) object over the same stream, since object serialized into Xml contain the type name of the object.
ObjectSocket socket = new ObjectSocket();
socket.AddTypeHandler(typeof(string)); // Tells the socket the types
socket.AddTypeHandler(typeof(int)); // of objects we will want
socket.AddTypeHandler(typeof(bool)); // to send and receive.
socket.AddTypeHandler(typeof(Person)); // When it gets data, it looks for
socket.AddTypeHandler(typeof(Address)); // these types in the Xml, then uses
// the appropriate serializer.
socket.Connect(_host, _port);
socket.Send(new Person() { ... });
socket.Send(new Address() { ... });
...
Object o = socket.Read();
Type oType = o.GetType();
if (oType == typeof(Person))
HandlePerson(o as Person);
else if (oType == typeof(Address))
HandleAddress(o as Address);
...
I've considered a few solutions to this, including creating a master "state" type class, which is the only type of object sent over my socket. This moves away from the functionality I've worked out with Xml Serializers, though, so I'd like to avoid that direction.
The second option would be to wrap protobuf objects in some type of wrapper, which defines the type of object. (This wrapper would also include information such as packet ID, and destination.) It seems silly to use protobuf-net to serialize an object, then stick that stream between Xml tags, but I've considered it. Is there an easy way to get this functionality out of protobuf or protobuf-net?
I've come up with a third solution, and posted it below, but if you have a better one, please post it too!
Information on field bounds bug (using System.String):
Hashing:
protected static int ComputeTypeField(Type type) // System.String
{
byte[] data = ASCIIEncoding.ASCII.GetBytes(type.FullName);
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
return Math.Abs(BitConverter.ToInt32(md5.ComputeHash(data), 0));
}
Serialization:
using (MemoryStream stream = new MemoryStream())
{
Serializer.NonGeneric.SerializeWithLengthPrefix
(stream, o, PrefixStyle.Base128, field); // field = 600542181
byte[] data = stream.ToArray();
_pipe.Write(data, 0, data.Length);
}
Deserializaion:
using (MemoryStream stream = new MemoryStream(_buffer.Peek()))
{
lock (_mapLock)
{
success = Serializer.NonGeneric.TryDeserializeWithLengthPrefix
(stream, PrefixStyle.Base128, field => _mappings[field], out o);
}
if (success)
_buffer.Clear((int)stream.Position);
else
{
int len;
if (Serializer.TryReadLengthPrefix(stream, PrefixStyle.Base128, out len))
_buffer.Clear(len);
}
}
field => _mappings[field] throws a KeyNotFoundException while looking for 63671269.
If I replace ToInt32 with ToInt16 in the hash function, the field value is set to 29723 and it works. It also works if I explicitly define System.String's field to 1. Explicitly defining the field to 600542181 has the same effect as using the hash function to define it. The value of the string being serialized does not change the outcome.
This functionality is actually built in, albeit not obviously.
In this scenario, it is anticipated that you would designate a unique number per message type. The overload you are using passes them all in as "field 1", but there is an overload that lets you include this extra header information (it is still the job of the calling code to decide how to map numbers to types, though). You can then specify different types as different fields is the stream (note: this only works with the base-128 prefix style).
I'll need to double check, but the intention is that something like the following should work:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ProtoBuf;
static class Program
{
static void Main()
{
using (MemoryStream ms = new MemoryStream())
{
WriteNext(ms, 123);
WriteNext(ms, new Person { Name = "Fred" });
WriteNext(ms, "abc");
ms.Position = 0;
while (ReadNext(ms)) { }
}
}
// *** you need some mechanism to map types to fields
static readonly IDictionary<int, Type> typeLookup = new Dictionary<int, Type>
{
{1, typeof(int)}, {2, typeof(Person)}, {3, typeof(string)}
};
static void WriteNext(Stream stream, object obj) {
Type type = obj.GetType();
int field = typeLookup.Single(pair => pair.Value == type).Key;
Serializer.NonGeneric.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128, field);
}
static bool ReadNext(Stream stream)
{
object obj;
if (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(stream, PrefixStyle.Base128, field => typeLookup[field], out obj))
{
Console.WriteLine(obj);
return true;
}
return false;
}
}
[ProtoContract] class Person {
[ProtoMember(1)]public string Name { get; set; }
public override string ToString() { return "Person: " + Name; }
}
Note that this doesn't currently work in the v2 build (since the "WithLengthPrefix" code is incomplete), but I'll go and test it on v1. If it works, I'll all the above scenario to the test suite to ensure it does work in v2.
Edit:
yes, it does work fine on "v1", with output:
123
Person: Fred
abc
I've come up with another solution, but I decided to put it as an answer, instead of in the question, because that makes more sense to me. It's pretty ugly, in my opinion, and I've been warned against using reflection, so please comment on it or provide better answers if you have them. Thanks!
class Program
{
static void Main(string[] args)
{
Person person = new Person
{
Id = 12345,
Name = "Fred",
Address = new Address
{
Line1 = "Flat 1",
Line2 = "The Meadows"
}
};
object value;
using (Stream stream = new MemoryStream())
{
Send<Person>(stream, person);
stream.Position = 0;
value = Read(stream);
person = value as Person;
}
}
static void Send<T>(Stream stream, T value)
{
Header header = new Header()
{
Guid = Guid.NewGuid(),
Type = typeof(T)
};
Serializer.SerializeWithLengthPrefix<Header>(stream, header, PrefixStyle.Base128);
Serializer.SerializeWithLengthPrefix<T>(stream, value, PrefixStyle.Base128);
}
static object Read(Stream stream)
{
Header header;
header = Serializer.DeserializeWithLengthPrefix<Header>
(stream, PrefixStyle.Base128);
MethodInfo m = typeof(Serializer).GetMethod("DeserializeWithLengthPrefix",
new Type[] {typeof(Stream), typeof(PrefixStyle)}).MakeGenericMethod(header.Type);
Object value = m.Invoke(null, new object[] {stream, PrefixStyle.Base128} );
return value;
}
}
[ProtoContract]
class Header
{
public Header() { }
[ProtoMember(1, IsRequired = true)]
public Guid Guid { get; set; }
[ProtoIgnore]
public Type Type { get; set; }
[ProtoMember(2, IsRequired = true)]
public string TypeName
{
get { return this.Type.FullName; }
set { this.Type = Type.GetType(value); }
}
}
[ProtoContract]
class Person {
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
[ProtoMember(3)]
public Address Address { get; set; }
}
[ProtoContract]
class Address {
[ProtoMember(1)]
public string Line1 { get; set; }
[ProtoMember(2)]
public string Line2 { get; set; }
}

Categories