Search code examples
c#asp.netpostasp.net-mvc-5razor-2

Populating nested list object of model based on checkbox selections in ASP.NET MVC


I am relatively new to ASP.NET MVC and I find myself a bit on a stick wicket or rather, in a soup kinda situation. This is a length description so please bear with me. And I'm sorry if this seems a bit long and boring :). So, here's my problem :

This is my model.

public class SocialAccount
{
    public int SocialAccountID { get; set; }

    public int UserID { get; set; }

    public int SocialSiteID { get; set; }

    public string SocialAccountUsername { get; set; }

    public string SocialAccountUserID { get; set; }

    public string OAuthToken { get; set; }

    public string Captcha { get; set; }

    public string UserCaptchaValue { get; set; }
}

public class SocialSitePost
{
    public int PostID { get; set; }

    public string PostContent { get; set; }

    public string PostAttachments { get; set; }

    public List<SocialSite> SelectedSocialSites { get; set; }

    public List<SocialAccount> SocialAccount { get; set; }

}

public class SocialSite : Audit
{
   public int SocialSiteID { get; set; }
   public string SocialSiteName { get; set; }     
}

When a user is successfully authenticated, I store some user details in session and redirect to the home page. This is the code for that:

//action method in some Controller
public Login()
{
     //Authenticate user

     //Get all social account related information
     SomeRepository someRepository = new SomeRepository();
     List<SocialAccount> socialAccountDetails = someRepository.SomeMethod(user.UserID);

     //Add social account related information to session
     HttpHelper.StoreInSession("UserDetails", socialAccountDetails);

     //redirect to HomePage
     return RedirectToAction("HomePage", "Account");
}

In the HomePage action method, I retrieve the user object from session and check the registered social accounts. I also create a list object of SocialSite to maintain a list of all registered social sites. I then pack it in ViewData and send it to the view. This is the code for that:

//action method in some other controller
public ActionResult HomePage()
{
     List<SocialSite> allSocialSites = null;
     List<SocialAccount> userSocialAccountDetails = HttpHelper.RetrieveFromSession("UserSocialSiteDetails") as List<SocialAccount>;

     if (userSocialAccountDetails != null && userSocialAccountDetails.Count != 0)
     {
          allSocialSites = new List<SocialSite>();
          bool hasFacebookAccount = userSocialAccountDetails.Exists(x => x.SocialSiteID == Convert.ToInt32(CommonConstants.SocialSite.Facebook));
          if (hasFacebookAccount)
          {
               allSocialSites.Add(new SocialSite() { SocialSiteID = 1, SocialSiteName = "Facebook" });
          }
          bool hasTwitterAccount = userSocialAccountDetails.Exists(x => x.SocialSiteID == Convert.ToInt32(CommonConstants.SocialSite.Twitter));
          if (hasTwitterAccount)
          {
               allSocialSites.Add(new SocialSite() { SocialSiteID = 2, SocialSiteName = "Twitter" });
          }
     }
     ViewData["SocialSites"] = allSocialSites;
     return View();
 }

I don't have a strongly-typed view. What I need to do is post the user response. So I create a form for POSTing. Now within this form, I want to display a checkbox for all the registered social accounts (ViewData would give me this info). So, lets say, if user A registered only a Facebook account, then only 1 checkbox representing Facebook should be visible. If user B registered a Facebook, Twitter and Instagram account, then 3 checkboxes should be visible one representing each of these social accounts. I guess you get the idea. This is my code for that:

@model SocialSitePost
@{
      using (Html.BeginForm("ProcesssRequest", "Account", FormMethod.Post))
      {
           <div class="divProcessRequest">
               <br />
               <div>
                   @Html.TextAreaFor(model => model.PostContent)
               </div>
               <div>
                   @foreach (var socialSite in ViewData["SocialSites"] as List<SocialSite>)
                   {
                        @Html.CheckBox("SocialSiteID", new { value = socialSite.SocialSiteID, @checked = true });
                        @Html.Label(socialSite.SocialSiteName)
                   }        //Tried this..And am able to display the checkbox

                   @*@for (int i = 0; i < Model.SelectedSocialSites.Count; i++)
                   {
                        @Html.CheckBoxFor("SocialSiteID", new { value = Model.SelectedSocialSites[i].SocialSiteID, @checked = true });
                   }*@        //Tried this too..I know this shouldn't work and rightly so, it doesn't work :)
               </div>
               <br />
               <div>
                   <input type="submit" id="btnPost" value="Post" />
               </div>
           </div>
      }
 }

Enough said!!! Now my real problem:

When the user selects any checkbox, I want to populate the SelectedSocialSites property of the SocialSitePost object. This is the model whose object the form is posting back. I need to be able to access the SelectedSocialSites property in the POST method. This is how my POST method is :

[HttpPost]
public ActionResult ProcessRequest(SocialSitePost post)
{
     if (ModelState.IsValid)
     {
          List<SocialSite> allSocialSites = ViewData["SocialSites"] as List<SocialSite>;        //Can't get anything here
          int socialSiteNo = post.SelectedSocialSites.SocialSiteID;    //This is what I want to be able to do
          return View("HomePage");
     }
}

Since, I am relatively new to ASP.NET MVC, I'm not really sure if this is the right way of doing this. I did try out a few things like EditorFor, trying to send the SocialSitePost object the first time the view is rendered (I don't want to do this as it doesn't make any logical sense).

Can someone tell me how I can get the SelectedSocialSites property of the SocialSitePost class populated in the POST method based on the checkbox selections made by the user?

Also, can someone tell me if I am doing something wrong here as I haven't found any questions here so far on SO which seem similar to this kind of situation?


Solution

  • The main problem here is that the model is not completely suitable for what needs to be displayed. That is, the format of the model with respect to the view and its representation at the backend is different. Hence, a ViewModel needs to be used here. This is what solved my issue.

    These were the ViewModels I ended up designing:

    public class SocialSiteViewModel : SocialSite
    {
        public bool IsSelected { get; set; }
    
        public string SocialSitePostID { get; set; }
    }
    
    public class SocialSitePostViewModel : SocialSitePost
    {
        public List<SocialSiteViewModel> SocialSiteViewModel { get; set; }
    }
    

    And then I could easily use them on the view as such:

    using (Html.BeginForm("ActionMethodName", "ControllerName", FormMethod.Post))
    {
    
        @Html.CheckBoxFor(x => x.SocialSiteViewModel[i].IsSelected)
        @Html.HiddenFor(x => x.SocialSiteViewModel[i].SocialSiteID)
        @Html.HiddenFor(x => x.SocialSiteViewModel[i].SocialSiteName)
    }