I am working through the book "ASP.NET MVC 5 with Bootstrap and Knockout.js" by Jamie Munro. I am an experienced programmer but a rank amateur with MVC and web development. This is an attempt to learn how to do it.
As I worked through the book, I made a mistake somewhere in my application, so now I can't save submits or edits to my database. And I don't know how to figure out what the error is.
The example code only gives a generic error. It would be helpful to know how to expose the inner exception or error message. The snippet of code that generates this generic error banner is this:
self.errorSave = function () {
$('.body-content').prepend('<div class="alert alert-danger"><strong>Error!</strong> There was an error saving the author.</div>');
}
I have two questions, one specific and one generic.
Specifically: How can I get a more detailed error message in my web browser?
Generic: How do one debug web applications? I know how to debug in Visual Studio when it isn't a web app, but I can't get this error trapped. Breakpoints in my controller or model or even my forms do not give me any useful information. I also tried the debugger in Chrome and Internet Explorer but haven't found how to get anything more useful than "Internal Server error: 500" which doesn't tell me all that much.
The code example for the book can be found here: https://github.com/oreillymedia/ASP_NET-MVC-5-with-Bootstrap-and-Knockout_js
That example works, as far as I know. I didn't run it since I don't have a local SQL Server, but if I'm really desperate I'll have to set it up just to run the two sets of code side by side. Comparing my code to it didn't point out my error. Of course, that is a fully worked example and my code is only up to date about 1/3 through the book.
In my Action Method, the first check is ModelState.IsValid, which returns false. Hence, no save.
public ActionResult Create([Bind(Include = "Id,FirstName,LastName,Biography")] Author author)
{
if (ModelState.IsValid)
{
db.Authors.Add(author);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(author);
}
It appears that the model binding isn't working properly.
In the form, here is how the First Name field is set up:
<div class="form-group">
@Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" }, data_bind = "value: author.firstName" })
@Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })
</div>
</div>
In the Model, Author.cs:
public class Author
{
[JsonProperty(PropertyName ="id")]
public int Id { get; set; }
[JsonProperty(PropertyName = "firstName")]
[Display(Name = "First Name")]
[Required]
public string FirstName { get; set; }
[JsonProperty(PropertyName = "lastName")]
[Display(Name = "Last Name")]
[Required]
public string LastName { get; set; }
[JsonProperty(PropertyName = "biography")]
public string Biography { get; set; }
[JsonProperty(PropertyName = "books")]
public virtual ICollection<Book> Books { get; set; }
}
AuthorFormViewModel.js looks like this:
function AuthorFormViewModel(author) {
var self = this;
//variables to track state during saving
self.saveCompleted = ko.observable(false);
self.sending = ko.observable(false);
self.isCreating = author.id == 0;
//variable to track changes to model
self.author = {
id: author.id,
firstName: ko.observable(),
lastName: ko.observable(),
biography: ko.observable(),
};
self.validateAndSave = function (form) {
if (!$(form).valid())
return false;
self.sending(true);
//include anti forgery token
self.author.__RequestVerificationToken = form[0].value;
$.ajax({
url: (self.isCreating) ? 'Create' : 'Edit',
type: 'post',
contentType: 'application/x-www-form-urlencoded',
data: ko.toJS(self.author)
})
.success(self.successfulSave)
.error(self.errorSave)
.complete(function () { self.sending(false) });
};
self.successfulSave = function () {
self.saveCompleted(true);
$('.body-content').prepend('<div class="alert alert-success"><strong>Success!</strong> The author has been saved.</div>');
setTimeout(function () {
if ( self.isCreating )
location.href = './'
else
location.href = '../'
}, 1000);
}
self.errorSave = function () {
$('.body-content').prepend('<div class="alert alert-danger"><strong>Error!</strong> There was an error saving the author.</div>');
}
}
Finally, the script section at the bottom of the Create/Edit form tries to tie all that together in this way:
@section Scripts {
@Scripts.Render("~/bundles/jqueryval", "/Scripts/ViewModels/AuthorFormViewModel.js")
<script>
var viewModel = new AuthorFormViewModel(@Html.HtmlConvertToJson(Model));
ko.applyBindings(viewModel);
</script>
}
Also, the string returned from @Html.HtmlConvertToJson(Model) appears well-formatted. This is what a Create attempt returns: + str { "id": 0, "firstName": null, "lastName": null, "biography": null, "books": null } System.Web.HtmlString
When debugging, it appears that the ko.ApplyBindings(viewModel); statement is skipped completely. Maybe an error is thrown during viewModel construction ( the @Html.HtmlConvertToJson(Model) statement appears successful ) but it is frustrating that that error is not available to me, and I can't figure out how to get an exception / breakpoint / call stack / something so I can investigate.