How can I get data from a HTTP post in C# Xamarin? - c#

I am trying to POST some data using HTTPClient. I have managed to use the following code when simply getting data in JSON format, but it doesn't seem to work for a POST.
This is the code I'm using:
public static async Task<SwipeDetails> SaveSwipesToCloud()
{
//get unuploaded swips
IEnumerable<SwipeDetails> swipesnotsved = SwipeRepository.GetUnUploadedSwipes();
foreach (var item in swipesnotsved)
{
//send it to the cloud
Uri uri = new Uri(URL + "SaveSwipeToServer" + "?locationId=" + item.LocationID + "&userId=" + item.AppUserID + "&ebCounter=" + item.SwipeID + "&dateTimeTicks=" + item.DateTimeTicks + "&swipeDirection=" + item.SwipeDirection + "&serverTime=" + item.IsServerTime );
HttpClient myClient = new HttpClient();
var response = await myClient.GetAsync(uri);
//the content needs to update the record in the SwipeDetails table to say that it has been saved.
var content = await response.Content.ReadAsStringAsync();
}
return null;
}
This is the method it's trying to contact. As you can see, the method also returns some data in JSON format so as well as a POST it's also getting some data back which I need to be able to work with:
[HttpPost]
public JsonResult SaveSwipeToServer(int locationId, int userId, int ebCounter, long dateTimeTicks, int swipeDirection, int serverTime)
{
bool result = false;
string errMsg = String.Empty;
int livePunchId = 0;
int backupPunchId = 0;
IClockPunch punch = null;
try
{
punch = new ClockPunch()
{
LocationID = locationId,
Swiper_UserId = userId,
UserID = ebCounter,
ClockInDateTime = DateTimeJavaScript.ConvertJavascriptDateTime(dateTimeTicks),
ClockedIn = swipeDirection.Equals(1),
};
using (IDataAccessLayer dal = DataFactory.GetFactory())
{
DataAccessResult dalResult = dal.CreatePunchForNFCAPI(punch, out livePunchId, out backupPunchId);
if (!dalResult.Result.Equals(Result.Success))
{
throw dalResult.Exception;
}
}
result = true;
}
catch (Exception ex)
{
errMsg = "Something Appeared to go wrong when saving punch information to the horizon database.\r" + ex.Message;
}
return Json(new
{
result = result,
punchDetails = punch,
LivePunchId = livePunchId,
BackUpPunchId = backupPunchId,
timeTicks = DateTimeJavaScript.ToJavaScriptMilliseconds(DateTime.UtcNow),
errorMessage = errMsg
}
,JsonRequestBehavior.AllowGet);
}
At the moment the data being stored in 'content' is just an error message.

You can post the parameters in the body of the request.
public static async Task<SwipeDetails> SaveSwipesToCloud() {
//get unuploaded swips
var swipesnotsved = SwipeRepository.GetUnUploadedSwipes();
var client = new HttpClient() {
BaseAddress = new Uri(URL)
};
var requestUri = "SaveSwipeToServer";
//send it to the cloud
foreach (var item in swipesnotsved) {
//create the parameteres
var data = new Dictionary<string, string>();
data["locationId"] = item.LocationID;
data["userId"] = item.AppUserID;
data["ebCounter"] = item.SwipeID;
data["dateTimeTicks"] = item.DateTimeTicks;
data["swipeDirection"] = item.SwipeDirection;
data["serverTime"] = item.IsServerTime;
var body = new System.Net.Http.FormUrlEncodedContent(data);
var response = await client.PostAsync(requestUri, body);
//the content needs to update the record in the SwipeDetails table to say that it has been saved.
var content = await response.Content.ReadAsStringAsync();
}
return null;
}

