WCF Rest POST method to accept both JSON and XML - c#

Looking for some way to make one post method in WCF restful service which can accept both xml and json. I can see that is possible with Get method which automatically returns json/xml based on request header.
One solution I could think of is:
Get the post data as "Stream" and read it to a string.
Check request header and deserialize it to json or xml.
Set OutgoingResponse format accrodingly and return response.
I'm able to do #1 but stuck in #2 and #3.

Microsoft has already done this for you, don't reinvent the wheel.
public class DataController : ApiController
{
public void Post(DataModel model)
{
// Whether the body contains XML, JSON, or Url-form-encoded it will be deserialized
// into the model object which you can then interact with in a strongly-typed manner
}
}
public class DataModel
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
You can download ASP.NET MVC4 for free which includes the new Web API. http://www.asp.net/mvc/mvc4 . This is basically the final product of the WCF Web API, which is no longer supported. Unless you have so much code already written with the original Web API that it wouldn't be practical to make the switch, this will save you a lot of time in the long run. Otherwise you will be stuck with a Beta product that has bugs which will never be fixed.

Related

Unable to receive POST data using a class defined in a class library

I have two projects: one is a .NET Core class library, the other is a .NET Core minimal web API.
SomeClassLibrary.csproj
// SomeClass.cs
namespace SomeClassLibrary.Some.Arbitrary.Namespace
{
public class SomeClass
{
public SomeClass() {}
public SomeSubClass? subClassObject {get; set;}
}
public class SomeSubClass
{
public SomeSubClass() {}
public string? id {get; set;}
}
}
SomeMinimalWebAPI.csproj
// Program.cs
using SomeClassLibrary.Some.Arbitrary.Namespace;
...
app.MapPost("/endpoint", ([FromBody] SomeClass someClassObject) =>
{
// do stuff with someClassObject
return "OK";
});
...
If I POST from Postman, for example, with JSON like so:
{
"subClassObject" :
{
"id" : "some value"
}
}
I get a 200 status code, but no OK in the response body as expected.
I have tested that using [FromBody] SomeClass someClassObject works just fine when referenced in the same solution, but referenced from a class library as I have demonstrated here makes it seem like MapPost is hanging for some reason. I get no error codes. And to reiterate, I get a 200 in Postman, but no OK in the response body. Further, if I configure the Postman POST request with invalid JSON, I do receive an error response telling Required parameter "SomeClass someClassObject" was not provided from body.
I can even new up a SomeClass object, including its nested SomeSubClass object, and use it in the code. But as soon as I try to pass it as a parameter to be deserialized FromBody, it hangs with zero indication as to what is happening.
Everything seems to be referenced correctly, as my using statements referencing SomeClassLibrary.Some.Arbitrary.Namespace grant me access to the controllers and models and stuff I have written in the class library. I have rebuilt, cleaned, unloaded, reloaded everything multiple times.
A datapoint that may or may not matter: my class library depends on Newtonsoft.Json to do work on serializing/deserializing my objects. I can't see why I would need to reference that in my web API since it's calling outside of itself to do all of that work, and it's not throwing any errors.
I am truly at a loss as to why I can't grab this object FromBody specifically when referencing its class from a class library. Any help would be greatly appreciated. And further clarification can of course be provided.
EDIT
For further clarification, I am noticing some extremely bizarre behavior depending on what JSON I feed the POST endpoint.
{
"id" : "some value"
}
returns OK in the response body.
but
{
"subClassObject" :
{
"id" : "some value"
}
}
still returns nothing.
I cannot stress enough that using the second JSON shape DOES WORK in the project that references SomeClass within the same solution exactly as intended.

Why I get Unsuported Media Type on GET request? (.NET 6)

