skylake
skylake

Reputation: 417

MVC - DropDownList/PartialView issue - "Viewdata has the key int.. ..must be IUnumerable..."

The ViewData item that has the key 'SelectedRating' is of type 'System.Int32' but must be of type 'IEnumerable'.

This is the error I get when I'm trying to add a DropDownList via partial view. I tried to follow this solution but I somehow failed to do so due to my low experience in coding.

This is how my code look for the moment:

 [HttpGet]
    public ActionResult Rating()
    {
        RateReviewVM model = new RateReviewVM()
        {
            ReviewId = id,
            RatingList = new SelectList(Enumerable.Range(1, 10))
        };
        return View(model);
    }

     [HttpPost]
     public ActionResult Rating(RateReviewVM model) {

         if (!ModelState.IsValid)
         {                
            model.RatingList = new SelectList(Enumerable.Range(1, 10));
            return View(model);
         }

       //...

View for _RatingPartial

@model xxxx.ViewModel.RateReviewVM

<h6>Rate the review</h6>

using (Html.BeginForm()) {

<div class="form-group">


    @Html.LabelFor(m => m.SelectedRating)
    @Html.DropDownListFor(m => m.SelectedRating, Model.RatingList, "-Please select-", new { @class = "form-control" })
    @Html.ValidationMessageFor(m => m.SelectedRating)

</div>

<button type="submit" class="btn btn-default submit">Rate</button>
    }
}

This is how I try to display _RatingPartial view

@{
    var newRating = new xxxx.ViewModel.RateReviewVM { ReviewId = Model.Id };

    Html.RenderPartial("_RatingPartial", newRating);

}

Model

    public class RateReviewVM {

            public System.Guid Id { get; set; }

            public System.Guid UserId { get; set; }

            public System.Guid ReviewId { get; set; }

            public bool HasLiked { get; set; }

            public Nullable<int> Rating { get; set; }

            public int SelectedRating { get; set; }

            public IEnumerable<SelectListItem> RatingList { get; set; }
     }

EDIT: ---

Details View(Main)

    @model xxxx.Review

    @{
        ViewBag.Title = "Details";
    }

    @*@{
        //var newRating = new xxxx.ViewModel.RateReviewVM { ReviewId = Model.Id };

        @Html.RenderPartial("_RatingPartial", newRating)

    }*@

    <h2>Details</h2>

    <div>

        <h4>Review</h4>
        <hr/>
        <dl class="dl-horizontal">
            <dt>
                @Html.DisplayNameFor(model => model.Title)
            </dt>

            <dd>
                @Html.DisplayFor(model => model.Title)
            </dd>

            <dt>
                @Html.DisplayNameFor(model => model.Description)
            </dt>

            <dd>
                @Html.DisplayFor(model => model.Description)
            </dd>
            @* and so on..  *@

        </dl>
    </div>
    <p>
        @Html.ActionLink("Back to List", "Index")
    </p>

Controller for Details

            public ActionResult Details(Guid? id) {

            Review review = db.Reviews.Find(id);

            return View(review);
        }

Model Review for Details

        public System.Guid Id { get; set; }

        public System.Guid CreatorUserId { get; set; }

        [Required]
        public string Title { get; set; }

        [Required]
        public string Description { get; set; }

        public System.DateTime CreatedDate { get; set; }

        //etc - some irrelevant property        

        public virtual ICollection<RateReview> Users { get; set; }

Upvotes: 1

Views: 93

Answers (2)

user3559349
user3559349

Reputation:

The reason for the error is that the value of RatingList is null (refer The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable' for a detailed explanation).

In you case, your using the RenderPartial() method to pass a model to the view, and you do not set the value of property RatingList (RenderPartial does not call your public ActionResult Rating() method.

You could solve the error by using

@{
    var newRating = new xxxx.ViewModel.RateReviewVM
    {
        ReviewId = Model.Id,
        RatingList = new SelectList(Enumerable.Range(1, 10))
    };
    Html.RenderPartial("_RatingPartial", newRating);
}

so that the property is populated, but since you already have a GET method, then it would be better to use the RenderAction() method which does call the server method

@{ Html.RenderAction("Rating", yourControllerName, new { id = Model.Id }); }

Note I'm assuming your method is actually public ActionResult Rating(Guid id) since the current code would throw an exception since id is not declared.

However, that will not solve all your problems since the POST method returns the review is ModelState is invalid (as it should), but its a different view from your current one. It will also throw an exception because your not repopulating the RatingList property.

The correct approach to solve this is to have a view model for Review which contains another view model for comment.

public class ReviewVM
{
    ... properties of review including property for collection of existing commnets
    public CommentVM Comment { get; set; } // for generating a new comment form
}

and in the Review.cshtml view, add the form for generating new comment, and post back to to a method which has ReviewVM as the parameter. Then if ModelState is invalid, repopulate the properties of ReviewVM based on its ID, and repopulate the RatingList property of its Comment property. Alternatively, if your wanting to allow the user to add multiple comments for a Review, consider a dialog form for adding a new comment and use ajax to post the form.

Upvotes: 1

iamnapo
iamnapo

Reputation: 52

Use ViewBag for your dropdrownlist and it will work for partialView too. and then you don't need ViewModels. your model classes are fine to work with.

First add CreateRate action to Reviews Controller

 public ActionResult CreateRate(RateReview rate)
    {
        if (ModelState.IsValid)
        {
            rate.Id = Guid.NewGuid();
            db.RateReviews.Add(rate);
            db.SaveChanges();
            return RedirectToAction("Details", "Reviews", new { id = rate.ReviewId });
        }

        return View();
    }

Modify Details action to create a viewbag data as below

public ActionResult Details(Guid? id)
{

    Review review = db.Reviews.Find(id);
    ViewBag.RatingList = new SelectList(Enumerable.Range(1, 10));

    return View(review);
}

Modify your PartialView as below to target CreateRate action, add a HiddenFor Helper for ReviewId and modify dropdownlistfor to read from ViewBag. I use RateReview Model instead of your ViewModel

@model WebApp.Models.RateReview


@using (Html.BeginForm("CreateRate", "Reviews")) 
{
@Html.AntiForgeryToken()

<div class="form-horizontal">
    <h4>RateReview</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

    @Html.HiddenFor(model => model.ReviewId)

    <div class="form-group">
        @Html.LabelFor(model => model.SelectedRating, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.SelectedRating, (ViewBag.RatingList as SelectList), new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.SelectedRating, "", 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>
}

at end your Details View is just fine :

@model WebApp.Models.Review

@{
ViewBag.Title = "Details";
}

@{
    var newRating = new WebApp.Models.RateReview { ReviewId = Model.Id };

    Html.RenderPartial("_RatingPartial", newRating);

}


<h2>Details</h2>

<div>
<h4>Review</h4>
<hr />
...

Upvotes: 1

Related Questions