I'm trying to use the Bulk Import API endpoint within Marketo to fetch the warnings and failures associated with a bulk job I created within Marketo.
The output of these jobs is stated as an ObservableOfInputStreamContent, yet the response of these endpoints returns a csv string (with header column), that's not a JSON object. To compound to this problem, we are using the generated swagger files with the swagger definition file provided by Marketo. These generated c# client side files have the ObservableOfInputStreamContent object, but it's an empty object. I'm not sure if this is intended, or a mistake on Marketo's side. The generated files will attempt to use Newtonsoft.Json.JsonConvert.DeserializeObject<ObservableOfInputStreamContent>(responseText, JsonSerializerSettings); to deserialize the API response to the ObservableOfInputStreamContent.
Generated code that deserializes the API response:
var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseText, JsonSerializerSettings);
return new ObjectResponseResult<T>(typedBody, responseText);
}
catch (Newtonsoft.Json.JsonException exception)
{
var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
}
Problem one is that the API does not return JSON to begin with (eg):
address,city,country,
123 lane, new york, USA
745 street, new york, USA
This call will return this error because of this:
Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: a. Path '', line 0, position 0.
The second issue is that ObservableOfInputStreamContent is defined as an empty object to begin with in the generated files. So if the API response was valid JSON, I don't think it would know how to convert to that empty ObservableOfInputStreamContent object. The good news about the generated code is that it gives me an option to extend the ObservableOfInputStreamContent because it's defined as a partial class.
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.11.0 (Newtonsoft.Json v11.0.0.0)")]
public partial class ObservableOfInputStreamContent
{
}
That said, is there any possible way I can use the JsonSerializerSettings to get around this issue? Could I extended the ObservableOfInputStreamContent class to hold a string property and then create my own JsonConverter to convert the string returned from the API into the new ObservableOfInputStreamContent?
Related
I have an endpoint that looks like:
public IHttpActionResult SignUp([FromBody] AuthInput input)
Whenever the request body includes an escape character "\" the API fails to bind the data to the model. I have a guess that this is because the JSON would be considered "malformed."
I'd like to format the request body before the API attempts to bind it to the model and change all "\" to "\\"
Request body
{
"email": "mrsmith#usa.com",
"password": "badpassword\",
"firstName": "John",
"lastName": "Smith"
}
The backspace makes the input object "null"
Using c# ASP.NET 4.6.2
If you're generating the request body yourself:
Ideally you should be generating your Request Body JSON payload by serializing an existing DTO class.
If you can't do that, then you should safely escape string-values for use in directly-rendered JSON using .NET's built-in JavaScript string-escape utility methods:
In the .NET Framework Full Profile (so not .NET Core, .NET Standard, or .NET Framework Client Profile) you can use System.Web.HttpUtility.JavaScriptStringEncode.
Otherwise, use System.Text.Encodings.Web.JavaScriptEncoder.
If you're receiving bad input that you have no control over:
Approach 1: Work with the request-body directly:
This is the simplest approach, but requires you to do it for every controller action that receives malformed JSON:
public async Task<IHttpActionResult> SignUp()
{
String requestBody;
using( StreamReader rdr = new StreamReader( this.Request.Body ) )
{
requestBody = await rdr.ReadToEndAsync();
}
//
// Tweak the raw JSON text to make it parseable:
String requestBodyTweaked = requestBody.Replace( "\\\",", "\"," );
// Parse it and pass it on to the original `SignUp` method:
AuthInput dto = JsonConvert.DeserializeObject<AuthInput>( requestBodyTweaked );
return this.SignUp( dto );
}
// This is your current SignUp action method, it's unchanged except it's now `private`:
private IHttpActionResult SignUp( AuthInput input)
{
}
Approach 2: Middleware to intercept and modify all request bodies:
You could mitigate this using a middleware layer that intercepts the request body - but be very careful.
See this QA for instructions on how to intercept and modify an incoming request body: ASP NET Core modify/substitute a request body
I assume you'd want to edit the raw JSON text rather than parse it:
I have a web API OnGet() that takes a ZapScan parameter as its input and returns it as JSON or XML.
[HttpGet, FormatFilter]
public ActionResult<ZapScan> OnGet([FromQuery] ZapScan scan)
{
return Ok(scan); // return JSON/XML representation of "scan" argument
}
However, if the provided ZapScan argument contains invalid values, then I would like to return a 500 status-code, a custom error message, and the ZapScan object formatted as JSON/XML:
HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
await HttpContext.Response.WriteAsync("Invalid request.");
...
return scan;
The problem is that after a call to HttpContext.Response.WriteAsync() then return scan; has no effect - i.e. scan is not returned as JSON in the response together with the "Invalid request." error message.
Can someone clarify?
If it's input validation error, standard way would be to return:
ModelState.AddModelError("", "ZapScan invalid");
return new BadRequestObjectResult(new ValidationProblemDetails(ModelState));
This would return 400 Bad Request error with your error response as JSON. You don't have to return scan object to the user, because user has sent it, he knows the value already.
You can automate this process if you put ApiControllerAttribute on your Controller and use System.ComponentModel.DataAnnotations validation attributes on your ZapScan class properties (Required, MaxLength, etc...). You can read more about this at https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1
I'm new to dialogflow and trying to use permission handler to ask for location permission using .NET core webapi. I've created intent, entities and event(google.assistent.permission) in dialogflow console. Now I want to send a request from my webapi to send the request to access location.
Can somebody please provide a code sample how to send request to access location from my webhook?
You need to include the helper intent DialogFlow JSON as part of the payload:
WebhookResponse response;
Struct payload;
response.Payload = payload;
Alternatively, it can be added as a fulfillment message with the payload type1.
The payload struct can be parsed from JSON:
response.Payload = Struct.Parser.ParseJson(#"{
""google"": {
""expectUserResponse"": true,
""systemIntent"": {
""intent"": ""actions.intent.PLACE"",
""data"": {
""#type"": ""type.googleapis.com/google.actions.v2.PlaceValueSpec"",
""dialogSpec"": {
""extension"": {
""#type"": ""type.googleapis.com/google.actions.v2.PlaceValueSpec.PlaceDialogSpec"",
""permissionContext"": ""To find a location"",
""requestPrompt"": ""Where would you like to go?""
}
}
}
}
}
}");
Or created using the Protobuf API (slightly faster due to skipping the parsing step and type safe, but incredibly ugly):
response.Payload = new Struct
{
Fields =
{
["google"] = Value.ForStruct(new Struct
{
Fields =
{
["expectUserResponse"] = Value.ForBool(true),
["systemIntent"] = Value.ForStruct(new Struct
{
// ... and so on
})
}
})
}
};
Keep in mind that including any message in the payload (which is necessary to call the helper) will override any other messages you added previously and ignore anything added afterwards (they are still part of the returned object, but stripped out by DialogFlow). That means: If you want any other rich response, it currently also needs to be manually added to the payload. At that point, you might as well create the entire JSON response from scratch.
I am getting an exception while reading the post data.
I get error on this line:
HttpContext.Current.Request.Form["UserID"].ToString();
And the error is :
System.Collections.Specialized.NameValueCollection.this[string].get
returned null.
In method I have put this code :
StreamReader reader = new StreamReader(HttpContext.Current.Request.InputStream);
string requestFromPost = reader.ReadToEnd();
and data comes in that properly like this:
{
"UserID": "1000",
"Password": "ABCD"
}
Why I am not getting value in this HttpContext.Current.Request.Form["UserID"].ToString()? I also tried Request.QueryString but no success here.
Where am I doing wrong? Any help or suggestion would be much appreciated. Thanks!
There is no Form on this request. For a request body to be interpreted as form data, it must:
have a content type of x-www-form-urlencoded
be actually formatted as form encoded values, i.e. UserID=foo&Password=bar
JSON content is JSON, it will not be interpreted as form-data.
Web API should already take care of this for you. Given an action method:
public void Action(Credentials credentials)
where the Credentials class looks something like:
public class Credentials
{
string UserID { get; set;}
string Password { get; set; }
}
You shouldn't have to do anything else to have the framework turn this incoming JSON data into an instance of Credentials and pass it to the action method. This is automatic unless you've done something strange that breaks the conventions that WebAPI expects.
I am very new to dialogflow and WebAPIs, and having trouble with a simple dialogflow fulfillment webhook written in C# and hosted on Azure. I am using dialogflow V2.0 API version.
Currently my fulfillment works and returns a simple response but has no regard to the intent and parameters. I am trying to now parse the JSON get the intent do a simple select case and return the value of the parameters recevied. And this is giving me lot of trouble. The webhook link, my code and the error message returned in the "catch" block are given below
public JsonResult Post(string value)
{
try
{
dynamic obj = JsonConvert.DeserializeObject(value);
string Location = string.Empty;
switch (obj.intent.displayName)
{
case "getstock":
Location = obj.outContexts[0].parameters[0].Location;
break;
}
WebhookResponse r = new WebhookResponse();
r.fulfillmentText = string.Format("The stock at {0} is valuing Rs. 31 Lakhs \n And consists of items such as slatwall, grid and new pillar. The detailed list of the same has been email to you", Location);
r.source = "API.AI";
Response.ContentType = "application/json";
return Json(r);
} catch(Exception e)
{
WebhookResponse err = new WebhookResponse();
err.fulfillmentText = e.Message;
return Json(err);
}
}
The error message :
Value cannot be null.
Parameter name: value
The above function is called via POST, you can use POSTMAN and you will get the JSON response.
Moreover i am using ASP.Net Web Api with Visual Studio 2017 with Controllers
First install the nuget package Google.Apis.Dialogflow.v2 and its dependencies. It'll save you a lot of work later on as it has dialogflow response/request c# objects which will make navigating the object graph easier.
Second add the using for the package using Google.Apis.Dialogflow.v2.Data;
Change your method to something like
public GoogleCloudDialogflowV2WebhookResponse Post(GoogleCloudDialogflowV2WebhookRequest obj)
{
string Location = string.Empty;
switch (obj.QueryResult.Intent.DisplayName)
{
case "getstock":
Location = obj.QueryResult.Parameters["Location"].ToString();
break;
}
var response = new GoogleCloudDialogflowV2WebhookResponse()
{
FulfillmentText = $"The stock at {Location} is valuing Rs. 31 Lakhs \n And consists of items such as slatwall, grid and new pillar. The detailed list of the same has been email to you",
Source = "API.AI"
};
return response;
}
I think your main issue in your code is "obj.outContexts[0]" outContexts isn't where you'll find your parameters, and unless you've setup an output content this will be null. You need to look in queryResult for your parameters.