I am trying to post data to MVC controller action but have been unsuccessful so far.
Here is the structure of the post data:
private string makeHttpPostString(XmlDocument interchangeFile)
{
string postDataString = "uid={0}&localization={1}&label={2}&interchangeDocument={3}";
InterchangeDocument interchangeDocument = new InterchangeDocument(interchangeFile);
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
interchangeFile.WriteTo(xmlTextWriter);
string interchangeXml = HttpUtility.UrlEncode(stringWriter.GetStringBuilder().ToString());
string hwid = interchangeDocument.DocumentKey.Hwid;
string localization = interchangeDocument.DocumentKey.Localization.ToString();
string label = ConfigurationManager.AppSettings["PreviewLabel"];
return (string.Format(postDataString, hwid, localization, label, interchangeXml));
}
}
Here is the request:
HttpWebRequest webRequest = (HttpWebRequest) WebRequest.Create(controllerUrl);
webRequest.Method = "POST";
// webRequest.ContentType = "application/x-www-form-urlencoded";
string postData = makeHttpPostString(interchangeFile);
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
webRequest.ContentLength = byteArray.Length;
using (Stream dataStream = webRequest.GetRequestStream())
{
dataStream.Write(byteArray, 0, byteArray.Length);
}
HttpWebResponse webresponse = (HttpWebResponse) webRequest.GetResponse();
When I set the contenttype of the request to "application/x-www-form-urlencoded" GetReponse() fails with server error code 500. When I comment that out and only httpencode the xml data, "interchangeXml", the post is sent but only the 3rd parameter, "label" reaches the controller. The others are null.
What is the correct way to post values to a controller action when one of those values is xml data?
Thanks!
Update
I am send all the parameter with the exception of the XML via the query string. However, the problem now is that I do not know how to access the posted data in the controller action. Can someone tell me how I access the xml from the HttpRequest from with my Controller Action?
Update
I have refactored the above code to use the suggests made to me by Darin. I am recieveing an internal server error (500) using the WebClient UploadValues().
Action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult BuildPreview(PreviewViewModel model)
{
...
}
Request:
private string PostToSxController(XmlDocument interchangeFile, string controllerUrl)
{
var xmlInterchange = new InterchangeDocument(interchangeFile);
using (var client = new WebClient())
{
var values = new NameValueCollection()
{
{"uid", xmlInterchange.DocumentKey.Hwid},
{"localization", xmlInterchange.DocumentKey.Localization.ToString()},
{"label", ConfigurationManager.AppSettings["PreviewLabel"]},
{"interchangeDocument", interchangeFile.OuterXml }
};
byte[] result = null;
try
{
result = client.UploadValues(controllerUrl, values);
}
catch(WebException ex)
{
var errorResponse = ex.Response;
var errorMessage = ex.Message;
}
Encoding encoding = Encoding.UTF8;
return encoding.GetString(result);
}
}
Route:
routes.MapRoute(
"BuildPreview",
"SymptomTopics/BuildPreview/{model}",
new { controller = "SymptomTopics", action = "BuildPreview", model = UrlParameter.Optional }
);
Too complicated and unsafe your client code with all those requests and responses. You are not encoding any of your request parameters, not to mention this XML which is probably gonna break everything if you don't encode it properly.
For this reason I would simplify and leave the plumbing code about encoding, etc... to the .NET framework:
using (var client = new WebClient())
{
var values = new NameValueCollection
{
{ "uid", hwid },
{ "localization", localization },
{ "label", label },
{ "interchangeDocument", interchangeFile.OuterXml },
};
var result = client.UploadValues(controllerUrl, values);
// TODO: do something with the results returned by the controller action
}
As far as the server side is concerned, as every properly architected ASP.NET MVC application, it would obviously use a view model:
public class MyViewModel
{
public string Uid { get; set; }
public string Localization { get; set; }
public string Label { get; set; }
public string InterchangeDocument { get; set; }
}
with:
[HttpPost]
public ActionResult Foo(MyViewModel model)
{
// TODO: do something with the values here
...
}
Obviously this could be taken a step further by writing a view model reflecting the structure of your XML document:
public class Foo
{
public string Bar { get; set; }
public string Baz { get; set; }
}
and then your view model will become:
public class MyViewModel
{
public string Uid { get; set; }
public string Localization { get; set; }
public string Label { get; set; }
public Foo InterchangeDocument { get; set; }
}
and the last part would be to write a custom model binder for the Foo type that will use a XML serializer (or whatever) to deserialize back the InterchangeDocument POSTed value into a Foo instance. Now that's serious business.
I'm wrestling the same beast over here: Trying to set up a controller action as Xml endpoint
You may be getting the internal server error because either you have page validation on (solution: addotate with ValidateInput(false)), or you're not sending an Accept-Encoding header with your request. I would very much like to hear how I can get MVC to accept posted input without the Accept-Encoding HTTP header...
I just found that you can call a controller, even a dependency injected one, even from a Web Forms code behind using the "T4MVC" Nuget package:
https://github.com/T4MVC/T4MVC
Related
I am trying to make a post request from WPF to Web API using the following code but the request parameter is always null.
Request Model
public class Document
{
public string FileName { get; set; }
public byte[] Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
WPF Client
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"C:\Users\john.doe\Downloads\I Love Coding.pdf.pdf")
}
}
};
using (var http = new HttpClient())
{
var encodedJson = JsonConvert.SerializeObject(obj);
var conent = new StringContent(encodedJson, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.PostAsync("https://my-app.com/api/upload", conent);
response.EnsureSuccessStatusCode();
}
Web API
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromBody] Request request)
{
// request is always null when app is running in production
// https://my-app.com/api/upload
//request is not null when running on https://localhost:8080/api/upload
}
}
Please what am I missing in the above implementation?
The request parameter is not null on localhost but always null in production.
Please what am I missing in the above implementation? The request
parameter is not null on localhost but always null in production.
Well, not sure how are getting data on local server becuse, you are sending MultipartFormData means your POCO object and file buffer. As you may know we can send json object in FromBody but not the files as json. Thus, I am not sure how it working in local and getting null data is logical in IIS Or Azure.
what am I missing in the above implementation?
As explained above, for sending both POCO object and Files as byte or steam we need to use FromForm and beside that, we need to bind our request object as MultipartFormDataContent to resolve your null data on your UploadDocumentsAsync API action.
Required Change For Solution:
WPF:
In your WPF http request please update your request code snippet as following:
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"YourFilePath")
}
}
};
var httpClient = new HttpClient
{
BaseAddress = new("https://YourServerURL")
};
var formContent = new MultipartFormDataContent();
formContent.Add(new StringContent(obj.Uploader), "Uploader");
formContent.Add(new StringContent(obj.Documents[0].FileName), "Documents[0].FileName");
formContent.Add(new StreamContent(new MemoryStream(obj.Documents[0].Buffer)), "Documents[0].Buffer", obj.Documents[0].FileName);
var response = await httpClient.PostAsync("/api/upload", formContent);
if (response.IsSuccessStatusCode)
{
var responseFromAzureIIS = await response.Content.ReadAsStringAsync();
}
Note: Class in WPF side would remain same as before. No changes required.
Asp.net Core Web API:
In asp.net core web API side you should use [FromForm] instead of [FromBody]
So your controller Action would as following:
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromForm] Request file)
{
if (file.Documents[0].Buffer == null)
{
return Ok("Null File");
}
return Ok("File Received");
}
}
Note: For remote debugging I have checked the logs and for double check I have used a simple conditionals whether file.Documents[0].Buffer == null. I have tested both in local, IIS and Azure and working accordingly.
Update POCO Class in API Project:
For buffer you have used byte for your WPF project but for Web API project update that to IFormFile instead of byte. It should be as following:
public class Document
{
public string FileName { get; set; }
public IFormFile Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
Output:
If you would like to know more details on it you could check our official document here
I am using Asp.Net Core 2.2 WebApi and WinForm Client for sending requests. I am using RestSharp library as Restclient. It seems Property values with DateTime type is not correctly handled in WebApi Controller.
Source DateTime:
Target DateTime:
Client side code:
internal virtual void Add(T1 businessObject)
{
RestRequest request = new RestRequest(ManagementControllerName + #"/" + AddActionPrefix, Method.POST);
request.RequestFormat = DataFormat.Json;
request.AddJsonBody(businessObject);
var response = Globals.ServiceStackClient.Execute(request);
if (!response.IsSuccessful)
{
var ex = JsonConvert.DeserializeObject<AppExceptionMessage>(response.Content);
throw new ClientSideException(ex);
};
}
Business object:
public class DmPresentationBo : CatalogBo
{
public DmPresentationBo()
{
}
public string DocItemNumber { get; set; }
public DateTime DocItemDt { get; set; }
}
WebApi Controller:
[HttpPost]
[Route("Add")]
public ActionResult<T1> Add([FromBody] T1 businessObject)
{
try
{
CurrentDbStorage.StartTransaction();
var mainCmd = CurrentDbStorage.GenerateCrudInsertCommand(businessObject);
mainCmd.ExecuteNonQuery();
ProcessPostAdd(businessObject);
CurrentDbStorage.CommitTransaction();
}
catch (Exception ex)
{
CurrentDbStorage.RollbackTransaction();
var parsedEx = CurrentExceptionMgr.ParseErrorMsg(ex.Message);
return new BadRequestObjectResult(parsedEx);
};
return Ok(businessObject);
}
Setting Breakpoint within controller, it showed me that BusinessObject deserialized incorrectly, like DocItemDt property value is less than actual one which sent by Client. Surfing google, there are some problems with Json DateTime handling, however none of them helped me.
Thanks for your help.
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
}
Here is my question,
I got a Web API and client(winform), client will send out data with a Serialize Object. My Web API do have received and return a response to client. But I can't view the data on Web API, I do have try using Deserialize Object and convert it into string but not working neither.
Please help me,Thanks!
Here is my code:
Client
private string WebApiPost(string sParam, string sJson)
{
var client = new HttpClient();
var content = new StringContent(sJson, Encoding.UTF8, "application/json");
var response = client.PostAsync(sWebAPI_URL + sParam, content).Result;
var body = response.Content.ReadAsStringAsync().Result;
return body;
}
This is my Web API
public object Post([FromBody]object hL7)
{
//what should I do???
//I've tried set hL7 into string but it wont get any data;
//I've also tried deserialize it but will get 500 internal server error.
return hL7;
}
This is my WebAPI model
public class HL7MID
{
public string LOC { get; set; }
public string COMPANY { get; set; }
}
public class HL7MID_List
{
public string sMSG { get; set; }
public List<HL7MID> data = new List<HL7MID>();
}
Because sJson matches HL7MID, you can use that type in as a paramter of your Post function, and just use that type.
public HL7MID Post([FromBody]HL7MID hL7)
{
//use hL7 here
return hL7;//also since you know the return type, changing that to HL7MID is suggested
}
I made a post request class that I could re-use to make POST requests to an external api and return the objects they send me (JSON):
class PostRequest
{
private Action<DataUpdateState> Callback;
public PostRequest(string urlPath, string data, Action<DataUpdateState> callback)
{
Callback = callback;
// form the URI
UriBuilder fullUri = new UriBuilder(urlPath);
fullUri.Query = data;
// initialize a new WebRequest
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fullUri.Uri);
request.Method = "POST";
// set up the state object for the async request
DataUpdateState dataState = new DataUpdateState();
dataState.AsyncRequest = request;
// start the asynchronous request
request.BeginGetResponse(new AsyncCallback(HandleResponse),
dataState);
}
private void HandleResponse(IAsyncResult asyncResult)
{
// get the state information
DataUpdateState dataState = (DataUpdateState)asyncResult.AsyncState;
HttpWebRequest dataRequest = (HttpWebRequest)dataState.AsyncRequest;
// end the async request
dataState.AsyncResponse = (HttpWebResponse)dataRequest.EndGetResponse(asyncResult);
if (dataState.AsyncResponse.StatusCode.ToString() == "OK")
{
Callback(dataState); // THIS IS THE LINE YOU SHOULD LOOK AT :)
}
}
}
public class DataUpdateState
{
public HttpWebRequest AsyncRequest { get; set; }
public HttpWebResponse AsyncResponse { get; set; }
}
}
the Callback method gets the datastate object and pushes it to this function:
public void LoadDashboard( DataUpdateState dataResponse )
{
Stream response = dataResponse.AsyncResponse.GetResponseStream();
//Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
//StreamReader readStream = new StreamReader(response, encode);
//readStream.Close();
Deployment.Current.Dispatcher.BeginInvoke(() => {
App.RootFrame.Navigate(new Uri("/Interface.xaml", UriKind.RelativeOrAbsolute));
});
}
I'm now unsure of how to get the body of that reply that has been sent to me from the API. It is returning a json format, I need to be able to map it to a nice C# class and use it to display stuff on the phone.
I can't find an example that doesn't use JSON.NET (which doesn't have an assembly for windows phone 8)
This is error I get installing HTTPClient class:
Attempting to resolve dependency 'Microsoft.Bcl (≥ 1.1.3)'.
Attempting to resolve dependency 'Microsoft.Bcl.Build (≥ 1.0.4)'.
Successfully installed 'Microsoft.Bcl.Build 1.0.10'.
Successfully installed 'Microsoft.Bcl 1.1.3'.
Successfully installed 'Microsoft.Net.Http 2.2.13'.
Successfully added 'Microsoft.Bcl.Build 1.0.10' to UnofficialPodio.
Executing script file ***\packages\Microsoft.Bcl.Build.1.0.10\tools\Install.ps1'.
This reference cannot be removed from the project because it is always referenced by the compiler.
This reference cannot be removed from the project because it is always referenced by the compiler.
This reference cannot be removed from the project because it is always referenced by the compiler.
This reference cannot be removed from the project because it is always referenced by the compiler.
Executing script file ***\packages\Microsoft.Bcl.Build.1.0.10\tools\Uninstall.ps1'.
Successfully uninstalled 'Microsoft.Bcl 1.1.3'.
Successfully uninstalled 'Microsoft.Bcl.Build 1.0.10'.
Install failed. Rolling back...
Failed to add reference to 'System.IO'.
"{
\"access_token\": \"123803120312912j\",
\"token_type\": \"bearer\",
\"ref\": {
\"type\": \"user\",
\"id\": 123123
},
\"expires_in\": 28800,
\"refresh_token\": \"234234f23f423q432f\"
}"
...
public class Auth
{
[DataMember(Name = "access_token")]
public string AccessToken { get; set; }
[DataMember(Name = "token_type")]
public string TokenType { get; set; }
[DataMember(Name = "expires_in")]
public string ExpiresIn { get; set; }
[DataMember(Name = "refresh_token")]
public string RefreshToken { get; set; }
//[DataMember(Name = "ref")]
//public string Ref { get; set; }
}
To get the response data you need to call GetResponseStream() on the HttpWebResponse object and then read from the stream. Something like this:
using (Stream s = response.GetResponseStream())
{
using (TextReader textReader = new StreamReader(s, true))
{
jsonString = textReader.ReadToEnd();
}
}
To get the data from the json string, you need to create a data contract class to describe the json data exactly like this:
[DataContract]
public class ApiData
{
[DataMember(Name = "name")] <--this name must be the exact name of the json key
public string Name { get; set; }
[DataMember(Name = "description")]
public string Description { get; set; }
}
Next you can deserialize the json object from the string:
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(ApiData));
ApiData obj = (ApiData)serializer.ReadObject(stream);
return obj;
}
WebRequest will work fine, but I would recommend installing the NuGet package for the HttpClient class. It makes life much simpler. for instance, you could make the above request code in just a few lines:
HttpClient httpClient = new HttpClient();
HttpRequestMessage msg = new HttpRequestMessage(new HttpMethod("POST"), escapedUrl);
HttpResponseMessage response = await httpClient.SendAsync(msg);
In answer to you question below, here is the generic json converter code that I use:
public static class JsonHelper
{
public static T Deserialize<T>(string json)
{
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
T obj = (T)serializer.ReadObject(stream);
return obj;
}
}
public static string Serialize(object objectToSerialize)
{
using (MemoryStream ms = new MemoryStream())
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(objectToSerialize.GetType());
serializer.WriteObject(ms, objectToSerialize);
ms.Position = 0;
using (StreamReader sr = new StreamReader(ms))
{
return sr.ReadToEnd();
}
}
}
}
JSON.Net does support windows phone 8, it's available as a portable class library according to this answer.
Just try adding the package via nuget...