0

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.

user2316154
  • 302
  • 2
  • 13
  • Put visual studio breakpoint in your action method which saves the data. Inspect the variable value and see they match with your expected values. – Shyju Aug 26 '16 at 16:50
  • @Shyju Thank you for the suggestion. See my edit above with more information. – user2316154 Aug 26 '16 at 16:53
  • So what is the real problem now ? ModelState.IsValid will return `false` when the model validation fails. That is the expected behavior. – Shyju Aug 26 '16 at 16:54
  • Okay. How can I tell what is wrong with the model, though? I'm used to getting stack traces or exceptions that I can read to troubleshoot, hence the newbie questions! – user2316154 Aug 26 '16 at 16:55
  • 1
    If you want to know in the code, take a look at [How to figure out which key of ModelState has error](http://stackoverflow.com/questions/15296069/how-to-figure-out-which-key-of-modelstate-has-error). Or If you have validation helper methods in your UI, It should show the error messages in the UI – Shyju Aug 26 '16 at 16:57
  • The only validation helper method I have is that a couple of the fields are required. Those trigger when they should. I'm taking a look at that link now, thank you. I've inspected the page source and it looks like my model is being populated OK. I have placed a breakpoint in the code on the ko.applyBindings method, and that breakpoint never triggered, which is highly suspicious to me - but the edit form loads in the values, so ... not sure what is happening! I don't know enough about this just yet. – user2316154 Aug 26 '16 at 16:59
  • Thank you, that was a helpful suggestion! I determined that the ViewData.ModelState considers the model fields to be empty, even though there are values in the text box ( the ErrorMessage reads: "The First Name field is required" which is the validation error. So I think this points back to knockout not triggering properly, or the binding not working properly. I don't know how to investigate that, though. I've stared at the code and checked spelling, case, etc over and over and didn't see any mistakes there. – user2316154 Aug 26 '16 at 17:06
  • 1
    @user2316154 it sounds like your model isn't binding, which is usually down to form field `name` attributes not matching model properties. Try inspecting the post data in the browser. Time to look up how model binding works in MVC. – Ant P Aug 26 '16 at 17:07
  • @Ant P - I agree, it seems like the model isn't binding. This is supposed to happen via Knockout ( which is what the book is all about ). I have examined my name attributes at length since that is the most obvious breaking point, but it seems to match. I wonder if somehow Knockout isn't available to the solution? I'll check my nuget packages and includes. I'll also try to inspect the post data ( again, being brand-new at web development, I don't know how to do it. But at least I know what to Google for now! ) – user2316154 Aug 26 '16 at 17:14
  • There are some subtleties and limitations to the way MVC binds properties - if you have specific examples it would be worth putting the post data and model class in your question – Ant P Aug 26 '16 at 17:30
  • Also bear in mind that knockout and MVC are two distinct technologies - it's up to you to make sure they are speaking the same language – Ant P Aug 26 '16 at 17:32
  • @Ant P - thank you or your responses! Yes, this is definitely a wobbly learning curve. I guess my main frustration is that it feels like the code is just a black hole: "Oh hey, 500 Server Error! Good luck finding out where that comes from!" I'm used to an IDE environment where finding the source of the problem ( syntax error, null reference, whatever it might be ) isn't this hard. I took a look at the post data but of course, the tutorial is being fancy and it is encrypted ( with an anti-forgery token, I guess? ) so I can't make head or tails of it. I will post my model class. – user2316154 Aug 26 '16 at 17:38
  • Im struggling to understand your frustration in debugging to be honest - once execution hits your MVC app you should be able to debug like any other code. If it's not getting that far then it points to a problem either at the webserver or routing level – Ant P Aug 26 '16 at 17:43
  • More generally I would probably recommend picking up MVC in isolation first and understanding that in depth before moving on to client side frameworks if you are new to web programming – Ant P Aug 26 '16 at 17:45
  • This is all just on a local laptop, directly out of Visual Studio. No fancy things like webservers or routing involved. If I set a breakpoint in the form, it is hit as expected, but it seems as if some exception or something throws and the important part of the code ( where knockout is supposed to apply bindings ) doesn't happen. There's nothing I can see that explains why that happens, though! I can post step-by-step screen caps of how Visual Studio behaves to illustrate. – user2316154 Aug 26 '16 at 17:47
  • You're probably right, I do have a nice fat apress MVC book to go through. I chose to tackle this tutorial because it seemed rather superficial, and I thought having some idea of different ways to do something would be helpful. But I'm probably approaching the learning curve upside down. I do have some experience with php, plain old html and css, plain old javascript and ajax and so on, from some years back, so I thought I could leap more easily into a book that should be a step by step tutorial. Well, we can't be right all the time :) – user2316154 Aug 26 '16 at 17:50
  • 1
    @user2316154, I just could provide you suggestions for troubleshooting this issue like using AllErrors property. http://stackoverflow.com/questions/2996139/500-internal-server-error-in-asp-net-mvc. Please also enable the Exception settings in your debugging, it would throw the unhandle Exception. – Jack Zhai Aug 28 '16 at 06:20

1 Answers1

0

I wanted to follow up on this question.

First, I want to thank the commenters who suggested I start with vanilla MVC. That was solid advice. I've spent many evenings on that these past months and I'm happy to say that that was a much better direction to take.

After I gained some solid working knowledge of MVC and built some small projects to learn it, I returned to this problem and was able to resolve it.

For anybody finding this, here is where the error was.

In the AuthorFormViewModel, my code uses Knockout to bind model data to the form.

The error is right at the top of the viewmodel, where the variables are set up as observable Knockout bindings:

self.author = {
    id: author.id,
    firstName: ko.observable(),
    lastName: ko.observable(),
    biography: ko.observable(),
};

Empty variables! Not bound to the author model being passed in from the MVC model binding.

It should be:

self.author = {
    id: author.id,
    firstName: ko.observable(author.firstName),
    lastName: ko.observable(author.lastName),
    biography: ko.observable(author.biography),
};

The irony is that the error wasn't in the MVC portion at all, but it did cause me to learn ASP.NET and MVC, which I'm grateful for.

I don't think I've ever gained this much logic solving this trivial of an error.

user2316154
  • 302
  • 2
  • 13