3

I am new to C# and MVC and am coming into the Visual Studio environment from a PHP background. I'm groping around a little (lot), but getting there. I need help in understanding some essential concepts, and undoubtedly this is a very hamfisted effort, but helping me solve this will help me understand how things work.

I have a SQL db, hooked up to a VS2012 web project (C#) by adding an ADO.NET Entity Class Model. Suffice to say that that end is working no problem, and the automatically generated default CRUD views work no problem also.

Use case:

I wanted a webpage where the user would be presented with a list of items drawn from the database. This was already achieved in the default Index view, but I also wanted that list to be editable then and there without having to enter an Edit view for each individual item. Therefore I changed the list HTML to a form, and set up an ActionName for an 'Update' submission. This also works no problem:

MODEL:

namespace Library3.DAL {
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;

    public partial class PrmTbl_Level {
        public PrmTbl_Level() {
            this.Tbl_Books = new HashSet<Tbl_Book>();
        }

        [Required]
        [Display(Name = "ID")]
        public int LevelID { get; set; }

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

        [Required]
        [Display(Name = "Entered By")]
        public string EnteredBy { get; set; }

        public Boolean Active { get; set; }

        public virtual ICollection<Tbl_Book> Tbl_Books { get; set; }
    }
}

VIEW:

@model IEnumerable<Library3.DAL.PrmTbl_Level>


@using (Html.BeginForm("Update", "ReaderLevel", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)

<fieldset>
    <table>
        <thead>
            <tr>
                <th> @Html.DisplayNameFor(model => model.LevelID) </th>
                <th> @Html.DisplayNameFor(model => model.LevelDesc) </th>
                <th> @Html.DisplayNameFor(model => model.EnteredBy) </th>
                <th> @Html.DisplayNameFor(model => model.Active) </th>
            </tr>
        </thead>

        @foreach (var item in Model) {

            var thisID = item.LevelID;
            var thisDesc = item.LevelDesc;
            var thisEnteredBy = item.EnteredBy;
            var thisActive = item.Active;

            <tr>
                <td class="tdsm centered fade"> @thisID </td>
                <td> <input type="text" name="@(thisID + ".desc")" value="@thisDesc" /> </td>
                <td class="tdmed centered fade"> @thisEnteredBy </td>
                <td class="tdsm centered">
                    @{
                        if(@thisActive) {
                            <text><input type="checkbox" name="@(thisID + ".active")" value="true" checked="checked" /></text>
                        }
                        else {
                            <text><input type="checkbox" name="@(thisID + ".active")" value="true" /></text>
                        }
                    }
                </td>
            </tr>
        }all of this
    </table>
    <p>
        <input type="submit" value="Update" />
    </p>
</fieldset>
}

CONTROLLER:

namespace Library3.Controllers {
    public class ReaderLevelController : Controller {
        private NSSchoolLibraryEntities db = new NSSchoolLibraryEntities();

        public ActionResult Index() {
            ViewBag.Title = "Reader Levels";
            return View(db.PrmTbl_Levels.ToList());
        } 


        [HttpPost, ActionName("Update")]
        [ValidateAntiForgeryToken]
        public ActionResult Update(FormCollection MyArray) {

                (... Process UPDATE submission ...)

                db.SaveChanges();

            }
        }

        return RedirectToAction("Index");

        }
    }
}

Now I want to put an empty field at the top of my list, whereby new entries may be made. This basically means the one view file will now be catering for both Create and Update functions. I inserted the following HTML:

