We have a swagger generated OpenAPI definition which has an endpoint that defines it's responses as the following:
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "file"
}
},
"application/json": {
"schema": {
"type": "file"
}
},
"text/json": {
"schema": {
"type": "file"
}
}
}
}
}
When using NSwagStudio to generate a C# Client from swagger.json, the response type translates into a class which gets defined into the generated file like so...
NSwagStudio Generated Endpoint example
public System.Threading.Tasks.Task<FileResponse> FileAsync(LayeredVideo body)
{
return FileAsync(body, System.Threading.CancellationToken.None);
}
NSwagStudio Geneated FileResponse class
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.15.10.0 (NJsonSchema v10.6.10.0 (Newtonsoft.Json v12.0.0.0))")]
public partial class FileResponse : System.IDisposable
{
private System.IDisposable _client;
private System.IDisposable _response;
public int StatusCode { get; private set; }
public System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> Headers { get; private set; }
public System.IO.Stream Stream { get; private set; }
public bool IsPartial
{
get { return StatusCode == 206; }
}
public FileResponse(int statusCode, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.IO.Stream stream, System.IDisposable client, System.IDisposable response)
{
StatusCode = statusCode;
Headers = headers;
Stream = stream;
_client = client;
_response = response;
}
public void Dispose()
{
Stream.Dispose();
if (_response != null)
_response.Dispose();
if (_client != null)
_client.Dispose();
}
}
however if I attempt to generate the same C# client within VS2019 via the Connected Services interface while the endpoint itself generates the same, it does not define the actual FileResponse class
VS2019 Generated Endpoint example
public System.Threading.Tasks.Task<FileResponse> FileAsync(LayeredVideo body)
{
return FileAsync(body, System.Threading.CancellationToken.None);
}
This leads to the following error in the generated code.
By my understanding, NSwagStudio and Connected Services use basically the same tech in the background to generate code, so I am confused as to why one solution generates working code and the other doesn't.
Any ideas?
Related
I'm learning AWS Lambda with C#. My function looks sort of like this:
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace Function_Redeem
{
public class Function
{
public FunctionOutput FunctionHandler(FunctionInput input, ILambdaContext context)
{
// do work with input
// return FunctionOutput
}
public class FunctionInput
{
public string someData { get; set; }
}
public class FunctionOutput
{
public string someAnswer { get; set; }
}
}
}
It works fine when using the Test button in AWS, as well as the test feature in Visual Studio.
Now, I'm trying to call this from Unity.
So first, I added an API Gateway trigger, and left the defaults:
API endpoint: [the url]
API type: HTTP
Authorization: NONE
Cross-origin resource sharing (CORS): No
Enable detailed metrics: No
Method: ANY
Resource path: /FunctionName
Stage: default
Then in Unity,
private static IEnumerator TestFunction(string uri, string data)
{
UnityWebRequest webRequest = UnityWebRequest.Put(uri, data);
yield return webRequest.SendWebRequest();
if (webRequest.isNetworkError)
Debug.LogError("Network error: " + webRequest.error);
else
Debug.Log(webRequest.downloadHandler.text);
}
I call it, with data being
{"someData":"Hello"}
The function call works, I know that it is reaching my function, but the input data (i.e. the someData field) is null. It seems like it's not parsing the data I'm sending so FunctionInput defaults to null someData.
What am I missing?
Since you are using API Gateway as a trigger to your lambda function, accept APIGatewayProxyRequest as input parameter to your handler(instead of FunctionInput). The field Body would have your serialized payload {"someData":"Hello"}
public class Function
{
public FunctionOutput FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
{ var requestBody = JsonConvert.DeserializeObject<FunctionInput>(request.Body);
// do work with input
// return FunctionOutput
}
public class FunctionInput
{
public string someData { get; set; }
}
public class FunctionOutput
{
public string someAnswer { get; set; }
}
}
}
I am sending an object from my web project to my API project within the same solution. I have a class that uses RestSharp and acts as the sender to the API for all services.
Currently, there is one API controller that is receiving the object, but not all of the parameters are being retained and show up with null values via PUT. However, a different controller using the ApiClient's 'PutAsync' receives its own object with all the values intact.
I've even tried changing the method to receive as a POST, but still no success.
Am I missing something, or is there something wrong that is happening with the serialization/de-serialization of the object?
public class UserInfo
{
public int UserId { get; set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string EmailAddress { get; private set; }
}
internal class WebService : IWebService
{
public async Task<bool> UpdateProfile(UserInfo userInfo)
{
try
{
var url = "/User/UpdateProfile";
return await apiClient.PutAsync<UserInfo, bool>(url, userInfo);
}
catch (Exception ex)
{
this.logger.LogError("Error in UpdateProfile", ex);
throw;
}
}
}
RestSharp Setup
internal class ApiClient : IApiClient
{
public async Task<TOut> PutAsync<TIn, TOut>(string url, TIn data) where TIn : new()
{
return await PushData<TIn, TOut>(url, data, Method.PUT);
}
private async Task<TOut> PushData<TIn, TOut>(string url, TIn data, Method method) where TIn : new()
{
var request = new RestRequest(url, method);
request.AddHeader(HttpRequestHeader.Authorization.ToString(), $"Bearer {GetApiAccessToken()}");
request.AddJsonBody(data);
var result = await client.ExecuteAsync<TOut>(request);
if (result.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Unable to push API data");
}
return result.Data;
}
}
Data in the request prior to being sent out, found under the Parameters, are as followed:
Data sent to API UserController
[Produces("application/json")]
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpPut]
[Route("UpdateProfile")]
public async Task<IActionResult> UpdateProfile([FromBody] ProfileUpdateInfo profileInfo)
{
try
{
var status = await this.service.UpdateProfile(profileInfo);
return Ok(status);
}
catch (Exception ex)
{
return BadRequest("Error in updating profile");
}
}
}
This is the Data that shows up in the parameter:
Data Consumed by API UserController
After reviewing my code again, the problem was that I had the set accessor to private on all string properties, which is why the int property was still coming through.
I'm just trying to create a simple POST function that let's me POST a JSON. I've tried to copy examples but I'm not sure what I'm doing differently. Any help would be appreciated, I feel like it's something simple that I'm missing.
What I'm trying to post:
POST Address: http://localhost:49653/save/file
Headers:
Content-Type: application/json
Raw Body:
{
uuid: "someUuid",
fileName: "test",
dateTime: "dateee",
json: "some json"
}
namespace SomeNamespace.Model
{
[Route("/save/file", "POST")]
public class SaveFileRequest
{
public Stream RequestStream { get; set; }
}
public class SaveFileResponse
{
public bool Success { get; set; }
}
}
namespace SomeNamespace.ServiceInterface
{
[EnableCors(allowedMethods:"POST")]
public class SaveFileService : Service
{
public object Any(SaveFileRequest request)
{
var response = new SaveFileResponse { Success = false };
string savedataJson;
using (var reader = new StreamReader(Request.InputStream))
{
savedataJson = reader.ReadToEnd();
}
try
{
Console.WriteLine(savedataJson); // When I debug, the contents are ""
}
catch(Exception ex) {...}
}
}
}
}
Your SaveFileRequest Request DTO needs to implement IRequiresRequestStream.
Here are the docs for reading directly from the request stream:
Reading directly from the Request Stream
Instead of registering a custom binder you can skip the serialization of the request DTO, you can add the IRequiresRequestStream interface to directly retrieve the stream without populating the request DTO.
//Request DTO
public class RawBytes : IRequiresRequestStream
{
/// <summary>
/// The raw Http Request Input Stream
/// </summary>
Stream RequestStream { get; set; }
}
Which tells ServiceStack to skip trying to deserialize the request so you can read in the raw HTTP Request body yourself, e.g:
public object Post(RawBytes request)
{
byte[] bytes = request.RequestStream.ReadFully();
string text = bytes.FromUtf8Bytes(); //if text was sent
}
I have some legacy code that I'm trying to work with and need to return an existing object that contains a Stream from an ApiController
public class LegacyObject
{
public bool Result { get; set; }
public Stream Stream { get; set; }
}
API Code
public class BindJson : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string rawRequest;
using (var stream = new StreamReader(actionContext.Request.Content.ReadAsStreamAsync().Result))
{
stream.BaseStream.Position = 0;
rawRequest = stream.ReadToEnd();
}
rawRequest = rawRequest.ToString();
var obj = JsonConvert.DeserializeObject<LegacyParameters>(rawRequest.ToString());
actionContext.ActionArguments["parameter"] = obj;
}
}
public class ReportsController : ApiController
{
[BindJson]
[HttpPost]
public LegacyObject ReturnReport([FromBody]LegacyParameters parameter)
{
LegacyObject r = LegacyClass.GetReportStream(parameters);
return r; //Object properties are correctly set and no errors at this point
}
}
My call to the Api is
using (var httpClient = new System.Net.Http.HttpClient())
{
httpClient.BaseAddress = new Uri("http://myserver/");
string contents = JsonConvert.SerializeObject(paramList);
var response = httpClient.PostAsync("/api/ReturnReport", new StringContent(contents, Encoding.UTF8, "application/json")).Result;
}
I get a 500 Internal Server Error on the PostAsync when my LegacyObject.Stream has content. It works when the stream content is null. I'm working locally on my development PC and web server for the API is IIS Express.
Any advice would be appreciated.
So in order to get more detail on the 500 Internal Server Error, I added this to my projects WebApiConfig Register
config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());
With this class
public class UnhandledExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
var log = context.Exception.ToString();
//Write the exception to your logs
}
}
The log variable now gives me the detail I need to debug
FYI - The error was a read timeout on my Stream
We develop ASP.NET MVC5 app with WebApi2 and AngularJs. For serialization and deserialization we use custum JsonNetFormatter as follow:
public class JsonNetFormatter : MediaTypeFormatter
{
// other codes for formatting
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
string NameOfSet = "";
ObjectWrapperWithNameOfSet obj = value as ObjectWrapperWithNameOfSet;
if (obj != null)
{
NameOfSet = obj.NameOfSet;
value = obj.WrappedObject;
}
_jsonSerializerSettings.ContractResolver = new CustomContractResolver(NameOfSet);
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task writing the serialized content
return Task.Factory.StartNew(() =>
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(writeStream, SupportedEncodings[0])) { CloseOutput = false })
{
serializer.Serialize(jsonTextWriter, value);
jsonTextWriter.Flush();
}
});
}
}
and WebApiConfig as follow:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.Formatters.Clear();
config.Formatters.Insert(0, new JsonNetFormatter());
}
}
The application work fine. but in some situation we get an errors when deserialization json data. My question is How can we handle these errors and send to the client side?
Example for the errors:
You can create your own custom exception handler to modify any error messages before sending it to client
public class ErrorHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult()
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." + "Please contact support so we can try to fix it."
};
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError, Content);
return Task.FromResult(response);
}
}
}
and then register this custom class in config class like following
config.Services.Replace(typeof(IExceptionHandler), new ErrorHandler());