Swagger - how attach an example to particular end-point - c#

I'm working on API where my controllers just returns JsonDocument's instead of DTO's.
I need to provide some response examples for API documentation. But examples should be dedicated for particular end-points.
I've created example provider IExampleProvider<JsonDocument> - and unfortunately the example is populated for all JsonDocument's, instead of the only one decorated by the attribute:
[SwaggerResponseExample(200, typeof(MySpecificResponseExample))]
Even if attribute mentioned above is not specified, the example documentation is included.
Startup.cs:
c.ExampleFilters();
c.OperationFilter<AddResponseHeadersFilter>();
services.AddSwaggerExamplesFromAssemblyOf<Startup>();
Example provider:
public class (MySpecificResponseExample))] : IExamplesProvider<JsonDocument>
{
public JsonDocument GetExamples()
{
return JsonDocument.Parse(#"Example JSON is here.");
}
}
Controller:
[HttpGet]
[ProducesResponseType(typeof(JsonDocument), 200)]
[SwaggerResponseExample(200, typeof(MySpecificResponseExample))]
public async Task<IActionResult> GetSomething() {}
How to include examples to end-points with specific attribute only? I have another end-point where [ProducesResponseType(typeof(JsonDocument), 200)] is specified, but I want to ommit MySpecificResponseExample.

You can not return JsonDocument over the http since it needs to be converted to json , so I don't see any sense in double parcing and unparcing you better try this and parse to JsonDocument when you get it. But I highly recommend to use JObject Or JArray instead
public JsonResult GetExamples()
{
return new JsonResult( #"Example JSON is here.");
}

Related

Use serializer within ASP.NET Core MVC controller?

ASP.NET Core MVC serializes responses returned from my Microsoft.AspNetCore.Mvc.ControllerBase controller methods.
So the following will serialize a MyDTO object to JSON:
[Produces("application/json")]
[HttpGet]
public MyDTO test()
{
return new MyDTO();
}
However, I want to use the exact same serializer within the method itself. Is that possible? Something like
[Produces("application/json")]
[HttpGet]
public MyDTO test()
{
var result = new MyDTO();
string serialized = this.<SOMETHING GOES HERE>(result);
Console.WriteLine($"I'm about to return {serialized}");
return result;
}
It's important that it's the same serializer, including any options set in the stack.
Don't think that's possible because it's hidden away within IActionResultExecutor<JsonResult>. Default will be the SystemTextJsonResultExecutor but could be overwritten on startup when using e.g. .AddNewtonsoftJson(...)...
You could set the Json Format Options explicitly in startup.cs and then just use the same options wherever else you need to serialize. That way all serializations will be done exactly the same way.
e.g. If you are using JsonSerializer:
public static class JsonSerializerExtensions
{
public static JsonOptions ConfigureJsonOptions(this JsonOptions options)
{
// your configuration
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.WriteAsString;
return options;
}
}
startup.cs
services
.AddControllers()
.AddJsonOptions(options => options.ConfigureJsonOptions());
Conroller.cs
string serialized =JsonSerializer.Serialize(new MyDTO(), options: new JsonOptions().ConfigureJsonOptions().JsonSerializerOptions);

Is there way to choose between Newtonsoft.json and System.Text.Json in Actions?

I use ASP.NET Core 5, I don't want to migrate from Newtonsoft.Json to System.Text.Json, but in some cases, I want to use System.Text.Json to increase performance in controller actions.
For example, in ActionA, I want to use default behavior of Newtonsoft.Json serializer and in ActionB, I want to change behaviour to System.Text.Json serializer.
As far as I know, there is no build-in way to specific the Jsonconvert for specific controller.
If you want to modify the generated json result jsonconvert, I suggest you could try to use this way.
I suggest you could try to use actionfilter to achieve your requirement.
By using actionfilter, you could modift the input formatter to use other jsonconvert to convert the data.
public class CaseActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext ctx)
{
if (ctx.Result is ObjectResult objectResult)
{
objectResult.Formatters.Add(new SystemTextJsonOutputFormatter(new JsonSerializerOptions
{
IgnoreNullValues = true
}));
}
}
}
Usage:
[HttpPost]
[CaseAction]
public ActionResult Index([FromForm]Member member) {
if (ModelState.IsValid)
{
RedirectToAction("Index");
}
return Ok();
}
If you want to set the convert for the model binder, the only way is create a cusotmer modle binder and modify the json formatter according to each model type. There is no way to achieve it according to asp.net core has modified the iresoucefilter to not support change is formater.

