Simple is, that I am trying to make a PUT request to my API but I get the type error of:
_CastError (type 'List<String>' is not a subtype of type 'String' in type cast)
My API accepts an Array of strings (string[]), which I know work because I am currently consuming it on the sibling web platform. So I am trying to replicate that on the Flutter app with the below code. At the moment it is just static code.
I know, that the http module is only accept string but is there a way to get around this? As it does not make sense, as what happens if we want to post an int, bool or <List<String>>. I know you can obviously convert using .toString() but my API has certain validation and is rigid on what it can accept.
Code below:
When I use this payload it works because it follows the rigid types of the Http module (<String, String>)
Map<String, String> payloadThatWorks = {"first_name": "First Name"};
Now when I want to give the payload of the type Map<String, List<String>> with the code below:
Map<String, List<String>> payload = {
"personal_settings": ["allow-notification-discussion-mentions"]
};
It throws the error of _CastError (type 'List<String>' is not a subtype of type 'String' in type cast)
in the http.put function below:
Main API Helper function
static Future<http.Response> queryPut(String apiPath, {dynamic body}) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String accessToken = prefs.getString('access_token');
var response = await http.put(
Uri.encodeFull(_urlBase + '$apiPath'),
body: body,
headers: {
HttpHeaders.authorizationHeader: "Bearer $accessToken",
'Accept': 'application/json'
},
);
return response;
}
However, when helper function is called in my Widget...
http.Response response =
await ApiService.queryPut('/api/users/$username', body: payload);
So i'm in a spot where the http module does not accept anything put <String> but the API does not accept anything but an Array or <List<String>>
How can I get round this or understand why the http put method is so rigid?
Thank you in advance :)
Sam
You can import dart:convert and use its jsonEncode to convert your map or array of map into a string
So basically you will have to do the following
String payloadStr = jsonEncode(payload)
and use this payloadStr to pass in the http methods. Similarly you can use JsonDecoder to convert json string to Map or Array of map.
Related
Trying out minimal APIs in .NET 6 and can't make it work with XML content type. If I use standard controllers, using .AddXmlSerializerFormatters() extension does the job:
builder.Services.AddControllers().AddXmlSerializerFormatters();
But when I switch from controller to .MapPost(..), I start getting 415 HTTP responses.
app.MapPost("/endpoint", ([FromBody] Request request) => {})
.Accepts<Request>("text/xml");
HTTP response: 415 Microsoft.AspNetCore.Http.BadHttpRequestException: Expected a
supported JSON media type but got "text/xml"
Is there any other way I can declare XML formatters that will work with minimal APIs?
As suggested by the post linked by guru-stron, it's possible to pass XML documents by implementing your own wrapping model that provides a BindAsync method.
internal sealed class XDocumentModel
{
public XDocumentModel(XDocument document) => Document = document;
public XDocument Document { get; init; }
public static async ValueTask<XDocumentModel?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasXmlContentType())
throw new BadHttpRequestException(
message: "Request content type was not a recognized Xml content type.",
StatusCodes.Status415UnsupportedMediaType);
return new XDocumentModel(await XDocument.LoadAsync(context.Request.Body, LoadOptions.None, CancellationToken.None));
}
}
I added a extension method to HttpRequest for convenient Content-Type validation.
internal static class HttpRequestXmlExtensions
{
public static bool HasXmlContentType(this HttpRequest request)
=> request.Headers.TryGetValue("Content-Type", out var contentType)
&& string.Equals(contentType, "application/xml", StringComparison.InvariantCulture);
}
You can then use the model directly as a paramter by your minimal API endpoint.
app.MapGet("/xml-test", (XDocumentModel model) =>
{
// model.Document <- your passed xml Document
return Results.Ok(new { Value = model.Document.ToString() });
})
Some final thoughts: This implementation enables you to pass a generic XML document to the endpoint. However, if you expect a certain document structure, you could implement this by making the XDocumentModel expect a generic type parameter and extracting this type's properties from the XDocument instance.
I did it this way:
app.MapPost("/endpoint", (HttpContext c) =>
{
var reader = new StreamReader(c.Request.Body);
var xml = reader.ReadToEndAsync().Result;
// You can do with your xml string whatever you want
return Results.Ok();
}).Accepts<HttpRequest>("application/xml");
I am trying make an http post request with the method below. when I run the method I keep getting the message that an exeption has occured and am being pointed to this utils.dart file. From what am seeing my best guess is that there is a problem converting the map data I have provided in the url into the query string. Icant seem to find what am doing wrong tho.How may I solve this.
Request Method
Future<List>getdata()async{
print("Loading ....");
String theUrl ="https://http://127.0.0.1/codeishweb/notifiedUser.php";
var res = await http.post(Uri.encodeFull(theUrl),headers: {"Accept":"application/json"},body: {"notID":2},);
var responseBody = json.decode(res.body);
print("results ::$responseBody");
//print("the response at position something::::\nHeading\n${responseBody[0][7]['post_head']}\nBody\n${responseBody[0][7]['post_body']}");
return responseBody;
}
Error message in utils.dart
/// Converts a [Map] from parameter names to values to a URL query string.
///
/// mapToQuery({"foo": "bar", "baz": "bang"});
/// //=> "foo=bar&baz=bang"
String mapToQuery(Map<String, String> map, {Encoding encoding}) {
var pairs = <List<String>>[];
map.forEach((key, value) => pairs.add([
Uri.encodeQueryComponent(key, encoding: encoding),
Uri.encodeQueryComponent(value, encoding: encoding)
]));
return pairs.map((pair) => "${pair[0]}=${pair[1]}").join("&");
}
I want to send parameters with an http GET request in dart. The first answer here demonstrates it well: How do you add query parameters to a Dart http request?
var uri =
Uri.https('www.myurl.com', '/api/query', queryParameters);
However, a major problem is that queryParameters only accepts:
Map<String, String>
This doesn't allow me to pass in lists/arrays.
If i change my get request to a post request, I am easily able to send a
body (as a json encoded String) as outlined by the flutter docs: https://pub.dev/documentation/http/latest/http/post.html
post(url, {Map<String, String> headers, body, Encoding encoding})
However, the get request has no such equivalent argument for query parameters: https://pub.dev/documentation/http/latest/http/get.html
get(url, {Map<String, String> headers})
I have also tried adding query parameters directly to the url like so:
get('www.myurl.com/api/query/?array[]=value1&array[]=value2&array[]=value3)
but the square brackets [] always get transformed into %5B%5D when I receive it in the server.
Any help is appreciated.
Thanks
In fact, queryParameter takes Map<String, dynamic>. Check the source code:
factory Uri(
{String scheme,
String userInfo,
String host,
int port,
String path,
Iterable<String> pathSegments,
String query,
Map<String, dynamic /*String|Iterable<String>*/ > queryParameters,
String fragment}) = _Uri;
The dynamic can be either a String or an Iterable<String>. So,
var uri = Uri(
scheme: 'http',
host: 'www.myurl.com',
path: '/api/query/',
queryParameters: {
'array': ['value1', 'value2', 'value3'],
},
);
print(uri);
prints:
http://www.myurl.com/api/query/?array=value1&array=value2&array=value3
Goal: retrieve the single value "test" in Angular.
Problem: I get an error message
Error: SyntaxError: Unexpected token e in JSON at position 1 at JSON.parse ()
What syntax am I missing?
ASP.NET
// https://localhost:44353/api/Jobadvertisement/VVValidate/4
[HttpGet("VVValidate/{id:int}")]
public ActionResult<String> VVValidate(int id)
{
return "test";
}
Angular
const url = environment.url
let asdfasdf2 = url + 'api/Jobadvertisement/VVValidate/' + "4";
var dsfsdssf = this.http.get(asdfasdf2).subscribe(data => {
console.log(data);
});
Can you please try using httpOptions in Angular with something like this:
const url = environment.url;
let asdfasdf2 = url + 'api/Jobadvertisement/VVValidate/' + "4";
var dsfsdssf = this.http.get(asdfasdf2, { responseType: 'text' }).subscribe(data => { console.log(data); });
You need to change ActionResult to string:
[HttpGet("VVValidate/{id:int}")]
public string VVValidate(int id)
{
return "test";
}
Angular's HttpClient expects JSON data per default, which is why you get a JSON deserialization error upon returning plain text from your API.
You need to pass responseType: 'text' as option to the get method, in order to prevent HttpClient from treating your data as JSON:
httpClient.get("url", { responseType: 'text' });
You can find more information regarding this in Angular's Documentation.
Even though your API code works as it is, I'd like to point out two things:
You can unwrap your String and remove the ActionResult, as it is not needed.
I would encourage you to use the string type instead of System.String. For a detailed explanation, please refer to this.
Your code would look like this after applying these changes:
[HttpGet("VVValidate/{id:int}")]
public string VVValidate(int id)
{
return "test";
}
I'm trying to connect to a webservice that provides some customer data through a POST request but the response gets cut in the middle (or it might be that the trigger function doesn't await the response to complete).
This is done in a flutter environment and the initState() triggers the request.
I have a data service for the customer stuff, CustomerDataService which extends DataService that contain some common stuff such as sending the request and so on.
So in short initState() invoke CustomerDataService.getCustomers(request) which in turn invokes and await DataService.post(endpoint, request).
Http-package: import 'package:http/http.dart' as http;
initState() which is the starting point:
final CustomerDataService _dataService =
new CustomerDataServiceProvider().getCustomerDataService();
#override
void initState() {
_getCustomers();
super.initState();
}
void _getActors() async {
_dataService.getCustomers(
request: new GetCustomersRequest(
navigations: _dataService.suggestedNavigations
),
).then((response) {
_customersResponse = response;
/// Set the state
refresh();
});
}
And then we have the CustomerDataService:
class _CustomerDataService extends DataService implements CustomerDataService
#override
Future<GetCustomersResponse> getCustomers(
{#required GetCustomersRequest request}) async {
String endpoint = createEndpoint(<String>[
App.appContext.identityInstance.rootUrl,
_CUSTOMERS_CONTROLLER,
_GET_CUSTOMERS_ENDPOINT
]);
http.Response res = await post(endpoint: endpoint, request: request.toJson());
if (res.body == null) {
return null;
}
try {
/// This prints an invalid JSON that is cut in the middle
print(res.body);
/// This one naturally throws an exception since res.body isn't valid.
dynamic json = jsonDecode(res.body);
return new GetCustomersResponse.fromJson(json);
} catch (e) {
print("Exception caught when trying to get customers");
print(e);
}
return null;
}
The exception from jsonDecode is
Bad state: No element
And then we have the DataService:
Future<http.Response> post(
{#required String endpoint,
Map<String, String> header,
Map<String, dynamic> request}) async {
if (header == null) {
header = _getHeader();
} else {
header.putIfAbsent(_AUTHORIZATION_KEY, () => _headerAuthorizationValue());
}
http.Response res = await http.post(Uri.parse(endpoint), headers: header);
_validateReponse(res);
return res;
}
I'm clearly doing something wrong but I just can't see it...
The request in DataService.post doesn't add the body (request parameter) in this code and that is another ticket i will file after I've looked more into it, the workaround for now is to change the service to not expect a body.
I've verified that the service behaves as expected with postman.
I hope someone can see where my error(s) is.
Thanks!
Edit 1:
I changed the code a bit so that initState() doesn't use the DataServices created by me but used the http-package directly.
http.post('http://localhost:50140/api/customer/getcustomers').then((res) {
if(res == null) {
print('Response is empty');
}
print('Status code ${res.statusCode}');
print(res.body);
});
super.initState();
}
And the exact same thing happens so I don't think this is due to the dataservices at least.
Edit 2:
Before someone digs to deep into this I just want to say that it doesn't seem to be the response from the service, the http package or the dataservices.
This blog will be updated as soon as I find the cause of the Bad state: no element exception.
Okay, I don't know how to do this but it turns out the question title is incorrect.
It's the terminal that cuts the text when it's too big...
There were no errors in the dataservices or the http package but rather in the conversion from the response body to my strongly typed model, deep down the model tree.
An associative property to the Customer model have an enum, both server- and client side.
The service serialize the enum with the index, the library I use for mapping tries to get the enum by name (case sensitive).
The entity with the problem
#JsonSerializable()
class Item extends Object with _$ItemSerializerMixin
The auto-generated mapping
json['itemType'] == null
? null
: /// I could probably just remove the singleWhere and add [(int)json['itemType']] instead but that would cause some hassle when I run build_runner again.
ItemType.values
.singleWhere((x) => x.toString() == "ItemType.${json['itemType']}")
So, as soon as I did some changes server side (ignored the serialization of the enum and added another property which returned the enum string value instead, to lower case) it started working. I want to look into this further so that I can serialize the enum index instead of the string value and have it mapped that way instead but unfortunately I don't have the time to do it now.
The packages used for the auto-mapping is build_runner and json_serializable.
I'm glad I found a solution, and I'm sorry that the solution turned out to be completely unrelated to the actual post. I hope this can help someone at least.