Create/Consume REST WebService using WCF and Universal Apps - c#

I want to create an IIS-hosted webservice which I will consume using a universal windows store aoo (windows phone/windows 8.1/windows RT).
As I understand universal applications do not support proxy class generation and SOAP calls using "Add service reference" so I need to create a RESTful webservice and manually consume it in the universal application.
I've tried dozens of tutorials and approaches throughout the net but I never managed to actually POST data to the webservice.
I need to send objects of a custom class which is defined in a shared library to the webservice. I understand that I will need to serialize the Object and include it in the POST request, however no matter what I try I end up with different issues - e.g HTTP 400 Bad Request: The incoming message has an unexpected message format 'Raw'. The expected message formats for the operation are 'Xml'; 'Json'.
I've seen several approaches to manually set the content type header, however the methods I found are not available in a universal application.
Can someone provide information or an example which is fitting my scenario (POST-ing via universal app)?
update 1: For further clarification: I am aware how WCF works and I was already able to complete a basic GET request like described in this post. However I was unable to extend that to also work with POST requests.
Some code I've tried:
public async static void SendStartup(CustomClass customObject)
{
var httpClient = new HttpClient();
var serialized = JsonConvert.SerializeObject(customObject);
var response = await httpClient.PostAsync("http://localhost:49452/Metrics.svc/LogStartup", new StringContent(serialized));
string content = await response.Content.ReadAsStringAsync();
}
Web Service Interface:
[OperationContract]
[WebInvoke(UriTemplate = "LogStartup", Method="POST", BodyStyle=WebMessageBodyStyle.Wrapped)]
string LogStartup(CustomClass obj);
Implementation:
public void LogStartup(CustomClass obj)
{
// nothing
}
This for example failes at runtime with the error mentioned above

There are two problem with your code.
1) You have to send the Content-Type header while your are making a request
var content = new StringContent(serialized,Encoding.UTF8,"application/json");
2) You have to use BodyStyle = WebMessageBodyStyle.Bare
WebMessageBodyStyle.Bare can work with one parameter as in your example, but if you want to post more parameters then you have to use WebMessageBodyStyle.Wrapped but then, your object you post should be modified as
var serialized = JsonConvert.SerializeObject(new { obj = customObject });
Here is a working code you can test with self-hosted WCF service
async void TestRestService()
{
var ready = new TaskCompletionSource<object>();
Task.Factory.StartNew(() =>
{
var uri = new Uri("http://0.0.0.0:49452/Metrics.svc/");
var type = typeof(Metrics);
WebServiceHost host = new WebServiceHost(type, uri);
host.Open();
ready.SetResult(null);
},TaskCreationOptions.LongRunning);
await ready.Task;
var customObject = new CustomClass() { Name = "John", Id = 333 };
var serialized = JsonConvert.SerializeObject(new { obj = customObject });
var httpClient = new HttpClient();
var request = new StringContent(serialized,Encoding.UTF8,"application/json");
var response = await httpClient.PostAsync("http://localhost:49452/Metrics.svc/LogStartup", request);
string content = await response.Content.ReadAsStringAsync();
}
[ServiceContract]
public class Metrics
{
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped)]
public string LogStartup(CustomClass obj)
{
return obj.Name + "=>" + obj.Id;
}
}
public class CustomClass
{
public string Name { set; get; }
public int Id { set; get; }
}
PS: If you want to return a json response then you can use ResponseFormat=WebMessageFormat.Json. You should then change the WebInvoke attribute as
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped,ResponseFormat=WebMessageFormat.Json)]
BTW: You can still dynamically choose the returned content type(xml or json) by setting AutomaticFormatSelectionEnabled.

Have you seen this article?
How to use HttpClient to post JSON data
Basically it seems like you need to add more parameters to your StringContent() constructor like this:
new StringContent(serialized, System.Text.Encoding.UTF8, "application/json");

One thing you need to know about Windows Communication Foundation is the ABC's.
A : Address
B : Binding
C : Contract
So the theory is incredibly simple, though while your coding it, it is quite odd. A simple tutorial can be found here or here. Several other tutorials can be found at Code Project for this exact approach.
Understanding Polymorphism may be helpful for understanding Windows Communication Foundation as it relies heavily on it.
[ServiceContract]
public interface IContent
{
[OperationContract]
void DoSomething(SomeModel model);
}
So what you're doing here is defining your service, defining your method. As I mentioned above we've explicitly declared our contract but we haven't implemented our method. Also we intend to pass SomeModel which would be our Data Contract.
We will build our model:
[DataContract]
public class SomeModel
{
[DataMember]
public string Name { get; set; }
}
The model can be incredibly simple like above, or incredibly complex. It will depend on usage.
Now we would like to implement our method:
public class Content : IContent
{
public void DoSomething(SomeModel model)
{
// Implementation
}
}
Now on the client, you simply consume your service. Once you understand the basics and how it serializes and deserializes you can use it for REST. Which tutorials also exist for that.

