I have developed a webservice, it accepts object as input
namespace Someservice
{
public bool CreateBehavior(ClassInput classInput)
{
// code here
}
}
now I have defined this ClassInput as a datacontract in business entities which is a different namespace
namespace Someservice.BusinessEntities
{
[DataContract]
public class ClassInput
{
[DataMember]
public int intvariable{ get; set; }
[DataMember]
public string stringVariable { get; set; }
}
}
To use this ClassInput in the webservice i have added project reference to Business entities project.
Now how can i expose this datacontract to consumer of this webservice?
For instance in client side, they will need this classInput, but its not exported via service!!
SomeserviceClient someserviceClient = new SomeserviceClient();
someserviceClient.CreateBehavior(classInput)
I read about datacontractsurrogate but i am not able to map that to this scenario
Related
i got a Class Library myCore.dll
it includes a class
public interface IMyClassA {
public string A { get; set; }
public string B { get; set; }
}
public class MyClassA: IMyClassA {
public string A { get; set; }
public string B { get; set; }
}
then i got a console project with a selfhosted http service. it references myCode.dll and does some interfacing with json via http.
but i want to hide Member 'B' of MyClassA if i do serialization in this project.
im using Newtonsoft.Json. But i dont want to reference Newtonsoft in myCode.dll to set the [JsonIgnore] Attribute on MyClassA.B.
so how do i create a custom interface in my console-project that inherits from IMyClassA?
You have to write a custom converter.
If you use NewtonSoft you will have two methods to override: ReadJson(...) and WriteJson(...), one is for serializing the other for deserializing. That way you can write you own code responsible for serializing and deserializing.
Having your own code you can just ignore the member 'B' of MyClassA.
To register the converter, instead of using an anotation on your DTO
[JsonConverter(typeof(MyCustomConvreter))]
public interface IMyClassA {
public string A { get; set; }
public string B { get; set; }
}
which would lead to an undesired reference to Newtonsoft, you can do this to register the custom converter:
var jsonSerializer = new JsonSerializer();
jsonSerializer.Converters.Add(new MyCustomConverter());
Check NewtonSoft docs for a custom converter: https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
In order to use a WCF SOAP service, I am using the "Add a service reference" tool in VS2015.
The problem is when adding the service reference this way, the members of the base class are not exposed in the derived classes.
Here is my interface for my types:
[ServiceKnownType(typeof(DbEntry))]
public interface ILogEntry
{
[DataMember]
Int64 Id { get; set; }
[DataMember]
String Error { get; set; }
[DataMember]
String CustomMessage { get; set; }
[DataMember]
Int32 ApplicationId { get; set; }
Int32 ServiceId { get; set; }
[DataMember]
Guid ApplicationGuid { get; set; }
[DataMember]
String ApplicationName { get; set; }
...etc.
Here it the base class which implements the interface:
[DataContract]
[KnownType(typeof(DbEntry))]
public abstract class LogEntry : ILogEntry
{
private Int64 _Id;
[DataMember]
public Int64 Id
{
get { return _Id; }
set { _Id = value; }
}
private String _Error;
[DataMember]
public String Error
{
get { return _Error; }
set { _Error = value; }
}
private String _CustomMessage;
[DataMember]
public String CustomMessage
{
get { return _CustomMessage; }
set { _CustomMessage = value; }
}
private Int32 _ApplicationId;
[DataMember]
public Int32 ApplicationId
{
get { return _ApplicationId; }
set { _ApplicationId = value; }
}
private Int32 _ServiceId;
public Int32 ServiceId
{
get { return _ServiceId; }
set { _ServiceId = value; }
}
private Guid _ApplicationGuid;
[DataMember]
public Guid ApplicationGuid
{
get { return _ApplicationGuid; }
set { _ApplicationGuid = value; }
}
...etc
Here is my derived class :
[DataContract]
public class DbEntry : LogEntry
{
private String _Message;
[DataMember]
public String Message
{
get { return _Message; }
set { _Message = value; }
}
}
In the client the only datamember that is being exposed in the DbEntry class is Message. When I use the CS file that is generated by accessing the WSDL directly then everything works great.
Can anyone tell me why the base class members aren't exposed in the DbEntry derived class when I use the "Add a service reference" tool?
I commend you for attempting to program your code against interfaces, but I find that doing this out of WCF is not possible. I still program against interfaces, but at the WCF level I include conversion code that takes the Interface instance and translates it into an equivalent WCF Model.
When you discover the service, it will only create objects based on what it can pass in or receive. Your problem also deals with inheritance, as WCF won't extrapolate that for you, either. You must reference both LogEntry and DbEntry as seperate objects that can be either passed in or returned by your Service functions. In other words, you should have a seperate Operation Contract for LogEntry AND DbEntry.
Thanks,
Jibba
Is it your WCF service? If so, place the models and interface for the service in a separate project, and reference that library from both your WCF service and your client application. I often name that something like MyServiceName.Interface.
(Even if it's not your WCF service but you're able to reference the models from your client app this still works.)
Then, when adding the service reference, click "Advanced" and ensure that "Reuse types in referenced assemblies" is checked.
Then, when you add the reference and the proxy class is created it will reuse your models as declared in your interface library. [DataContract] and [DataMember] attributes won't matter. Because you're using the exact classes that you defined (not creating new proxy classes) your inherited properties will be accessible.
If I have a class/interface pair defined in AssemblyA under namespace AssemblyA.Entities:
public IEntity
{
string Name { get; set; }
}
[Serializable]
public Entity : IEntity
{
public string Name { get; set; }
}
And I serialize it to XML using XmlSerializer:
var myEntities = new List<IEntity>();
// myEntities is populated somehow
var data = XmlSerializationManager.Serialize(myEntities);
// 'data' gets saved to disk somewhere as a file
Then, if I duplicate the code/namespaces into AssemblyB so that I have namespace AssemblyA.Entities and the exact same code:
public IEntity
{
string Name { get; set; }
}
[Serializable]
public Entity : IEntity
{
public string Name { get; set; }
}
If I attempt to deserialize the previously serialized XML, will I get back AssemblyB's list of AssemblyA.Entities.IEntity? Or will it fail?
At what point does the serializer stop caring about what it's deserializing to? Can the assembly differ? Can the namespaces differ? Do the type names matter so long as the properties are named the same?
This will work. You will get AssembyB entity.
This is essentially how Web services work when the client would scaffold classes based on information in the wsdl, the client would then deserialise the data from the soap message into these scaffolded classes.
My client has 10 tables that it needs to load via an internal WCF to a server. Since all this is internal, I can write both client and server using whatever technique i want.
On the Client, I thought to use LINQ to load data from the tables to a List, List and so on...
On the Server, I thought to have a [DataContract] as follow:
[DataContract]
[KnownType(typeof(Table1))]
[KnownType(typeof(Table2))]
[KnownType(typeof(Table3))]
public class GenericType<T>
{
[DataMember]
public List<T> Data { get; set; }
}
and then add the classes that will represent the matching Tables on the Client.
[DataContract]
public class Table1
{
[DataMember]
public int UserID { get; set; }
[DataMember]
public string FullName { get; set; }
}
[DataContract]
public class Table2
{
[DataMember]
public int UserID { get; set; }
[DataMember]
public string Address1 { get; set; }
}
[DataContract]
public class Table3
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Description { get; set; }
}
When I create the client reference, i'm NOT getting all the classes declared on the server and it seems that ONLY the 1st [KnownType] specified on the [DataContract] becomes visible to the Client.
I was under the impression that Generics was meant to allow multiple types but am I right to think that WCF can only handle one [KnownType] x class ??
And if so, my only way to code this would be to copy and paste 10 times the GenericType class and on each copy, change the [KnownType] ??
Cause if that's the only solution, then what are the real benefits to use Generic instead of straight defined List, List for my params ??
Any thought will help clarify my mind here
The problem happens because unless ONE of the WCF methods uses any of the CLASSES declared as [DataContract] ...it seems that WCF does NOT brings those classes to the Client.
Is this the expected case?
You could try attributing your interface method with the ServiceKnownType attribute for each of the classes.
There is another option, which is to implement the generic lists in classes that are attributed with CollectionDataContract:
[CollectionDataContract]
public class Table1Collection
Inherits List<Table1>
On the client side, you can the edit Reference.svcmap and enumerate each of the collections in the CollectionMappings section:
<CollectionMappings>
<CollectionMapping TypeName="My.Namespace.Table1Collection" Category="List" />
This allows you to reuse the same code on both ends of the pipe.
I have created two WCF Services (Shipping & PDFGenerator). They both, along with my ClientApp, share an assembly named Kyle.Common.Contracts. Within this assembly, I have three classes:
namespace Kyle.Common.Contracts
{
[MessageContract]
public class PDFResponse
{
[MessageHeader]
public string fileName { get; set; }
[MessageBodyMember]
public System.IO.Stream fileStream { get; set; }
}
[MessageContract]
public class PDFRequest
{
[MessageHeader]
public Enums.PDFDocumentNameEnum docType { get; set; }
[MessageHeader]
public int? pk { get; set; }
[MessageHeader]
public string[] emailAddress { get; set; }
[MessageBodyMember]
public Kyle.Common.Contracts.TrackItResult[] trackItResults { get; set; }
}
[DataContract(Name = "TrackResult", Namespace = "http://kyle")]
public class TrackResult
{
[DataMember]
public int SeqNum { get; set; }
[DataMember]
public int ShipmentID { get; set; }
[DataMember]
public string StoreNum { get; set; }
}
}
My PDFGenerator ServiceContract looks like:
namespace Kyle.WCF.PDFDocs
{
[ServiceContract(Namespace="http://kyle")]
public interface IPDFDocsService
{
[OperationContract]
PDFResponse GeneratePDF(PDFRequest request);
[OperationContract]
void GeneratePDFAsync(Kyle.Common.Contracts.Enums.PDFDocumentNameEnum docType, int? pk, string[] emailAddress);
[OperationContract]
Kyle.Common.Contracts.TrackResult[] Test();
}
}
If I comment out the GeneratePDF stub, the proxy generated by VS2010 realizes that Test returns an array of Kyle.Common.Contracts.TrackResult. However, if I leave GeneratePDF there, the proxy refuses to use Kyle.Common.Contracts.TrackResult, and instead creates a new class, ClientApp.PDFDocServices.TrackResult, and uses that as the return type of Test.
Is there a way to force the proxy generator to use Kyle.Common.Contracts.TrackResult whenever I use a MessageContract? Perhaps there's a better method for using a Stream and File Name as return types?
I just don't want to have to create a Copy method to copy from ClientApp.PDFDocServices.TrackResult to Kyle.Common.Contracts.TrackResult, since they should be the exact same class.
After a lot of extra digging, I realize that it was actually the Enum that "broke" it. It has do with the way DataContractSerializer works vs. XmlSerializer. Long story short, the solution was to turn the Enum into a nullable.
[MessageContract]
public class PDFRequest
{
[MessageHeader]
public Enums.PDFDocumentNameEnum? docType { get; set; }
[MessageHeader]
public int? pk { get; set; }
[MessageHeader]
public string[] emailAddress { get; set; }
[MessageBodyMember]
public Kyle.Common.Contracts.TrackItResult[] trackItResults { get; set; }
}
I ran into the same problem (MessageContract+enums) and your post helped me. Indeed if you explicitly set the enum fields to nullable it works. The issue is that when enums are used, WCF uses the XML serializer which cannot tell null from empty string.
There is a detailed explanation of this behaviour here by one of the actual WCF team members.
In the case of document/literal when using bare messages, WCF falls back to XmlSerializer when handling enum types. ... XmlSerializer treats null as missing by default ... we encounter a schema without nillable="true" ... The detection logic for value types currently only handles primitive value types, it does not check for enums.
In other words WCF does not like enums... But hey, it works, you just need to be aware of it!
You can instruct Visual Studio to re-use classes from referenced assemblies. So if your test project has an assembly reference to the Kyle.Common.Contracts assembly, it should re-use those types defined in there rather than adding new client-side proxy classes.
The switch to enable this is on the Advanced page in the Add Service Reference dialog window - it should be on by default:
Make sure that your project
has an assembly reference to the common data contract assembly
that this setting is really ON when you add the service reference