I'm surely missing something, because most questions around 415 error are referring to POST requests.
In this case, this is a very simple GET request, which works when I enumerate all action parameters, but if I create a simple DTO to contain all of these, I start receiving a 415 error.
I'm calling the api with https://localhost:555/some/test?field1=aaa&field2=1
This works:
[ApiController]
public class SomeController : ControllerBase
{
[Route("Test")]
public SomeResponse GetSomeResponse(string field1, int field2)
{
return new SomeResponse(field1, field2);
}
}
But this doesn't:
[ApiController]
public class SomeController : ControllerBase
{
[Route("Test")]
public SomeResponse GetSomeResponse(SomeRequest request)
{
return new SomeResponse(request.Field1, request.Field2);
}
}
public class SomeRequest
{
public string Field1 { get; set; }
public int Field2 { get; set; }
}
public class SomeResponse
{
public Someresponse(string field1, int field2)
{
Field1 = field1;
Field2 = field2;
}
public string Field1 { get; set; }
public int Field2 { get; set; }
}
The controller class is only using Microsoft.AspNetCore.Mvc;
When I try to use SomeRequest class, the API answers "415 - Unsuported Media Type"
The only difference is the way to receive the values, I'm not switching from uri to body (which could be json or plain text, etc.)
But since I'm not using the body, I can't understand which media-type it is referring to
My startup class is the same as the WeatherForecast, created with the project template ASP.NET Core Web API, with Visual Studio 2022, .Net6
Well, a possible solution is to specify [FromQuery]:
public SomeResponse GetSomeResponse([FromQuery] SomeRequest request)
Tho, I am not very happy with this as reaction to "Unsuported Format", so, other suggestions are welcome.
Ben Foster has a great writeup on this in Custom Model Binding in ASP.NET 6.0 Minimal APIs
I haven't tried it myself yet but adding this TryParse method to your SomeRequest type might help:
static bool TryParse(string? value, IFormatProvider? provider, out T parameter)
Generally I try not to bind objects to HttpGet because standard object serialization uses characters that have other meanings in standard URL routing and therefore need to be escaped in the url, which just over complicates the server and the client implementation.
I support the concept for common complex type style structures like Point that might be reused thought you application in many controllers and endpoints, but to create an object wrapper for each request would be ludicrous, that is a common client side technique in generated clients but we try to avoid that on the server-side.
I recommend against binding objects as GET parameters as it is a non-standard style of code that introduces a level of technical debt to the solution both in terms of API management and client implementation.
Even if you use custom binders or route handlers these solutions end up being a lot more custom code than it would have been to use primitive parameters and map them to your preferred type implementation in the first line of your endpoint method.
You should use the HttpGetAttribute to bind the request to query string parameters and specifically scope the requests so that only GET is allowed:
[HttpGet("Test")]
public SomeResponse GetSomeResponse2(SomeRequest request)
{
return new SomeResponse(request.Field1, request.Field2);
}
It is subtle but since .Net 5 (when FromUriAttribute was replaced and the various OData and MVC routing mechanisms were combined into a common pipeline) we are encouraged to use HttpGetAttribute (Or the other Http Method variants) instead of RouteAttribute as a way of minimising the configuration.
This code has similar functionality to the answer provided by #Zameb but it prevents users from attempting to use POST to access this endpoint. [FromQuery] creates a controller that even when a JSON object is POSTed to the endpoint the parameters are specifically mapped to the Query Parameters.

Send logic over Http

