WebAPI get not converting properly to model binding object - get

I"m using WebAPI with MVC4, doing a http get that looks like this:
api_version=2&products=[{"id":97497,"name":"iPad"}]&pageno=1
The signature of the get action controller that maps to this call is:
[HttpGet]
public string Get([FromUri] ProductRequest request){ ... }
The problem is that the ProductRequest object passed into the Get action method above contains nulls for products, while all other values are Ok.
So it seems that it has trouble converting products=[{"id":97497,"name":"iPad"}] into the right object type, which is defined as:
public IEnumerable<Products> products { get; set;} in ProductRequest model and Products class looks like:
public int id { get; set; }
public string name { get; set; }
As, an additional information, when using the same call with a POST instead of a GET, it works fine, the object is converted properly.
So, what am I doing wrong, how can I get http GET to properly convert the query parameters to the model passed in?

I think you confused between HTTP POST and HTTP GET that's why you did get the product as null. You could have a look at What's the difference between GET and POST
Basically, I think you could use TempData but it has pros and cons and depend on the context how you use it.

You can do it through the url, but you don't use JSON. Here's what your URL should look like:
api_version=2&products[0].id=97497&products[0].name=iPad&pageno=1
If you wanted to add more products in the same request, you would increment the array index:
{urlasabove}&products[1].id=4234&products[1].name=iPadmini
This is fine for your request, but can quickly get out of hand. For a complex object in a GET request you may consider using a POST instead. Or, you could include the parameters in the GET body but that's not necessarily the best idea. See discussion on this SO question.

Related

How to spec base64 encoded object in query param in OpenAPI

I am trying to document an existing API using the OpenAPI spec (specifically using Swashbuckle and ASP.NET Core).
For many of the endpoints, the api uses a single query parameter which is a filter object – holding the actual parameters – that is base64-encoded.
I have successfully added the Swashbuckle library and can generate a swagger.json.
The generated spec however does not correctly describe the endpoints described above. Rather, the property names of the filter object are stated as query parameters, and thus autogenerated clients based off the spec do not work.
The spec mentions base64 only in relation to format of String and File, not Object.
Is it possible (and if so, how) to describe this type of endpoint in OpenAPI?
Is it possible (and if so, how) to generate this description correctly using Swashbuckle?
EDIT
In response to comment (probably necessary for answering subquestion 2) ).
An endpoint in the API source may look something like:
[HttpGet("")]
public async Task<IActionResult> Query([FromQuery] ThingFilter filter)
{
var results = await _dataContext.ThingService.Search(filter);
return Ok(results);
}
And a ThingFilter might be something like:
public class ThingFilter
{
public string Freetext { get; set; }
public List<PropertyFilter> PropertyFilters { get; set; }
}
In Startup.cs there is also registered a custom modelbinder that handles conversion from base64.

How do you get ServiceStack.ToJson to *sometimes* ignore properties?

I have a ServiceStack DTO:
[Route("/images", "POST")]
public class PostImageCommand
{
public string Notes { get; set; }
public byte[] Image { get; set; }
//other properties removed for brevity
}
I also have a part in my code that logs messages using log4net. I do this by serializing the command. For example, I just do this:
var command = new PostImageCommand(){
//set properties
};
var commandJson = command.ToJson();
MyLogClass.Log(commandJson);
The problem: The Image byte array can get pretty large. I do not want to serialize this property when logging, or else my log will be filled with large amounts of image data.
I tried putting the [IgnoreDataMember] attribute on the Image byte[] in the DTO. However, that causes the build to fail without Visual Studio saying why. I can put the Attribute on the Image property, but when I try to build I see this:
No reason why the build failed, just these messages. The Output tab only says "The operation was canceled".
My question: What is the easiest way to ignore a DTO property from being serialized in my situation?
This previous answer lists the different ways to ignore properties in ServiceStack.Text. But really if you just want to ignore the property from being logged you can just set the property to null then restore it, map it to a different model with just the types you want serialized using the built-in AutoMapping tools, Use ToObjectDictionary() extension method to serialize your model into a dictionary then remove the items you don't want logged.
The issue you're having with the [IgnoreDataMember] attribute is because you haven't referenced the System.Runtime.Serialization .NET Framework Assembly.

trying to use POST, but API tries to use GET

