0

So I have this problem when trying to save an item to the database in my asp.net mvc4 web app. I have three classes listed below

public class Posts
{
    [Key]
    public int PostID { get; set; }

    [Required(ErrorMessage="A Title is required for your Post")]
    [Display(Name="Title")]
    public string PostTitle { get; set; }


    [Required(ErrorMessage="This Field is Required")]
    [Display(Name = "Post")]
    public string PostContent { get; set; }

    [Required()]
    [DataType(DataType.DateTime)]
    public DateTime PostDate { get; set; }
    //public int AuthorID { get; set; }
    //public int CommentID { get; set; }

    [NotMapped]
    public virtual List<Comments> Comment { get; set; }
    public virtual Users user { get; set; }

}

and this class has a many to one relationship with the users class below

public class Users
{

    public Users()
    {
    }

    [Key]
    public int UserID { get; set; }

    [Required()]
    public string Firstname { get; set; }

    [Required()]
    public string Lastname { get; set; }

    [Required()]
    [DataType(DataType.Date, ErrorMessage="Enter a Valid Date")]
    public DateTime DOB { get; set; }

    //[Required()]
    //public string Photo { get; set; }

    [Required()]
    public string Sex { get; set; }

    [Required()]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    public DateTime RegDate { get; set; }

    [Required()]
    [Column("Username")]
    //[Remote("doesUserNameExist", "Account", HttpMethod = "POST", ErrorMessage = "User name has already been taken. Please enter a different User name.")]
    public string Username { get; set; }

    [Required()]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    public string PasswordSalt { get; set; }

    [System.ComponentModel.DataAnnotations.Compare("Password", ErrorMessage="Passwords do not match")]
    [DataType(DataType.Password)]
    //[NotMapped]
    //public string ConfirmPassword { get; set; }


    public virtual List<Posts> Post { get; set; }
    public virtual List<Comments> Comment { get; set; }

}

The database table for the Posts.cs class has a field called user_UserID which i assume is to store the id of the user that creates a post. I'm trying to save the posts to the database using the below code

@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)

<fieldset>
    <legend>Posts</legend>

    <div class="form-group">
        @Html.LabelFor(model => model.PostTitle)
        @Html.TextBoxFor(model => model.PostTitle, new {@class = "form-control"})
        @Html.ValidationMessageFor(model => model.PostTitle)
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.PostContent)
        @Html.TextAreaFor(model => model.PostContent, new {@class = "form-control"})
        @Html.ValidationMessageFor(model => model.PostContent)
    </div>
        <input type="hidden" name="user.UserID" value="@Session["LoggedUserID"]" />

    <div>
        <input type="submit" value="Submit" />
    </div>
</fieldset>

}

As you can see, the user ID i'm saving to the database table is gotten from the user ID stored in the Session["LoggedUserID"] variable. The controller that handles the saving is below

public class PostsController : Controller
{
    //
    // GET: /Posts/
    BlogContext db = new BlogContext();
    public ActionResult Index()
    {
        return View();
    }

    [HttpGet]
    public ActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Create(Posts Post)
    {
        var errors = ModelState.Values.SelectMany(v => v.Errors);
        try
        {
            if (ModelState.IsValid)
            {
                var newPost = db.Post.Create();
                newPost.PostTitle = Post.PostTitle;
                newPost.PostContent = Post.PostContent;
                newPost.PostDate = DateTime.Now;
                newPost.user.UserID = Post.user.UserID;
                db.Post.Add(newPost);
                db.SaveChanges();
                return RedirectToAction("Index", "Posts");
            }
            else
            {
                ModelState.AddModelError("", "Something is wrong with the model");
            }
        }

        catch (NullReferenceException ex)
        {
            Debug.WriteLine("Processor Usage" + ex.Message);
        }

        return View();
    }

}

I attached a breakpoint to the Modelstate.IsValid line and then debugged, but i noticed the ModelState is always evaluating to false and the ModelState.AddModelError is showing that is there is something wrong with validating the model. Ive tried all possible tweakings all to no avail. I need help. How can i save the Posts to the database table without this problem. Please what am i doing wrong?

