How to access custom attributes defined in WCF service using C#? - wcf

First question is, how can I get the type of an object stored in a variable? Generally we do:
Type t = typeof(ClassName); //if I know the class
but, how can I say something:
Type t = typeof(varClassName); //if the class name is stored in a variable
Second question, a broader picture is, I have a WCF service that contains a DataContract class say "MyClass" and I have defined a custom attribute called "MyAttribute" to it. There is one method say "GetDataUsingDataContract" with a parameter of type MyClass. Now on client, I invoke the webservice. I use MethodInfo and ParameterInfo classes to get the parameters of the method in question. But how can I access the attributes of the method parameter which is actually a class Myclass? Here is the code that I tried:
MyService.Service1Client client = new MyService.Service1Client();
Type t = typeof(MyService.Service1Client);
MethodInfo members = t.GetMethod("GetDataUsingDataContract");
ParameterInfo[] parameters = members.GetParameters();
foreach (var parameter in parameters)
{
MemberInfo mi = parameter.ParameterType; //Not sure if this the way
object[] attributes;
attributes = mi.GetCustomAttributes(true);
}
Above code doesn't retrieve me the custom attribute "MyAttribute". I tried the concept in the class that is defined in the same project and it works. Please HELP!

but, how can I say something:
Type t = typeof(varClassName); //if the class name is stored in a variable
Try
Type.GetType("varClassName", false, true);
As to your second question:
Above code doesn't retrieve me the
custom attribute "MyAttribute". I
tried the concept in the class that is
defined in the same project and it
works. Please HELP!
Just guessing, I'm not sure that attributes are exposed to the client, by default. I think its the same issue as an untrusted assembly. Some attributes are sensitive info. See this:
http://blogs.msdn.com/b/haibo_luo/archive/2006/02/21/536470.aspx
But you could try linking the service project types into your app by first referencing the service assembly in your client project, then going to your service reference -> "Configure Service Reference" and selecting "Reuse types in all referenced assemblies". I'm not sure this option will affect the service interface classes, but I use it often with my domain objects. Worth a try.

Type mi = parameter.ParameterType; //Not sure if this the way
object[] attributes;
attributes = mi.GetCustomAttributes(true);
Ensure your proxy class has knowledge on attributes
Hope this will help
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Reflection;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
StartService();
}
string url = "http://localhost:234/MyService/";
private void StartClient()
{
IMyService myService = ChannelFactory<IMyService>.CreateChannel(new BasicHttpBinding(), new EndpointAddress(url));
Type t = typeof(IMyService);
MethodInfo members = t.GetMethod("MyMethod");
ParameterInfo[] parameters = members.GetParameters();
foreach (var parameter in parameters)
{
Type mi = parameter.ParameterType;
object[] attributes;
attributes = mi.GetCustomAttributes(true);
}
}
private void StartService()
{
ServiceHost host = new ServiceHost(typeof(MyService), new Uri(url));
host.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "");
host.Open();
}
private void button1_Click(object sender, EventArgs e)
{
StartClient();
}
}
[AttributeUsage(AttributeTargets.Interface)]
public class MyAttrib : Attribute
{
}
[MyAttrib]
public interface IMyContract
{
string Name { get; set; }
}
[DataContract]
public class MyContract : IMyContract
{
[DataMember]
public string Name { get; set; }
}
[ServiceContract]
public interface IMyService
{
[OperationContract]
bool MyMethod(IMyContract dummy);
}
[ServiceBehavior(UseSynchronizationContext = false)]
public class MyService : IMyService
{
public bool MyMethod(IMyContract dummy)
{
return true;
}
}
}

Related

Derived type's properties missing in JSON response from ASP.NET Core API

