I figure I have to be doing something obviously incorrect in my .NET code but I cannot figure out the issue.
I have two applications talking via RestSharp calls and, no matter what I attempt, the POST value coming from one application to another is ALWAYS NULL. Here is my sending code:
var client = new RestClient(_context.CloudUrl + ":" + _context.CloudPort.ToString() + "/api/web/AddRegisteredLocation");
var request = new RestRequest(Method.POST);
request.RequestFormat = DataFormat.Json;
request.AddHeader("cache-control", "no-cache");
request.AddParameter("application/json", JsonConvert.SerializeObject(myLocation, Formatting.None), ParameterType.RequestBody);
I have tried .AddObject, .AddJsonBody, .AddBody - not using NewtonSoft.Json and even passing XML but this method always receives a NULL value:
[Route("AddRegisteredLocation")]
[HttpPost()]
public async Task<IActionResult> AddRegisteredLocation([FromBody] string NewStringLocation)
{
try
{
RegisteredLocation newLocation = JsonConvert.DeserializeObject<RegisteredLocation>(NewStringLocation);
await _Manager.AddRegisteredLocation(newLocation);
return new OkObjectResult(true);
}
catch (Exception exc)
{
eventWriter.WriteEntry("AddRegisteredLocation failed with the exception: " + exc.ToString(), System.Diagnostics.EventLogEntryType.Error);
return new NotFoundResult();
}
I have also tried this method:
//POST: api/web/AddRegisteredLocation
[Route("AddRegisteredLocation")]
[HttpPost()]
public async Task<IActionResult> AddRegisteredLocation([FromBody] RegisteredLocation NewLocation)
{
try
{
await _Manager.AddRegisteredLocation(NewLocation);
return new OkObjectResult(true);
}
catch (Exception exc)
{
eventWriter.WriteEntry("AddRegisteredLocation failed with the exception: " + exc.ToString(), System.Diagnostics.EventLogEntryType.Error);
return new NotFoundResult();
}
}
And I have dropped the [FromBody] tag - nothing is working. When I walk the code, the incoming value is ALWAYS Null.
If I use a Postman script and send in raw JSON through a POST request, that works fine so it has to be something on the Request side but I cannot figure it out.
Does anybody have a suggestion?
As it turns out, the Bson ObjectId was the culprit - for some reason none of the serializers can handle it. There is a BsonWriter and BsonReader options but both of those essentially use Base64-encoding which is slow and bloats messages.
To quickly overcome the issue, I ended up writing a set of classes without the BsonId and then a bunch of methods that manually copy the data from the MongoDB-based classes to the "serialization-friendly" classes.
This worked perfectly and, on the other end, the lack of an Object ID does not cause any issues with MongoDB. I simply look the object up by a unique string name and assign the ID before making any changes.
This is probably NOT the most elegant solution but it does work!
Related
I am making a communication between two microservices. I am trying to obtain the content from the model "UserBookPreference". I already debugged and the content that is being sent in the PostAsync() request is correct (let's say, data from microservice A). The problem arises when I try to receive the contents from the Post method in microservice B. The model type I have as parameter is the same I am sending from the postAsync. Since I am using .NET 5, the JsonContent.Create() method is a good approach according to my reasearch. I have tried other methodologies, such as using the StringContent() method but still I get the null object in microservice B. This is the code I am using.
In microservice A:
public async Task<string> AddFavoriteBook(UserBookPreference bookModel)
{
JsonContent content = JsonContent.Create(bookModel);
var response = await httpClient.PostAsync(URLHelper._baseUserPreferencesMicroserviceURL + "/Book", content);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
In microservice B:
[HttpPost("Favorites/Book")]
public IActionResult AddUserFavoriteBook(UserBookPreference bookModel)
{
try
{
_prefService.AddUserBookFavorite(bookModel);
return Ok(bookModel);
}
catch (Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
Thank you for your time.
You need to either add [FromBody] attribute before UserBookPreference in your endpoint or add a [ApiController] attribute to your controller to bind UserBookPreference to incoming body. docks
I have a data-entry app built in Xamarin.Forms that's designed to capture data into a particular format, then post that via an ASP.NET web service into a SQL Server. I'm using RestSharp to make this easier. Below is the code I currently have to do this:
private async void Button_Clicked(object sender, EventArgs e)
{
try
{
string userResponse = await DisplayActionSheet("Are you sure?", "Yes", "No");
if (userResponse == "Yes")
{
using (SQLiteConnection conn = new SQLiteConnection(App.DatabaseLocation))
{
List<DataToBeSent> d = conn.Query<DataToBeSent>("select * from DataToBeSent");
var dConvert = JsonConvert.SerializeObject(d, Formatting.Indented);
var client = new RestClient("url here");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.RequestFormat = DataFormat.Json;
Token token = conn.Query<Token>("select * from Token").FirstOrDefault();
request.AddHeader("Authorization", "Bearer " + token.access_token);
request.AddParameter("application/json", dConvert, ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
await DisplayAlert("", response.StatusCode.ToString() + response.Content.ToString(), "OK");
}
await Navigation.PopToRootAsync();
}
}
catch (Exception ex)
{
await DisplayAlert("Error", $"Data failed to be inserted.\n{ex}", "OK");
}
}
And it returns the following alert when I try to submit any data:
The Token class uses a successful POST request to retrieve an OAuth token for the app to use to GET data when it starts up.
I've also tested with Postman, sample request below:
Which causes the following error in my web service:
It is worth noting that the table to which I am posting data does not have a primary key. It's unfortunate but it's a requirement that it does not have one. Table now has a primary key (named UniqueID). I also noticed that the List<DataToBeSent>, when serialized, returns with escape characters. Why are these errors occurring? Does anyone know how to fix them?
Update 1: As requested;
JSON being passed as below.
"[\n {\n \"column1\": \"data1\",\n \"column2\": \"data2\"\n }\n]"
Code for database model as below.
<!--Errors Found During Generation:
warning 6002: The table/view 'databaseName.dbo.DataToBeSent' does not have a primary key defined. The key has been inferred and the definition was created as a read-only table/view.-->
<EntityType Name="DataToBeSent">
<Key>
<PropertyRef Name="column1" />
<PropertyRef Name="column2" />
</Key>
<Property Name="column1" Type="nvarchar" MaxLength="50" Nullable="false" />
<Property Name="column2" Type="nvarchar" MaxLength="50" Nullable="false" />
</EntityType>
<EntitySet Name="DataToBeSent" EntityType="Self.DataToBeSent" store:Type="Tables" store:Schema="dbo">
<DefiningQuery>
SELECT
[DataToBeSent].[column1] AS [column1],
[DataToBeSent].[column2] AS [column2]
</DefiningQuery>
</EntitySet>
Update 2: I have tried updating my POST request code in the Xamarin backend. This is what I have currently.
List<DataToBeSent> DATA = conn.Query<DataToBeSent>("select * from DataToBeSent");
var data = JsonConvert.SerializeObject(DATA, Formatting.None);
Token token = conn.Query<Token>("select * from Token").FirstOrDefault();
var client = new RestClient("url");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer " + token.access_token);
request.AddJsonBody(data);
IRestResponse response = client.Execute(request);
await DisplayAlert("Response", response.StatusCode + response.Content, "OK");
I don't know why the Serialization error occurs - when viewing the results in Visual Studio as a JSON object the escape characters are absent. This would indicate to me that the serialization is occuring correctly, yet I still get the same error message (BadRequest). I am trying to submit a List<> of objects so I'm wondering if JsonConvert.SerializeObject is the right method to be using.
Update 3: I have tried POSTing a single object as suggested by #Mat J below. Code is the same as Update 2 except for changing the List<> object to a single object with .FirstOrDefault(). See below.
DataToBeSent DATA = conn.Query<DataToBeSent>("select * from DataToBeSent").FirstOrDefault();
var data = JsonConvert.SerializeObject(DATA, Formatting.None);
Response as displayed in app.
Below is my controller's code from the web service. I generated it automatically as a Web API 2 Controller, using Entity Framework, and added the square brackets above it to use my OAuth security methods.
[Authorize(Roles = "Admin, User")]
[HttpPost]
[Route("api/v/DataToBeSent")]
[ResponseType(typeof(DataToBeSent))]
public IHttpActionResult PostDataToBeSent(DataToBeSent dataToBeSent)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.DataToBeSent.Add(dataToBeSent);
try
{
db.SaveChanges();
}
catch (DbUpdateException)
{
if (DataToBeSentExists(dataToBeSent.UniqueID))
{
return Conflict();
}
else
{
throw;
}
}
return CreatedAtRoute("DefaultApi", new { id = dataToBeSent.UniqueID }, dataToBeSent);
}
The DataToBeSent table now has a Primary Key, named UniqueID as above.
I do want to be able to submit a whole List<> of objects rather than doing something like a for-loop and making a separate POST call for each individual object in the list.
I would suggest reading RestSharp docs. We tried to make it easier to use, I am not sure why you are not taking benefit of it.
Let's look at this code:
var data = JsonConvert.SerializeObject(DATA, Formatting.None);
Token token = conn.Query<Token>("select * from Token").FirstOrDefault();
var client = new RestClient("url");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer " + token.access_token);
request.AddJsonBody(data);
IRestResponse response = client.Execute(request);
To start with, AddJsonBody accepts an object, which gets serialized for you. You are sending a string, which already contains a serialized object, so this would never work.
By calling AddJsonObject you already set the content type to application/json, there's no need to do it again.
Instead of using the bearer header set manually, you can just use the JwtAuthenticator that is built for the purpose of authentication without a need to manipulate headers for each request.
The client is advised to be a singleton, not to be re-created for each request.
I can suggest using the following:
public void WhateverIsCalledOnce()
{
var roken = conn.Query<Token>("select * from Token").First();
_client = new RestClient("url").UseNewtonsoftJson();
_client.Authenticator = new JwtAuthenticator(token.access_token);
}
public async Task CallRemoteServer()
{
var data = await _connection.GetDataFromDb("query");
var request = new RestRequest("/api/v/DataToBeSent")
.AddJsonBody(data);
var result = await _client.PostAsync<DataToBeReturned>(request);
}
The UseNewtonsoftJson extension method is available in the RestSharp.Serializers.NewtonsoftJson package on NuGet.
I worked out the issue, posting it here for anyone experiencing problems in the future. As #Mat J pointed out in the question's comments, I was trying to send an array when the controller was only set up to receive single objects. Step 1 was updating that code to allow the controller to receive a List<> instead of a single object. Step 2 was realizing that I'd set the POST controller's [Route] attribute to be identical to the table's GET controller. The working Xamarin-side code can found in the question's Update 2, although #Alexey Zimarev's answer would probably be more efficient. Updated web service controller code can be found below.
[Authorize(Roles = "Admin, User")]
[HttpPost]
[Route("api/postall/datatobesent")]
public IHttpActionResult PostAllDataToBeSent([FromBody] List<DataToBeSent> dataToBeSent)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
foreach(var data in dataToBeSent)
{
db.DataToBeSent.Add(data);
}
try
{
db.SaveChanges();
return Ok();
}
catch (Exception)
{
throw;
}
}
Thank you to all who commented and contributed.
I am trying to get one API (reports API) to talk to another API (stored procedure runner API) which is a solution we are sort of forced to adopt given that we created the first API in .NET Core 2.2 and the Sap.Data.SQLAnywhere.v4.5 drivers only play nice with the .NET 4.7.2 framework. So we segregated them to compensate. All the answers I have seen over the last 4 or 5 hours leads me to believe I am doing this correctly but it still doesn't work.
I can hit the stored procedure runner from Postman with a json/text body just fine and get results from the database. However, when I try to hit this from C# I was first getting Unsupported Media Type which I think I fixed but now I get 500 errors and when debugging through to the stored procedure runner from the reportsAPI I notice that I don't get a parameter passed from the body.
[HttpPost]
[Route("api/Reports/GetStuff")]
[ResponseType(typeof(ResponseObject))]
public ResponseObject GetStuff([FromBody]string report)
{
var response = new ResponseObject();
try
{
response = new ReportService().RunStuff(report);
}
catch (Exception e)
{
throw;
}
return response;
}
The above is the SprocRunnerAPI and I get as far as
response = new ReportService().RunStuff(report);
before it fails out because it has nothing in "report".
public class ApiService
{
public static HttpClient ApiClient { get; set; }
public static void InitializeClient()
{
ApiClient = new HttpClient();
//ApiClient.BaseAddress = new Uri("");
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
This is where I initialize everything then use it in the following class method:
public ResponseObject RunReportToCSV(string report)
{
var rep = new ResponseObject();
ApiClient.ApiService.InitializeClient();
string url = #"http://localhost/EnterpriseReportRunner/api/reports/GetStuff";
var httpContent = new StringContent(report, Encoding.UTF8, "application/json");
//ServicePointManager.Expect100Continue = false; I honestly don't know where this goes... or if it is needed.
var response = ApiClient.ApiService.ApiClient.PostAsync(url , httpContent).Result;
if (response.IsSuccessStatusCode)
{
rep = response.Content.ReadAsAsync<ResponseObject>().Result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
return rep;
}
I get as far as
var response = ApiClient.ApiService.ApiClient.PostAsync(url, httpContent).Result;
when it calls the aforementioned api method externally and it fails out. I had noticed no differences between the bodies in what I put in C# and what I put in Postman nor did I notice a difference looking through Fiddler on the Headers when making either call. I am seriously confused as to why what appears to be what I have seen everywhere used, that this is not working.
Thank you.
Adding Stack Trace from the SprocRunnerAPI… this isn't much help because I know exactly why this happens. By this point I expect a JsonSerialized object and I don't have it because it wasn't passed from the first API to the second.
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at EnterpriseReportRunner.Service.ReportService.RunStuff(String report) in C:\Workspace\Solution.NET\LOB\InternalReportsAdminConsole Solution\EnterpriseReportRunner\EnterpriseReportRunner\Service\ReportService.cs:line 27
at EnterpriseReportRunner.Controllers.ReportsController.GetStuff(String report) in C:\Workspace\Solution.NET\LOB\InternalReportsAdminConsole Solution\EnterpriseReportRunner\EnterpriseReportRunner\Controllers\ReportsController.cs:line 67
To be clear this is the stack from the SprocRunnerAPI not the ReportsAPI that calls it. I am trying to find the body inside the HttpContext to see the difference between the Postman call and the C# call but can't seem to find it, is it really buried in the InputStream?
The issue seems to be one that I thought would be redundant. The bottom line is that even if you expect a serialized JSON object you need to serialize it again, otherwise when you pass the StringContent via the HttpClient the body contents don't show in the receiving controller's parameter for consumption.
var httpContent = new StringContent(JsonConvert.SerializeObject(report), Encoding.UTF8, "application/json");
Even though "report" is already a string... really weird to me. But that solves it.
Found out that the strings that come into a controller are also wrapped inside another object so .Root seems to be your friend when deserializing. At least I learned something from the experience.
Thank you for all your input. Might want to link this to this other question.
Does anyone know how to properly search for games using the Internet Game Database API as of version 3? I'm trying to use IGDB to do a simple game search. For each game that matches the search terms, I'd like to retrieve the game's name, genres, developers and publishers, it's initial release date, and the URL of the cover. Through some Googling I'd gotten it working through the old URL parameters, but something seems to have changed on their end, and those no longer work. The version 3 documentation says to use Apicalypse to send the fields you want back in the body of the web request, but I can't for the life of me figure out how to do that. I'm using AJAX to send the search terms to the controller, and sending the results back via a JSON object. I'm getting a 400 Bad Request error every time, no matter the syntax I use for the fields. The documentation says that using URL parameters should still work, but they do not. Here's my controller code.
[HttpPost]
[WebMethod]
public JsonResult LookUpGames(string search)
{
string url = "https://api-v3.igdb.com/games/?search=" + search
+ "&fields=name,genres,involved_companies,first_release_date,cover";
HttpWebRequest gameRequest = (HttpWebRequest)WebRequest.Create(url);
gameRequest.Accept = "application/json";
gameRequest.Headers.Add("user-key", "[MYUSERKEY]");
WebResponse gameResponse = (HttpWebResponse)gameRequest.GetResponse();
string responseString = new StreamReader(gameResponse.GetResponseStream()).ReadToEnd();
return Json(new { result = responseString });
}
UPDATE: Thanks for the pointer, Jake. I'm now hitting the servers with the following code.
HttpResponse<JsonResult> jsonResponse = Unirest.post("https://api-v3.igdb.com/games")
.header("user-key", "[MYUSERKEY]")
.header("Accept", "application/json")
.body("fields name,genres,platforms,involved_companies,cover").asJson<JsonResult>();
JsonResult jsonResult = Json(new { result = jsonResponse });
return jsonResult;
There is no JsonNode in C# apparently, so I tried JsonResult, and the .asJson() seems to be .asJson(). I just fiddled with it until it worked. But I'm still not getting back a list. I'm getting a 400 Bad Request error. So even in this new format, it's still not liking the fields I'm giving it. According to the structure in the documentation, the fields I'm giving it are in fact there in the Game endpoint. So I don't know what could be wrong. Any other ideas anyone?
I decided to try the query approach again, and somehow it now works. Here's my controller method that works. Not sure which tweaks made it work again, but it does.
[HttpPost]
[WebMethod]
public JsonResult LookUpGames(string search)
{
string url = "https://api-v3.igdb.com/games?search=" + search +
"&fields=name,genres.name,platforms.name,involved_companies.*, involved_companies.company.*,first_release_date,cover.url";
HttpWebRequest gameRequest = (HttpWebRequest)WebRequest.Create(url);
gameRequest.Accept = "application/json";
gameRequest.Headers.Add("user-key", "[MYUSERKEY]");
WebResponse gameResponse = gameRequest.GetResponse();
StreamReader sr = new StreamReader(gameResponse.GetResponseStream());
string responseString = sr.ReadToEnd();
sr.Close();
JsonResult jsonResult = Json(new { result = responseString });
return jsonResult;
}
So, my main question is in the title. How is it possible to avoid the browser to throw Http Statuscode 406 once an invalid method is supplied?
I've tried by default to allow all incoming methods by using [AcceptVerbs("GET", "POST", "PUT", "DELETE")] and after that to filter out the actual allowed method by using this method:
private bool CheckAllowedMethod(HttpMethod allowed, HttpMethod given)
{
if (allowed == given)
{
return true;
}
throw new InvalidMethodException("This method is not available over " + Request.Method.Method);
}
Even though this works it isn't very neat. The behaviour I want to avoid when using [HttpPost], or any equivalent of those, is that the browser throws a Http Statuscode 406 and literaly prints nothing to the site even though I want to display a JSON string at all times.
So, is this possible any easier or do I have to use my current method?
Full code:
[AcceptVerbs("GET", "POST", "PUT", "DELETE")]
[Route("api/Auth/Login/{apikey}")]
public HttpResponseMessage GenerateLoginCode(string apikey = "") {
HttpResponseMessage response = CreateResponse();
try {
CheckAllowedMethod(HttpMethod.Post, Request.Method);
ChangeContent(response, JSONString.Create(Apikey.Login(apikey)));
} catch (Exception ex) {
ChangeContent(response, Error.Create(ex));
}
return response;
}
private HttpResponseMessage CreateResponse() {
return Request.CreateResponse(HttpStatusCode.OK);
}
private void ChangeContent(HttpResponseMessage res, string data) {
res.Content = new StringContent(data, System.Text.Encoding.UTF8, "application/json");
}
private bool CheckAllowedMethod(HttpMethod allowed, HttpMethod given) {
if (allowed == given) {
return true;
}
throw new InvalidMethodException("This method is not available over " + Request.Method.Method);
}
I would not do this via accepting all methods and filtering manually, but rather with a middleware that catches the error response and rewrites it.
I dug quite deeply into error handling in Web API 2 earlier this year, and expanded on my findings in this blog post. If you do something similar to that, you could handle the exception from a disallowed method in a special catch clause in the middleware and write whatever you want to the response.