Return type of a file for Swagger documentation with dotnet core

I'm using Swagger for dotnet core to document my dotnet core Web API.
I've read the documentation telling me that I need to add
[ProducesResponseType(typeof(XXXXX),200)] above the controller methods to help swagger determine the response type of the method.
I've got a controller method that returns a file and i'm trying to work out how I can tell swagger i'm returning a file.
public class DocumentController : Controller
{
private readonly IDocumentService _documentService;
public DocumentController(IDocumentService documentService)
{
_documentService = documentService;
}
[HttpGet("{documentId}", Name= DocumentRoutes.Document)]
[ProducesResponseType(typeof(XXXXX), 200)] // <== What goes here?
public async Task<IActionResult> GetDocument(Guid documentId)
{
DocumentAdto documentAdto = await _documentService.GetAsync(documentId);
return File(documentAdto.DocumentBytes, documentAdto.ContentType, documentAdto.Name);
}
}
Does anyone have any ideas?
I've thought about byte[] but that just says the return type is "byte".
What you need is the ProducesAttribute and specify the content type as the parameter (e.g. "application/pdf" for a PDF file).
Edit: it appears Swagger may not pick up on the ProducesAttribute. My suggestion would then be to leave the Type unset for ProducesResponseType and add a /// <response code="200">Returns the requested file</response> comment to the method.

Using caching with a strongly typed service implementation in ServiceStack

