As a learning project, I have a MVC & Typescript project and a Web 2.0 & entity framework project, the MVC project is trying to talk to the Web 2.0 project and I have a weird error.
This is my Web API 2.0 Player Controller:
public class PlayerController : ApiController
{
// GET api/<controller>/5
public Player Get(int? id)
{
if (id == null || id == -1)
{
var player = new Player();
LeaderBoardContext.Current.Players.Add(player);
LeaderBoardContext.Current.SaveChanges();
return player;
}
return LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == id);
}
// PUT: api/Scores/5
[ResponseType(typeof(void))]
public IHttpActionResult PostPlayer(LearningCancerAPICalls.Models.Player player)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var model = LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == player.PlayerId);
LeaderBoardContext.Current.Entry<Player>(player).State = EntityState.Modified;
try
{
LeaderBoardContext.Current.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
}
return StatusCode(HttpStatusCode.NoContent);
}
}
Its gone through a few iterations by this point, at one point it was initialising its own DB context at the top of the file but that was mysteriously null during the post. So now i'm using the style that we use in other projects which looks like this:
public static LeaderBoardContext Current
{
get
{
try
{
//added because 'HttpContext.Current.Items["_EntityContext"] ' was mysteriously comming back null....
if (HttpContext.Current.Items["_EntityContext"] == null)
{
HttpContext.Current.Items["_EntityContext"] = new LeaderBoardContext();
}
var obj = HttpContext.Current?.Items["_EntityContext"] as LeaderBoardContext;
return obj;
}
catch (Exception) //should only get here if using background task
{
return null;
}
}
}
So the first weirdness is in the post the context insisted on being null, but forcing it not to be null through the convoluted method above hasn't improved the situation much. Notice the first EF call that I have now put in to basically be the same as the GET:
var model = LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == player.PlayerId);
I have called the GET in both styles (with -1, with valid ID) and it works fine, but the POST has so far led to this error:
Which I would usually associate with a badly initialised EF project, but the GET works! it does exactly what it should do. I have even tried posting to a EF scafold controller with a different model and had the same problem!
The major difference between the two (apart from GET/POST) is the way I call them, this is how I use the GET:
var playerId = -1;
var activeUser:Player;
function initPlayerOnGameStart() {
if (host === undefined) {
host = 'http://localhost:52316';
}
if (playerId === undefined) {
playerId = -1;
}
var uri = host + '/api/Player/' + playerId;
jQuery.getJSON(uri).done(data => {
activeUser = data;
playerId = activeUser.PlayerId;
});
}
In a pure Typescript Json call. To do the POST I am experimenting with AJAX.Helper:
@model LearningCancerAPICalls.Models.Player
<a id="contact-us">Share Score!</a>
<div id="contact-form" class="hidden" title="Online Request Form">
@using (Ajax.BeginForm("", "", null, new AjaxOptions
{
HttpMethod = "POST", Url = "/api/Player",
OnSuccess ="OnSuccess",
OnFailure ="OnFailure"
}, new { id = "formId", name = "frmStandingAdd" }))
{
@Html.LabelFor(m => m.PlayerName);
@Html.TextBoxFor(m => m.PlayerName);
@Html.LabelFor(m => m.Email);
@Html.TextBoxFor(m => m.Email);
@Html.HiddenFor(m => m.PlayerId);
@Html.Hidden( "PlayerId");
<input type="submit" name="submit" value="Ok" />
}
</div>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script>
function OnSuccess() {
alert('Success');
}
function OnFailure(ajaxContext) {
alert('Failure');
}
</script>
Where I set PlayerID from the typescript. This successfully calls the post but crashes on the first use of EF. The other peculiar thing is that if I put a debug on the post. The model doesnt seem correct, as in, when I hover over it, it shows itself as a Player model, there has been no casting error, but it does not let me expand its properties. If I use variables or the imediate window to inspect variables then they are all fine. But I thought it was worth mentioning.
I am going to try a pure ajax call later to see if it resolves it, but I don't understand why the Ajax.helper would be at fault here, it technically does its job and the error is not related to the model that I can see.
UPDATE 1 So I tried the pure ajax call:
Html:
Name: <input type="text" name="fname" id="userName"><br />
<button onclick="postJustPlayer()"> Ok </button>
Typescript
function postJustPlayer() {
let level = jQuery("#chooseGridLevel").val();
let name = jQuery("#userName").val();
let uri = host + '/api/Player';
let player: Player = <Player>{};
player.Email = "Testing";
player.PlayerName = name;
jQuery.post(uri, player);
}
And this WORKS!?? I have no idea why the pure jQuery works, surely as far as EF is concerned it does the exact same thing? why would an AJAX.helper post be any different...
Solved it! This was a true puzzle, only solved when I delved into the network data (tools ftw).
For other newbies to web stuff I will explain how I found the route of this problem. In Chrome Dev Tools there is a Network tab that will show your web requests & responses. So by opening it after clicking my OK Button I can see this for my pure AJAX call:
I could then compare this to when I clicked "Submit" on my ajax form:
I Copy and paste these both into KDiff3, which highlighted one VERY important difference the local host address!
You will notice in the pure ajax request I specified the host, this is because as I mentioned, my web api project and my website project are separate, therefore they are on separate hosts!
So, in reality, the AJAX helper call should never have worked, but as it happens the day before I decided I needed a model from my API project in my website project and at the time thought "I probably shouldn't include my API project as a reference in my main website, but just for now....". So this lead to the API call with the wrong host being valid! With of course the fundamental difference that EF was not set up on THAT host.
So poor old ajax helper got plenty of my cursing for an error that only a special kind of idiot set up could lead to. Changing ajax helper to use the full path:
@model LearningCancerAPICalls.Models.Player
<a id="contact-us">Share Score!</a>
<div id="contact-form" class="hidden" title="Online Request Form">
@using (Ajax.BeginForm("", "", null, new AjaxOptions
{
HttpMethod = "POST", Url = "http://localhost:52316/api/Player",
OnSuccess ="OnSuccess",
OnFailure ="OnFailure"
}, new { id = "formId", name = "frmStandingAdd" }))
{
@Html.LabelFor(m => m.PlayerName);
@Html.TextBoxFor(m => m.PlayerName);
@Html.LabelFor(m => m.Email);
@Html.TextBoxFor(m => m.Email);
@Html.HiddenFor(m => m.PlayerId);
@Html.Hidden( "PlayerId");
<input type="submit" name="submit" value="Ok" />
}
</div>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script>
function OnSuccess() {
alert('Success');
}
function OnFailure(ajaxContext) {
alert('Failure');
}
</script>
Solved the problem! Thank you for anyone who scratched their head over this one, hopefully, this breakdown of a weird bug will be useful to someone.