I am not sure how you host your service, it is not clear from your code. I hosted mine in Web API controller SwipesController in application HttpClientPostWebService. I don't suggest to use JsonResult. For mobile client I would just return the class you need.
You have 2 options:
Use get not post.
Use post.
Both cases are below
Controller:
namespace HttpClientPostWebService.Controllers
{
public class SwipesController : ApiController
{
[System.Web.Http.HttpGet]
public IHttpActionResult SaveSwipeToServer(int locationId, int userId, int ebCounter, long dateTimeTicks, int swipeDirection, int serverTime)
{
return Ok(new SwipeResponse
{
TestInt = 3,
TestString = "Testing..."
});
}
[System.Web.Http.HttpPost]
public IHttpActionResult PostSwipeToServer([FromBody] SwipeRequest req)
{
return Ok(new SwipeResponse
{
TestInt = 3,
TestString = "Testing..."
});
}
}
public class SwipeRequest
{
public string TestStringRequest { get; set; }
public int TestIntRequest { get; set; }
}
public class SwipeResponse
{
public string TestString { get; set; }
public int TestInt { get; set; }
}
}
Client:
async private void Btn_Clicked(object sender, System.EventArgs e)
{
HttpClient client = new HttpClient();
try
{
var result = await client.GetAsync(#"http://uri/HttpClientPostWebService/Api/Swipes?locationId=1&userId=2&ebCounter=3&dateTimeTicks=4&swipeDirection=5&serverTime=6");
var content = await result.Content.ReadAsStringAsync();
var resp = JsonConvert.DeserializeObject<SwipeResponse>(content);
}
catch (Exception ex)
{
}
try
{
var result1 = await client.PostAsync(#"http://uri/HttpClientPostWebService/Api/Swipes",
new StringContent(JsonConvert.SerializeObject(new SwipeRequest() { TestIntRequest = 5, TestStringRequest = "request" }), Encoding.UTF8, "application/json"));
var content1 = await result1.Content.ReadAsStringAsync();
var resp1 = JsonConvert.DeserializeObject<SwipeResponse>(content1);
}
catch (Exception ex)
{
}
}

Related

Get the ID of file just streamed to SharePoint Document Folder

Please read the bottom of this post, since the question has been modified.
I have been successful in taking a file off of my computer and sending it to a document library in SharePoint. However, the "Title" column in the document is blank. I would like to set the "Title" column to a certain value, but not sure how to go about it.
Here is the code I use to upload the file
public static async Task PutFileAsync()
{
string genName = App.Generator;
genName = genName.Replace(" ", "-");
StorageLibrary videoLibrary = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Videos);
string readFolder = videoLibrary.SaveFolder.Path;
StorageFolder videoFolder = await StorageFolder.GetFolderFromPathAsync(readFolder);
string readFileName = App.Date + "-" + App.StartTime + "-" + App.IBX + "-" + genName + ".xlsx";
StorageFile readFile = await videoFolder.GetFileAsync(readFileName);
byte[] result;
using (Stream stream = await readFile.OpenStreamForReadAsync())
{
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
result = memoryStream.ToArray();
}
}
var (authResult, message) = await Authentication.AquireTokenAsync();
var httpClient = new HttpClient();
HttpResponseMessage response;
string posturl = MainPage.spfileurl + readFile.Name + ":/content";
var request = new HttpRequestMessage(HttpMethod.Put, posturl);
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
request.Content = new ByteArrayContent(result);
response = await httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
await Task.Run(() =>
{
File.Delete(readFile.Path);
return TaskStatus.RanToCompletion;
});
}
Any suggestions would be great!
Thanks!
Other information
public static string rooturl = "https://graph.microsoft.com/v1.0/sites/mycompanyinc.sharepoint.com,495435b4-60c3-49b7-8f6e-1d262a120ae5,0fad9f67-35a8-4c0b-892e-113084058c0a/";
string submiturl = rooturl + "lists/18a725ac-83ef-48fb-a5cb-950ca2378fd0/items";
public static string spfileurl = rooturl + "drive/root:/Generator_Runs/";
public static string fileurl = rooturl + "lists/edd49389-7edb-41db-80bd-c8493234eafa/drive/items/01JDP7KXPY64K4C3P4YJC2CJ2IUFG7DAP7/content";
Since it is not possible to add column data while uploading, I need to figure out what the listitem ID is for the file that I just uploaded is.
This is the responseString after the submission
[JSON]
#odata.context: "https://graph.microsoft.com/v1.0/$metadata#sites('mycompanyinc.sharepoint.com%2C495435b4-60c3-49b7-8f6e-1d262a120ae5%2C0fad9f67-35a8-4c0b-892e-113084058c0a')/drive/root/$entity"
#microsoft.graph.downloadUrl: "https://mycompanyinc.sharepoint.com/sites/GeneratorApp/_layouts/15/download.aspx?UniqueId=...&ApiVersion=2.0"
createdDateTime: "12/29/2018 6:43:00 PM"
eTag: ""{BB51689A-9FF5-412C-8B45-D01D2B61A789},2""
id: "01JDP7KXM2NBI3X5M7FRAYWROQDUVWDJ4J"
lastModifiedDateTime: "12/29/2018 6:43:00 PM"
name: "FileNameJ.xlsx"
webUrl: "https://mycompanyinc.sharepoint.com/sites/GeneratorApp/_layouts/15/Doc.aspx?sourcedoc=%7BBB51689A-9FF5-412C-8B45-D01D2B61A789%7D&file=FileName.xlsx&action=default&mobileredirect=true"
cTag: ""c:{BB51689A-9FF5-412C-8B45-D01D2B61A789},4""
size: 47079
createdBy
lastModifiedBy
parentReference
file
fileSystemInfo
Okay, it took a lot of trial and error. I had to create another class to be able to parse out the data:
public class SharePointDocumentNew
{
public class RootObject
{
public string eTag { get; set; }
public string id { get; set; }
public string name { get; set; }
public string webUrl { get; set; }
}
}
AND
public class SharePointDocumentItems
{
public class Value
{
[JsonProperty("#odata.etag")]
public string OdataEtag { get; set; }
public string ETag { get; set; }
public string Id { get; set; }
public string WebUrl { get; set; }
}
public class RootObject
{
[JsonProperty("#odata.context")]
public string OdataContext { get; set; }
public List<Value> value { get; set; }
}
}
Then I found the matching data between the two and used REGEX to get the matching data with this code:
public static async Task PutFileAsync()
{
List<SharePointListItems.Lookup> Lookups = new List<SharePointListItems.Lookup>();
string genName = App.Generator;
genName = genName.Replace(" ", "-");
StorageLibrary videoLibrary = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Videos);
string readFolder = videoLibrary.SaveFolder.Path;
StorageFolder videoFolder = await StorageFolder.GetFolderFromPathAsync(readFolder);
string readFileName = App.Date + "-" + App.StartTime + "-" + App.IBX + "-" + genName + ".xlsx";
StorageFile readFile = await videoFolder.GetFileAsync(readFileName);
byte[] result;
using (Stream stream = await readFile.OpenStreamForReadAsync())
{
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
result = memoryStream.ToArray();
}
}
var (authResult, message) = await Authentication.AquireTokenAsync();
var httpClient = new HttpClient();
HttpResponseMessage response;
string posturl = MainPage.spfileurl + readFile.Name + ":/content";
var request = new HttpRequestMessage(HttpMethod.Put, posturl);
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
request.Content = new ByteArrayContent(result);
response = await httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
JObject json = JObject.Parse(responseString);
var result3 = JsonConvert.DeserializeObject<SharePointDocumentNew.RootObject>(responseString);
eTag = result3.eTag.ToString();
eTag = eTag.Replace("\"{", "");
string replacement = "";
string endPattern = "}.*";
Regex rgxend = new Regex(endPattern);
eTag = rgxend.Replace(eTag, replacement);
eTag = Regex.Replace(eTag, #"[A-Z]+?", m => m.ToString().ToLower());
await Task.Run(() =>
{
File.Delete(readFile.Path);
return TaskStatus.RanToCompletion;
});
}
public static async Task GetFileDataAsync()
{
List<SharePointDocumentItems.Value> Value2 = new List<SharePointDocumentItems.Value>();
var (authResult2, message2) = await Authentication.AquireTokenAsync();
var httpClient2 = new HttpClient();
HttpResponseMessage response2;
string geturl = "https://graph.microsoft.com/v1.0/sites/mycoinc.sharepoint.com,495435b4-60c3-49b7-8f6e-1d262a120ae5,0fad9f67-35a8-4c0b-892e-113084058c0a/lists/edd49389-7edb-41db-80bd-c8493234eafa/items";
var request2 = new HttpRequestMessage(HttpMethod.Get, geturl);
request2.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult2.AccessToken);
response2 = await httpClient2.SendAsync(request2);
var responseString2 = await response2.Content.ReadAsStringAsync();
JObject json2 = JObject.Parse(responseString2);
var result2 = JsonConvert.DeserializeObject<SharePointDocumentItems.RootObject>(responseString2);
foreach (var d in result2.value)
{
string ETAG = d.ETag;
string startPattern = "^\\\"";
string replacement = "";
string endPattern = ",.*";
Regex rgxstart = new Regex(startPattern);
ETAG = rgxstart.Replace(ETAG, replacement);
Regex rgxend = new Regex(endPattern);
ETAG = rgxend.Replace(ETAG, replacement);
if (ETAG == eTag)
{
fileID = d.Id;
}
}
}
Now I have the ID I need to update the other columns associated with the file.