The JSON response from my ASP.NET Core 3.1 API controller is missing properties. This happens when a property uses a derived type; any properties defined in the derived type but not in the base/interface will not be serialized to JSON. It seems there is some lack of support for polymorphism in the response, as if serialization is based on a property's defined type instead of its runtime type. How can I change this behavior to ensure that all public properties are included in the JSON response?
Example:
My .NET Core Web API Controller returns this object that has a property with an interface type.
// controller returns this object
public class Result
{
public IResultProperty ResultProperty { get; set; } // property uses an interface type
}
public interface IResultProperty
{ }
Here is a derived type that defines a new public property named Value.
public class StringResultProperty : IResultProperty
{
public string Value { get; set; }
}
If I return the derived type from my controller like this:
return new MainResult {
ResultProperty = new StringResultProperty { Value = "Hi there!" }
};
then the actual response includes an empty object (the Value property is missing):
I want the response to be:
{
"ResultProperty": { "Value": "Hi there!" }
}
While the other answers are good and solves the problem, if all you want is the general behavior to be like pre netcore3, you can use the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package and in Startup.cs do:
services.AddControllers().AddNewtonsoftJson()
More info here. This way, you don't need to create any extra json-converters.
I ended up creating a custom JsonConverter (System.Text.Json.Serialization namespace) which forces JsonSerializer to serialize to the object's runtime type. See the Solution section below. It's lengthy but it works well and does not require me to sacrifice object oriented principles in my API's design. (If you need something quicker and can use Newtonsoft then check out the top voted answer instead.)
Some background: Microsoft has a System.Text.Json serialization guide with a section titled Serialize properties of derived classes with good information relevant to my question. In particular it explains why properties of derived types are not serialized:
This behavior is intended to help prevent accidental exposure of data
in a derived runtime-created type.
If that is not a concern for you then the behavior can be overridden in the call to JsonSerializer.Serialize by either explicitly specifying the derived type or by specifying object, for example:
// by specifying the derived type
jsonString = JsonSerializer.Serialize(objToSerialize, objToSerialize.GetType(), serializeOptions);
// or specifying 'object' works too
jsonString = JsonSerializer.Serialize<object>(objToSerialize, serializeOptions);
To accomplish this with ASP.NET Core you need to hook into the serialization process. I did this with a custom JsonConverter that calls JsonSerializer.Serialize one of the ways shown above. I also implemented support for deserialization which, while not explicitly asked for in the original question, is almost always needed anyway. (Oddly, supporting only serialization and not deserialization proved to be tricky anyway.)
Solution
I created a base class, DerivedTypeJsonConverter, which contains all of the serialization & deserialization logic. For each of your base types, you would create a corresponding converter class for it that derives from DerivedTypeJsonConverter. This is explained in the numbered directions below.
This solution follows the "type name handling" convention from Json.NET which introduces support for polymorphism to JSON. It works by including an additional $type property in the derived type's JSON (ex: "$type":"StringResultProperty") that tells the converter what the object's true type is. (One difference: in Json.NET, $type's value is a fully qualified type + assembly name, whereas my $type is a custom string which helps future-proof against namespace/assembly/class name changes.) API callers are expected to include $type properties in their JSON requests for derived types. The serialization logic solves my original problem by ensuring that all of the object's public properties are serialized, and for consistency the $type property is also serialized.
Directions:
1) Copy the DerivedTypeJsonConverter class below into your project.
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
public abstract class DerivedTypeJsonConverter<TBase> : JsonConverter<TBase>
{
protected abstract string TypeToName(Type type);
protected abstract Type NameToType(string typeName);
private const string TypePropertyName = "$type";
public override bool CanConvert(Type objectType)
{
return typeof(TBase) == objectType;
}
public override TBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// get the $type value by parsing the JSON string into a JsonDocument
JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader);
jsonDocument.RootElement.TryGetProperty(TypePropertyName, out JsonElement typeNameElement);
string typeName = (typeNameElement.ValueKind == JsonValueKind.String) ? typeNameElement.GetString() : null;
if (string.IsNullOrWhiteSpace(typeName)) throw new InvalidOperationException($"Missing or invalid value for {TypePropertyName} (base type {typeof(TBase).FullName}).");
// get the JSON text that was read by the JsonDocument
string json;
using (var stream = new MemoryStream())
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Encoder = options.Encoder })) {
jsonDocument.WriteTo(writer);
writer.Flush();
json = Encoding.UTF8.GetString(stream.ToArray());
}
// deserialize the JSON to the type specified by $type
try {
return (TBase)JsonSerializer.Deserialize(json, NameToType(typeName), options);
}
catch (Exception ex) {
throw new InvalidOperationException("Invalid JSON in request.", ex);
}
}
public override void Write(Utf8JsonWriter writer, TBase value, JsonSerializerOptions options)
{
// create an ExpandoObject from the value to serialize so we can dynamically add a $type property to it
ExpandoObject expando = ToExpandoObject(value);
expando.TryAdd(TypePropertyName, TypeToName(value.GetType()));
// serialize the expando
JsonSerializer.Serialize(writer, expando, options);
}
private static ExpandoObject ToExpandoObject(object obj)
{
var expando = new ExpandoObject();
if (obj != null) {
// copy all public properties
foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead)) {
expando.TryAdd(property.Name, property.GetValue(obj));
}
}
return expando;
}
}
2) For each of your base types, create a class that derives from DerivedTypeJsonConverter. Implement the 2 abstract methods which are for mapping $type strings to actual types. Here is an example for my IResultProperty interface that you can follow.
public class ResultPropertyJsonConverter : DerivedTypeJsonConverter<IResultProperty>
{
protected override Type NameToType(string typeName)
{
return typeName switch
{
// map string values to types
nameof(StringResultProperty) => typeof(StringResultProperty)
// TODO: Create a case for each derived type
};
}
protected override string TypeToName(Type type)
{
// map types to string values
if (type == typeof(StringResultProperty)) return nameof(StringResultProperty);
// TODO: Create a condition for each derived type
}
}
3) Register the converters in Startup.cs.
services.AddControllers()
.AddJsonOptions(options => {
options.JsonSerializerOptions.Converters.Add(new ResultPropertyJsonConverter());
// TODO: Add each converter
});
4) In requests to the API, objects of derived types will need to include a $type property. Example JSON: { "Value":"Hi!", "$type":"StringResultProperty" }
Full gist here
The documentation shows how to serialize as the derived class when calling the serializer directly. The same technique can also be used in a custom converter that we then can tag our classes with.
First, create a custom converter
public class AsRuntimeTypeConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value?.GetType() ?? typeof(object), options);
}
}
Then mark the relevant classes to be used with the new converter
[JsonConverter(typeof(AsRuntimeTypeConverter<MyBaseClass>))]
public class MyBaseClass
{
...
Alternately, the converter can be registered in startup.cs instead
services
.AddControllers(options =>
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new AsRuntimeTypeConverter<MyBaseClass>());
}));
I had a similar issue, where I was returning an enumerable of type TAnimal (but the object instances were of derived types such as Dog, Cat, etc.):
[HttpGet]
public IEnumerable<TAnimal> GetAnimals()
{
IEnumerable<TAnimal> list = GetListOfAnimals();
return list;
}
This only included properties defined in TAnimal.
However, in ASP .NET Core 3.1 at least, I found that I could just cast the object instances to object, and the JSON serializer then included all the properties from the derived classes:
[HttpGet]
public IEnumerable<object> GetAnimals()
{
IEnumerable<TAnimal> list = GetListOfAnimals();
return list.Select(a => (object)a);
}
(Note that the signature of the GetAnimals method must also changed, but that doesn't usually matter much in a web API context). If you need to provide type information for Swagger or whatever, you can annotate the method:
[HttpGet]
[Produces(MediaTypeNames.Application.Json, Type = typeof(TAnimal[]))]
public IEnumerable<object> GetAnimals()
{
...
}
Casting to object is a simple solution if you only have a 1-layer-deep object hierarchy to worry about.
This is the expected result. You're upcasting when you do that, so what will be serialized is the upcasted object, not the actual derived type. If you need stuff from the derived type, then that has to be the type of the property. You may want to use generics for this reason. In other words:
public class Result<TResultProperty>
where TResultProperty : IResultProperty
{
public TResultProperty ResultProperty { get; set; } // property uses an interface type
}
Then:
return new Result<StringResultProperty> {
ResultProperty = new StringResultProperty { Value = "Hi there!" }
};
I solved it by writing this extension:
public static class JsonSerializationExtensions
{
public static string ToJson<T>(this IEnumerable<T> enumerable, bool includeDerivedTypesProperties = true)
where T : class
{
var jsonOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
if (includeDerivedTypeProperties)
{
var collection = enumerable.Select(e => e as object).ToList();
return JsonSerializer.Serialize<object>(collection, jsonOptions);
}
else
{
return JsonSerializer.Serialize(enumerable, jsonOptions);
}
}
}
I was also struggling with this in a .NET Core 3.1 API, where I wanted the result to include $type attribute.
As suggested, install the correct package and then 'AddNewtonsoftJson'.
I wanted the $type field to be added to show the derived type handling, to get that
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All;
});
Not knocking Newtonsoft, but I found an easier way to resolve this with the built handlers.
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "/emps", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
List<emp> GetEmps();
//[DataContract(Namespace = "foo")] <<< comment/removed this line
public class emp
{
public string userId { get; set; }
public string firstName { get; set; }
}
public class dept
{
public string deptId{ get; set; }
public string deptName{ get; set; }
}
In my case dept objects where working fine, but emp ones were not - they came across as empty.

