Dealing with WebMessageBodyStyle.Wrapped when making a GET request with RestSharp - restsharp

I have a simple object:
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
that is being returned from a WCF service with the following Operational Contract:
[OperationContract]
[WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "/Countries/{aId}")]
[return: MessageParameter(Name = "Country")]
Country Country(string aId);
Basically the object is wrapped inside a Country:{} block which is what I want. On the client side I do the following:
private void RequestPrepare(out RestClient aRestClient, out RestRequest aRestRequest, string aRequestUri, Method aRequestMethod = Method.GET)
{
aRestClient = new RestClient(BASE_URL);
aRestRequest = new RestRequest(aRequestUri, aRequestMethod);
aRestRequest.AddHeader("Accept", "application/json");
}
and call like so:
RestClient restClient;
RestRequest restRequest;
RequestPrepare(out restClient, out restRequest, "Countries/{aId}");
restRequest.AddUrlSegment("aId", "1"); // replaces matching token in request.Resource
var restResponse = restClient.Execute<Country>(restRequest);
List<Country> listCountry = new List<Country>();
listCountry.Add(new Country {Id = restResponse.Data.Id, Name = restResponse.Data.Name});
return listCountry;
the JSON response is:
Content = "{\"Country\":{\"Id\":1,\"Name\":\"Australia\"}}"
so basically restResponse.Data.Id and restResponse.Data.Name do not contain valid data because of the "Country" wrapping.
1) So how do I configure RestSharp to deal with the Country{} wrap?
2) As a secondary question how do I make the Restsharp call so that it will automatically deserialise List

Related

How to create an ASP.NET Core API controller action for HttpPost that can accept a request that has content of type MultipartFormDataContent