How do I stream a large file from api to api without using disk and running out of memory?

I have a API HTTPGET that retrieves a file from another API, and spits it out. This works for smaller files, but the problem I'm having is that the files retrieved can be rather large in size (up to 2gb), and the MemoryStream is a limitation. Any ideas how to stream the file content without using disk and avoiding the 'out of memory' exception?
Controller:
[Route("{id}/file", Name = "GetContentFile")]
[HttpGet]
public IHttpActionResult GetContentFile(string id)
{
if (String.IsNullOrEmpty(id))
return BadRequest();
ContentFile cfl = new ContentFile();
var ret = new HttpResponseMessage(HttpStatusCode.OK);
try
{
cfl = otcrepo.GetContentFile(id);
var mstream = new MemoryStream(cfl.Data);
ret.Content = new StreamContent(mstream);
ret.Content.Headers.ContentType = new MediaTypeHeaderValue(cfl.ContentType);
ret.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
}
catch
{
return InternalServerError();
}
if (cfl != null)
{
ResponseMessageResult responseMessageResult = ResponseMessage(ret);
return responseMessageResult;
}
else
{
return NotFound();
}
}
Model:
public class ContentFile
{
public string Filename { get; set; }
public byte[] Data { get; set; }
public StreamContent DataStream { get; set; }
public string ContentType { get; set; }
}
Repository call:
public ContentFile GetContentFile(string id)
{
ContentFile fl = new ContentFile();
using (var htc = new HttpClient())
{
var response = htc.GetAsync(ConfigurationManager.AppSettings["BaseUrl"] + "/api/v2/nodes/" + id + "/content/").Result;
fl.Data = response.Content.ReadAsByteArrayAsync().Result;
fl.ContentType = response.Content.Headers.GetValues("Content-Type").FirstOrDefault();
}
return fl;
}
Thanks.
So instead of GetContentFile reading the complete stream into an array and returning it we return the stream. Than there is no need for the MemoryStream.
[Route("{id}/file", Name = "GetContentFile")]
[HttpGet]
public async Task<IHttpActionResult> GetContentFile(string id)
{
...
var result = await otcrepo.GetContentFile(id);
ret.Content = new StreamContent(result.stream);
ret.Content.Headers.ContentType = new MediaTypeHeaderValue(result.contentType);
...
}
public async Task<(Stream stream, string contentType)> GetContentFile(string id)
{
var htc = new HttpClient()
var response = await htc.GetAsync(ConfigurationManager.AppSettings["BaseUrl"] + "/api/v2/nodes/" + id + "/content/");
var stream = await response.Content.ReadAsStreamAsync();
var contentType = response.Content.Headers.GetValues("Content-Type").FirstOrDefault();
return (stream, contentType);
}

