Unable to deserialize contract after changing route registration - asp.net

I have an existing WCF Web API app that is registering routes using the following pattern:
RouteTable.Routes.Add(new ServiceRoute("MyService", new WebServiceHostFactory(), typeof(MyServiceImplementation)));
I recently updated to Preview 6. I also updated the registration pattern in Global.asax to use the enhancements extension:
routes.SetDefaultHttpConfiguration(new MyServiceConfiguration());
routes.MapServiceRoute<MyServiceImplementation>("MyService");
I also have a contract that I am posting to a method in a request.
[WebInvoke(UriTemplate = "/MyOperation", Method = "POST")]
Contact MyOperation(Contact contact);
...
[DataContract( Name = "Contact" )]
public class Contact : IExtensibleDataObject
{
[StringLength(50)]
[StringNotEmpty]
[DataMember(Name = "FirstName", Order = 1)]
public string FirstName { get; set; }
[DataMember(Name = "LastName", Order = 1)]
public string LastName { get; set; }
//[StringLength(50)]
//[DataMember(Name = "Location", Order = 1)]
//public String Location { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
The issue I am getting is that when I post a previously acceptable contract, such as:
<Contact xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://myservice/schema">
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<Location i:nil="true" />
</Contact>
I get the following exception:
The server encountered an error processing the request. Please see the
service
help page for constructing valid requests to the service. The
exception message is 'The service operation 'MyOperation' expected a
value assignable to type 'Contact' for input parameter 'contact' but
received a value of type 'HttpRequestMessage`1'.'. See server logs for
more details.
I found that if I remove the xmlns="http://myservice/schema" from my request, the service accepts the request. I have existing api clients that will be making calls to the new service with this present, so I have to make sure these messages are accepted.
I understand that the WCF Web Api Enhancements (extension method) I am using uses different classes under the hood; but I am a little ignorant at the moment as to why one would be able to deserialize and the other would not.
Cheers.

The WCF Web API doesn use the DataContact serializer but the XmlSerializer so you will need to use those attributed to decorate your Contract class. Try adding the XmlRootAttribute with a Namespace to the Contract class.
[XmlRoot(Namespace = "http://myservice/schema")]
public class Contact
{
// ...
}

Related

Swagger UI doesn't render body parameter field for my complex type parameter in GET action of my Controller

I have an ASP.NET Web API 2 project to which I have added Swagger - Swashbuckle v5.6.0. Everything works fine. Swagger UI renders test endpoints for my API as expected.
I added a new Controller to my API. There is a GET action with a complex type parameter. For complex types, Web API tries to read the value from the message body. This is the default behaviour.
Here is my GET action:
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok();
}
And her is my complex type:
public class SearchModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string Telephone { get; set; }
public string MobilePhone { get; set; }
}
The problem:
But Swagger UI doesn't render body parameter field for my complex type in the GET action. For POST and PUT actions Swagger UI renders body parameter fields as expected but not for the complex type in my GET action.
As can be seen in the screenshot Swagger UI renders query parameters fields for attributes in my complex type instead of rendering a body parameter field for my type as it does in the case of POST and PUT.
My GET action is working fine when testing from Postman and filling the json in the body of the request. By setting breakpoint in the action inside Visual Studio I can see the values are bound to my object in the action parameter.
I have tried to decorate the parameter in my action with [FromBody] (which is the default for complex type) but same result.
Is this a bug in Swagger? Or am I missing something?
Sadly, you can't do what you want with Swagger. You can't send a request model in an HTTP GET method. You can however change the swagger UI to look like this:
but you won't be able to receive the model in your controller.
This is a known issue within the Swagger developers and it was discussed in 2016 and the final decision is that swagger won't support a request body in an HTTP GET method. Here is the link to the already closed issue.
You have three options here:
Leave the method as it is, and test it in Postman, but not in Swagger.
Follow the below steps to achieve the picture above, but please note, that it will only fix the UI part and you will always end up with null SearchModel in the controller when you press Try it out! in swagger.
Make it a [HttpPost method instead of [HttpGet].
How to make swagger UI display GET method with request body:
First, create one Attribute class:
public class ModelInBodyAttribute : Attribute
{
public ModelInBodyAttribute(string modelName, string description, bool isRequired)
{
this.ModelName = modelName;
this.Description = description;
this.IsRequired = IsRequired;
}
public string ModelName { get; set; }
public bool IsRequired { get; set; }
public string Description { get; set; }
}
Then you can decorate your method in the controller:
[ModelInBody(modelName: nameof(SearchModel), description: "My model description", isRequired: true)]
[HttpGet]
[Route("search")]
[ResponseType(typeof(List<SearchModel>))]
public IHttpActionResult Search(SearchModel searchOptions)
{
//....
return Ok(new List<SearchModel>());
}
After that create IOperationFilter class (ModelInBodyOperationFilter):
public class ModelInBodyOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var attribute = apiDescription.GetControllerAndActionAttributes<ModelInBodyAttribute>().FirstOrDefault();
if (attribute == null)
{
return;
}
operation.parameters.Clear();
operation.parameters.Add(new Parameter
{
name = attribute.ModelName,
description = attribute.Description,
#in = "body",
required = attribute.IsRequired,
schema = new Schema { #ref = $"#/definitions/{attribute.ModelName}" }
});
}
}
Lastly, don't forget to register the IOperationFilter in SwaggerConfig:
c.OperationFilter<ModelInBodyOperationFilter>();
When you send the request via swagger, you will notice that the Curl part is absolutely correct, but still, in your controller there is nothing.
There are endless discussions on whether you should have a PAYLOAD "Body content" in a GET request. As you mentioned it's supported by HTTP but you will find in the internet that many people suggest not to do it. I guess that swagger team also expect you not to use it.

