1
votes

I was trying to save some new posts to my database but i got an error repeatedly.. I have a class "Posts.cs" below

    public partial class Post
    {
        public Post()
        {
            this.Replies = new HashSet<Reply>();
        }

        public int PostId { get; set; }
        public string PostTitle { get; set; }
        public string PostContent { get; set; }
        public int AuthorId { get; set; }
        public int CategoryId { get; set; }
        public System.DateTime DateCreated { get; set; }

        public virtual Category Category { get; set; }
        public virtual UserProfile UserProfile { get; set; }
        public virtual ICollection<Reply> Replies { get; set; }
    }
}

and a ViewModel which i used to render a view for users to add posts

 public class AddPostsVM
    {
        [Display(Name = "CategoryId")]
        public int CategoryId { get; set; }

        [Required()]
        [Display(Name="Title")]
        public string PostTitle { get; set; }

        [Required()]
        [Display(Name="Content")]
        public string PostContent { get; set; }

        [Required()]
        [Display(Name="Select a Category")]
        public string CategoryName { get; set; }

        public List<SelectListItem>CategoriesList { get; set; }
    }

I used the view model to render a view with a dropdown list in the view to fetch the list of categories in the database so that when users are adding a post they can select the category they want the post to belong to

[HttpGet]
    public ActionResult CreatePost()
    {
        AddPostsVM model = new AddPostsVM();
        var categories = (from c in db.Categories
                         select new
                         {
                             CategoryName = c.CategoryName,
                             CategoryId = c.CategoryId
                         }).ToList();

        model.CategoriesList = categories.Select(c => new SelectListItem
        {
            Text = c.CategoryName,
            Value = c.CategoryId.ToString()

        }).ToList();


        return View(model);
    }

In my view CreatePost.cshtml I have this

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

    <div class="form-horizontal">
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.PostTitle, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.PostTitle, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.PostTitle, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.PostContent, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @*@Html.TextAreaFor(model => model.PostContent, new { htmlAttributes = new { @class = "form-control" } })*@
                <textarea class="form-control" name="PostContent" id="PostContent" rows="3"></textarea>
                @Html.ValidationMessageFor(model => model.PostContent, "", new { @class = "text-danger" })
            </div>
        </div>
                <div class="form-group">
            @Html.LabelFor(model => model.CategoryId, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DropDownListFor(model => model.CategoryId, Model.CategoriesList, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.CategoryId, "", new { @class = "text-danger" })
            </div>
        </div>


        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

Whenever i try to add a new post to the database by using the Post action below

[HttpPost]
public ActionResult CreatePost(AddPostsVM model)
{
    try
    {
        if (ModelState.IsValid)
        {
            var newPost = db.Posts.Create();
            newPost.PostTitle = model.PostTitle;
            newPost.PostContent = model.PostContent;
            FormsIdentity identity = (FormsIdentity)User.Identity;
            int nUserID = Int32.Parse(identity.Ticket.UserData);
            newPost.AuthorId = nUserID;
            newPost.CategoryId = model.CategoryId;
            newPost.DateCreated = DateTime.Now;
            db.Posts.Add(newPost);
            db.SaveChanges();
            return RedirectToAction("Index", "Posts");
        }
        else
        {
            ModelState.AddModelError("", "Invalid Model");

        }

    }
    catch (DbEntityValidationException e)
    {
        foreach (var eve in e.EntityValidationErrors)
        {
            Debug.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
                 eve.Entry.Entity.GetType().Name, eve.Entry.State);
            foreach (var ve in eve.ValidationErrors)
            {
                Debug.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
                    ve.PropertyName, ve.ErrorMessage);
            }
        }
        throw;


    }
    return View(model);
}

I could see that the ModelState.IsValid was returning false and I got the error saying

The ViewData item that has the key 'CategoryId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.

Please how do i solve this?

1
Initialize the model.CategoriesList in the post action also. Then you can see the validation errors. The error you are getting occurs when model.CategoriesList is null and in that case framework tries to read the list from ViewData and that value is not expected type as specified in that error message.Mat J
@Matthew i did that but i didnt get any error except the on i set my self that if the modelstat is invalid i should get "Invalid Model". Apart from that no erroribnhamza
@ibnhamza, When you return the view, model.CategoriesList is null so you get that error. If you return the view, you must reassign the SelectListuser3559349
@StephenMuecke can You help me with rewrite the code with the correct one. Will appreciate it. Been dabbling around since.ibnhamza
@ibnhamza, In the POST method, before the line return View(model);, just copy the code you have in the GET method for assigning model.CategoriesList i.e. var categories = ....; model.CategoriesList = .....;user3559349

1 Answers

2
votes

The reason that ModelState always returns false is that property CategoryName has the [Required] attribute and you do not render a control and post back a value so its always null and therefore invalid. However CategoryName is related to the selected Category associated with you dropdown list so you should remove this property and just rely on the CategoryId which is bound to the dropdownlist.

The reason for the error is that when you return the view (which was always happening becuase of the issue above), you are not reassigning the value of CategoriesList so its null and @Html.DropDownListFor(model => model.CategoryId, Model.CategoriesList, ..) throws the exception.

To avoid repetition , you can re-factor common code into a private method (note this assumes you change public List<SelectListItem> CategoriesList { get; set; } to public SelectList CategoriesList { get; set; })

private void ConfigureEditModel(AddPostsVM model)
{
  model.CategoriesList = new SelectList(db.Categories, "CategoryID", "CategoryName");
  // any other common stuff
}

which you can now call in the GET method and the POST method

[HttpGet]
public ActionResult CreatePost()
{
    AddPostsVM model = new AddPostsVM();
    ConfigureEditModel(model);
    return View(model);
}

[HttpPost]
public ActionResult CreatePost(AddPostsVM model)
{
  if (!ModelState.IsValid)
  {
    ConfigureEditModel(model); // repopulate select list
    return View(model); // return the view to correct errors
  }
  // Save and redirect
}