Combine URL data with HTTP POST request body in ServiceStack - c#

I'd like to be able to POST data like this to a REST API:
POST /foo/b HTTP/1.1
Accept: application/json
Content-Type: application/json
{ "Qux": 42, "Corge": "c" }
The URL segment after foo (i.e. b) also contains data that I need to capture in a server-side variable. I've tried to implement this feature in ServiceStack (see code below), but the response body is null.
Here's first the request type:
[Route("/foo/{Bar}", "POST")]
public class PostFooRequest : IReturn<PostFooResponse>
{
public string Bar { get; set; }
[ApiMember(ParameterType = "body")]
public Foo Body { get; set; }
}
As you can see, Bar is a URL variable. The Foo class is defined like this:
public class Foo
{
public int Qux { get; set; }
public string Corge { get; set; }
}
Furthermore, the response looks like this:
public class PostFooResponse
{
public string Bar { get; set; }
public Foo Foo { get; set; }
}
Finally, the service itself is defined like this:
public class ReproService : Service
{
public object Post(PostFooRequest request)
{
return new PostFooResponse { Bar = request.Bar, Foo = request.Body };
}
}
Notice that this method simply echoes the values of the request in the response.
When I execute the above request, I only get the Bar value back:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"bar":"b"}
Setting a breakpoint in the Post method reveals that request.Body is null.
How do I write the code so that the API has the desired contract?
FWIW, I'm aware of this question, but the answer only explains what the problem is; not how to solve it.

If you would translate your current request to the following DTO the serializer should be able to fill the properties:
[Route("/foo/{Bar}", "POST")]
public class PostFooRequest : IReturn<PostFooResponse>
{
public string Bar { get; set; }
public int Qux { get; set; }
public string Corge { get; set; }
}
The serializer has no way to know how to deserialize the object you're sending.
Looking at your DTO and the request I would expect a different request.
POST /foo/b HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"Foo": { "Qux": 42, "Corge": "c" }
}
Other way of retrieving the FormData would be using the following property in your Servicestack service
Request.FormData. Make sure you're not calling the DTO but capital Request.

Related

How to call API with specific class parameter containing IFormFile and other attributes?

Imagine a class like this :
public class Container
{
public List<Test> Tests { get; set; }
}
which Test is:
public class Test
{
public IFormFile file { get; set; }
public string name { get; set; }
public string DateOfExpire { get; set; }
public string ExternalId { get; set; }
public string RelatedId { get; set; }
}
And also from Api side, I have this endpoint:
public async Task<IActionResult> SaveFiles([FromForm] Container container)
{
//whatever
}
I want to know in this case How I should createHttpRequestMessage and MultipartFormDataContent objects on the consumer side to send a proper request?
For more elaboration:
I have read here, and it's obvious for an endpoint with a specific class parameter containing List<IFormFile> files I should use something like this :
Model:
public class FileDataDto
{
public List<IFormFile> FilesToUpload { get; set; }
[ModelBinder(BinderType = typeof(FormDataJsonBinder))]
public DataDto Data { get; set; }
}
Api:
public IActionResult Upload([FromForm] FileDataDto dto)
{
// code responsible for file processing
return Ok();
}
Consumer side:
using var content = new MultipartFormDataContent
{
// files
{ new StreamContent(stream), "FilesToUpload", "Test.txt" },
{ new StreamContent(anotherStream), "FilesToUpload", "NewTest.txt" },
// payload
...
};
It means each boundary of the File in the Multipart/data is the same name which would be mapped to the list at the end. and also other attributes have their own boundary and are mapped correctly. and the result would be :
POST https://localhost:5001/file HTTP/1.1
Host: localhost:5001
traceparent: 00-882ae85a7db6a74597e78460578430ba-590a3900255d784c-00
Content-Type: multipart/form-data; boundary="d9b747c2-c028-4cc2-b53d-904af4f558f1"
Content-Length: 565
--d9b747c2-c028-4cc2-b53d-904af4f558f1
Content-Disposition: form-data; name=FilesToUpload; filename=Test.txt; filename*=utf-8''Test.txt
--d9b747c2-c028-4cc2-b53d-904af4f558f1
Content-Disposition: form-data; name=FilesToUpload; filename=NewTest.txt; filename*=utf-8''NewTest.txt
--d9b747c2-c028-4cc2-b53d-904af4f558f1
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=Data
{"Name":"payload name","Tags":["tag1","tag2"],"ChildData":{"Description":"test description"}}
--d9b747c2-c028-4cc2-b53d-904af4f558f1--
As you know here we have the same name for files and boundaries with the same name will be mapped the list at the end. But what if each file would be inside a separate class, it faces a problem using the same name. How should I create a request for this case?