Related

Convert Web API backward compatible with WCF

Lets say I have a WCF service Service1.svc that contains GetData(value).
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
}
I also have a client that already auto-generate the proxy to consume this service, something like this:
using (var client = new ServiceReference1.Service1Client())
{
var result = client.GetData(1);
//Assert.AreEqual("You entered: 1", result);
}
Now, I removed that WCF service and replaced it with a new Web API service, something like this:
[RoutePrefix("Service1.svc")]
public class DataController : ApiController
{
[HttpGet]
[Route("GetData")]
public string GetDataOld(int value)
{
return string.Format("You entered: {0}", value);
}
}
So when I try to use the client Service1Client() it doesn't work anymore. I am pretty sure this is possible but what I have to do in order to accomplish this goal?
Update 05/23/2016
Since this is not possible, I decided to create a proxy so the client(s) can easily implement the new Restful Web API.
WebApi is designed to be REST endpoints, so the wcf way of var client = new ServiceReference1.Service1Client() is counter to the concept.
Rather you should try to access it through the url, for your example it'll be something like ~\Service1.svc\GetData\1. Also check your route configuration to be sure.
Your ServiceReference1.Service1Client() is directly related to WCF. You cannot reuse it for interacting with WebApi endpoints.
If you want a client that interacts with your WebApi endpoints, you will need to write a wrapper client yourself. RestSharp is my library of choice for such a thing. Example:
var client = new RestClient("http://localhost:8000");
int value = 12345;
var request = new RestRequest($"Service1.svc/GetData/{value}", Method.GET);
IRestResponse response = client.Execute(request);
var content = response.Content; // returns "You entered: 12345"
(untested)
This is possible, and we are currently doing something similar - we replaced our WCF/SOAP service endpoints with WebAPI/JSON endpoints, without changing the client's proxies.
To do this, you have to use the WebHttpBinding and add some more attributes to your service interface so that the proxy generates the right calls:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet]
string GetData(int value);
[OperationContract]
[WebInvoke(Method="POST", RequestFormat = WebMessageFormat.Json)]
void PostData(MyDataType data);
}
This will make the proxy use HTTP calls, rather than SOAP calls, to access your service. As you can see, this works both for GET and POST requests, and automatically serializes your data as JSON (or XML, if you prefer).
There are more ways to customize it - like using the UriTemplate property to specify more complex HTTP routes (or have two methods, GetData and PostData, both map to the same route and differ only by verb, the RESTful way). And there are caveats as well - from what I could see, you can only pass strings to GET requests, not ints. But all in all, it works quite well.
Would we have chosen to go with WCF proxies mapped to WebAPI endpoints if we had started the project with WebAPI/REST? Probably not. But this allowed us to switch back-ends with relatively little front-end code changes.

WCF Restful returning HttpResponseMessage wants to negotiate when setting content