I have my service definitions, which use strongly typed return DTOs via the IReturn interface
[Route ("/items/{id}", "GET")]
public class FindItems : IReturn<FindItemResponse>
{
public int Id { get; set; }
}
public class FindItemResponse { ... }
I also use that type with my service's implementation
public FindItemResponse Get (FindItems request)
{
...
}
This worked very well, until I tried to use ServiceStack's caching abilities. According to the documentation, one should use the Request.ToOptimizedResultUsingCache? method, which returns object and not FindItemResponse. I checked the actual return type, sometimes its a JSON string, sometimes its aServiceStack.CompressedResult`, so no chance to manually convert it.
How is caching supposed to be done in ServiceStack, when you use a strongly typed response (as is recommended by #mythz)?
ToOptimizedResultUsingCache returns a CompressedResult on subsequent calls because it's returning the cached compressed bytes of the format (e.g. json) being requested.
This should be a transparent implementation detail for your clients which just receive a cached version of your Service. When using ToOptimizedResult* APIs your Service needs to be able to return a Cached response so you'll need to change the return type to object, e.g:
public object Get(FindItems request)
{
return Request.ToOptimizedResultUsingCache(base.Cache,cacheKey,()=>
{
return new FindItemResponse { ... }
});
}
Changing from an object to ResponseDto return Type is functionally equivalent and has no behavioral differences.
Since it has no effect my Services almost always have an object return Type unless I need to call it internally from other Services using base.ResolveService<T>, it's more important to specify the Response Type in the IReturn<T> interface marker, e.g:
public class FindItems : IReturn<FindItemResponse> {}
Which will enable the succinct typed APIs that .NET clients will be able to take advantage of.
An alternative to using ToOptimizedResult* APIs is to use the newer [CacheResponse] Attribute which will let your Services return a Typed Response DTO, e.g:
[CacheResponse(Duration = 60)]
public FindItemResponse Get(FindItems request)
{
return new FindItemResponse { ... }
}
Where for subsequent calls will return the cached response instead of calling your Service.

ASP.NET Core v2 (2015) MVC : How to get raw JSON bound to a string without a type?

Similar to this old question about prior ASP.NET versions, I want to get the request body of an HTTP POST to be bound to a string. It seems that the method binds, but that value is null, when ASP.NET invokes my controller method:
namespace Demo.Controllers
{
[Route("[controller]")]
public class WebApiDemoController : Controller
{
...
// POST api/values
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]string value)
{
// expected: value = json string, actual: json = null.
}
Do I still have to go grab the body from a stream? Or should this just work? When testing the above method, I used the following http headers:
Accept: Application/json
Content-Type: Application/json;charset=UTF-8
I'm passing in the following in the body: { "a": 1 }
I do NOT want to bind to a string variable named a. I want to bind any JSON I get, and then I want to use the JSON content, any arbitrary content at all, from within my method.
If I understood the documentation, the [FromBody] attribute should have done what I wanted, but I'm guessing that the ASP.NET core MVC binding mechanism won't bind a json to a "string value", but perhaps I could do something else that gets me an equivalent level of flexibility.
A similar question here gives me the idea maybe I should have written [FromBody] dynamic data instead of using [FromBody] string value.
Update: There are answers here for .net core 6 and other modern .net core versions.
The cleanest option I've found is adding your own simple InputFormatter:
public class RawJsonBodyInputFormatter : InputFormatter
{
public RawJsonBodyInputFormatter()
{
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
And in your Startup.cs inside ConfigureServices:
services
.AddMvc(options =>
{
options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
});
That will let you get at the raw JSON payload in your controllers:
[HttpPost]
public IActionResult Post([FromBody]string value)
{
// value will be the request json payload
}
The following works in .net core 1.x, but not in .net core 2.x.
As I commented, the solution is to use [FromBody]dynamic data as my parameter list, using dynamic instead of string, and I will receive a JObject.
Caution: If your architecture calls for a single WebApi server to be equally fluent in producing XML and JSON, depending on content-type header entries, this kind of direct-JSON-consumption strategy can backfire on you. (Supporting both XML and JSON on the same service is possible with sufficient work, but then you're taking stuff that was further UP the MVC asset pipeline and moving it down into your controller methods, which turns out to be against the spirit of MVC, where models come to you as POCOs already parsed.)
Once you convert to a string inside the method, converting the incoming JObject (Newtonsoft.JSON in memory data type for JSON) to a string.
Found at other answer here.
Sample code, thanks to Jeson Martajaya:
With dynamic:
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]dynamic value)
{
//...
}
Sample code with JObject:
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]Newtonsoft.Json.Linq.JObject value)
{
//...
}
Found a solution for ASP.NET Core 3.1 Web API.
Looks like following:
public async Task<IActionResult> PutAsync([FromBody] System.Text.Json.JsonElement entity)
{
// your code here
}
The following two methods works in ASP.NET core 2 to read the raw json string.
1) This one has better performance.
[HttpPost]
public async Task<ActionResult<int>> Process()
{
string jsonString;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
jsonString = await reader.ReadToEndAsync();
}
2)
[HttpPost]
public async Task<ActionResult<int>> Process([FromBody]JToken jsonbody)
{
var jsonString = jsonBody.ToString();
Alternatively, you could also just accept a JObject and you would be able to use Linq to Json ot even directly ToString() if you really need the string.
Based on Saeb Amini's excellent answer above, this extends his solution to be for plain-text as well. The only changes here are adding the "text/plain" mime-type, and adding a namespace and required usings.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
namespace AspExtensions // or whatever
{
// see: https://stackoverflow.com/a/47807117/264031
public class RawStringBodyInputFormatter : InputFormatter
{
public RawStringBodyInputFormatter()
{
this.SupportedMediaTypes.Add("text/plain");
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using(var reader = new StreamReader(request.Body)) {
string content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
}
If you don't mine forgoing the automagic binding, this can be placed directly in an Http handler on a Controller:
using StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8);
var value = reader.ReadToEndAsync().GetAwaiter().GetResult();
I see that Sam has already been down voted for saying pretty much the same thing, but in testing using Postman I find that if I set the request body to just a simple double quoted string ASP binds it fine with the default '[FromBody]string value' argument.
"just send your string like this without any curly braces"
Not sure whether application/json is supposed to accept data in this format. Hopefully by posting this someone knowledgeable will pipe up and state whether this is valid or not.
You need a type to bind the data. Example:
public class Person
{
public string Name {get; set;}
}
for data { "Name" : "James"}
If you want to receive a string you need to pass it as a string. Your JSON should be enclosed in quotes:
'{ "a": 1 }'

Categories