Postman returns 415 when trying to do a POST using form-data, but the same request with JSON works fine

I'm building a REST API and testing it out using Postman. I have an end-point which works fine when I test it by sending in raw json data, but I want to expand on this endpoint and allow it to take both json data and accept a file, so I wanted to test my current endpoint without any modifications, and see if I would get back the same result when I test my API using form-data instead of JSON, but it always throws a 415 exception.
On this picture I make a request with form-data.
And here I make the request to the same endpoint but with json data
Note that I have not added any customs headers when sending the requests, the (10) you see in the top is Temporary Headers. I also tried adding Content-Type: multipart/form-data, but got the same result.
Here's the code behind
PeopleController.cs
[HttpPost]
public ActionResult<PersonDto> PostPerson(PersonForCreationDto person)
{
var personEntity = _mapper.Map<Entities.Person>(person); //Maps PersonForCreationDto to Entites.Person. This is possible because of the mapping in PeopleProfile.cs
_personLibraryRepositry.AddPerson(personEntity);
_personLibraryRepositry.Save();
var personToReturn = _mapper.Map<PersonDto>(personEntity);
return CreatedAtRoute("GetPerson",
new { personId = personToReturn.PersonId },
personToReturn);
}
PersonForCreationDto
public class PersonForCreationDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string ReasonsForBeingOnTheList { get; set; }
public ICollection<PictureForCreationDto> Pictures { get; set; }
= new List<PictureForCreationDto>();
}
PersonLibraryRepository.cs
public void AddPerson(Person person)
{
if (person == null)
{
throw new ArgumentNullException(nameof(person));
}
person.PersonId = Guid.NewGuid(); //API is responsibile for creating new IDS.
foreach (var picture in person.Pictures)
{
picture.PictureId = Guid.NewGuid();
}
_context.People.Add(person);
}
In your form data, add this to the Headers
Content-Type: application/json

Post generic class in .net core web API

I'm using .net core Web API. I'm posting generic QueryFilter class from angular. But, generic part of class return null. When, i change to parameter like this, it is working perfectly :
public async Task<ServiceResult> GetStudentsForGrid([FromQuery]QueryFilter queryFilter,[FromQuery]StudentFilter studentFilter)
{ } //This working perfectly
QueryFilter.cs
public class QueryFilter<T> where T : class
{
public string SortBy { get; set; }
public bool IsSortAscending { get; set; }
public int PageFirstIndex { get; set; }
public byte PageSize { get; set; }
public T CustomFilter { get; set; }
}
StudentFilter.cs
public class StudentFilter
{
public string Name { get; set; }
public string Surname { get; set; }
}
Controller.cs (not working)
[HttpGet("GetStudentsForGrid")]
public async Task<ServiceResult> GetStudentsForGrid([FromQuery]QueryFilter<StudentFilter> queryFilter)
{ } //This not working
I don't want to send every time two parameter. So, I want to use generic way. How can i fix this null exception?
If you want to use a "generic" way , let's say your controller action is :
public IActionResult GetStudentsForGrid([FromQuery]QueryFilter<StudentFilter> queryFilter)
{
var x= queryFilter;
return new JsonResult(x);
}
you have to sent the request with a well formatted querystring :
GET https://localhost:5001/api/students?sortBy=Hello&pageSize=10&customFilter.Name=1&customFilter.SurName=2 HTTP/1.1
Note the way we pass the parameters of customFilter.Name and customFilter.SurName.
The response will be :
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
{
"sortBy": "Hello",
"isSortAscending": false,
"pageFirstIndex": 0,
"pageSize": 10,
"customFilter": {
"name": "1",
"surname": "2"
}
}