I'm trying to get an api method to use POST but it insists on trying to use GET. I've Googled and searched SO, but everything I try just returns the same message.
Here's my controller:
[Route("api/game/{gameId}/createcharacter/{name}")]
[AcceptVerbs("POST")]
[HttpPost]
public IHttpActionResult PostNewCharacter([FromBody]string name)
{
return Created("created", CharacterGenerationService.CreateNewCharacter(name));
}
Here's the message I get no matter what I try:
message: "The requested resource does not support http method 'GET'."
Request
URL:http://localhost:61645/api/game/452/createcharacter/testChar1
Request Method:GET Status Code:405 Method Not Allowed Remote
Address:[::1]:61645
I am using : using System.Web.Http;
Is there a trick to this?
Thanks
use this code:
[Route("api/game/{gameId}/createcharacter/{name}")]
[HttpPost]
public IHttpActionResult PostNewCharacter(string name)
{
return Created("created", CharacterGenerationService.CreateNewCharacter(name));
}
In the route template you said you will get the name parameter value from the URL but in the signature of your method your decorate that same parameter with FromBody attribute which tell that the value will be retrieved from the message body.
You have two choices :
remove the FromBody attribute and your URL will work correctly
or remove the {name} parameter in your route template and post the name value into your request body.
What about the gameId parameter ?
Don't you need it in your method ? If yes, then you need to pass it as a method parameter.

ServiceStack: Writing an API without needing multiple DTOs?

Subject may be unclear, but I'd like to expose two API calls that are almost identical, like so:
Routes
.Add<GameConsole>("/consoles", "GET")
.Add<GameConsole>("/consoles/count", "GET");
What I have now is "/consoles" giving me a list of all GameConsole objects from my repository. What I'd like to add is "/consoles/count", which gives me a count of all the GameConsole objects from my repository.
But since the service can only map one DTO in the routes, I can only have:
public object Get(GameConsole request)
{
return mRepository.GetConsoles();
}
Not sure I truly understand the limitations of only having one route map to a DTO; is there a way around this? As a side note, it seems odd that I have to pass the DTO to my service method, even though it's not being used at all (mapping to the route is the only purpose?)
Since the 2 routes don't contain any mappings to any variables and are both registered with the same request, you wont be able to tell the matching route from just the Request DTO, e.g:
public object Get(GameConsole request)
{
return mRepository.GetConsoles();
}
i.e. You would need to introspect the base.Request and look at the .PathInfo, RawUrl or AbsoluteUri to distinguish the differences.
If it mapped to a variable, e.g:
Routes
.Add<GameConsole>("/consoles", "GET")
.Add<GameConsole>("/consoles/{Action}", "GET");
Then you can distinguish the requests by looking at the populated request.Action.
But if they have different behaviors and return different responses then they should just be 2 separate services, e.g:
Routes
.Add<GameConsole>("/consoles", "GET")
.Add<GameConsoleCount>("/consoles/count", "GET");
The other option is to only have a single coarse-grained service that returns the combined dataset of both services (i.e. that also contains the count) that way the same service can fulfill both requests.
In very similar situations, I have been creating a subclass DTO for each separate routing service, inheriting the shared elements.
It has been working very well.
So the pattern is
public class SharedRequestDto
{
public string CommonItem { set; get; }
public string CommonId { set; get; }
}
then
[Route("/api/mainservice")]
public class MainServiceRequest : SharedRequestedDto
{
}
[Route("/api/similarservice")]
public class SimilarServiceRquest : SharedRequestDto
{
public string AddedItem { set; get; }
}
This allows differing but similar DTOs to be routed to individual services to process them. There is no need to perform introspection.
You can still use common code when necessary behind the concrete services because they can assume that their request object parameter is a SharedRequestDto.
It probably is not the right solution for every use case, but it is effective, especially since many of my DTOs are in families that share a great deal of data.

Unit test rest service without specifying URL

Using servicestack, there are examples of unit testing using types, etc. Here is an example:
GetFactorial
I would like to test my REST style service with a test similar to the above.
Here is an example REST unit test FileService
Notice how in the PUT unit test, the Path argument has to be specified in the URL text instead of in the class argument. Another example is here, where we have perfectly good request models that have to be translated into the URL. For testing, I would like to get away from having to build the arguments in the url and use a system similar to the one above like this:
var response = restClient.Put<FilesResponse>(new Files { TextContents = ReplacedFileContents, Path = "README.txt" });
or
var singleCustomer = restClient.Get<Customer>(new Customer {Id=1};
Is this possible?
Then there is the DirectServiceClient. Would that help? In the end, with servicestack, we get to write services and they can be called from many different type clients - I would like to write my unit test like that.
Is this possible?
If you decorate your DTOs with the route variable and use ServiceStack's "New API" then it can discover the routes automatically. You can get away with writing very minimal code and still get a strong typed rest API.
Your code could look something like this:
Customer singleCustomer = restClient.Get(new Customer {Id=1});
See https://github.com/ServiceStack/ServiceStack/wiki/New-Api
In response to your comments, your DTO needs to adhere to the IReturn interface:
[Route("/customer/{Id}")]
public Customer : IReturn<Customer> {
public int Id {get;set;}
}
The IRestClient interface below will now be able to work with your DTO without specify the type since it is expecting an IReturn object.
public interface IRestClient
{
TResponse Get<TResponse>(IReturn<TResponse> request);
...
}

Resources