How to read the contents of a json post with asp before processing the request?

What I want to do is extremely simple in php. I just want to read the contents of the post. It also extremely simple on sailsjs / node ... I just return the result from within the async function.
In c# asp the answer is eluding me. I want the function to read the contents of the post before it attempts to process the post.
Sometimes the following code works. Sometimes the reading of the json from the post happens too slowly and jsonText is read as "" so nothing is processed.
In all of the test runs the json is being sent in the body of the post.
What is the best way to return a httpResponse after making sure the contents of the post is read first?
public HttpResponseMessage Post()
{
string content;
try
{
string result = String.Empty;
Newtonsoft.Json.Linq.JObject jObject = null;
string jsonText = String.Empty;
var syncTask = new Task<string>( () => {
return Request.Content.ReadAsStringAsync().Result;
});
/* I'm expecting that this will finish */
syncTask.RunSynchronously();
jsonText = syncTask.Result;
/* before this line of code executes */
System.Net.Http.HttpResponseMessage response = new HttpResponseMessage();
if (jsonText == "")
{
result = "{\"error\":\"body is empty\"}";
response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
}
else
{
jObject = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JRaw.Parse(jsonText);
string ipAddress = System.Web.HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
jObject["ipAddress"] = ipAddress;
Models.JsonXML jsonXml = new JsonXML(jObject.ToString(Newtonsoft.Json.Formatting.None));
System.Xml.XmlDocument document = new System.Xml.XmlDocument();
document.LoadXml(jsonXml.xml);
result = ReferralsManager.ProcessReferral(document);
if (result == "")
{
result = "{}";
}
response.StatusCode = System.Net.HttpStatusCode.OK;
}
response.Content = new StringContent(result);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return response;
}
catch (Exception ex)
{
content = ErrorMessage.ServerException(Converter, ex);
return Request.ToResponseMessage(content);
}
finally
{
LogManager.GetCurrentClassLogger().Info(InfoMessage.FUNC_ENDS, "Process Referral");
}
}
The working modified code after the answer from #Mekap is
public class ProcessReferralAddressModel {
public ProcessReferralAddressModel() { }
public string address { get; set; }
public string name { get; set; }
}
public class ProcessReferralModel
{
public ProcessReferralModel()
{
}
public string uuid { get; set; }
public DateTime date { get; set; }
public ProcessReferralAddressModel from { get; set; }
public ProcessReferralAddressModel[] to { get; set; }
public string subject { get; set; }
public string text { get; set; }
public string html { get; set; }
}
/// <summary>
/// Process a referral.
/// </summary>
/// <param name="userid">The userid.</param>
/// <returns></returns>
public HttpResponseMessage Post([FromBody] ProcessReferralModel processReferralModel)
{
string content;
string jsonText = Newtonsoft.Json.JsonConvert.SerializeObject(processReferralModel) ;
try
{
string result = String.Empty;
Newtonsoft.Json.Linq.JObject jObject = null;
System.Net.Http.HttpResponseMessage response = new HttpResponseMessage();
if (jsonText == "" || jsonText == null )
{
result = "{\"error\":\"body is empty\"}";
response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
}
else
{
jObject = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JRaw.Parse(jsonText);
string ipAddress = System.Web.HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
jObject["ipAddress"] = ipAddress;
Models.JsonXML jsonXml = new JsonXML(jObject.ToString(Newtonsoft.Json.Formatting.None));
System.Xml.XmlDocument document = new System.Xml.XmlDocument();
document.LoadXml(jsonXml.xml);
result = ReferralsManager.ProcessReferral(document);
if (result == "")
{
result = "{}";
}
response.StatusCode = System.Net.HttpStatusCode.OK;
}
response.Content = new StringContent(result);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return response;
}
catch (Exception ex)
{
content = ErrorMessage.ServerException(Converter, ex);
return Request.ToResponseMessage(content);
}
finally
{
LogManager.GetCurrentClassLogger().Info(InfoMessage.FUNC_ENDS, "Process Referral");
}
}
The json you're fetching, for our example will look something like
{ "ID" : 3,
"StringCmd" : "ls -l"
}
For starters, we are going to write a small class who's representing our data in your web api
public class StringCmdModel
{
public StringCmdModel()
{
}
public int ID { get; set; }
public string StringCmd { get; set; }
}
Now, we just have to write our Entry point in our WebAPI :
[HttpPost]
public HttpResponseMessage PostFonction([FromBody] StringCmdModel NewEntry)
You don't have to check for the existence of the data inside the function. But you should still do proper checks on theirs values, in case you get bad formated json or malicious calls.
But, if you get a call with json that is not matching the StringCmdModel you gave in parameter from the body, this function will not be executed, and the server will throw on its own a 500 error.