I am working on an ASP.NET Core 2.0 API in VS2017.
I want to create a controller action for an HTTP Post method that accept string and byte[] values that I will then use to create records in my SQL database.
From what I understand, if I want to post both string data and a byte[] that represents a file, I have to use MultipartFormDataContent as the type of content in the request from my client.
So, on the API controller action, how is that mapped? Can I have a DTO class in the API that has properties for both the string values and the byte[] value and have it passed into the API controller action via the [FromBody]UploadsDto dto
For example, have a DTO class like this...
public class UploadFileRecordForCreationDto
{
public int LocationId { get; set; }
public string FileName { get; set; }
public byte[] UploadedFile { get; set; }
}
Then have a controller action with this signature...
[HttpPost(Name = "CreateUploadFileRecord")]
public IActionResult CreateUploadFileRecord([FromBody]UploadFileRecordForCreationDto dto)
{
...
...
...
return CreatedAtRoute("GetUploadedFileFile", new { id = linkedResourceToReturn["Id"] }, linkedResourceToReturn);
}
And then have that API action accept a request created using something similar to what I am doing with this test console application on the client side;
static async Task CreateUploadFileRecordAsync()
{
httpClient.BaseAddress = new Uri("https://localhost:44369");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string relativeUrl = "/api/UploadFilesManager";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, relativeUrl);
HttpResponseMessage response = new HttpResponseMessage();
using (var content = new MultipartFormDataContent("--UploadTest"))
{
var values = new[]
{
new KeyValuePair<string,string>("LocationId","1"),
new KeyValuePair<string,string>("FileName","TestFile-01.txt"),
};
foreach (var keyvaluepair in values)
{
content.Add(new StringContent(keyvaluepair.Value, Encoding.UTF8, "application/json"), keyvaluepair.Key);
}
var fileContent = new ByteArrayContent(File.ReadAllBytes(#"C:\testfile-01.txt"));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue(DispositionTypeNames.Attachment)
{
Name = "UploadedFile",
FileName = "testfile-01.txt"
};
content.Add(fileContent);
request.Content = content;
response = await httpClient.SendAsync(request);
}
if (response.IsSuccessStatusCode)
{
string result = response.Headers.Location.ToString();
Console.WriteLine("Success:\n");
Console.WriteLine($"New Record Link: [{result}]\n");
}
else
{
Console.WriteLine($"Failed to create new UploadFile record. Error: {0}\n", response.ReasonPhrase);
}
}
If it doesn't just map to a DTO in the FromBody, can anyone provide an example of how to deal with this use case?

Wcf Client: Passing XML string in the WCF REST service using WebInvoke

With out parameter for Display method it is working in browser i.e http://localhost:2617/UserService.svc/test
When i add one parameter i am unable to browse it also.
I have the following contract.
[ServiceContract]
public interface IUserService
{
[OperationContract]
[WebInvoke(Method="PUT",UriTemplate = "/tes/{name}",
BodyStyle=WebMessageBodyStyle.WrappedRequest)]
string Display(string name);
}
public string Display(string name)
{
return "Hello, your test data is ready"+name;
}
I am trying to call using the following code
string url = "http://localhost:2617/UserService.svc/test"; //newuser
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
string xmlDoc1 = "<Display xmlns=\"\"><name>shiva</name></Display>";
req.Method = "POST";
req.ContentType = "application/xml";
byte[] bytes = Encoding.UTF8.GetBytes(xmlDoc1);
req.GetRequestStream().Write(bytes, 0, bytes.Length);
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
Stream responseStream = response.GetResponseStream();
var streamReader = new StreamReader(responseStream);
var soapResonseXmlDocument = new XmlDocument();
soapResonseXmlDocument.LoadXml(streamReader.ReadToEnd());
I am unable to get output for that.please help me on this.
There are a few things that are not quite right in your code.
Client
On the client you need to specify the namespace to be tempuri, since you have not declared an explicit one, so your client code would need to be this:
string url = "http://localhost:2617/UserService.svc/test"; //newuser
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
string xmlDoc1 = "<Display xmlns=\"http://tempuri.org/\"><name>shiva</name></Display>";
req.Method = "POST";
req.ContentType = "application/xml";
byte[] bytes = Encoding.UTF8.GetBytes(xmlDoc1);
req.GetRequestStream().Write(bytes, 0, bytes.Length);
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
Stream responseStream = response.GetResponseStream();
var streamReader = new StreamReader(responseStream);
var soapResonseXmlDocument = new XmlDocument();
soapResonseXmlDocument.LoadXml(streamReader.ReadToEnd());
Service
On the service the UriTemplate is not quite right - you are specifying /tes/{name} so that will be expecting a URL like http://localhost:2617/UserService.svc/tes/shiva but you are wanting to post XML data to it in the body so you should change that to UriTemplate = "/test" (I am assuming you meant test and not tes as in your question).
Also, the method should be POST if you are wanting to POST data to it (the client needs to match the service and I am assuming what you have on the client is what you want).
So, in conclusion, your IUserService should look like this:
[ServiceContract]
public interface IUserService
{
[OperationContract]
[WebInvoke(Method = "POST",
UriTemplate = "/test",
BodyStyle = WebMessageBodyStyle.WrappedRequest)]
string Display(string name);
}
You still need to create a class
public class Test
{
public string name { get; set; }
}
You can also use fiddler to check if {name:999} could be passed as a parameter.

Simple WCF POST with Uri Template

I thought this would be incredibly simple, but I must be missing something. I am trying to make a simple WCF POST request in conjunction with a UriTemplate. I have read numerous examples where people use a stream paramater as the last paramater, and this is supposed to pick up the POST body. I can only get this to work if the stream is the only paramater.
I've gone back to basics with a simple Hello World service.
Here is my code on the client
static string Test()
{
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("http://localhost:1884/MyAPI/Service.svc/HelloWorld");
req.Method = "POST";
req.ContentType = "text/plain";
Stream reqStream = req.GetRequestStream();
byte[] fileToSend = System.Text.UTF8Encoding.UTF8.GetBytes("sometext");
reqStream.Write(fileToSend, 0, fileToSend.Length);
reqStream.Close();
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
var sr = new StreamReader(resp.GetResponseStream());
return sr.ReadToEnd();
}
And this is the code on the service
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "HelloWorld")]
Stream HelloWorld(Stream content);
}
public Stream HelloWorld(Stream content)
{
var sr = new StreamReader(content);
string text = sr.ReadToEnd();
return new System.IO.MemoryStream(Encoding.UTF8.GetBytes("Hello World! " + text));
}
This all works fine. Then I make this change:
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "HelloWorld/test/{test}")]
Stream HelloWorld(string test, Stream content);
}
public Stream HelloWorld(string test, Stream content)
{
var sr = new StreamReader(content);
string text = sr.ReadToEnd();
return new System.IO.MemoryStream(Encoding.UTF8.GetBytes("Hello World! " + text + test));
}
And change the client code to hit HelloWorld/test/sometext
I get a 500 Internal Server Error. I've tried about 10 different variations including using a ?key=value type UriTemplate, returning strings instead of streams etc, and no luck.
Feels like I'm missing some tiny little thing that is going to make this work, as I have seen countless examples of exactly this all over the web. Theirs works, mine doesn't.
Any ideas?
I am not sure what went wrong, but after trying everything, I resolved this by creating a new project and copying all the code over. Never worked out what the differences were, maybe something got corrupted
Edit: in the end we discovered we had to specify WebServiceHostFactory in the Service.svc. This was there by default in the new project
When streaming, the Stream must be the only parameter: http://msdn.microsoft.com/en-us/library/ms789010.aspx
You may be able to use message headers: Add filename and length parameter to WCF stream when Transfermode = Stream
You can use new single file WCF model to configure and adjust endpoint behaviour. I combined your contract and service class into one file to show you how to do this.
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;
namespace StreamService
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MergedEndpoint
{
[WebGet(RequestFormat = WebMessageFormat.Xml, UriTemplate = "Data/{someid}",
ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public string GetData(string someid)
{
return string.Format("You entered: {0}", someid);
}
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, UriTemplate = "HelloWorld",
ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public Stream HelloWorld1(Stream content)
{
var sr = new StreamReader(content);
string text = sr.ReadToEnd();
return new System.IO.MemoryStream(Encoding.UTF8.GetBytes("Hello World from single file! " + text));
}
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, UriTemplate = "HelloWorld/test/{testparam}",
ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public Stream HelloWorld2(string testparam, Stream content)
{
var sr = new StreamReader(content);
string text = sr.ReadToEnd();
return new System.IO.MemoryStream(Encoding.UTF8.GetBytes("Hello World from single file! " + testparam+ text));
}
}
}
Input parameters need to be same name as method params. Their type is also string. You need to do the conversion if you want different input param.
You need to create WCf project and add Global.asax file with routing info for this file. You may need to add reference to System.ServiceModel.Activation to setup routing.
Example:
protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
RouteTable.Routes.Add(new ServiceRoute("MergedEndpoint", new WebServiceHostFactory(), typeof(MergedEndpoint)));
}
Your Client code has one change to content type.
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("http://localhost:55166/MergedEndpoint/HelloWorld/test/234");
req.Method = "POST";
//req.ContentType = "text/plain";
req.MediaType = "HTTP/1.1";
req.ContentType = "application/json; charset=utf-8";
Stream reqStream = req.GetRequestStream();
byte[] fileToSend = System.Text.UTF8Encoding.UTF8.GetBytes("sometext");
reqStream.Write(fileToSend, 0, fileToSend.Length);
reqStream.Close();
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
var sr = new StreamReader(resp.GetResponseStream());
string outp = sr.ReadToEnd();
Console.WriteLine("Response:"+outp);
You can still read the raw content even if it is set to Json type.

