I have written a core webservice. This webservice handles all the calculations, data operations, etc.
This webservice is in the same solution as my microservice.
My microservice Should communicate with my core webservice, and return the object it received from the core webservice. The problem is that it won't work, as when I start my solution it is unable to translate the reference object for the data contract.
The exception on the WCF test client:
error CS0644: 'System.ComponentModel.PropertyChangedEventHandler'
cannot derive from special class 'System.MulticastDelegate'
When I added the COREservice as a reference, It auto-generated classes that implemented INotifyPropertyChanged.
Now, I of course could write extensive converters that would convert all the objects received from the core webserver to object with the same name, but locally defined, only this would be a lot of work and I doubt there isn't any other faster/more elegant way of solving this.
IService:
[OperationContractAttribute(AsyncPattern = true)]
IAsyncResult BeginOperation(string Salesperson, decimal Timestamp, AsyncCallback asyncCallback, object state);
CoreWebservice.ReturnObj EndOperation (IAsyncResult result);
Service:
public CoreWebservice.ReturnObj Operation(string Salesperson = null, decimal? Timestamp = null, OperationContext opContext = null)
{
CoreWebservice.ReturnObj result = CoreService.Operation(Salesperson, Timestamp ?? default(decimal));
return result;
}
Endpoint:
<client>
<endpoint address="http://localhost:8733/Design_Time_Addresses/WcfServiceLibrary1/Service1/"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_M3ApiCalls"
contract="COREservice.ApiCalls" name="BasicHttpBinding_M3ApiCalls" />
</client>
All the classes or "Resultobjects" are written in the same manner (paired with a result, and a recordresult class)
[Serializable()]
[DataContract]
[XmlSerializerFormat()]
[XmlRoot(ElementName = "CRS100MI_List",DataType = "System.Xml.XmlElement",Namespace = "http://www.the.namespace/of/company")]
public class CRS100MI_ListResult
{
[DataMember]
[System.Xml.Serialization.XmlElement(Order = 0)]
public string Result = "";
//etc....
[DataMember]
[System.Xml.Serialization.XmlElementAttribute(Order = 3)]
public List<CRS100MI_ListRecordResult> Record = new List<CRS100MI_ListRecordResult>();
public CRS100MI_ListResult Parse(List<Dictionary<string, string>> list)
{
//parse a list of dictionaries to fill the fields of the
//recordresult, and return the entire object populated with records.
return this;
}
}
[Serializable()]
[DataContract(Namespace = "http://www.rexel.nl/M3/ApiCalls")]
[XmlSerializerFormat()]
[XmlRoot(ElementName = "CRS100MI_ListRecord", DataType = "System.Xml.XmlElement", Namespace = "http://www.the.namespace/of/company")]
public class CRS100MI_ListRecordResult
{
[DataMember]
[System.Xml.Serialization.XmlElementAttribute(Order = 0)]
public string Result { get; set; }
[DataMember]
[System.Xml.Serialization.XmlElementAttribute(Order = 1)]
public string ErrorMessage { get; set; }
[DataMember]
[System.Xml.Serialization.XmlElementAttribute(Order = 2)]
public List<string> Messages { get; set; }
//etc...
}
So, to summarize:
COREservice is referenced by service reference by the MICROservice
COREservice returns an object, which is to be returned by the MICROservice
MICROservice is dependent on the COREservice
Error CS0644 is thrown, because it probably isn't able to derive the full class from the COREservice
Is this maybe solvable by how I reference to the COREservice? or is there perhaps another solution I have overlooked?
Related
I have a WCF service that implements a data contract. I then have a client that consumes that service with it's own implementation of the data contract.
If the data contracts don't match exactly, it doesn't generate any sort of error, nor does it return any data.
public class RecipeClient : ClientBase<IRecipeService>, IRecipeService
{
public RecipeEntity[] GetAllRecipes()
{
var recipe = Channel.GetAllRecipes();
return recipe;
}
}
In the above example, after the call is made, recipe contains an empty array of RecipeEntity.
I would expect it to not return any data, but why doesn't it generate an error?
It is for backward compatibility. If you add in datacontract of existing service some not required properties, all existing clients will work without errors.
As if was mentioned it's for backward compatibility, but you can mark some properties as required. And if there is no such property in a message, an exception will be thrown:
[DataContract]
public class Recipe
{
[DataMember]
public string Name { get; set; }
[DataMember(IsRequired = true)]
public string Rank { get; set; }
}
I have a WCf service with Contracts shown below.
[MessageContract]
public class ServiceRequest
{
[MessageBodyMember]
public int RequestId { get; set; }
[MessageBodyMember]
public OrderDetails OrderDetails { get; set; }
}
[DataContract]
public class OrderDetails
{
[IsLogRequired]
public int OrderId { get; set; }
[IsLogRequired]
public int Quantity { get; set; }
public string CustomerName { get; set; }
}
[IsLogRequired] is custom Attribute.
We need to get all properties in the request which have "[IsLogRequired]" attribute when the request is received. We want to do it as generic solution so that it can be plugged into all services.
We thought of using "MessageInspector" to do this implementing "IDispatchMessageInspector".
How do i get the actual request object from "System.ServiceModel.Channels.Message" parameter of IDispatchMessageInspector.AfterReceiveRequest() method?
Please correct me if i am using a wrong interface or wrong method. Any other solution to this?
I am assuming that "[IsLogRequired] is custom property." means a custom attribute...
Simple answer is that there is no solution to transfer custom attributes that are decorating the data contract as you described it.
Data contracts should be pure and not encumbered by business logic. The know how about the what should be done with various fields belongs to a service implementation.
Possible approach could look like this:
public class OrderService : IOrderService
{
private void ProcessOrder(Order order)
{
var ra = new AuditMetadataResourceAccess();
MethodInfo[] fieldsToLog = ra.GetLoggingFields(typeof(OrderDetal));
if (fieldsToLog.Any())
{
var logger = new LogingEngine();
logger.Log(fieldsToLog, order.OrderDetails);
}
}
}
You could move this implementation inside message inspector or operation invoker. Carlos Figueira has extensive description of each WCF extensibility point.
"How do i get the actual request object from "System.ServiceModel.Channels.Message" parameter of IDispatchMessageInspector.AfterReceiveRequest() method?"
I am assuming you are referring to Web request. WebOperationContext.Current but you need to have ASP.NET Compatibility Mode turned on.
I am following a pattern that has objects for the Request and Response of a WCF service. I have multple request objects that have the same return type and name. Any help would be greatly appreciated.
I'm getting the following exception:
An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
System.InvalidOperationException: An exception was thrown in a call to a WSDL export extension: System.ServiceModel.Description.DataContractSerializerOperationBehavior
contract: http://tempuri.org/:IService ----> System.InvalidOperationException: The Service.ServiceContract.IService.RetrieveUsers operation references a message element [http://tempuri.org/:WeekEndingId] that has already been exported from the Service.ServiceContract.IService.RetrieveDepartments operation. You can change the name of one of the operations by changing the method name or using the Name property of OperationContractAttribute. Alternatively, you can control the element name in greater detail using the MessageContract programming model.
Edit: I have used the Name attribute on the properties to give them unique names and this does resolve the issue but we are needing to use the name "WeekEndingId" for all requests. I would like to try and find a fix for this while still using the same name for the property.
Listed below are the classes that are causing the issues:
RetrieveDepartmentsRequest:
[MessageContract(WrapperName = "RetrieveDepartmentsRequest", WrapperNamespace = "http://Service.V1")]
public class RetrieveDepartmentsRequest
{
[MessageBodyMember(Order = 0)]
public int WeekEndingId { get; set; }
[MessageBodyMember(Order = 1)]
public string UserId { get; set; }
[MessageBodyMember(Order = 2)]
public string MachineName { get; set; }
}
RetrieveUsersRequest:
[MessageContract(WrapperName = "RetrieveUsersRequest", WrapperNamespace = "http://Service.V1")]
public class RetrieveUsersRequest
{
[MessageBodyMember(Order = 0)]
public int WeekEndingId { get; set; }
[MessageBodyMember(Order = 1)]
public string UserId { get; set; }
[MessageBodyMember(Order = 2)]
public string MachineName { get; set; }
}
IService:
[OperationContract]
[FaultContract(typeof(ServiceFault))]
RetrieveDepartmentsResponse RetrieveDepartments(RetrieveDepartmentsRequest request);
[OperationContract]
[FaultContract(typeof(ServiceFault))]
RetrieveUsersResponse RetrieveUsers(RetrieveUsersRequest request);
I believe it's a type conflict. In that:
WeekEndingId is an int in RetrieveDepartmentsRequest and a decimal in RetrieveUsersRequest.
Use MessageBodyMember's Name property to resolve the conflict.
OR
just change the name of the property in RetrieveUsersRequest.
OR BETTER yet: shouldn't WeekEndingId always be an int?
This exception can also occur when using the MessageHeader attribute in a MessageContract. What I discovered is that all MessageHeaders which are used in across all OperationContracts within a given ServiceContract must contain a distinct "Name" for the data type used.
Basically you can't have OperationContracts:
* void MethodA(MessageContractA a)
* void MethodB(MessageContractB b)
where the MessageContractA object has a MessageHeader declared with a name "prop1" and the MessageContractB object has a MessageHeader declared with the same "prop1" name but a different data type.
This wrecks havoc on the Mex Metadata creation in conjunction with the wsdl.
I believe that issue you are seeing is because the WrapperNamespace is the same for both objects. I think what you want to do is:
[MessageContract(WrapperNamespace = "USEFUL_NAMESPACE_HERE.RetrieveDepartmentsRequest")]
and
[MessageContract(WrapperNamespace = "USEFUL_NAMESPACE_HERE.RetrieveUsersRequest")]
or you could try:
[MessageContract(IsWrapped = false)]
I have a WCF service that passes back and forth the following DataContracts:
[DataContract]
public class RequestWrapper
{
[DataMember]
public FooDataContract FooDataContract;
}
[DataContract]
public class ResponseWrapper
{
[DataMember]
public FooDataContract FooDataContract;
}
[DataContract]
public class FooDataContract
{
public FooDataContract(string data, Guid id)
{
Data = data;
ID = id;
}
[DataMember]
public string Data { get; set; }
[DataMember]
public Guid ID { get; set; }
}
It's called via a proxy class like this:
void CallService(string data)
{
var id = Guid.NewGuid();
var response = proxy.CallService(new RequestWrapper
{
new FooDataContract(data, id);
});
}
This is then passed (over the service) to the database via a repository using EF:
public void RepoMethod(FooDataContract foo)
{
var guid = foo.ID; // - Breakpoint here shows all zeros!
efContext.DoSomething(foo.Data, foo.ID);
}
Here's the service call:
public ResponseWrapper CallService(RequestWrapper request)
{
var foo = request.FooDataContract;
repository.RepoMethod(foo);
var response = new ResponseWrapper{ FooDataContract = foo };
return response;
}
Here's the proxy:
public class Proxy : IMyService
{
static readonly ChannelFactory<IMyService> channelFactory =
new ChannelFactory<IMyService>("IMyService");
ResponseWrapper CallService(RequestWrapper request)
{
return channelFactory.UseService(s => s.CallService(request));
}
}
internal static class UseServiceFunction
{
internal static R UseService<T, R>
(this ChannelFactory<T> channelFactory, Func<T, R> useService)
{
var service = channelFactory.CreateChannel();
try
{
R response = useService(service);
return response;
}
finally
{
var channel = service as ICommunicationObject;
try
{
if (channel.State != CommunicationState.Faulted) channel.Close();
}
catch { channel.Abort(); }
}
}
}
I've put a watch on the Guid in the VS debugger. When the service is called from a client web application, the generated Guid is a valid Guid of seemingly random hex characters. Great, that's working.
But when the data is serialized, goes over the wire, and comes out the other side (in my repository), the Guid is all zeros!
I've double, triple checked that the Guid is indeed marked with the [DataMember] attribute. I'm wondering if the extra layer of DataContract (how a FooDataContract is wrapped with the RequestWrapper data contract) is causing a serialization issue?
I think your problem here is that the constructor you've made in your DataContract class doesn't get passed to the proxy on the client side. WSDL won't know anything about this. Think of your data contracts as just a place to stick data with no other functionality. To confirm, you can look in the reference.cs class that got generated in the client when you added the service reference.
I'd suggest re-writing the code so that you explicitly set each of the values in your data contract rather than relying on the constructor.
You can also write a hand coded proxy that has whatever behavior you want and then share that file with the client. That would work, but then you'll be more tightly coupling your client to your service.
Turns out, my translation layer wasn't updated to convert between the DTOs! Whooooops!
I'm trying to use ASMX/WCF to pass objects between sites (public / private). I can get the serialized object from my private ASMX service to my public WCF service, but I can't deserialize the object. Code below followed by error.
WCF service that calls a private ASMX service.
[WebGet(UriTemplate = "{part}")]
public Distributor GetDistributorInventory(string part)
{
const string url = "http://www.site.com/service/lookup.asmx/StockCheck?part=" + part;
//This is a wrapper for a HttpWebRequest that returns a string
string results = WebHelper.HttpRequest("GET", "text/xml", null, url, new CookieContainer());
byte[] byteArray = Encoding.ASCII.GetBytes(results);
MemoryStream stream = new MemoryStream(byteArray);
DataContractSerializer deserialize = new DataContractSerializer(typeof(Distributor));
return (Distributor)deserialize.ReadObject(stream);
}
Contract used in both Public/Private Services
[DataContract(Namespace = "http://www.site.com/Services/", Name = "Inventory")]
public class Inventory
{
[DataMember(Order = 1)]
public string MPN{ get; set; }
[DataMember(Order = 2)]
public string DataSheetURL { get; set; }
[DataMember(Order = 3)]
public List<Distributor> Stock { get; set; }
}
[DataContract(Namespace = "http://www.site.com/Services/", Name = "Distributor")]
public class Distributor
{
[DataMember(Order = 1)]
public string Name { get; set; }
[DataMember(Order = 2)]
public string Part { get; set; }
[DataMember(Order = 3)]
public int Quantity { get; set; }
[DataMember(Order = 4)]
public string URL { get; set; }
}
Error Message:
Error in line 1 position 166. Expecting element 'Distributor' from namespace 'http://www.site.com/Services/'.. Encountered 'Element' with name 'Inventory', namespace 'http://www.site.com/Services/'.
I might be going about this the entirely wrong way, so suggestions on a better approach (with sample) would greatly appreciate. My end goal is to pass objects between WCF & WCF or ASMX services using custom objects and DataContracts.
Looks like it's trying to deserialize as Distributor but the response from the StockCheck call is returning a Inventory.
The asmx service is probably not using the DataContractSerialiser. Here is short video on on how to do custom serialisation in a the ASMX service.
Or you could deserialise in the WCF service using the same serialiser that the asmx service is using (XmlSerialiser?)
Another option is to use the Json serialiser instead. There is an example here.
Here is the final solution I found for making this work, with the least amount of work possible.
I switched from passing my object as XML to JSON (was my final goal, tho I started with XML)
After getting my JSON object received, I noticed it had a wrapper "d:" and had a property "__type:" that was being added. Knowing these needed to be removed, I decided to find a generic way to remove the elements.
My generic solution was to use this code/article on Codeplex that had extension methods that did the clean-up using Reg Ex. Very simple.
Here is my resulting code:
[WebGet(UriTemplate = "{part}")]
public Distributor GetDistributorInventory(string part)
{
const string url = "http://www.site.com/service/lookup.asmx/StockCheck";
string results = WebHelper.HttpRequest("POST", "application/json; charset=utf-8", "{part: " + part + "}", url, new CookieContainer());
Inventory inventory = new JavaScriptSerializer().CleanAndDeserialize<Inventory>(results);
return inventory;
}
Another perk to this solution, is the ability to convert the object regardless of namespace. This results in being able to use the object "sender.Inventory" in my source ASMX service and the object "receiver.Inventory" in my WCF service where I'm going to consume the object.