ibnhamza
  • 861
  • 1
  • 15
  • 29
  • Inspect the value of `ModelState` to see which is the invalid property. Note you should not be rendering a hidden input for the user in the view. Just get this in the controller method when you post back. In any case you should be using a view model containing only the 2 properties you want to edit. –  Nov 25 '14 at 23:59
  • @StephenMuecke when i replaced the hidden field in the view with a call from the controller, the user.UserID was returning null. And How do i use a view model to contain only the two properties i want to edit? Please help! – ibnhamza Nov 26 '14 at 06:48
  • Why do you need the value of `user.UserID` in the view? The problem I think is that your pass a value for `user.UserID` which forces the `DefaultModelBinder` to initialize the property `user` which would then contain `ModelState` errors because of the validation attributes you have applied to `User`. But I cant be sure since you you don't seem to want to inspect `ModelState` to see what the errors are! –  Nov 26 '14 at 06:58
  • I needed to store the ID of the author of the post in the Posts table and that field in the post table is named "user_UserID". How else do i get the UserID stored in the session variable to store it in the table as the author of the post. Not that I dont want to inspect the model state but i think i've done that by setting a break point and debugging which caused an error like this "The parameter conversion from type System.String to type Bloggosphere.Models.Users failed because no type converter can convert between these types". Or how else do i get the values causing the modelstate to false – ibnhamza Nov 26 '14 at 10:02
  • Exactly. its the author of the post so it does not need to be rendered in the view. You just get it from Session in the POST method (as opposed to getting it in the GET method, sending it to the client, doing nothing with, and sending it back). This is no different from what you are doing with the `PostDate` property. If you want I can post and answer showing a view model approach so there is no rislk of model state errors –  Nov 26 '14 at 10:10
  • @StephenMuecke I will appreciate if you can post an answer showing a view model approach so there is no risk of model state errors. Thanks. Anticipating your answer. – ibnhamza Nov 26 '14 at 11:17

1 Answers1

0

I suspect ModelState is invalid because you are posting a value for user.UserId (the hidden input) which is initializing the property User which is invalid because of the validation attributes applied to other properties of User. It would be better to create a view model for creating new Posts that contain only the properties you need to display/edit. It is not necessary to include a property for User (the author of the post) since this can be set in the controller when you save the data (similar to what you are doing for the PostDate property

View model

public class PostVM
{
  [Required(ErrorMessage="A Title is required for your Post")]
  [Display(Name="Title")]
  public string Title { get; set; }
  [Required(ErrorMessage="This Field is Required")]
  [Display(Name = "Post")]
  public string Content { get; set; }
}

Controller

[HttpGet]
public ActionResult Create()
{
  PostVM model = new PostVM();
  return View(model);
}

[HttpPost]
public ActionResult Create(PostVM model)
{
  if(!ModelState.IsValid)
  {
    return View(model);
  }
  // Initialize new Posts
  // Map properties from the view model, set the user and date
  // Save and redirect
}

View

@model PostVM
@using (Html.BeginForm()) {
  ....
  <div class="form-group">
    @Html.LabelFor(model => model.Title)
    @Html.TextBoxFor(model => model.Title, new {@class = "form-control"})
    @Html.ValidationMessageFor(model => model.Title)
  </div>
  <div class="form-group">
    @Html.LabelFor(model => model.Content)
    @Html.TextAreaFor(model => model.Content, new {@class = "form-control"})
    @Html.ValidationMessageFor(model => model.Content)
  </div>
  <input type="submit" value="Submit" />
}
  • Thanks a lot for your help so far. Youve opened my eyes to some best practices in mvc. But this is my first time working with a view model and i see the reason why it should be done with view model. But what is now the function of my domain model? i mean the Post.cs class. Also will the userID i stored in the session be available in the controller so i can store it into the database as the author of the posts?. One more thing, what do you mean by "map properties from ViewModel?".Sorry if i sound dumb!! but i will really appreciate you if you can make me understand this stuff. – ibnhamza Nov 27 '14 at 10:38
  • You should always use a view model for editing data. [What is a view model in MVC](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc). The view model is specific to MVC and the view. Your domain model is still important and maps to your database and could be used by in say a WinForms or WPF app as well. Yes session is available in the controller (and that's where you should access it - not from the view). But you should consider adding this to your form authentication cookie. –  Nov 27 '14 at 10:46
  • By mapping, I mean copying the data from the view model to the data model `dataModel.Property = viewModel.Property` (just as you did in your question with `newPost.PostTitle = Post.PostTitle;`). You should consider using tools such as [automapper](https://github.com/AutoMapper/AutoMapper) to make this easier. –  Nov 27 '14 at 10:48
  • Thanks a bunch @stephenmuecke. You've been so wonderfully helpful and patient. – ibnhamza Nov 27 '14 at 12:14
  • Thankyou's are discouraged. Accepting the answer is all that's necessary (and you should do the same for your last question) –  Nov 27 '14 at 12:19