Call HttpPut with parameter C#

So I create an HttpPut method in an ASP.NET web api.
[Route("api/Account/Save")]
[HttpPut]
public IHttpActionResult SaveAccount(Account acc) {
// do stuff
}
I pass in an instant of the Account class.
class Account
{
public int AccountID { get; set; }
public string AccountName { get; set; }
}
Now I want to call this from a console application. I am trying to do this but it's not working. It's not throwing any exception either.
var acc = new Account() { AccountID = 1234, AccountName = "zzzzP" };
string json = JsonConvert.SerializeObject(acc);
HttpContent content = new StringContent(json);
response = await client.PutAsync("api/Account/Save", content);
Json returned:
"{\"AccountID\":1234,\"AccountName\":\"zzzzP\"}"
You probably want something like this
static async Task PutAccount()
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://yourWebSite.com");
var acc = new Account() { AccountID = 1234, AccountName = "zzzzP" };
string json = JsonConvert.SerializeObject(acc);
using (HttpResponseMessage response = await client.PutAsync("api/Account/Save", new StringContent(json)))
{
return response.EnsureSuccessStatusCode();
}
}
}

EF 6.1.1 and Web API Error adding entity with children

When I try to add an entity with new children, I get InvalidOperationException in the EntityFramework.dll.
I have set a small test app to attempt to understand this issue.
I have two models: Parent and Child.
public class Parent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ParentId { get; set; }
public String Name { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ChildId { get; set; }
public Guid ParentId { get; set; }
public string Name { get; set; }
// Navigation
[ForeignKey("ParentId")]
public Parent Parent { get; set; }
}
at the WebAPI side I have a controller ParentController
// PUT: api/Parents/5
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutParent(Guid id, Parent parent)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != parent.ParentId)
{
return BadRequest();
}
db.Entry(parent).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ParentExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
I have thrown together a WPF app to exercise the API.
On a Button click:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
ParentApi parentApi = new ParentApi();
var response = await parentApi.GetParents();
if(response.ResponseCode.Equals(200))
{
var parent = ((List<Parent>)response.ResponseObject).Where(prnt => prnt.Name.Equals("Parent1", StringComparison.Ordinal)).Single();
if(parent != null)
{
// Put child entity/
if (parent.Children == null)
parent.Children = new List<Child>();
Child newChild = new Child();
newChild.Name = "Child One";
parent.Children.Add(newChild);
response = await parentApi.PutParent(parent.ParentId, parent);
if(response.ResponseCode.Equals(200))
{
// Success
Debug.WriteLine(response.ResponseObject.ToString());
}
else
{
// Other/
if (response.ResponseObject != null)
Debug.WriteLine(response.ResponseObject.ToString());
}
}
}
}
ParentAPi looks like:
public class ParentApi : ApiBase
{
public async Task<ApiConsumerResponse> GetParents()
{
return await GetAsync<Parent>("http://localhost:1380/api/Parents/");
}
public async Task<ApiConsumerResponse> PutParent(Guid parentId, Parent parent)
{
return await PutAsync<Parent>(parent, "http://localhost:1380/api/Parents/" + parentId);
}
}
ApiBase and ApiConsumerResponse look like:
public class ApiBase
{
readonly RequestFactory _requester = new RequestFactory();
public async Task<ApiConsumerResponse> GetAsync<T>(string uri)
{
ApiConsumerResponse result = new ApiConsumerResponse();
try
{
var response = await _requester.Get(new Uri(uri));
result.ResponseCode = response.ResponseCode;
result.ReasonPhrase = response.ReasonPhrase;
if (result.ResponseCode == 200)
{
result.ResponseObject = await Task.Factory.StartNew(
() => JsonConvert.DeserializeObject<List<T>>(
response.BodyContentJsonString));
}
else
{
string msg = response.ReasonPhrase + " - " + response.BodyContentJsonString;
result.ErrorReceived = true;
}
}
catch (Newtonsoft.Json.JsonReaderException jsonE)
{
result.ErrorReceived = true;
}
catch (Exception e)
{
// Some other error occurred.
result.ErrorReceived = true;
}
return result;
}
public async Task<ApiConsumerResponse> PutAsync<T>(T apiModel, string uri)
{
ApiConsumerResponse result = new ApiConsumerResponse();
try
{
string json = await Task.Factory.StartNew(
() => JsonConvert.SerializeObject(
apiModel, Formatting.Indented));
var response = await _requester.Put(new Uri(uri), json);
result.ResponseCode = response.ResponseCode;
result.ReasonPhrase = response.ReasonPhrase;
// if 200: OK
if (response.ResponseCode.Equals(200))
{
result.ResponseObject = await Task.Factory.StartNew(
() => JsonConvert.DeserializeObject<T>(
response.BodyContentJsonString));
}
else
{
string msg = response.ReasonPhrase + " - " + response.BodyContentJsonString;
result.ErrorReceived = true;
}
}
catch (Newtonsoft.Json.JsonReaderException jsonE)
{
result.ErrorReceived = true;
}
catch (Exception e)
{
// Some other error occurred.
result.ErrorReceived = true;}
return result;
}
}
public class ApiConsumerResponse
{
public int ResponseCode { get; set; }
public string ReasonPhrase { get; set; }
public object ResponseObject { get; set; }
public bool ErrorReceived { get; set; }
}
RequestFactory (which is not a factory) and it's response class look like:
public class RequestFactory
{
public async Task<NetworkWebRequestMakerResponse> Get(Uri uri)
{
if (uri.UserEscaped)
{
uri = new Uri(Uri.EscapeUriString(uri.OriginalString));
}
using (var client = new HttpClient())
{
try
{
client.Timeout = TimeSpan.FromSeconds(60);
var response = await client.GetAsync(uri);
var stringResponse = await response.Content.ReadAsStringAsync();
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = false,
UnknonErrorExceptionObject = null,
ResponseCode = (int)response.StatusCode,
ReasonPhrase = response.ReasonPhrase,
BodyContentJsonString = stringResponse,
};
}
catch (Exception Ex)
{
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = true,
UnknonErrorExceptionObject = Ex,
ResponseCode = -1,
ReasonPhrase = "NONE",
BodyContentJsonString = "{NONE}",
};
}
}
}
public async Task<NetworkWebRequestMakerResponse> Post(Uri url, string json)
{
using (var client = new HttpClient())
{
HttpResponseMessage response;
try
{
Debug.WriteLine("POSTING JSON: " + json);
var content = new StringContent(json);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(#"application/json");
response = await client.PostAsync(url, content);
var stringResponse = await response.Content.ReadAsStringAsync();
/*
* For the reason given in the post below I will not call EnsureSuccessCode as it blows away the data
* http://stackoverflow.com/questions/14208188/how-to-get-the-json-error-message-from-httprequestexception
*
*/
// response.EnsureSuccessStatusCode();
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = false,
UnknonErrorExceptionObject = null,
ResponseCode = (int)response.StatusCode,
ReasonPhrase = response.ReasonPhrase,
BodyContentJsonString = stringResponse,
};
}
catch (Exception Ex)
{
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = true,
UnknonErrorExceptionObject = Ex,
ResponseCode = -1,
ReasonPhrase = "NONE",
BodyContentJsonString = "{NONE}",
};
}
}
}
public async Task<NetworkWebRequestMakerResponse> Put(Uri url, string json)
{
using (var client = new HttpClient())
{
HttpResponseMessage response;
try
{
Debug.WriteLine("PUTING JSON: " + json);
var content = new StringContent(json);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(#"application/json");
response = await client.PutAsync(url, content);
var stringResponse = await response.Content.ReadAsStringAsync();
/*
* For the reason given in the post below I will not call EnsureSuccessCode as it blows away the data
* http://stackoverflow.com/questions/14208188/how-to-get-the-json-error-message-from-httprequestexception
*
*/
// response.EnsureSuccessStatusCode();
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = false,
UnknonErrorExceptionObject = null,
ResponseCode = (int)response.StatusCode,
ReasonPhrase = response.ReasonPhrase,
BodyContentJsonString = stringResponse,
};
}
catch (Exception Ex)
{
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = true,
UnknonErrorExceptionObject = Ex,
ResponseCode = -1,
ReasonPhrase = "NONE",
BodyContentJsonString = "{NONE}",
};
}
}
}
public async Task<NetworkWebRequestMakerResponse> Delete(Uri url)
{
using (var client = new HttpClient())
{
HttpResponseMessage response;
try
{
response = await client.DeleteAsync(url);
var stringResponse = await response.Content.ReadAsStringAsync();
/*
* For the reason given in the post below I will not call EnsureSuccessCode as it blows away the data
* http://stackoverflow.com/questions/14208188/how-to-get-the-json-error-message-from-httprequestexception
*
*/
// response.EnsureSuccessStatusCode();
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = false,
UnknonErrorExceptionObject = null,
ResponseCode = (int)response.StatusCode,
ReasonPhrase = response.ReasonPhrase,
BodyContentJsonString = stringResponse,
};
}
catch (Exception Ex)
{
return new NetworkWebRequestMakerResponse()
{
UnknownErrorReceived = true,
UnknonErrorExceptionObject = Ex,
ResponseCode = -1,
ReasonPhrase = "NONE",
BodyContentJsonString = "{NONE}",
};
}
}
}
}
public class NetworkWebRequestMakerResponse
{
public bool UnknownErrorReceived { get; set; }
public Exception UnknonErrorExceptionObject { get; set; }
public int ResponseCode { get; set; }
public string ReasonPhrase { get; set; }
public string BodyContentJsonString { get; set; }
}
So all good. Testing the Get Method (not shown) it returns parent entities - GOOD.
The problem I have is when I try to 'PUT' a parent entity with a new child entity. As Shown in the Button_Click method.
The Parent entity with the new child arrives at the parentController however when I try to set state as modified:
db.Entry(parent).State = EntityState.Modified;
The Error is thrown: A referential integrity constraint violation occurred: The property value(s) of 'Parent.ParentId' on one end of a relationship do not match the property value(s) of 'Child.ParentId' on the other end.
Now as a test I changed out the PUT method on the controller To emulate the attempt from the client.
Modified PUT method:
public async Task<IHttpActionResult> PutParent(Guid id, Parent parent)
{
parent = db.Parents.Where(pe => pe.Name.Equals("Parent1", StringComparison.Ordinal)).Single();
var child = new Child();
child.Name = "Billy";
if (parent.Children == null)
parent.Children = new List<Child>();
parent.Children.Add(child);
db.Entry(parent).State = EntityState.Modified;
var result = await db.SaveChangesAsync();
Debug.Write(result.ToString());
}
Which works perfectly. The Child gets added to the DB it's ParentID is updated and it's own key is generated.
So why does the object that comes across the wire blow up EF?
I tried attaching the object first (db.Parents.Attach(parent);) but that throws the same error.
Im confused.
Entity Framework needs to track the objects to know which goes where and generates the SQL query accordingly, and part of this is done by you, by setting the State of the object, so if you set the parent's state to be modified but the new child's state was not set to be Added (Default is Unchanged), the entity frameowork here will treat this object as already exists in memory, and this is not the case.
but when added the child to the list of the children within the API the entity framework will set the Child's state to be Added, and will generate the SQL to insert the new child and link the ids accordingly.
Hope that helps.
EDIT
In the case of disconnected scenario, where you send the objects across the wire to modify, add, delete objects, I do define an enum that will pass with each Dto/entity I send to the client, and the client will modify this property to let the server knows what the status of each object when you try to save the whole graph with Entity framework, so the enum will look like this
public enum ObjectState
{
/// <summary>
/// Entity wasn't changed.
/// </summary>
Unchanged,
/// <summary>
/// Entity is new and needs to be added.
/// </summary>
Added,
/// <summary>
/// Entity has been modified.
/// </summary>
Modified,
/// <summary>
/// Entity has been deleted (physical delete).
/// </summary>
Deleted
}
and then I define a method that will translate this enum value to the entity' state that the entity framework knows about, my method will have something like this
// I do this before when the dbcontext about to be saved :
foreach (var dbEntityEntry in ChangeTracker.Entries())
{
var entityState = dbEntityEntry.Entity as IObjectState;
if (entityState == null)
throw new InvalidCastException(
"All entites must implement " +
"the IObjectState interface, this interface " +
"must be implemented so each entites state" +
"can explicitely determined when updating graphs.");
**dbEntityEntry.State = StateHelper.ConvertState(entityState.ObjectState);**
var trackableObject = dbEntityEntry.Entity as ITrackableObject;
// we need to set/update trackable properties
if (trackableObject == null)
{
continue;
}
var dateTime = DateTime.Now;
// set createddate only for added entities
if (entityState.ObjectState == ObjectState.Added)
{
trackableObject.CreatedDate = dateTime;
trackableObject.CreatedUserId = userId;
}
// set LastUpdatedDate for any case other than Unchanged
if (entityState.ObjectState != ObjectState.Unchanged)
{
trackableObject.LastUpdatedDate = dateTime;
trackableObject.LastUpdatedUserId = userId;
}
}
And finally this is my helper class to convert the states from my ObjectState => EF State, and Vise versa.
public class StateHelper
{
public static EntityState ConvertState(ObjectState state)
{
switch (state)
{
case ObjectState.Added:
return EntityState.Added;
case ObjectState.Modified:
return EntityState.Modified;
case ObjectState.Deleted:
return EntityState.Deleted;
default:
return EntityState.Unchanged;
}
}
public static ObjectState ConvertState(EntityState state)
{
switch (state)
{
case EntityState.Detached:
return ObjectState.Unchanged;
case EntityState.Unchanged:
return ObjectState.Unchanged;
case EntityState.Added:
return ObjectState.Added;
case EntityState.Deleted:
return ObjectState.Deleted;
case EntityState.Modified:
return ObjectState.Modified;
default:
throw new ArgumentOutOfRangeException("state");
}
}
}
hope that helps.

Categories