I have a WCF Restful service and I would like the methods to return HttpResponseMessage because it seems structured rather than just returning the data or the exception or whatever else might make its way there.
I am assuming this is a correct, if not let me know, but my problem is what happens when I try to set HttpResponseMessage.Content. When I do this, the client in which I made the RESTful call request authentication.
Here is my code:
In the interface:
[WebGet(UriTemplate = "/GetDetailsForName?name={name}"
, ResponseFormat = WebMessageFormat.Json)]
HttpResponseMessage GetDetailsForName(string name);
In the class:
public HttpResponseMessage GetDetailsForName(string name)
{
HttpResponseMessage hrm = new HttpResponseMessage(HttpStatusCode.OK)
{
//If I leave this line out, I get the response, albeit empty
Content = new StringContent("Hi")
};
return hrm;
}
I wanted to try to use Request.CreateResponse but I can't seem to get to Request from my WCF Restful method. OperationContext.Current.RequestContext does not have CreateResponse.
Any pointers?
Unfortunately this will not work. The demonstrated code says:
Construct an HttpResponseMessage object, serialize it with a JSON serializer and pass the result over the wire.
The problem is HttpResponseMessage is disposable and not meant to be serialized, while StringContent cannot be serialized at all.
As to why you are redirected to an authentication form -
the service throws an exception when it cannot serialize StringContent
and returns a 400 HTTP status code which gets interpreted as an authentication issue.
I had a similar error, but not quite the same. I was trying to serialize a plain object and was getting an net::ERR_Conection_Reset message. The wcf method executed 7 times and never threw an exception.
I discovered I had to annotate the class I was returning so that my JSON serializer would understand how to serialize the class. Here is my wcf method:
[OperationContract]
[WebGet(
UriTemplate = "timeexpensemap",
ResponseFormat = WebMessageFormat.Json)]
public TimeexpenseMap timeexpensemap() {
string sql = "select * from blah"
DbDataReader reader = this.GetReader(sql);
TimeexpenseMap tem = null;
if (reader.Read()) {
tem = new TimeexpenseMap();
// Set properties on tem object here
}
return tem;
}
My original class which failed to serialize had no annotations:
public class TimeexpenseMap {
public long? clientid { get; set; }
public int? expenses { get; set; }
}
The annotated class serialized without issues:
[DataContract]
public class TimeexpenseMap {
[DataMember]
public long? clientid { get; set; }
[DataMember]
public int? expenses { get; set; }
}
If I am calling, for example a public string getDetails(int ID) and an error is thrown, this works ...
catch(Exception ex)
{
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.OK; //this returns whatever Status Code you want to set here
response.StatusDescription = ex.Message.ToString(); //this can be accessed in the client
return "returnValue:-998,message:\"Database error retrieving customer details\""; //this is returned in the body and can be read from the stream
}

WCF Json deserialization preserve polymorphic collection types

