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.
Related
I have a stateless REST API build on Spring Boot 1.4.2. I want to log all the API calls into elk. Requests and responses data (headers, parameters, payload) need to be logged as well. I don't want to log them 1:1 - I want to filter out sensitive data etc.
I made an aspect that is intercepting my #RestController's methods invocation. I created custom annotation for method's parameter that should be logged (I use it on payloads annotated as well by #RequestBody) following this article and it gave me access to my data transfer objects in my #Around advice. I dont care about their type - I would like to call logger.debug(logObject) and send this log to logstash.
As far as I understand log message should be send as JSON with JSONLayout set in Log4j2 appender to ease things on the logstash side. So I serialize my logObject into JSON log message but during this and this only serialization I want to filter sensitive data out. I can not use transient because my controller depends on the same field.
Can I somehow create an #IgnoreForLogging annotation, that will be detected only by my custom Gson serializer that I use within logging advice and will be ignored within standard Spring's infrastructure? Is my logging into logstash approach even correct (I am trying to set it up for the first time)?
I can't believe I missed that in documentation. Here is the link
My custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface IgnoreForLogging {
}
Strategy for serializing objects:
public class LoggingExclusionStrategy implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getAnnotation(IgnoreForLogging.class) != null;
}
#Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
Serializing log message in aspect class:
Gson gson = new GsonBuilder()
.setExclusionStrategies(new LoggingExclusionStrategy())
.create();
String json = gson.toJson(logObject);
This way Spring internally uses default serializer that doesn't know about #IgnoreForLogging and I can take advantage of my annotation in other places.
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.
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.
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.
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);
...
}