@using (Html.BeginForm("New", "ReaderLevel", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>

        <table>
            <tr>
                <td class="tdsm centered fade">
                    @Html.EditorFor(model => model.LevelID)
                    @Html.ValidationMessageFor(model => model.LevelID)</td>
                <td><input type="text" name="desc" />
                    @Html.EditorFor(model => model.LevelDesc)
                    @Html.ValidationMessageFor(model => model.LevelDesc)</td>
                <td class="tdmed centered fade">
                    @Html.EditorFor(model => model.EnteredBy)
                    @Html.ValidationMessageFor(model => model.EnteredBy)</td>
                <td class="tdsm centered">
                    @Html.EditorFor(model => model.Active)
                    @Html.ValidationMessageFor(model => model.Active)</td>
            </tr>
        </table>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

...the problem being the the model being referenced here should be for a NEW intance of the PrmTbl_Level class - not using the existing IEnumberable to spit out existing data from the table (at least thats what I THINK the problem is!).

The default seperate Create view references the namespace via @model (sans IEnumberable), and the data is returned as a single object (public ActionResult Create(PrmTbl_Level prmtbl_level)) which is then saved effortlessly to the db (db.PrmTbl_Levels.Add(prmtbl_level);) - how can I achieve the same using the one @model statement? (ELI5). All help - both practically and on the concepts involved - greatly appreciated!

Eamonn
  • 1,338
  • 2
  • 21
  • 53

2 Answers2

2

In brief, instead of using IEnumerable<Library3.DAL.PrmTbl_Level> for your model, you should create a custom class that represents both the list (to display and edit as it currently does) plus an additional PrmTbl_Level that you can populate to create a new entity/row.

So, something like this:

public class MyCustomModel
{
    public MyCustomModel()
    {
        MyList = new List<PrmTbl_Level>();
        MyItemToCreate = new PrmTbl_Level();
    }

    public List<PrmTbl_Level> MyList { get; set; }
    public PrmTbl_Level MyItemToCreate { get; set; }
}

Now your strongly typed model is MyCustomModel, and you need to change your view to reference the new model correctly (e.g. model => model.LevelID becomes model => model.MyList.LevelID).

Not a complete solution but should get you started.

Phil Sandler
  • 27,544
  • 21
  • 86
  • 147
  • Do I reference the model via IEnumberable? – Eamonn Aug 23 '13 at 01:47
  • Just to come back on this in more detail, as I'm getting compilation errors... I can kinda get what you're doing there, and have created a new 'MyCustomModel' class as above within the same namespace as the existing PrmTbl_Level class. I then reference this in my view with a straight-up @model call (no IEnumerable) and updated all the references to MyList.Whatever and MyItemToCreate.Etcetera. On running, it fails to find the definitions from the PrmTbl_Level class however. What must I do to point it in the right direction? – Eamonn Aug 23 '13 at 02:10
  • What do you mean by "On running, it fails to find the definitions from the PrmTbl_Level class however." Also, what lines of code are causing the error? Can you post the code line and error message? – Phil Sandler Aug 23 '13 at 03:24
  • Certainly. The error reads `Compiler Error Message: CS1061: 'System.Collections.Generic.List' does not contain a definition for 'LevelID' and no extension method 'LevelID' accepting a first argument of type 'System.Collections.Generic.List' could be found (are you missing a using directive or an assembly reference?)` and is thrown by the line `@Html.DisplayNameFor(model => model.MyList.LevelID)` (this would be the first usage of the model - `@model Library3.DAL.MyCustomModel` - in the file). – Eamonn Aug 23 '13 at 08:49
  • correction - the error is thrown on the first usage of the `MyList` object, which comes after the apparently successful usage of the the `MyItemToCreate` object. – Eamonn Aug 23 '13 at 09:37
  • Try commenting out everything in the *thead* section, and see if it works (you will lose the grid header). It's surprising to me that the code you posted (using displayNameFor) worked as posted, but stopped working after changing the model in the way I suggested. If commenting it out gets rid of the errors, see the following answer for how to use displayNameFor in a grid header with a collection: http://stackoverflow.com/a/13780314/151084 – Phil Sandler Aug 23 '13 at 12:48
  • Thanks Phil - I actually got it working by just referencing the base `PrmTbl_Level` class for the list portion, using an 'unbound' form for the create portion, and and then just mapped the submitted values (via `FormCollection`) to a new instance of the class. I could then use `db.PrmTbl_Levels.Add(thisRecord); db.SaveChanges();` to write to the db. Your method *is* the way to go however, as I find that I cannot utilise `ModelState.IsValid` as it is. I have to leave it for now and come back to it however, but I'll play with it over the weekend. It's powerful stuff! :) – Eamonn Aug 23 '13 at 13:28
0

To add Create view in same listing view You can use partial view. and add this partial view at top of your listing view that would solve your problem. all the field which is required to create a new entry should be in partial view.

check these link may be it could give you some help

check these link may be it could give you some help

Community
  • 1
  • 1
hungry
  • 37
  • 1
  • 13