WCF Json deserialization.
I'm building a middleware webservice in WCF using Dotnet 4.5, This server returns a polymorphic type.
[DataContract]
[KnownType(typeof(SomethingA))]
[KnownType(typeof(SomethingB))]
public class Something
{
[DataMember]
public int Item1 { get; set; }
[DataMember]
public string Item2 { get; set; }
}
[DataContract]
public class SomethingA : Something
{ }
[DataContract]
public class SomethingB : Something
{ }
/// <summary>
/// Contract for a service for testing various web operations.
/// </summary>
[ServiceContract]
[ServiceKnownType(typeof(SomethingA))]
[ServiceKnownType(typeof(SomethingB))]
public interface ITesting
{
/// <summary>
/// Test passing in and returning an object using POST and json.
/// </summary>
[OperationContract]
[WebInvoke(
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "use-polymorphic-somethings",
Method = "POST")]
List<Something> UsePolymorphicSomethings();
}
/// <summary>
/// Implementation of the ITesting service contract.
/// </summary>
public class Testing : ITesting
{
public List<Something> UsePolymorphicSomethings()
{
List<Something> retVal = new List<Something>();
retVal.Add(new SomethingA { Item1 = 1, Item2 = "1" });
retVal.Add(new SomethingB { Item1 = 1, Item2 = "1" });
return retVal;
}
}
On the client side I'm attempting to deserialize this in such a way as to preserve the different types in the collection. The MSDN documentation for this seems really weak to me. The first issue I encountered was that adding a reference to System.Web.Http created an undocumented dynamic dependency on on a third party open source component called Newtonsoft.Json which I had to download off the web.
The first two deserialization approaches fail, but I have found a third approach that works.
What I'd like to know is why do the first two approaches fail? Ideally I'd like to get the first approach to work as that is the most streamlined.
[TestMethod]
public void UsePolymorphicSomethings_Test1()
{
using (HttpClient http = new HttpClient())
{
http.BaseAddress = new Uri("http://localhost:8733/");
HttpResponseMessage response = http.PostAsJsonAsync(
"Design_Time_Addresses/InSite8WebServiceLib2/Testing/use-polymorphic-somethings",
new StringContent(string.Empty)).Result;
List<Something> ret = response.Content.ReadAsAsync<List<Something>>().Result;
// FAILS.
Assert.AreEqual(typeof(SomethingA), somethings[0].GetType());
Assert.AreEqual(typeof(SomethingB), somethings[1].GetType());
}
}
[TestMethod]
public void UsePolymorphicSomethings_Test2()
{
using (HttpClient http = new HttpClient())
{
http.BaseAddress = new Uri("http://localhost:8733/");
HttpResponseMessage response = http.PostAsJsonAsync(
"Design_Time_Addresses/InSite8WebServiceLib2/Testing/use-polymorphic-somethings",
new StringContent(string.Empty)).Result;
string ret1 = response.Content.ReadAsStringAsync().Result;
Newtonsoft.Json.JsonSerializerSettings s = new Newtonsoft.Json.JsonSerializerSettings();
s.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All;
List<Something> r = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Something>>(ret1, s);
// FAILS.
Assert.AreEqual(typeof(SomethingA), somethings[0].GetType());
Assert.AreEqual(typeof(SomethingB), somethings[1].GetType());
}
}
[TestMethod]
public void UsePolymorphicSomethings_Test3()
{
using (HttpClient http = new HttpClient())
{
http.BaseAddress = new Uri("http://localhost:8733/");
HttpResponseMessage response = http.PostAsJsonAsync(
"Design_Time_Addresses/InSite8WebServiceLib2/Testing/use-polymorphic-somethings",
new StringContent(string.Empty)).Result;
Stream stream = response.Content.ReadAsStreamAsync().Result;
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List<Something>));
List<Something> somethings = (List<Something>)serializer.ReadObject(stream);
// SUCCEEDS.
Assert.AreEqual(typeof(SomethingA), somethings[0].GetType());
Assert.AreEqual(typeof(SomethingB), somethings[1].GetType());
}
}
From my understanding, you are "worried" about the stream lining of the code that you have written. Seeing that you have the code working in your last method, and I hope my reasons for it working and the others not explain it to your satisfaction. We can still stream line you method a bit using a simple helper.
public T Deserialize<T>(Stream stream) where T : class
{
var serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}
you can then simply call this method like so
List<Something> somethings = Deserialize<List<Something>>(stream);
To make things maybe even easier in a sense, you could write the helper method as a extension method,
something like this
public static class Helpers
{
public static T Deserialize<T>(this Stream stream) where T : class
{
var serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}
}
You could then call this method like this
var result = stream.Deserialize<List<Something>>();
To go all the way up, you could create the extension method against the HttpResponseMessage
public static class Helpers
{
public static T Deserialize<T>(this HttpResponseMessage response) where T : class
{
var stream = response.Content.ReadAsStreamAsync().Result;
var serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}
}
You could call this method like this
var result = response.Deserialize<List<Something>>();
At least your code will now again be a one liner and if you in the future change your serializer, you will only have to change your client code in a single place. you might want to check the code out, as I don't have visual studio open currently to test it out for you. But it looks good to me.
I am adding a new helper example here, so there are more options / fixes to choose from.
public static class Helpers
{
public static Task<T> ReadAsAsyncCustom<T>(this HttpContent content)
{
var formatters = new MediaTypeFormatterCollection();
formatters.Clear();
formatters.Add(new JsonMediaTypeFormatter { UseDataContractJsonSerializer = true });
return content.ReadAsAsync<T>(formatters);
}
}
and this one could be used as follows
List<Something> ret = response.Content.ReadAsAsyncCustom<List<Something>>().Result;
The reason I call clear on the formatter variable in the helper method, is because the constructor of the MediaTypeFormatterCollection creates default formatters, we are not interested in those, so I clear them and add in just 1 formatter that we know works with your solution.
I generally try stick to the DRY rule, which is the reason I try keep "customisation" isolated in just a single place, so that when things change, I don't need to go through all the source and try remember or search for all the instances where it may have been used.
to put things in another way as well, while the framework does support your scenario, it of course requires a settings change if you will. If you were not using what you call polymorphic types, the standard out of the box methods would work great. I wrote up a solution mimicking yours this morning, and I could not see a quick way that could help you get away without making changes on your client side.
In my opinion, methods 1 and 2 instantiate an object of type T and then set its properties by reading the stream. In other words, these methods only known the type "Something" so they can only instantiate "Something". Third method also uses attributes DataContract and KnownType, so it is able to instantiate the known types "Something", "SomethingA" and "SomethingB".

POST to Web API action with IEnumerable<Interface> type parameter