I'm trying to send a class that contains a Function over http.
Can this be accomplished?
Its supposed to work like this:
1) Client contacts Web.API.
2) Web.API returns class with the Function
3) Client executes Function.
Class will look a bit like this :
public class UploadTicket
{
public string url { get; set; }
public List<Tuple<string, string>> Headers { get; set; }
public Func<string> SpecialFunction { get; set; }
}
I need this function to alter the uploadurl by appending an ID every time a package is sent - but only if it's to certain dataproviders, and in other cases uploadurls must be modified in other ways. I need to do it this way, so i can keep the client side code as general as possible.
Sounds a little goofy but yeah you can, on the condition that you send source to the client and not some pre-compiled version (otherwise it'd really be a gaping security hole). Just use the C# runtime compiler. But it implies the client is C#, that's why it sounds goofy.
This is actually a common pattern everybody uses every day. Think of the web browser that hits a web server and gets a page with javascript in it. The same thing happens. The browser compiles the JS and executes it, even though the source was produced remotely.

ASP.NET Web API XmlFormatter does not work, falls back to JsonFormatter

I have been pulling my hair out for the past two days trying to figure out why XmlFormatter (via DataContractSerializer) does not serialize data I return in my Web API method. WebAPI decides to use JSON anyway but I need the result to be in XML (as per application that will use this API). I have setup my browser to send Accept: application/xml for the resolver to use the XmlFormatter (but the result is always json).
Controller:
public class MyController : ApiController
{
public MyDataResultList GetData(string someArgument)
{
// magic here that gets the data
MyDataResultList items = MyDataResultList.GetData(someArgument);
return items;
}
}
MyDataResultList is contained in a dll and has this similar layout:
[DataContract]
[CollectionDataContract(Name = "MyDataList")]
[KnownType(typeof(List<MyDataItem>))]
public class MyDataResultList : List<MyDataItem>
{
[DataMember]
public string SomethingHere
{
get;
set;
}
[DataMember]
public TimeSpan StartTime
{
get;
set;
}
[DataMember]
public TimeSpan StopTime
{
get;
set;
}
}
I have tried setting UseXmlSerializer to true, but I need to use the DataContractSerializer on the client end to de-serialize the results back correctly.
So the final question is, is it possible to configure web API to throw an exception if it is unable to serialize using whatever formatter comes first? I believe (in my opinion) it's very misleading and too abstractive to have it just silently fall back to JSON without giving me any clue as to what is causing that.
Update: manually serializing MyDataResultList using DCS throws InvalidDataContractException: Type 'MyDataResultList' with CollectionDataContractAttribute attribute is an invalid collection type since it has DataContractAttribute attribute. But the underlying question remains: how to get the Web API to throw this to me instead of silently falling back to JSON? (and making debugging more difficult)
Update2: DataContract serializer seems to skip SomethingHere/StartTime/EndTime properties entirely even though they have [DataMember] on them.
Your way of diagnosis is correct and yeah Web API's content negotiation process will try to find the best formatter based on bunch of logic(ex: Accept header if present, Content-Type header if Accept-Header not present, asks each formatter if it can serialize a type etc.).
You can disable this default behavior (i.e finding the first formatter in the list of formatters which can write/serialize a type) by doing the following. This will result in a 406 Not Acceptable response being generated:
DefaultContentNegotiator negotiator = new DefaultContentNegotiator(excludeMatchOnTypeOnly: true);
config.Services.Replace(typeof(IContentNegotiator), negotiator);

Does UTF-8 encoding have to be explicitly defined in a web service response?

I am supporting a C# (.asmx) web service built on .NET 3.5. It is a very, very simple service that returns, among a few other things, a street address. The scope of the application that provides the service was recently widened and now the DB behind it houses international addresses as well. This means Latin foreign language characters can be stored (accent marks, umlauts, etc) and consequently returned in a web service response.
When I test the service locally using soapUI's automatically generated requests and without adding any special headers or any other intstructive information, I see this element exactly as it's stored in the database - with its accent mark:
<CompositeStreet>140-146 RUE EUGÈNE DELACROIX</CompositeStreet>
However, when a connecting system calls the service the IT staff report that the response contains the question mark typically used as the replacement for a non-supported character:
<compositeStreet>140-146 RUE EUG?NE DELACROIX</compositeStreet>
I'm unsure whose issue this is and I'm concerned that the simplistic design of the service may mean that it falls on my shoulders to recode to somehow ensure any consumer is guaranteed to see the data in UTF-8 encoding. I've been under the impression that UTF-8 was the default encoding and nothing explicit was required. (Please save the discussion about upgrading to WCF for another thread - it's not in scope for this right now.)
Can you have a look at the structure of the service and tell me if there is actually something that needs to be done on my side? (Code drastically slimmed down for the sake of this discussion)
The ASMX page - basically the WebMethod accepts a request and returns the response:
namespace Company.IT.Network.LocationStore.LS_Services
{
using . . .;
[WebService(Namespace = "Company.IT.Network.LocationStore.LS_Services.ClliRecordService")]
public class ClliRecordService : System.Web.Services.WebService
{
public ClliRecordService() { }
[WebMethod(Description = "Lengthy description")]
public VZBServiceResponse ClliLookup_VzbSite(VZBServiceRequest sRequest)
{
VZBServiceResponse sResponse = new VZBServiceResponse(sRequest);
return sResponse;
}
}
And the serializable class that is a property of the VZBServiceResponse type which ends up in the XML response. You'll see all there is is the [Serializable] attribute and the setting of the order of the elements returned.
namespace Company.IT.Network.LocationStore.LS_Lib.BO
{
using ...;
[Serializable]
public class Address
{
private string _compositeStreet;
private string _buildingName;
[XmlElement(Order = 0)]
public string BuildingName
{
get { return _buildingName; }
set { _buildingName = value; }
}
[XmlElement(Order = 1)]
public string CompositeStreet
{
get { return _compositeStreet; }
set { _compositeStreet = value; }
}
}
}
There is really not much more to it than that. No formatting of the XML response through a memory stream (which I've seen in some posts) or specific handling through Serialize or Deserialize methods.
Are there recommendations to improve on this so that service consumers are guaranteed to be presented foreign language characters or is that really up to the calling system? Thank you for any guidance!

Categories