Can't transmit standard serializable object across WCF

I have created a very simple server and client console app demonstrating the issue I have in that I am trying to bring an instance of a serializable object across to the client but it fails on the server.
What am I missing?? I am NOT concerned right now having it Service orientated using DataContracts - I am simply trying to understand why the code as it stands doesn't bring the EJob accross to the client (it DOES however calls the 'Hello from the server' message)
Many thanks.
EDIT
Even if I decorate the EJob class with a DataContract attribute (like below) it STILL doesn't work - the object I receive on the client has LastName set to null?????
[DataContract]
public class EJob
{
[DataMember]
public string LastName = "Smith";
}
SERVER
namespace testServer
{
[ServiceContract()]
public interface IRemoteClient
{
[OperationContract]
void SayHi(string msg);
[OperationContract]
void ProcessJob(EJob job);
}
[Serializable()]
public class EJob
{
public string LastName = "Smith";
}
class Program
{
static void Main(string[] args)
{
MngrServer.SendJob();
}
}
public class MngrServer
{
public static void SendJob()
{
try
{
// send this off to the correct exe
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, true);
string address = string.Format("net.tcp://localhost:33888/BatchMananger/client");
EndpointAddress epa = new EndpointAddress(address);
// create the proxy pointing to the correct exe
IRemoteClient clientProxy = ChannelFactory<IRemoteClient>.CreateChannel(binding, epa);
clientProxy.SayHi("Hello from server"); <-- THIS WORKS FINE
EJob job = new EJob { LastName = "Janssen" };
clientProxy.ProcessJob(job); <-- THIS RAISES AN EXCEPTION see below...
}
catch (Exception ex)
{
string msg = ex.Message;
//The formatter threw an exception while trying to deserialize the message: There was an error while
//trying to deserialize parameter http://tempuri.org/:job. The InnerException message was ''EndElement' 'job'
//from namespace 'http://tempuri.org/' is not expected. Expecting element 'LastName'.'.
}
}
}
}
CLIENT
namespace testClient
{
[ServiceContract()]
public interface IRemoteClient
{
[OperationContract]
void SayHi(string msg);
[OperationContract]
void ProcessJob(EJob job);
}
[Serializable()]
public class EJob
{
public string LastName = "Smith";
}
class Program
{
static void Main(string[] args)
{
MngrClient.Prepare();
Console.ReadLine();
}
}
/// <summary>
/// STATIC / INSTANCE
/// </summary>
public class MngrClient : IRemoteClient
{
public void SayHi(string msg)
{
Console.WriteLine(msg);
}
public void ProcessJob(EJob job)
{
Console.WriteLine(job.LastName);
}
public static void Prepare()
{
// allow this class to be used! - so instances are created and info directly passed on to its static members.
ServiceHost sh = new ServiceHost(typeof(MngrClient));
// create the net binding
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, true);
// define the tcpaddress
string address = string.Format("net.tcp://localhost:33888/BatchMananger/client");
// add a service point so my server can reach me
sh.AddServiceEndpoint(typeof(IRemoteClient), binding, address);
// now open the service for business
sh.Open();
}
}
}
Your EJob datacontract is in a different namespace on the server vs. the client. You need to either declare both classes in the same namespace, or use attributes to set the namespace on the client to match the namespace on the server
(Either the Datacontract attribute has a namespace value that you can pass, or there is a separate namespace attribute that you can use to tell WCF to use an alternate namespace for the contract, can't remember off the top of my head)
EDIT
Just verified -- it's the Namespace property of the DataContractAttribute that you want, so in your client-side declaration:
[DataContract(Namespace="EJobNamespaceAsItIsDeclaredOnTheServer")]
public class EJob ...
Now, it is very common to put all of your DataContracts in a separate assembly (called a contract assembly) that is referenced by both the client and the server. You would want just the contract class definitions in that assembly, nothing else.
You somehow have it all a bit backwards...
given your service contract of IRemoteClient, you should then have an implementation class on the server-side that implements that interface:
public class ServiceImplementation : IRemoteClient
{
public void SayHi(string msg)
{
.....
}
public void ProcessJob(EJob job)
{
.....
}
}
Also: the service methods should be returning something to the caller! Without a return type, you're kinda creating a black-hole of a service - you can call its methods, but nothing gets returned.... Plus: the service implementation class should NOT be hosting itself! Make that a separate class
you should then have a host class on the server side that hosts this service:
public class HostForYourService
{
public HostForYourService()
{
// send this off to the correct exe
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, true);
string address = string.Format("net.tcp://localhost:33888/BatchMananger/client");
EndpointAddress epa = new EndpointAddress(address);
ServiceHost sh = new ServiceHost(typeof(ServiceImplementation));
// define the tcpaddress
sh.AddServiceEndpoint(typeof(IRemoteClient), binding, address);
// now open the service for business
sh.Open();
}
and then your client should build the client-side proxy for this service and call it
public class YourServiceClient
{
public void CallService()
{
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, true);
string address = string.Format("net.tcp://servername:33888/BatchMananger/client");
EndpointAddress epa = new EndpointAddress(address);
// create the proxy pointing to the correct exe
IRemoteClient clientProxy = ChannelFactory<IRemoteClient>.CreateChannel(binding, epa);
clientProxy.SayHi("Hello from server"); <-- THIS WORKS FINE
EJob job = new EJob { LastName = "Janssen" };
clientProxy.ProcessJob(job);
}
}
But again: typically, your service methods should be returning something that the client can then operate on - after all, you typically don't want to do a Console.WriteLine on the server - you want to compute something, look up something etc. and return a response to the client which then in turns can e.g. output the result to the console or something....