passing collection or array type input parameter wcf service

I have written a WCf Service which has a Collection type input body parameter and another parameter as query string as following:
[WebInvoke(Method = "PUT", UriTemplate = "users/role/{userID}",BodyStyle=WebMessageBodyStyle.WrappedRequest)]
[OperationContract]
public bool AssignUserRole(int userID,Collection<int> roleIDs)
{
//do something
return restult;
}
Now when I am trying to pass this parameter I am getting de serializing error. I have tried following format:
<AssignUserRole xmlns="http://tempuri.org/">
<roleIDs>
<roleID>7</roleID>
</roleIDs>
</AssignUserRole>
<AssignUserRole xmlns="http://tempuri.org/">
<ArrayOfroleID>
<roleID>7</roleID>
</ArrayOfroleID>
</AssignUserRole>
<AssignUserRole xmlns="http://tempuri.org/">
<ArrayOfint>
<int>7</int>
</ArrayOfint>
</AssignUserRole>
Can some one help me how can I pass this Array(Collection type Body parameter)?
Thanks.
The correct format would be this:
<AssignUserRole xmlns="http://tempuri.org/">
<roleIDs xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>7</a:int>
<a:int>8</a:int>
</roleIDs>
</AssignUserRole>
One easy way to find out what the expected format is for a certain operation is to use a WCF client with the same contract, send a message with it and look at the operation using Fiddler. The program below does that.
public class StackOverflow_6339286
{
[ServiceContract]
public interface ITest
{
[WebInvoke(Method = "PUT", UriTemplate = "users/role/{userID}", BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[OperationContract]
bool AssignUserRole(string userID, Collection<int> roleIDs);
}
public class Service : ITest
{
public bool AssignUserRole(string userID, Collection<int> roleIDs)
{
return true;
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
host.Open();
Console.WriteLine("Host opened");
WebChannelFactory<ITest> factory = new WebChannelFactory<ITest>(new Uri(baseAddress));
ITest proxy = factory.CreateChannel();
proxy.AssignUserRole("1234", new Collection<int> { 1, 2, 3, 4 });
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
Also notice that there's a problem in your UriTemplate: the path variable {userId} cannot be of type int (it must be a string). This is fixed in the sample code above.
One more thing: if you don't want to use the default namespace for collections / arrays, you can use a [CollectionDataContract] class to change it. If instead of using Collection you used the class below, then the first body you tried should work:
[CollectionDataContract(Namespace = "http://tempuri.org/", ItemName = "roleID")]
public class MyCollection : Collection<int> { }

Calling a WCF Service without a Service reference

I have a project A which has a Service reference to a WCF Service. I want to invoke the service in project B without a Service reference.
From what I understand, the Service reference is just a way to generate the proxy and config and is not used at run-time.
I copied the proxy class and the node from project A to project B.
Can I just create an instance of the proxy class in project B and expect it to directly use the config. items and connect to the service without anything else?
(I cannot try this right now)
Short answer yes.
As long as you have the interface, a way to connect to the service, you can create a channel and talk to service without having the reference.
The reference simply makes it easier for you to develop against.
Look into creating channels from your service contracts with ChannelFactory.
Here is a working copy. For me it works fine and returns List
private List<MyClass> GetAllSiteDetailsJSON(string language)
{
Uri address =
new Uri(#"http://weburlpath/MyService/MyService.svc/GetAllList/"
+ language);
HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest;
request.Method = "GET";
request.ContentType = "application/x-www-form-urlencoded";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
StreamReader reader = new StreamReader(response.GetResponseStream());
string jSon = reader.ReadToEnd();
reader.Close();
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
List<MyClass> result = jsSerializer.Deserialize<List<MyClass>>(jSon);
return result;
}
}
And Here is the class
public class MyClass
{
public string ID { get; set; }
public string Name { get; set; }
public List<Location> Locations { get; set; }
}
public class Location
{
public string Region { get; set; }
public string Country { get; set; }
}