Search code examples
asp.net-mvcpostef-code-firstmany-to-many

How can i take many textboxes' value on Post in MVC


i have a problem about MVC, but first I am sorry for my english :D . Now i am trying to make a form for users and i have a critical issue when i want connect to values with database.

My Form is like this : https://i.hizliresim.com/vJ6r2p.png

Models :

[Table("Testers")]
    public class Testers
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int ID { get; set; }

        [StringLength(50),Required]
        public string testerName { get; set; }

        public ICollection<Scores> Scores { get; set; }
    }
    [Table("Technologies")]
        public class Technologies
        {
            [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public int ID { get; set; }
            [StringLength(50)]
            public string technologyName { get; set; }
            [StringLength(50)]
            public string type { get; set; }
        }
    [Table("Scores")]
    public class Scores
    {
        [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int ID { get; set; }
        [DefaultValue(0)]
        public int score { get; set; }

        public virtual Testers tester { get; set; }
        public virtual Technologies technology { get; set; }
    }

ViewModels:

  public class TechnologiesView
    {
        public List<Technologies> Technologies { get; set; }
        public Scores Scores { get; set; }
    }

Controller :

public ActionResult Page2()
        {
            TechnologiesView allTechs = new TechnologiesView();
            allTechs.Technologies = db.Technologies.ToList();
            return View(allTechs);
        }

View:

@model TechnologiesView
@{
    ViewBag.Title = "Page2";
}
<style>
    #lang {
        font-size: 15px;
        color: gray;
    }

    #tech {
        font-size: 13px;
        color: gray;
    }
</style>

<div class="container">
    <div class="row col-xs-12 bilgi" style="color:black">
        @HelperMethods.Title("Kendini Skorla!")
        <br />
        <i>Bilgi Düzeyini 0 ile 5 puan arasında notlar mısın? (0=Hiç 5= İleri Seviye)</i>
    </div>
</div>

<hr />
@using (Html.BeginForm())
{
    <div class="container-fluid" style="padding-left:50px; margin:0px">
        <div class="row" id="lang">

            @foreach (Technologies techs in Model.Technologies)
            {
                if (techs.type == "lang")
                {
                    <div class="col-md-1 col-sm-2 col-xs-6">
                        @(techs.technologyName)
                    </div>
                    <div class="col-md-1 col-sm-2 col-xs-6">
                        (@(Html.TextBoxFor(x => x.Scores.score, new
                     {
                         id = techs.ID,
                         name = "techID",
                         style = "display:inline; width:20px; height:20px; font-size:smaller; padding:0px; text-align:center",
                         @class = "form-control"
                     })))
                    </div>
                }
            }
        </div>
        <hr style="color:black" />
        <div class="row" id="tech">
            @foreach (Technologies techs in Model.Technologies)
            {
                if (techs.type == "tech")
                {
                    <div class="col-md-1 col-sm-2 col-xs-6" id="tech">
                        @(techs.technologyName)
                    </div>
                    <div class="col-md-1 col-sm-2 col-xs-6">
                        @Html.HiddenFor(x=>techs.ID)
                        (@(Html.TextBoxFor(x => x.Scores.score, new
                     {
                         id = techs.ID,
                         name = "techID",
                         style = "display:inline; width:20px; height:20px; font-size:smaller; padding:0px; text-align:center",
                         @class = "form-control"
                     })))
                    </div>
                }
            }
        </div>
        <hr />
        <div class="row col-xs-12" id="lang">
            <span>Kullandığınız IDE’ler (yazınız)</span>
            <br />
            <div style="margin-bottom:10px; text-align:center">
                @HelperMethods.TextArea("Ide", 3)
            </div>
        </div>
        <div style="text-align:right; margin-bottom:10px">
            @HelperMethods.Button("btnPage2")
        </div>
    </div>
}

Now user has to give a score to him/herself for every technologies or languages and after this i want to when user click to button "Follow the next page(it's turkish)" i will select the last saved user from maxID value in Testers and i have to connect scores with technologies and testers but i don't know how can i get textboxes' values and which technology's value is this value on post :D


Solution

  • You generating form controls which have no relationship at all to your model (which is also wrong anyway). Never attempt to change the name attribute when using the HtmlHelper methods (and there is no reason to change the id attribute either)

    Next, you cannot use a foreach loop to generate form controls for a collection. You need a for loop or EditorTemplate to generate the correct name attributes with indexers. Refer this answer for a detailed explanation.

    Then you cannot use a if block inside the loop (unless you include a hidden input for the collection indexer), because by default the DefaultModelBinder required collection indexers to start at zero and be consecutive.

    First start by creating view models to represent what your want to display/edit in the view.

    public class ScoreVM
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Score { get; set; }
    }
    public class TechnologiesVM
    {
        public List<ScoreVM> Languages { get; set; }
        public List<ScoreVM> Technologies { get; set; }
        public string Notes { get; set; } // for your textarea control
    }
    

    Note you will probably want to add validation attributes such as a [Range] attribute for the Score property

    In the GET method, initialize and populate your view model and pass it to the view

    public ActionResult Page2()
    {
        IEnumerable<Technologies> technologies = db.Technologies;
        TechnologiesVM model = new TechnologiesVM
        {
            Languages = technologies.Where(x => x.type == "lang")
                .Select(x => new ScoreVM{ ID = x.ID, Name = x.technologyName }).ToList(),
            Technologies = technologies.Where(x => x.type == "tech")
                .Select(x => new ScoreVM{ ID = x.ID, Name = x.technologyName }).ToList(),
        };
        return View(model);
    }
    

    and in the view

    @model TechnologiesVM
    ....
    @using (Html.BeginForm())
    {
        ....
        @for (int i = 0; i < Model.Languages.Count; i++)
        {
            @Html.HiddenFor(m => m.Languages[i].ID)
            @Html.HiddenFor(m => m.Languages[i].Name)
            @Html.LabelFor(m => m.Languages[i].Score, Model.Languages[i].Name)
            @Html.TextBoxFor(m => m.Languages[i].Score)
            @Html.ValidationMessageFor(m => m.Languages[i].Score)
        }
        @for (int i = 0; i < Model.Languages.Count; i++)
        {
            .... // repeat above
        }
        @Html.LabelFor(m => m.Notes)
        @Html.TextAreaFor(m => m.Notes)
        @Html.ValidationMessageFor(m => m.Notes)
    
        <input type="submit" />
    }
    

    and the POST method will be

    public ActionResult Page2(TechnologiesVM model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }
        ... // save the data and redirect
    }