unexpected http get request using JsonServiceClient

I am trying to make a json request on an external service, that would look like this :
GET request :
https://remotehost/path/mycount?foo=C&bar=21
response :
{"count":1000}
for this I use ServiceStack JsonServiceClient, which I like because you can pass a object as parameter. this makes it easy to use/read.
Here is my code :
var client = new JsonServiceClient(classifiedSearchBaseURL);
var response = client.Get<CountResponse>(
new MyRequest
{
foo = "C",
bar = 21
});
class MyRequest
{
public string foo { get; set; }
public int bar { get; set; }
}
class CountResponse
{
public string count;
}
But when debugging, instead of getting this http request to the server
GET /path/mycount?foo=C&bar=21
I am getting this
GET /path/json/reply/MyRequest?foo=C&bar=21
Any of you guys has an idea?
Thanks for your help!
The answer is that I should use the Route attribute to the Request object
Following is the modified code
[ServiceStack.Route("/mycount")]
class MyRequest
{
public string foo { get; set; }
public int bar { get; set; }
}

JSON passed to ApiController doesn't deserialize string values

I'm trying to pass a JSON array to an ApiController but the string values aren't deserializing (they are set to null values). The strange thing is that I still get the correct number of elements.
A have an ApiController:
[RoutePrefix("api/language")]
public class LanguagePairApiController : ApiController
With a post method:
// POST: api/language/create
[HttpPost]
[Route("create")]
public string Create([FromBody]LanguagePair[] languagePairs)
I'm sending JSON to it:
[
{"Key":"Test","Value":"Test","Version":"1.0"},
{"Key":"Areyousure","Value":"Are you sure?","Version":"1.0"},
{"Key":"File","Value":"File","Version":"1.0"}
]
And this is the class I'm trying to map it to:
public class LanguagePair
{
public string Key { get; set; }
public string Value { get; set; }
public string Version { get; set; }
}
But the string values are coming through as null:
What am I missing?
EDIT: I've figured out one answer to this and posted it below. But I'm still looking for a better answer...
I figured it out. I needed to decorate the class with DataContract and DataMember attributes:
{
[DataContract]
public class LanguagePair
{
[DataMember]
public string Key { get; set; }
[DataMember]
public string Value { get; set; }
[DataMember]
public string Version { get; set; }
}
}
Read Parameter Binding in ASP.NET Web API
You need to remove [FromBody] attribute from your action...
// POST: api/language/create
[HttpPost]
[Route("create")]
public string Create(LanguagePair[] languagePairs) { ... }
and you can keep your class lean as you originally had it:
public class LanguagePair
{
public string Key { get; set; }
public string Value { get; set; }
public string Version { get; set; }
}
Using [FromBody]
To force Web API to read a simple type from the request body, add the
[FromBody] attribute to the parameter:
public HttpResponseMessage Post([FromBody] string name) { ... }
In this example, Web API will use a media-type formatter to read the
value of name from the request body. Here is an example client
request.
POST http://localhost:5076/api/values HTTP/1.1
User-Agent: Fiddler
Host: localhost:5076
Content-Type: application/json
Content-Length: 7
"Alice"
When a parameter has [FromBody], Web API uses the Content-Type
header to select a formatter. In this example, the content type is
"application/json" and the request body is a raw JSON string (not a
JSON object).

Categories

Resources