I am trying to post to a Web API method from a client, as follows:
// Create list of messages that will be sent
IEnumerable<IMessageApiEntity> messages = new List<IMessageApiEntity>();
// Add messages to the list here.
// They are all different types that implement the IMessageApiEntity interface.
// Create http client
HttpClient client = new HttpClient {BaseAddress = new Uri(ConfigurationManager.AppSettings["WebApiUrl"])};
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Post to web api
HttpResponseMessage response = client.PostAsJsonAsync("Communications/Messages", messages).Result;
// Read results
IEnumerable<ApiResponse<IMessageApiEntity>> results = response.Content.ReadAsAsync<IEnumerable<ApiResponse<IMessageApiEntity>>>().Result;
My Web API controller action looks like this:
public HttpResponseMessage Post([FromBody]IEnumerable<IMessageApiEntity> messages)
{
// Do stuff
}
The problem I am having is that messages is always empty (but not null) when coming into the web API controller action. I have verified in the debugger that the messages object on the client side does have items in it right before being posted.
I suspect it might have something to do with the interface type not being converted to a concrete type when trying to pass the objects, but I don't know how to go about making it work. How can I achieve this?
I figured out how to do it without a custom model binder. Posting the answer in case anyone else has this issue...
Client:
// Create list of messages that will be sent
IEnumerable<IMessageApiEntity> messages = new List<IMessageApiEntity>();
// Add messages to the list here.
// They are all different types that implement the IMessageApiEntity interface.
// Create http client
HttpClient client = new HttpClient {BaseAddress = new Uri(ConfigurationManager.AppSettings["WebApiUrl"])};
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Post to web api (this is the part that changed)
JsonMediaTypeFormatter json = new JsonMediaTypeFormatter
{
SerializerSettings =
{
TypeNameHandling = TypeNameHandling.All
}
};
HttpResponseMessage response = client.PostAsync("Communications/Messages", messages, json).Result;
// Read results
IEnumerable<ApiResponse<IMessageApiEntity>> results = response.Content.ReadAsAsync<IEnumerable<ApiResponse<IMessageApiEntity>>>().Result;
Add to Register method in WebApiConfig.cs:
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
The key is to send the type as part of the json and turn on automatic type name handling, so that web API can figure out what type it is.
Why you use interface type in method? Looks like web API, has no idea what kind of instance should be used for materialize messages argument. Seems that you have to write custom model binder for this action.
I had a similar problem a few weeks ago with .NET Core WebAPI.
The proposed solution of adding the line below did not work for me:
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
I ended up creating a generic object that can carry my IEnumerable, where T is my intended class
[Serializable]
public class GenericListContainer<T> where T : class
{
#region Constructors
public GenericListContainer()
{
}
public GenericListContainer(IEnumerable<T> list)
{
List = list;
}
#endregion
#region Properties
public IEnumerable<T> List { get; set; }
#endregion
}
Then I changed my webapi method to :
[Route("api/system-users/save-collection-async")]
[HttpPost]
[ProducesResponseType(typeof(string), 200)]
public async Task<IActionResult> SaveSystemUserCollectionAsync([FromBody] GenericListContainer<SystemUser> dto)
{
var response = await _systemUserService.SaveSystemUserCollectionAsync(dto.List);
return Ok(response);
}
This method returns the saved user's id (Guid in my case).
Hope this helps someone else!

Invoking an ASP.NET web service method via an http request

I want to invoke an ASP.NET web service via an http POST request using C# (i.e. I don't want to use the SoapHttpClientProtocol object generated by running wsdl.exe).
As far as I can tell, the process involves:
creating an HttpWebRequest object which points to the url/method of the web service, with the method;
Creating a SOAP xml envelope;
Serialising any parameters I want to pass to the web method using an XmlSerializer;
Making the request, and parsing the response.
I would like to do this without having to copy and use generated code.
(1) seems pretty straightforward;
(2) I don't know if the envelope here is standard, or how it should change depending on the webservice method I am calling. I guess I might need to add custom soap headers if required by the service?
(3) What is the process of doing this? I assume that I need to do something like this:
MyClass myObj;
XmlSerializer ser = new XmlSerializer(myObj.GetType());
TextWriter writer = new StringWriter();
ser.Serialize(writer, myObj);
string soapXml = writer.ToString();
writer.Close();
Also, I guess I should add the soapXml to the soap:Body element
(4) I believe I should extract and deserialize the contents of the soap:Body element as well. Is it OK to use the reverse of the process in (3)?
Thanks,
K.
I don't know why I am doing this but here's an example of invoking a web service manually. Please promise to never use this in a production code.
Suppose you had the following SOAP service:
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service1 : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld(Foo foo)
{
return "Hello World";
}
}
You can invoke it manually like this:
class Program
{
static void Main(string[] args)
{
using (WebClient client = new WebClient())
{
client.Headers.Add("SOAPAction", "\"http://tempuri.org/HelloWorld\"");
client.Headers.Add("Content-Type", "text/xml; charset=utf-8");
var payload = #"<?xml version=""1.0"" encoding=""utf-8""?><soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema""><soap:Body><HelloWorld xmlns=""http://tempuri.org/""><foo><Id>1</Id><Name>Bar</Name></foo></HelloWorld></soap:Body></soap:Envelope>";
var data = Encoding.UTF8.GetBytes(payload);
var result = client.UploadData("http://localhost:1475/Service1.asmx", data);
Console.WriteLine(Encoding.Default.GetString(result));
}
}
}

Categories

Resources