In my latest project I want to have a user type in name and password on an Android phone, save it in JSON-format, and POST it to an ASP.NET MVC server which should return configuration data in a new JSON.
What I have so far:
Android (partial code):
String json = "{\"x\": \"val1\",\"y\":\"val2\"}";
URL url = new URL("http://xxxxx/Home/GetData");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json"); // have tried without this
conn.setRequestProperty("Content-Length", json.getBytes().length + "");
conn.setDoOutput(true);
os = conn.getOutputStream();
os.write(json.getBytes("UTF-8"));
os.close();
conn.connect();
The json string here is just for testing.
MVC:
[HttpPost]
public ActionResult GetData(String request)
{
Debug.WriteLine(request.Length); // not relevant to the question
var data = new { Name = "Test", Age = "39" };
return Json(data);
}
Returns JSON with test data.
The string argument in the MVC controller is probably wrong here. I have tried accepting e.g. byte[], but I always end up with NULL.
Any help with this or suggestions for alternatives is appreciated!
Cheers
If this is what's being posted to the server:
{
"x": "val1",
"y": "val2"
}
Then the method parameters should reflect those key/value pairs. Something like this:
public ActionResult GetData(string x, string y)
This will go through everything you post in the json. Prettu much like #David's but for when you have no clue whats being sendt to the GetData
public ActionResult GetData()
{
foreach (var key in Request.Form.AllKeys)
{
//This will iterate through everything in your post data
var value = Request.Form[key];
}
}
Related
In my REST Service I have the following:
AssetController:
// GET: <AssetController>
[HttpGet("{companyID}/{machineName}")]
public Asset Get(int companyID, string machineName)
{
Database db = new Database(configuration.ConnectionString);
//DataSet ds = db.executeFunctionSelect("fngetallassets2()");
DataSet ds = db.executeViewSelect("tblasset where LOWER(name) = '" + machineName.ToLower() + "'");
//DataSet ds = db.executeDataSetProc("getallassets", null);
DataTable table = ds.Tables[0];
DataRow row = table.Rows[0];
Asset asset = new Asset
{
ID = int.Parse(row["ID"].ToString()),
CompanyID = int.Parse(row["Company_ID"].ToString()),
Name = row["Name"].ToString(),
IPAddress = row["IP_Address"].ToString(),
CreateDate = DateTime.Parse(row["Create_Date"].ToString()),
IsActive = bool.Parse(row["Is_Active"].ToString())
};
return asset;
}
This works fine... Its the PUT that I need help with
// PUT /<AssetController>/5
// Insert record into the database
[HttpPut("{asset}")]
public void Put([FromBody] string asset)
{
Database db = new Database(configuration.ConnectionString);
db.executeNonQuery("sp_AssetInsert", null);
}
Here I am trying to pass (somehow) the same asset class
In the calling windows forms I use this way to call the PUT Method:
public void InsertAsset(Asset asset)
{
ArrayList parameters = new ArrayList
{
asset.Name,
asset.IPAddress
};
RestClient client = new RestClient("https://localhost:5001/Asset/");
RestRequest request = new RestRequest(Method.PUT);
request.AddJsonBody(asset);
IRestResponse<List<string>> response = client.Execute<List<string>>(request);
if (response.StatusCode == HttpStatusCode.OK)
{
}
I get an error on Response.StatusCode = unsupportedmedia or something like this.
I need to know how to serialize or somehow pass either the class or the JSON string of it or whatever...
Can someone please help me figure out how to call the PUT methods as I have dozens of these to do.
Here is the calling and receiving code used to make this work.
calling:
RestClient client = new RestClient("https://localhost:5001/Asset/");
RestRequest request = new RestRequest(Method.PUT);
request.AddJsonBody(asset); <-- Asset is a class object
RestResponse response = (RestResponse)client.Execute(request);
if (response.StatusCode == HttpStatusCode.OK)
{
}
Receiving Code:
// PUT /<AssetController>/5
// Insert record into the database
[HttpPut]
public void Put([FromBody] Asset asset)
{
Database db = new Database(configuration.ConnectionString);
db.executeNonQuery("sp_AssetInsert", null);
}
I needed to change the [FromBody] string asset to [FromBody] Asset asset
There are several ways to pass parameters:
as url route i.e. https://localhost:5001/Asset/42/MyCompanyName
as url parameter http:// localhost:5001/Asset?companyID=42&machineName=companyname
in body, typically as a json serialized object
when you specify the route in [HttpPut("{paramaters}")] you are specifying option 1. You can use FromBody and FromUrl attributes on the parameter to control this. Simple parameters like numbers and string would typically be part of the URL, while complex objects like Asset will probably be easier to pass in the body.
See also
restsharp parameter posting
asp.net parameter binding
I am trying to set up a small ASP.NET Web API projects so I can post data to the database from a small React.JS project. I tried alot of sollutions but the results made no sense and I have no idea how to fix it anymore.
I have this very simple model:
public class Hour
{
public int WeekID { get; set; }
}
And this is my controller
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
return Ok();
}
This is the method that I use to POST my data
export const SaveWeek = weekData=> {
const headers = new Headers();
headers.append("Content-Type", "application/json");
const Week= {
method: "POST",
headers,
mode: "cors",
body: weekData
};
console.log("Hours:");
// Returns {"WeekID": 1}
console.log(Hours.body);
return axios.post("http://localhost:52350/api/REST/AddHour", {
Week
});
};
The way I call this SaveWeek method in React is:
// The JSON parameter is for testing hard coded to: {"WeekID": 1}
handleSave = async json => {
const data = await SaveWeek(json);
console.log(data);
this.closeModal();
};
I know that the axios POST request works, the way I tested that is by changing the method to not use any parameters and looking at the result that where received:
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
// This returns a string in which the data that I sent
// can be found.
string body = Request.Content.ReadAsStringAsync().Result;
return Ok();
}
The weird thing is that the body will be filled with data when the method does not contain any parameters, but when I provide the method with the Hour object parameter the body will be an empty string (""). And also the Hour object parameter wont be filled with the values that I provide it.
What am I doing wrong here?
According to https://github.com/axios/axios#axiosposturl-data-config axios.post has following signature
axios.post(url[, data[, config]])
So you just need to change your request to
export const SaveWeek = weekData => {
//headers should be simple object, not Headers
const headers = {
"Content-Type": "application/json"
};
//removed body, because we pass data as second parameter
//removed method, because 'axios.post' implies using "post" method
const Config = {
headers,
mode: "cors"
};
const url = "http://localhost:52350/api/REST/AddHour";
return axios.post(url, weekData, Config);
}
An incoming request to the ASP.Net Web API pipeline is read as a forward-only stream for super speed. Once it has been read it cannot be read again.
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
// With model binding
// use hour.WeekID
}
In this first example model binding is already done and once it has been read it cannot be read again. Hence, Request.Content will be empty after that.
[HttpPost]
public IHttpActionResult AddHour()
{
// Without model binding
// use Request.Content
}
In second example it does not use model binding therefore still has the Request.Content property populated.
Use one or the other, not both, do not mix with MVC model binding which works differently.
A better explanation is available in this blog post
http://www.hackered.co.uk/articles/asp-net-web-api-why-is-the-request-Content-empty-when-the-model-is-populated
I am sending an httpPost parameter "client" of type IdentityServer4.Models.Client via a C# console application to a C# web api and client is always null.
If I send a different object using the same code, the parameter is received just fine. This seems to be a problem specific to Identity Server 4 and I don't know what's going on.
Server code:
[HttpPost]
public JsonResult Post(IdentityServer4.Models.Client client){
return Json(new { result = true });
}
Client code:
private static async Task Register(string clientName){
var controllerName = "BasicClient";
var basicClientApi = string.Format("http://localhost:5100/api/{0}", controllerName);
using (var httpClient = new HttpClient()){
var clientData = new IdentityServer4.Models.Client();
var client = new { client = clientData };
client.client.ClientName = clientName;
var json = JsonConvert.SerializeObject(client);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync(basicClientApi, content);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var rawResponse = await response.Content.ReadAsStringAsync();
JObject o = JObject.Parse(rawResponse);
Console.WriteLine(o.ToString());
}
}
}
EDIT
After applying [FromBody] and unwrapping the object, I am still getting null for client in the receiving Web API. One thing caught my eye on the console application debug screen though.. see the image below:
The actual client variable is null in the console application, yet JsonConvert was able to serialize it into something. What's that about?
You are wrapping your model inside an anonymous object, which will turn its JSON representation into something which has nothing in common with your original Client class.
This:
var clientData = new IdentityServer4.Models.Client();
var client = new { client = clientData };
client.client.ClientName = clientName;
var json = JsonConvert.SerializeObject(client);
Will result in a JSON similar to the following:
{
"client": {
"clientName": "foo",
"anotherProperty": "bar",
// other properties omitted for brevity
}
}
But what you really want is just the Client object:
{
"clientName": "foo",
"anotherProperty": "bar",
// other properties omitted for brevity
}
Do not wrap your clientData, just serialize it directly to follow the model inside your MVC Action Method:
var clientData = new IdentityServer4.Models.Client();
clientData.ClientName = clientName;
var json = JsonConvert.SerializeObject(clientData);
For everything to be working, you have to tell the model binder explicitly where to expect the data.
Use [FromBody] attribute on the model.
[FromBody]: Use the configured formatters to bind data from the request body. The formatter is selected based on content type of the request.
[HttpPost]
public IActionResult Post([FromBody]IdentityServer4.Models.Client client) {
return Json(new { result = true });
}
Reference Model Binding in ASP.NET Core
You're not going to believe this, but ultimately the reason why IdentityServer.Models.Client was null in the Web Api post parameter was because the class is decorated with [DebuggerDisplay("{ClientId}")] and I did not provide a ClientId in my test application, so it was always showing up as null when in fact it actually WAS THERE THE WHOLE TIME. I am glad this issue is behind me, but I am very angry about this "feature".
I have been stuck on this for a couple hours now and not even google can help anymore. I am trying to send a file from the client to the backend using xmlhttprequest. I cannot get the filename, type, or content on the C# side. I would appreciate help on doing this. A lot of code I came across had methods that I can only guess are not supported in ASP.Net 5 and MVC 6 (such as HttpContext.Current and HttpPostedFile)
Here is my client side JavaScript request. This sends the query strings which bind to my model no problem so that is easily accessible, but getting the file is what I am having trouble with.
var form = new FormData();
form.append("file", file);
var queryParams = "id=" + (id == null ? -1 : id);
queryParams += "&name=" + name;
queryParams += "&value=" + val;
xhrAttach(REST_DATA + "/attach?" + queryParams, form, function (item) {
console.log('attached: ', item);
alert(item.responseText);
row.setAttribute('data-id', item.id);
removeProgressIndicator(row);
setRowContent(item, row);
}, function (err) {
console.log(err);
//stop showing loading message
stopLoadingMessage();
document.getElementById('errorDiv').innerHTML = err;
});
function xhrAttach(url, data, callback, errback)
{
var xhr = new createXHR();
xhr.open("POST", url, true);
//xhr.setRequestHeader("Content-type", "multipart/form-data");
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
callback(parseJson(xhr.responseText));
}else{
errback("Error: "+xhr.responseText);
}
}
};
xhr.timeout = 1000000;
xhr.ontimeout = errback;
xhr.send(data);
}
Here is my Controller dealing with the request. attachment is a model and the query string binds to it no problem. I could not find out how to add a File parameter to the model, or if that would even matter. Things I have tried are under this code.
// POST: /api/db/attach
[Route("/api/[controller]/attach")]
[HttpPost]
public async Task<dynamic> attach(Attachment attachment)
{
//get the file somehow
}
i have tried many things, but cannot remember exactly what, here is one thing I did try though, which did not work.
var file = Request.Form["file"];
here is the attachment model in case it helps
namespace MyModel.Models
{
public class Attachment
{
public long id { get; set; }
public string name { get; set; }
public string value { get; set; }
}
}
Don't use query parameters or FormData if you're going to use a model on the MVC side. Just don't. And to me, it's better to just get the file into a base64 string first, than to try sending the File object, itself. I've posted about how to do that, here: Convert input=file to byte array
Then, declare and format a JSON object:
var dataObj = {
file = fileByteArray[0],
id = (id == null ? -1 : id),
name = name,
value = val
};
That fileByteArray[0] is referencing the object from my link. My answer there assumes you were just going to keep loading file base64 strings into that global array object. You can either keep it as an array, like I had, and loop through them one by one, replacing that [0] with [i], for example, as the indexer in a for loop, or just use a var fileByteArray = "" with that other code, make it so you don't push additional files but always just overwrite that variable, & just use that.
And a word of caution on that last parameter - don't use val if you use jQuery - it's a keyword. I only have it above because it's what you were passing to the URL parameter values.
Get rid of the queryParams in this line:
xhrAttach(REST_DATA + "/attach?" + queryParams, form, function (item) {
Change it to:
xhrAttach(REST_DATA + "/attach", form, function (item) {
Set the Content-Type, right where it's commented out, to:
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
Change what you are sending - it's no longer going to be FormData, it's the JSON object, and it needs to be stringified:
xhr.send(JSON.stringify(dataObj));
Fix your model to now include the file base64 string:
public class Attachment
{
public string file { get; set; }
public long id { get; set; }
public string name { get; set; }
public string value { get; set; }
}
Fix your POST method. 2 issues:
You can't use [HttpPost] if your class is inheriting ApiController, which you probably should be for this. It must be [System.Web.Http.HttpPost], and yes, it has to be completely spelled out, or it will assume it's [System.Web.Mvc.HttpPost] and not assign the route - you'd get a 404 - Not Found error when you try to do your POST. If you're inheriting from Controller, disregard this.
You will need a [FromBody] tag on your model if you are inheriting from ApiController:
public async Task<dynamic> attach([FromBody]Attachment attachment) { ... }
Then you get the file like this:
string base64FileString = attachment.file;
If you want to store it in a byte[] in the database, you can convert it:
byte[] bytes = System.Convert.FromBase64String(base64FileString);
And btw, I think your response handling is wrong. I would not do this:
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
callback(parseJson(xhr.responseText));
}else{
errback("Error: "+xhr.responseText);
}
}
};
This is how I would do it:
xhr.onreadystatechange = function(response){
if(xhr.readyState == 4 && xhr.status == 200){
callback(parseJson(response.target.responseText));
} else {
alert("Error: " + response.target.responseText);
}
};
Assuming that the response.target.responseText is getting the error sent back from the server-side in a way you can display. If not, sending it to a function that could parse it out would be the right choice. I don't think that xhr.responseText was correct.
I would suggest trying the following:
public async Task<dynamic> attach([FromURI]Attachment attachment, [FromBody] FormDataCollection formData)
And then the FormDataCollection should have the form data for retrieval.
Add a public get/set property called File to your Attachment model and the uploaded file should be bound to this property.
A model similar to yours:
https://github.com/aspnet/Mvc/blob/9f9dcbe6ec2e34d8a0dfae283cb5e40d8b94fdb7/test/WebSites/ModelBindingWebSite/Models/Book.cs#L8
public class Book
{
public string Name { get; set; }
public IFormFile File { get; set; }
}
Following controller has examples of different ways of model binding an uploaded file.
https://github.com/aspnet/Mvc/blob/9f9dcbe6ec2e34d8a0dfae283cb5e40d8b94fdb7/test/WebSites/ModelBindingWebSite/Controllers/FileUploadController.cs#L81
public KeyValuePair<string, FileDetails> UploadModelWithFile(Book book)
{
var file = book.File;
var reader = new StreamReader(file.OpenReadStream());
var fileContent = reader.ReadToEnd();
var parsedContentDisposition = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
var fileDetails = new FileDetails
{
Filename = parsedContentDisposition.FileName,
Content = fileContent
};
return new KeyValuePair<string, FileDetails>(book.Name, fileDetails);
}
if this does not work, then I suspect your request is not in correct format.
I've got an existing C# project with the following POST method:
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
return new ExternalLoginResult(provider, Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
}
Which uses:
OAuthWebSecurity.Login(provider, ...);
I'm kinda new to OAuth, and haven't done anything with it in this C# project. I do however implemented Google-authorization on an Android App, and I want to use the following class/method for the HttpPost:
public class TaskPostAPI extends AsyncTask<String, Void, String>
{
GoogleApiClient googleAPI;
public TaskPostAPI(GoogleApiClient googleAPI){
this.googleAPI = googleAPI;
}
#Override
protected String doInBackground(String... urls){
String response = "";
for(String url : urls){
DefaultHttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(url);
try{
List<NameValuePair> nvPairs = new ArrayList<NameValuePair>(2); //(3);
//nvPairs.add(new BasicNameValuePair("personName", Plus.PeopleApi.getCurrentPerson(googleAPI).getDisplayName()));
//nvPairs.add(new BasicNameValuePair("personGooglePlusProfile", Plus.PeopleApi.getCurrentPerson(googleAPI).getUrl()));
//nvPairs.add(new BasicNameValuePair("personEmail", Plus.AccountApi.getAccountName(googleAPI)));
nvPairs.add(new BasicNameValuePair("provider", ???));
URL u = new URL(url);
nvPairs.add(new BasicNameValuePair("returnUrl", u.getProtocol() + "://" + u.getHost()));
post.setEntity(new UrlEncodedFormEntity(nvPairs));
HttpResponse execute = client.execute(post);
InputStream content = execute.getEntity().getContent();
BufferedReader buffer = new BufferedReader(new InputStreamReader(content));
String s = "";
while((s = buffer.readLine()) != null)
response += s;
}
catch(Exception ex){
ex.printStackTrace();
}
}
return response;
}
}
So my question: What should I put at the POST-method's ???. So what is the Provider? In my Android App I log in using Google, and got the GoogleApiClient and Person (com.google.android.gms.plus.model.people.Person).
At this website I see the following url: https://accounts.google.com/o/oauth2/auth. Is this the url I should use as provider? Or should I add parameters to this url? Or should I use a completely different string as provider?
PS: If someone can send me a link to a list of provider-urls (like Google's, Facebook's, Twitter's, etc.) and how to use them for the C# OAuthWebSecurity I would appreciate it.
Thanks in advance for the responses.
Ok it turns out it was very simple.. The provider is just "Google", nothing more, nothing less.. This provider name I found at the RegisterClient method in de C# project:
OAuthWebSecurity.RegisterClient(client, "Google", extraData);
Now the POST works, except for the message "The required anti-forgery cookie "__RequestVerificationToken" is not present.", but at least I can continue. And adding a Cookie to a HttpPost isn't that hard I believe when using a CookieManager.