Odata v3 Web Api navigation with composite key

I have a Web Api using Odata v3, with some entities a composite key, like this one:
public class AerodromoAdministracaoData
{
[Key]
[Column("idAerodromo", Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public short IdAerodromo { get; set; }
[Key]
[Column("data", Order = 1, TypeName = "date")]
public DateTime Data { get; set; }
public virtual Aerodromo Aerodromo { get; set; }
}
I followed this msdn article and created a NavigationRoutingConvention. The application handles composite keys fine now. However, navigation Links like this one don't work:
http://localhost/WebApiV3/AerodromoAdministracaoData%28idAerodromo=1,data=%272014-10-24%27%29/Aerodromo
I keep getting a "No HTTP resource was found that matches the request" as if the method was not implemented in the controller. By the way, this is the controller method:
[EnableQuery]
public Aerodromo GetAerodromo([FromODataUri] short idAerodromo, [FromODataUri] DateTime data)
{
AerodromoAdministracaoData result = Store.AerodromoAdministracaoData.Find(idAerodromo, data);
if (result == null)
{
throw new HttpResponseException(new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
}
return result.Aerodromo;
}
I found this question talking about exactly the same problem, but I haven't figured out how Nikon handled the issue.
Eduardo
From MSDN article Support Composite Key in ASP.NET Web API OData
public class CompositeKeyRoutingConvention : EntityRoutingConvention
{
....
}
The above routing convention can cover the following Uri templates:
~/entityset/key
~/entityset/key/cast
But, it can't cover ~/entityset/key/navigation
The fix is simple, just derived from NavigationRouteConvention as below
public class CompositeKeyRoutingConvention : NavigationRoutingConvention
{
...
}
Below is the debug information:
Please make sure: if you want support both Uris:
/AerodromoAdministracaoData%28idAerodromo=1,data=%272014-10-24%27%29
/AerodromoAdministracaoData%28idAerodromo=1,data=%272014-10-24%27%29/Aerodromo
You must have two custom routing conventions, one derived from EntityRoutingConvention, the other one derived from NavigationRoutingConvention.
Hope it can help. Thanks.

ServiceStack DTO Model Binding for Route Parameters AND Body

I have a Request DTO set up for performing a PUT against a service that results in an update.
I require both route parameters AND a json payload to be sent as the PUT (this payload is the ApprovalRoleData object below, and represents the new state of the object I want to have reflected on the server):
[Route("/qms/{QAID}/reviewers/{RoleType}", "PUT")]
public class UpdateReviewer
{
public string QAID { get; set; }
public string RoleType { get; set; }
public ApprovalRoleData UpdatedRoleData { get; set; }
}
Within my service, I have a Put() call that accepts this DTO: The issue is that the ApprovalRoleData object is not being deserialized (but the QAID and RoleType are):
public object Put(UpdateReviewer request)
{
string QAID = request.QAID; //can see value
string RT = request.RoleType; //can see value
ApprovalRoleData ard = request.UpdatedRoleData; //null
}
Is there a way like in WebAPI to specify that I want model binding to work with both route parameters AND a body?
Side Note:
Also, getting the underlying stream so I can just parse myself with base.RequestContext.Get<IHttpRequest>().InputStream didn't work since there was no remaining stream to read (i'm assuming the part of ServiceStack that does the model binding probably consumed the stream by the time I got to it?)

ASP.NET MVC API model not parsing

I am having some problems parsing my model in ASP.NET MVC API
This is my API controller:
public class UserController : ApiController
{
// Hent liste af personer
public IEnumerable<UserModel> Get()
{
return new UserModel[] { new UserModel(), new UserModel() };
}
// Hente enkelt person
public UserModel Get(int id)
{
return new UserModel();
}
// Opret person
[ValidationActionFilter]
public CreateUserRespose Post([FromBody]UserModel model)
{
CreateUserRespose rs = new CreateUserRespose();
return rs;
}
// Rediger person
public UserModel Put(int id, [FromBody]UserModel model)
{
return new UserModel();
}
// Slet person
public UserModel Delete(int id)
{
return new UserModel();
}
}
}
And the UserModel:
public class UserModel
{
[Required]
[StringLength(500)]
public String FristName { get; set; }
[Required]
[StringLength(500)]
public String LastName { get; set; }
[Required]
[StringLength(250)]
public String Email { get; set; }
[Required]
public String MatrikelId { get; set; }
}
When I call though Fiddler to the Post command with the following body
FirstName=Fistname MiddleName&LastName=SomeName&Email=email#email.us&MatrikelId=1234
Will the action Post be called, but the model is null, and ModelState.IsValid is true, the same happens if I send no data with the body!
What am I doing wrong here?
Update:
I have tryed sending the data as json instead
Fiddler:
User-Agent: Fiddler
Host: localhost:51268
Content-Length: 102
Content-type: application/json
{"FristName":"Kasper asdasd","LastName":"asdasdasd","Email":"asdaasd#asdasd.us","MatrikelId":"132456asd"}
But should the model state not be invalid when the model is null?
The ASP.NET Web API is using content negotiation process in order to decide which MediaTypeFormatter to use for deserializing the body of the request. For the typical POST request it will check for Accept and Content-Type headers. If none is present it will use the first MediaTypeFormatter on the list (by default it is JsonMediaTypeFormatter).
In your case Web API was unable to determine the proper MediaTypeFormatter. Adding a Content-Type header with value of application/x-www-form-urlencoded to the request should resolve the issue.
If you want to get more detailed knowledge regarding Formatters, Model Binding and Content Negotiation in ASP.NET Web API I would suggest following reading:
Designing Evolvable Web APIs with ASP.NET -> Chapter 8. Formatters and Model Binding (you should look very close at the entire book if you are interesed in learning ASP.NET Web API)
Everything you want to know about ASP.NET Web API content negotiation

Deserializing JSON objects as List<type> not working with asmx service

I am having trouble deserializing my JSON string. I have a class of type person with public properties for sequence number of type int, first name, and last name. I want to pass an array of these objects in JSON format and have them deserialized as a list so I can loop through them on the server, but ASP.NET says something about not being supported to be deserialized as an array. I have validated the JSON I am producing, and it is valid. Is there something special about the JSON that ASP.NET needs to have before it can deserialize? The funny thing is if I serialize a list<person> object to JSON it looks exactly like the JSON I am producing. I must be missing something... To clarify, I'm using the ASP.NET Ajax library to deserialize. This is what I get back from the web service:
{"Message":"Type \u0027System.Collections.Generic.IDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]\u0027 is not supported for deserialization of an array."
Actually unfortunately this doesn't seem to have anything to do with deserializing, it appears that you can't pass an array of JSON objects to an asmx web service. Am I correct? If you can't do that, is it possible to pass a collection of JSON objects to a web service and have them processed on the server with ASP.NET and C#?
Update:
OK, here is my code. Here is the person class:
public class person
{
public person()
{
//
// TODO: Add constructor logic here
//
}
public int seq
{
get;
set;
}
public string firstName
{
get;
set;
}
public string lastName
{
get;
set;
}
}
And here is my JSON string:
[{"seq":1,"firstName":"Chris","lastName":"Westbrook"},
{"seq":2,"firstName":"sayyl","lastName":"westbrook"}]
And here is the code I'm using
[WebMethod]
public void updatePeople(string json)
{
IList<person> people =
new JavaScriptSerializer().Deserialize<IList<person>>(json);
//do stuff...
}
I figured it out. I wasn't wrapping my JSON in an object like ASP.NET Ajax requires. For future viewers of this question, all JSON objects must be wrapped with a main object before being sent to the web service. The easiest way to do this is to create the object in JavaScript and use something like json2.js to stringify it. Also, if using an asmx web service, the objects must have a __type attribute to be serialized properly. An example of this might be:
var person=new object;
person.firstName="chris";
person.lastName="Westbrook";
person.seq=-1;
var data=new object;
data.p=person;
JSON.stringify(data);
This will create an object called p that will wrap a person object. This can then be linked to a parameter p in the web service. Lists of type person are made similarly, accept using an array of persons instead of just a single object. I hope this helps someone.
Could you show the JSON string you are trying to deserialize and the way you are using the Deserialize method? The following works fine:
using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
namespace Test
{
class Program
{
class Person
{
public int SequenceNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static void Main()
{
string json = "[{\"SequenceNumber\":1,\"FirstName\":\"FN1\",\"LastName\":\"LN1\"},{\"SequenceNumber\":2,\"FirstName\":\"FN2\",\"LastName\":\"LN2\"}]";
IList<Person> persons = new JavaScriptSerializer()
.Deserialize<IList<Person>>(json);
Console.WriteLine(persons.Count);
}
}
}
Or even simpler, when you are doing the $.ajax(...) use
data:"{\"key\":"+JSON.stringify(json_array)+"}",
and then on the other side of the code, make your function use the parameter "object key"
[WebMethod]
public static return_type myfunction(object key){...}
SERVER SIDE
[WebMethod]
public void updatePeople(object json)
CLIENT SIDE
var person = "[{"seq":1,"firstName":"Chris","lastName":"Westbrook"}
,{"seq":2,"firstName":"sayyl","lastName":"westbrook"}]";
var str = "{'json':'" + JSON.stringify(person) + "'}";
I think the problem is what type you have to deserialize. You are trying to deserialize type
IList
but you should try to deserialize just
List
Since interface can not be instantiated this might is the root problem.

Resources