Self-host (No IIS or WAS) WCF with a service that requires parameters

Hopefully this is an easy one. I'm wondering if this is possible - perhaps it is not. I'm attempting to self-host a WCF service (in my example below it is a console application). The service does not have a default constructor. It only contains a single parameter signature constructor. I need the service to be able to handle user sessions. Currently I am using Ninject DI. Here is a simple code solution I came up with to demonstrate my issue:
using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using Ninject.Modules;
namespace ConsoleApplication1
{
public class Program
{
static void Main()
{
using (var webServiceHost = new WebServiceHost(typeof(MyWcf)))
{
var webHttpBinding = new WebHttpBinding();
var uri = new Uri("http://localhost:8000/");
webServiceHost.AddServiceEndpoint(typeof(IMyWcf), webHttpBinding, uri);
webServiceHost.Open();
Console.WriteLine("Service is ready...");
Console.ReadKey();
}
}
}
[ServiceContract]
public interface IMyWcf
{
[OperationContract, WebGet(UriTemplate = "")]
string HelloWorld();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyWcf : IMyWcf
{
private readonly IMessage _customMessage = new Message("Default Message.");
public MyWcf(IMessage message)
{
_customMessage = message;
}
public string HelloWorld()
{
return _customMessage.Text;
}
}
public interface IMessage
{
string Text { get; }
}
public class Message : IMessage
{
public Message (string message)
{
Text = message;
}
public string Text { get; set; }
}
public class NinjectSetup : NinjectModule
{
public override void Load()
{
Bind<IMessage>().To<Message>()
.WithConstructorArgument("message", "Injected String Message.");
}
}
}
Obviously commenting out the parameterized constructor allows the service to run. But that does me no good. I don't want to use ServiceHostFactory because that apparently requires me to have a .svc/IIS. Is there a way around this? Can I just create a new MyWebServiceHost that inherits from WebServiceHost and override some method that will create a instance for the service?
Using Ruben's suggestion (in the comments) above, I was able to locate a working example within the Ninject.Extensions.Wcf source repository.

What is wrong with my DataContract?

I am writing my first WCF service. I am trying to understand how Datacontracts work. I have read the MSDN Article that describes how custom types should be marked up to create a data contract but I cannot get my example to work.
I have a simple DTO object that I have placed in a shared library because I want the client and the service to know about this type (right?) it looks like this:
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace org.healthwise.gatewayinterfaces.mocks
{
[DataContract]
public class MockCheckInDTO : ICheckInDTO
{
[DataMember]
private string _testPackageFilePath = "testpackages\\973eb455-6acc-486b-a1dd-2cf527872b1e.zip";
[DataMember]
private IDictionary<string, string> _testMetaData;
public MockCheckInDTO()
{
_testMetaData = MakeTestMetaDataDictionary();
}
private IDictionary<string, string> MakeTestMetaDataDictionary()
{
IDictionary<string, string> testMetaData = new Dictionary<string, string>();
testMetaData.Add("Version", "9.0.1");
testMetaData.Add("Product Family", "Learning Modules");
return testMetaData;
}
[DataMember]
public string PackageFileLocation
{
get { return _testPackageFilePath; }
set { _testPackageFilePath = value; }
}
[DataMember]
public IDictionary<string, string> PackageMetaData
{
get { return _testMetaData; }
set { _testMetaData = value; }
}
}
}
This is the ServiceContract:
[ServiceContract]
public interface IIndexCheckIn
{
[OperationContract]
void AddToIndex(MockCheckInDTO mockCheckInDto);
}
I have created a little console application to attempt to send this MockCheckInDTO over to my service but it never gets there. It seems that I am having and issue serializing the MockCheckInDTO object. Can someone help me out?
This is the exception I am seeing:
System.Runtime.Serialization.SerializationException: Type 'org.healthwise.gatewayinterfaces.mocks.MockCheckInDTO' with data contract name 'MockCheckInDTO:http://schemas.datacontract.org/2004/07/org.healthwise.gatewayinterfaces.mocks' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known type
Try removing [DataMember] from the private fields, so it's just on the public properties. If you're still having trouble, it might be good for educating yourself on what's going on with your DataContract to, instead of having the DC in a shared library, have it automatically created from the service metadata. Then take a look at it and see if it's what you expect. If not, you'll at least have an idea of what's going wrong when you try to serialize/deserialize the object.
first of all, it is weired that you serialize the same data twice: the private fields and the public properties. As Tim S. said it's better to remove one.
I tried to reproduce your problem by using DataContractSerializer directly, but I failed.
DataContractSerializer serializer = new DataContractSerializer(typeof(MockCheckInDTO));
var data = new MockCheckInDTO();
using (var file = File.OpenWrite("dto.xml"))
using (var xmlWriter = XmlDictionaryWriter.CreateTextWriter(file))
{
serializer.WriteObject(xmlWriter, data);
}
using (var file = File.OpenRead("dto.xml"))
using (var xmlReader = XmlDictionaryReader.CreateTextReader(file, XmlDictionaryReaderQuotas.Max))
{
var result = serializer.ReadObject(xmlReader);
}

Type not exposed by WCF Service

I have a small test web service to emulate something odd I'm noticing in a real world app. As the demo shows the same behaviour as the app I will use the demo for brevity.
In short My service interface file looks as follows (as you can see it is the default WCF service created by VS2008 but I have added a new public method (GetOtherType()) and two new classes at the bottom (SomeOtherType and SomeComplexType). SomeOtherType manages a generic List of type SomeComplexType
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFServiceTest
{
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
[OperationContract]
SomeOtherType GetOtherType();
}
[DataContract]
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
}
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
}
[DataContract]
public class SomeOtherType
{
public List<SomeComplexType> Items { get; set; }
}
[DataContract]
public class SomeComplexType
{
}
}
My Service is implemented as follows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFServiceTest
{
public class Service1 : IService1
{
#region IService1 Members
public string GetData(int value)
{
throw new NotImplementedException();
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
throw new NotImplementedException();
}
#endregion
#region IService1 Members
public SomeOtherType GetOtherType()
{
throw new NotImplementedException();
}
#endregion
}
}
The problem I have is that if I include a service reference to this service in an ASP.NET Web Application, I cannot see SomeComplexType via intellisense. The error relates to the type or namespace cannot be found. However, SomeOtherType can be found (I'm assuming as the type is a return type from one of the public methods).
Am I right in thinking I can't expose a type from a WCF Service if that type is not featured in the method signature of one of my public methods (either return type or argument)? If so, how would I be able to iterate over the Items inside an instance of SomeOtherType on the client?
Many Thanks and I hope this is clear.
Simon
The problem I have is that if I
include a service reference to this
service in an ASP.NET Web Application,
I cannot see SomeComplexType via
intellisense. The error relates to the
type or namespace cannot be found.
However, SomeOtherType can be found
(I'm assuming as the type is a return
type from one of the public methods).
Am I right in thinking I can't expose
a type from a WCF Service if that type
is not featured in the method
signature of one of my public methods
(either return type or argument)? If
so, how would I be able to iterate
over the Items inside an instance of
SomeOtherType on the client?
You are absolutely right - your SomeComplexType is never used in any of the service methods, and it's also never tagged as a [DataMember] in any of the types that are indeed used as parameters in your service methods. Therefore, from the point of view of WCF, it's not needed, and won't show up in the WSDL/XSD for the service.
As Graham already pointed out - you are using the SomeComplexType in one place:
[DataContract]
public class SomeOtherType
{
public List<SomeComplexType> Items { get; set; }
}
but since the Items element is not tagged as a [DataMember], it (and therefore the type it uses) will not be included in the WSDL/XSD of your service. Since the Items are not marked as DataMember, they won't be in your serialized WCF message either, so you won't ever need to iterate over this collection :-)
So most likely, what you really want, is just add the [DataMember] attribute to your Items property; then it'll be included in the WSDL/XSD, and so will the SomeComplexType.
Looks like you need the [DataMember] attribute on your SomeOtherType.Items property, i.e.
[DataMember]
public List<SomeComplexType> Items { get; set; }
I'm not at all an expert on this topic, so just as a shot in the blue: Empty DataContracts are discarded by WCF? Try exposing anything in ComplexDataType (some int is enough) and see if that changes anything.
Also, I believe you can verify the availability of the type using the built-in wcftestclient (you need to turn metadata exchange on for this).
We can use Known type in Service in order to get exposed of the class and its members when it is not used in the operation contract signature directly or indirectly.
For such types where you just want it to be available on the client side even if its not used, below attribute class is handy to make it available on client side.
[KnownType(typeof(SomeComplexType))]