Problem passing object between WCF and ASMX using DataContracts - c#

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.

Related

Returning a Service reference object in WCF

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?

RESTAPI body parameters are passed null when calling in c#

I am trying to call a rest api method from c#. Problem is for all content types it passes null to body parameter.I shared my code below.Apart from this code I have tried to write body parameter to request as stream.It didn't work either. I have also tried 'application/x-www-form-urlencoded' as content type.
Calling rest api method from c# sample:
string token = Server.UrlEncode("v0WE/49uN1/voNwVA1Mb0MiMrMHjFunE2KgH3keKlIqei3b77BzTmsk9OIREken1hO9guP3qd4ipCBQeBO4jiQ==");
string url = "http://localhost:2323/api/Applications/StartProcess?token=" + token;
string data = #"{""ProcessParameters"": [{ ""Name"":""flowStarter"",""Value"": ""Waffles"" }],
""Process"": ""RESTAPISUB""}";
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.BaseAddress = new System.Uri(url);
byte[] cred = UTF8Encoding.UTF8.GetBytes("username:password");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(cred));
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
System.Net.Http.HttpContent content = new StringContent(data, UTF8Encoding.UTF8, "application/json");
HttpResponseMessage messge = client.PostAsync(url, content).Result;
string description = string.Empty;
if (messge.IsSuccessStatusCode)
{
string result = messge.Content.ReadAsStringAsync().Result;
description = result;
}
Rest api Method:
[HttpPost]
[ActionName("StartProcess")]
public int StartProcess([FromUri]string token,[FromBody]WorkflowStartParameters parameters)
{
try
{
LoginInformation info = CasheProcesses.ReadCashe(token);
eBAWSAPI api = Service.GetWSService();
WorkflowProcess proc = api.StartProcess(info.Id, info.Password, info.ImpersonateUserId, info.Language, parameters);
return proc.ProcessId;
}
catch (Exception ex)
{
throw new Exception("An error occured when starting process,exception detail:" + ex);
}
}
WorkflowStartParameters class structure:
public class WorkflowStartParameters
{
public WorkflowParameter[] ProcessParameters;
public string Process { get; set; }
}
public class WorkflowParameter
{
public string Name { get; set; }
public string Value { get; set; }
}
I have searched this problem a lot. It seems as a very common problem. I just found this solution working properly, passing request parameter to rest api method and reading body parameter from there. But it is not a valid solution for me.
If you have any idea,feel free to share.
Thanks,
Zehra
I don´t know if it can solve your problem, but let me try.
I guess you don´t have to utilize Server.UrlEncode in your call, but:
Dim myUri As New Uri(Token)
And I guess you must not encode also your username and password - try pass them as string.
Your problem appear to be here:
public class WorkflowStartParameters
{
public WorkflowParameter[] ProcessParameters; <---- needs get/set
public string Process { get; set; }
}
This needs to be a public property to serialize properly. Currently you have it set up as a public field. Just add { get; set; } and give that a try. I would also look into serializing with Newtonsoft.Json to ensure your object is properly serialized. Trying to do it with escape strings will be messing the more data you are sending.
By the way there can be issues sometimes serializing arrays, I would change that to :
public List<WorkflowParameter> ProcessParameters{get;set;}
Finally I have achieved to send filled out data to server. It was about serialization problem. But it didn't work with json serialization before send data. I have added DataContract attribute to my class and it works properly.
Unfortunately still I couldn't figure out this when I make ajax calls from java script it works without DataContract attribute but if I call it in c# it needs DataContract attribute. If someone share the information about this I would appreciate!
I am sharing new class structure, everything else but this still same:
[Serializable]
[DataContract]
public class WorkflowParameter
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Value { get; set; }
}
[Serializable]
[DataContract]
public class WorkflowStartParameters
{
[DataMember]
public WorkflowParameter[] ProcessParameters { get; set; }
[DataMember]
public string Process { get; set; }
}

ServiceStack: JsonServiceClient usage without IReturn in DTO

What I would like to do is the following:
var client = new JsonServiceClient(ServiceUrl);
var request = new FooQuery {Id = 1};
IEnumerable<Project> response = client.Get(request);
However, my FooQuery doesn't implement any IReturn, and I'd like it not to (it's in a library without ServiceStack references). Here's my service side:
Library of business objects:
public class ProjectQuery
{
public int Id { get; set; }
}
AppHost:
Routes.Add<ProjectQuery>("/project", "GET");
Service:
public object Get(Foo request)
{
// do stuff.
}
Is there some nice, clean way to create the JsonServiceClient without using the IReturn interface on my business object?
Looks like there's no way not to use IReturn if you don't want to provide a URL to the JsonServiceClient Get() requests. Just decided to create another set of DTOs in my ServiceStack implementation, that are essentially mirrors of the real DTOs in another library. Then when a request comes in to my SS DTO, I create the other library's DTO, set each property, and pass it along.
Not pretty, but that's the best I could find so far.
I had the same problem using IReturn and Routes, as I wanted to use the DTOs
in assemblies with business logic, without ServiceStack references.
It worked for me, using in the Client Model
public class TestRequest
{
public int vendorId {get; set; }
public string barcode {get; set; }
public string username { get; set; }
public string password { get; set; }
}
then in the AppHost
Routes.Add<TestRequest( "/TestAPI/Reservation/{vendorId}/{barcode}"," GET,OPTIONS")
.Add<TestRequest>("/TestAPI/Reservation", "POST, OPTIONS")
and the call for JsonServiceClient with POST
request.vendorId=12344;
request.barcode="AAS1223";
TestResponse response = client.Post<TestResponse>(server_ip + "/TestAPI/Reservation", request);
OR with GET
TestResponse response = client.Get<TestResponse>(server_ip + "/TestAPI/Reservation/12344/AAS1223?username=John&password=99");
Then in the service Get or Post functions
public TestResponse Get(TestRequest request)
{
// request members hold the values of the url.
return DoBusinessLayerWork(request);
}
Using the Send() method from the JsonServiceClient type is the way to go about doing this.

MessageBodyMembers with same return type and name causing exception - Element has already been exported

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)]

Order of returned WCF JSON

I have a WCF service that returns some JSON from a serialized object:
public class Response
{
public string Token { get; set; }
public int UserId { get; set; }
...
}
I've added some extra properties to this class, but now some of the implementations fails because they read it like:
string[] ResultLoginValues = e.Result.ToString().Split(',');
and it's returned in alphabetically order instead of the old order with the new properties last.
Is there any way I can change the order, or should they rewrite the clients?
For solution refer WCF DataContract DataMember order

Categories