Our WCF service has just one method:
[ServiceContract(Name = "Service", Namespace = "http://myservice/")]
[ServiceKnownType("GetServiceKnownTypes", typeof(Service))]
public interface IService {
Response Execute(Request request);
}
public class Service : IService {
public static IEnumerable<Type> GetServiceKnownTypes(ICustomAttributeProvider provider) {
return KnownTypesResolver.GetKnownTypes();
}
public Response Execute(Request request) {
return new MyResponse { Result = MyEnumHere.FirstValue };
}
}
Both the Request and Response class includes a ParameterCollection member.
[Serializable]
[CollectionDataContract(Name = "ParameterCollection", Namespace = "http://myservice/")]
[KnownType("GetKnownTypes")]
public class ParameterCollection : Dictionary<string, object> {
private static IEnumerable<Type> GetKnownTypes()
{
return KnownTypesResolver.GetKnownTypes();
}
}
Subclasses of Request and Response store their values into the ParameterCollection value bag.
I am using the KnownTypesResolver class to provide type information across all Service objects.
public static class KnownTypesResolver {
public static IEnumerable<Type> GetKnownTypes()
{
var asm = typeof(IService).Assembly;
return asm
.GetAllDerivedTypesOf<Response>() // an extension method
.Concat(new Type[] {
typeof(MyEnumHere),
typeof(MyEnumHere?),
typeof(MyClassHere),
typeof(MyClassListHere),
});
}
}
If I'm not mistaken, everything should have proper type information for proxy class generation tools to produce well-defined classes client-side.
However, whenever one of the Response subclasses (i.e. MyResponse) contains an enum value such as MyEnumHere, WCF starts complaining that the deserializer has no knowledge of the MyEnumHere value. It should have. I provided a KnownTypeAttribute for this very reason.
The client-side proxy class does have a MyEnumHere enum in the Reference.cs file; the problem is that the ParameterCollection class has no KnownTypeAttributes generated for it.
I resorted to hand-editing and including the following lines in the generated Reference.cs file:
//>
[KnownTypeAttribute(typeof(MyEnumHere))]
[KnownTypeAttribute(typeof(MyEnumHere?))]
[KnownTypeAttribute(typeof(MyClassHere))]
[KnownTypeAttribute(typeof(MyClassListHere))]
//<
public class ParameterCollection : Dictionary<string, object> { /* ... */ }
Hand-editing generated files is horrible. But this makes the clients work. What am I doing wrong? How can I define my Service objects so that the VS-proxy classes that are generated are correct from the get-go?
Thanks for your time.
WCF does not work well with Dictionary because it is not interoperable. You may use Array, List or custom collection to make sure that your data is properly serialized.
Code below uses List<ParamCollectionElement> instead of Dictionary. I also removed some redundant attributes.
[DataContract]
public class Request
{
[DataMember]
public ParameterCollection ParameterCollection { get; set; }
}
[DataContract]
public class Response
{
[DataMember]
public ParameterCollection ParameterCollection { get; set; }
}
[DataContract]
public class MyResponse : Response
{
[DataMember]
public MyEnumHere Result { get; set; }
}
public class ParamCollectionElement
{
public string Key { get; set; }
public object Value { get; set; }
}
[CollectionDataContract(Name = "ParameterCollection")]
public class ParameterCollection : List<ParamCollectionElement>
{
}
public static class KnownTypesResolver
{
public static IEnumerable<Type> GetKnownTypes()
{
return
new Type[] {
typeof(MyEnumHere),
typeof(MyEnumHere?),
typeof(Request),
typeof(Response),
typeof(MyResponse)
};
}
}
[DataContract]
public enum MyEnumHere
{
[EnumMember]
FirstValue,
[EnumMember]
SecondValue
}
[ServiceKnownType("GetServiceKnownTypes", typeof(Service))]
[ServiceContract(Name = "Service")]
public interface IService
{
[OperationContract]
Response Execute(Request request);
}
public class Service : IService
{
public static IEnumerable<Type> GetServiceKnownTypes(ICustomAttributeProvider provider)
{
return KnownTypesResolver.GetKnownTypes();
}
public Response Execute(Request request)
{
var result = new MyResponse
{
Result = MyEnumHere.FirstValue,
ParameterCollection = new ParameterCollection()
};
result.ParameterCollection.Add(new ParamCollectionElement {Key = "one", Value = MyEnumHere.FirstValue});
result.ParameterCollection.Add(new ParamCollectionElement { Key = "two", Value = new Response() });
return result;
}
}
Make sure you have [DataContract] on your enum and [EnumMember] on each of the enum members:
[DataContract]
public enum MyEnumHere
{
[EnumMember]
SomeValue,
[EnumMember]
OtherValue,
[EnumMember]
OneMoreValue
}
That should cause the proxy-enum to be built out (with its member values) in your client without having to manually change the Reference.cs file.
Related
I have defined the data contract as follows:
[DataContract]
public class TestResult
{
[DataMember]
public string[] NegResponses { get; set; }
[DataMember]
public bool Pass { get; set; }
[DataMember]
public string Request { get; set; }
[DataMember]
public string Response { get; set; }
}
Is it possible to return a list of the above type in the operation contract as follows:
[OperationContract]
[FaultContract(typeof(TestFault))]
List<TestResult> Tester(string nodeCaption);
And what else I have to look into to return a list of a type that has been defined ?
By the way I guess I am not using svcutil and instead using channel factory as follows:
private static readonly ITestService TestClient;
// initialize a channel factory
var channelFactory = new ChannelFactory<ITestService>(new NetTcpBinding(SecurityMode.None), endPoint);
// Create a channel
TestClient = channelFactory.CreateChannel();
The client-side and the server-side should share the same service contract and data contract. This is fundamental that we could consume the service on the client-side.
Here is an example, wish it is useful to you.
Server-side.
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:21011");
BasicHttpBinding binding = new BasicHttpBinding();
using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
{
sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior()
{
HttpGetEnabled=true
};
sh.Description.Behaviors.Add(smb);
}
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpBinding();
sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
sh.Opened += delegate
{
Console.WriteLine("Service is ready");
};
sh.Closed += delegate
{
Console.WriteLine("Service is clsoed");
};
sh.Open();
Console.ReadLine();
//pause
sh.Close();
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
List<Product> SayHello();
}
public class MyService : IService
{
public List<Product> SayHello()
{
return new List<Product>()
{
new Product()
{
ID=1,
Name="Apple"
},
new Product()
{
ID=2,
Name="Pear"
}
};
}
}
[DataContract(Namespace = "MyNamespace")]
public class Product
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
}
Client-side (Console application,we call the service with ChannelFactory).
class Program
{
static void Main(string[] args)
{
BasicHttpBinding binding = new BasicHttpBinding();
Uri uri = new Uri("http://10.157.13.69:21011");
ChannelFactory<IService> factory = new ChannelFactory<IService>(binding, new EndpointAddress(uri));
IService service = factory.CreateChannel();
try
{
var result = service.SayHello();
foreach (var item in result)
{
Console.WriteLine($"ID:{item.ID}\n,Name:{item.Name}");
}
}
catch (Exception)
{
throw;
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
List<Product> SayHello();
}
[DataContract(Namespace = "MyNamespace")]
public class Product
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
}
Data Contract should be decorated with Namespace property, this will guarantee that the serialization and deserialization process properly). Actually, the service contract also needs a namespace to represent the practical method name, which will be used to addressing the method on the server-side. but there is a default value in the namespace.
http://tempuri.org
Feel free to let me know if there is anything I can help with.
Hello i am trying to create a hierarchy of classes using a discriminated unionand it seems i can't serialize them.I keep getting this error :
Newtonsoft.Json.JsonSerializationException: 'Self referencing loop detected for property 'AsRun' with type 'MsgPattern.Message+Run'. Path ''.'
Base class
[Serializable]
public abstract partial class Message {
public enum Type {
WALK = 0,
RUN = 1
}
protected abstract Type Discriminator { get; }
public Type Kind => this.Discriminator;
internal static Message Create(string data) {
var message = JsonConvert.DeserializeObject<Message>(data);
switch (message.Kind) {
case Type.RUN:message= message.AsRun;break;
case Type.WALK:message= message.AsWalk;break;
}
return message;
}
[JsonIgnore]
public bool IsWalk => this.Kind==Type.Walk;
[JsonIgnore]
public bool IsRun => this.Kind==Type.Run;
[JsonIgnore]
public Message.Walk AsWalk => this as Message.Walk;
[JsonIgnore]
public Message.Run AsRun => this as Message.Run;
}
Dervived
partial class Message {
public class Run : Message {
protected override Type Discriminator => Type.RUN;
public string Location { get; set; }
public int Speed { get; set; }
}
}
partial class Message {
public class Walk : Message {
protected override Type Discriminator => Type.WALK;
public int Gait { get; set; }
public bool IsJogging { get; set; }
}
}
Usage
class Program {
static void Main(string[] args) {
Message.Run run = new Message.Run { Location = "asa", Speed = 33 };
string data = JsonConvert.SerializeObject(run);
Message msg=Message.Create(data);
}
}
I will get these type of messages via json and i want to be able to do actions based on their type. I do not understand why i can't serialize them .
P.S I know it's a self-referencing loop but I need those As[something] and Is[Something] fields.
I'm building some services which have a common header. This header has a certain layout in the request, and a different one in the response (i.e., there are two classes).
However, when I add a reference or use svcutil, the proxy is generated with the same header in both request and response types.
For instance:
[MessageContract]
class Contract<THeader, TBody>
{
[MessageHeader] public THeader Header { get; set; }
[MessageBodyMember] public TBody Body { get; set; }
}
class MyRequestHeader
{
public string RequestorId { get; set; }
}
class MyResponseHeader
{
public string ErrorMessage { get; set; }
}
The OperationContract is something like:
[OperationContract]
public Contract<MyResponseHeader, ResponseBody> Process(Contract<MyRequestHeader, RequestBody> data);
The proxy becomes something like:
var client = new ...;
var header = new MyRequestHeader();
var body = new RequestBody();
**ResponseBody**
response = client.Process(ref header, body);
As you can see, the header (Request) is passed as ref; That probably means WCF is having this header as the same in request and response. And the MyResponseHeader disappears.
Anyone can shed some light on the subject?
There is something strange going on here.
I tried to reproduce your problem, and got the following results (i had to mark some types public, and added [DataContract] to your header classes).
Here is a view of the WSDL:
The generated code (svcutil 4.0.30319.18046) uses MyRequestHeader in the Response message as well:
This is caused by the following XSD:
As you can see there is only one instance generated for the "Header" class.
I tried creating types for the generic classes, as follows:
[MessageContract]
public abstract class Contract<THeader, TBody>
{
[MessageHeader]
public THeader Header { get; set; }
[MessageBodyMember]
public TBody Body { get; set; }
}
[DataContract(Name="RequestHeader")]
public class MyRequestHeader
{
public string RequestorId { get; set; }
}
[DataContract(Name = "ResponseHeader")]
public class MyResponseHeader
{
public string ErrorMessage { get; set; }
}
[MessageContract]
public class RequestContract : Contract<MyRequestHeader, string>
{ }
[MessageContract]
public class ResponseContract : Contract<MyResponseHeader, string>
{ }
[ServiceContract]
public interface IService1
{
[OperationContract]
ResponseContract Process(RequestContract data);
}
But that did not fix the problem, the generated client ResponseContract still is generated using a Header of type RequestHeader.
Even changing the service code to use two diffrent message contracts:
[DataContract(Name="RequestHeader")]
public class MyRequestHeader
{
public string RequestorId { get; set; }
}
[DataContract(Name = "ResponseHeader")]
public class MyResponseHeader
{
public string ErrorMessage { get; set; }
}
[MessageContract]
public class RequestContract<TBody>
{
[MessageHeader]
public MyRequestHeader Header { get; set; }
[MessageBodyMember]
public TBody Body { get; set; }
}
[MessageContract]
public class ResponseContract<TBody>
{
[MessageHeader]
public MyResponseHeader Header { get; set; }
[MessageBodyMember]
public TBody Body { get; set; }
}
[ServiceContract]
public interface IService1
{
[OperationContract]
ResponseContract<string> Process(RequestContract<string> data);
}
does not solve the problem:
Even removing all shared inheritance and generics as follows:
[DataContract(Name="RequestHeader")]
public class MyRequestHeader
{
public string RequestorId { get; set; }
}
[DataContract(Name = "ResponseHeader")]
public class MyResponseHeader
{
public string ErrorMessage { get; set; }
}
[MessageContract(WrapperName="RequestMessage")]
public class RequestContract
{
[MessageHeader]
public MyRequestHeader Header { get; set; }
[MessageBodyMember]
public string Body { get; set; }
}
[MessageContract(WrapperName = "ResponseMessage")]
public class ResponseContract
{
[MessageHeader]
public MyResponseHeader Header { get; set; }
[MessageBodyMember]
public string Body { get; set; }
}
[ServiceContract]
public interface IService1
{
[OperationContract]
ResponseContract Process(RequestContract data);
}
still results in the RequestHeader being used in the ResponseMessage.
I think the answer lies somewhere in this documentation:
WSDL Considerations
When generating a Web Services Description
Language (WSDL) contract from a service that uses message contracts,
it is important to remember that not all message contract features are
reflected in the resulting WSDL [sic]. Consider the following points: WSDL
cannot express the concept of an array of headers. When creating
messages with an array of headers using the
MessageHeaderArrayAttribute, the resulting WSDL reflects only one
header instead of the array.
The resulting WSDL document may not reflect some protection-level
information.
The message type generated in the WSDL has the same name as the class
name of the message contract type.
When using the same message contract in multiple operations, multiple
message types are generated in the WSDL document. The names are made
unique by adding the numbers "2", "3", and so on, for subsequent uses.
When importing back the WSDL, multiple message contract types are
created and are identical except for their names.
I am using a Generic Class as a Response Data Contract. All is good and this is streamlining the design of my WCF service significantly.
Each request is given a standard response object with the following signature:
Status (Enum)
Message (String)
Result (T)
Below is the Response Class:
[DataContract]
public class Response<T>
{
public Response() {}
public Response(T result)
{
this.result = result;
if (result != null)
{
this.status = Status.StatusEnum.Success;
}
else
{
this.status = Status.StatusEnum.Warning;
}
}
public Response(T result, Status.StatusEnum status)
{
this.status = status;
this.message = message;
}
public Response(T result, Status.StatusEnum status, string message)
{
this.status = status;
this.message = message;
this.result = result;
}
[DataMember]
public Status.StatusEnum status { get; set; }
[DataMember]
public string message { get; set; }
[DataMember]
public T result { get; set; }
}
And this works brillantly. Only problem I have is that the WCF Client is given a really crappy name for this object "ResponseOfAccountnT9LOUZL"
Is there a way to get around this issue?
Should I be using this class as just a Abstract class which is inherited?
I'd rather not have multiple classes cluttering my code.
Ok found the Answer
You can specify the Serialised version using the following syntax:
[DataContract(Name = "MyClassOf{0}{1}")]
class MyClass { }
So if I had a Class called Response which takes a Generic T parameter
I would use
[DataContract(Name = "ResponseOfType{0}")]
class Response { }
[DataContract(Name = "ReturnObjectOfType{0}")]
public class ReturnObject<T>
{....
//Iservice
[OperationContract]
ReturnObject<AdresKisiBilgi> BeldeAdresKisiBilgiSorgula(string tcKimlikNo);
//Service
public ReturnObject<HbysBusiness.MernisGuvenService.AdresKisiBilgi> BeldeAdresKisiBilgiSorgula(string tcKimlikNo)
{
return new MernisBiz().BeldeAdresKisiBilgiSorgula(tcKimlikNo);
}
client:
public ReturnObjectOfAdresKisiBilgi BeldeAdresKisiBilgiSorgula(string tcKimlikNo)
{....
Thank you Harry
I have classes in my service defined as
[DataContract]
[KnownType(typeof(MyConcrete1)) ]
[KnownType(typeof(MyConcrete2)) ]
public abstract class MyAbstract
{
[DataMember]
public int AbsInt { get; set; }
}
[DataContract]
public class MyConcrete1 : MyAbstract
{
[DataMember]
public int Concrete1Int { get; set; }
}
[DataContract]
public class MyConcrete2 : MyAbstract
{
[DataMember]
public int Concrete2Int { get; set; }
}
and in my Service, I use it as
[ServiceContract]
public interface IService1
{
[OperationContract]
MyAbstract TestAbstract(MyAbstract value);
}
As you can see, the method TestAbstract takes and returns the abstract parameter MyAbstract, however in the client proxy generated by SvcUtil, the type "MyAbstract" is not abstract! It generated a concrete class.
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name = "MyAbstract", Namespace = "http://schemas.datacontract.org/2004/07/WcfService")]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(WcfService.MyConcrete1))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(WcfService.MyConcrete2))]
public partial class MyAbstract : object, System.Runtime.Serialization.IExtensibleDataObject
{
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
private int AbsIntField;
public System.Runtime.Serialization.ExtensionDataObject ExtensionData
{
get
{
return this.extensionDataField;
}
set
{
this.extensionDataField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute()]
public int AbsInt
{
get
{
return this.AbsIntField;
}
set
{
this.AbsIntField = value;
}
}
}
How can I force svcUtil to generate MyAbstract as an abstract class? I'm stuck, please help...
You cannot - svcutil cannot know that MyAbstract class is abstract. The metadata that's being exchanged between server and client just doesn't support such a concept. The SOA world doesn't always support everything the OO world has in store.
The service metadata only knows about things like services, method calls, and data contracts - anything else is not part of the service metadata.
You will need to add some extra logic and tweaking to the client code, once it's been creating, if that's a real requirement on your side.
Marc