2

I'm trying to create a full CRUD application on MVC without a database. I used the template view creator for "Create/Details/Edit/Delete..." I'm having trouble saving/editing/delete since most resources online work off of a database/framework. For "Create", after inputting in all the fields, it bring me back to the "List" page of all PersonModel without the newly input PersonModel. Am unable to get [HttpPost] to work for edit.

using MVCDemo.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MVCDemo.Controllers
{
    public class PersonController : Controller
    {
        List<PersonModel> people = new List<PersonModel>()
        {
               new PersonModel { First = "DAD", Last = "Nguyen", Name = "Study", About = "Study for test", Done = false, Id = 1 },
               new PersonModel { First = "David", Last = "Smith", Name = "Grocery", About = "Buy apple and bananana", Done = true , Id = 2},
               new PersonModel { First = "Tom", Last = "Davidson", Name = "Fix car", About = "Headlight and mirror is broken", Done = false, Id = 3 },
               new PersonModel { First = "Maddie", Last = "Madison", Name = "Job application", About = "Follow up on job interview ", Done = false , Id = 4}
        };

        // GET: Person
        public ActionResult Index()
        {
            return View(people);
        }
        [HttpGet]
        public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Create(PersonModel per)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    return View("Create", per);
                }
                people.Add(per);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        public ActionResult Details(int id)
        {
            PersonModel per = people.Find(emp => emp.Id == id);
            return View(per);
        }

        [HttpGet]
        public ActionResult Edit(int id)
        {
            PersonModel per = people.Find(emp => emp.Id == id);
            return View(per);
        }

        public ActionResult Delete()
        {
            return View();
        }


    }
}

Thank you.

Edit

Class model

public class PersonModel
   {
       public int Id { get; set; }
       public string First { get; set; }
       public string Last { get; set; }
       public bool Done { get; set; } 

       public string Name { get; set; }
       public string About { get; set; }

   }

Index cshtml

@model IEnumerable<MVCDemo.Models.PersonModel>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.First)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Last)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Done)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.About)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.First)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Last)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Done)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.About)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
            @Html.ActionLink("Details", "Details", new { id=item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Id })
        </td>
    </tr>
}

</table>

Create cshtml

@model MVCDemo.Models.PersonModel

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

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

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

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

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

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

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

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

Details cshtml

@model MVCDemo.Models.PersonModel

@{
    ViewBag.Title = "Details";
}

<h2>Details</h2>

<div>
    <h4>PersonModel</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.First)
        </dt>

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

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

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

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

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

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

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

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

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

    </dl>
</div>
<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
    @Html.ActionLink("Back to List", "Index")
</p>

Edit cshtml

@model MVCDemo.Models.PersonModel

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

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

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

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

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

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

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

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

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

<div>
    @Html.ActionLink("Back to List", "Index")
</div>
Truong Nguyen
  • 201
  • 2
  • 9
  • 2
    Where are you expecting the new/edited person to be stored/retrieved from? – melkisadek Dec 28 '19 at 03:49
  • Sorry, this is my first MVC project so I might be off but the input would be retrieved from user input from web input and I was hoping it would store inside the array through people.Add(per) – Truong Nguyen Dec 28 '19 at 03:53
  • What exactly is your requirement here? If you store user input in Array or List then that will be last without storing it in DB or send to API/Webservice etc. – Rajesh G Dec 28 '19 at 04:08
  • It seems your model is not valid so that's way not adding a new record into list – habib Dec 28 '19 at 04:09
  • post your cshtml file completely. – Hameed Syed Dec 28 '19 at 04:48
  • @RajeshG I'm not sure what you mean by requirement. – Truong Nguyen Dec 28 '19 at 04:50
  • @HameedSyed I made the edit – Truong Nguyen Dec 28 '19 at 05:08
  • 1
    An instance of the controller is created per request. So when you do the POST, the model is being added to the array for that instance only. And then the subsequent GET will use a new instance of the controller, with the array being initialized with the default value (not including the model from the previous POST). – devNull Dec 28 '19 at 05:19
  • When you debug `Create` `HttpPost` method do you see your `PersonModel` populated with data or is it just filled with `null`s? – GrayCat Dec 28 '19 at 09:28
  • @GrayCat The PersonModel is populated with data from the hardcode. Although the person list was able to be passed in to get populated, the list wasn't able to add. – Truong Nguyen Dec 28 '19 at 16:49

1 Answers1

3

As pointed in comments instance of Controller is created per request. Read more about it in this StackOverflow question

What it changes for you is that in this code

public class PersonController : Controller
{
    List<PersonModel> people = new List<PersonModel>()
    {
        ... adding instances ...
    };
    .... rest of your code ...
}

An instance of people is created anew with each request. And your code after adding data to people issues HTTP Redirect to user's browser with this line

return RedirectToAction("Index");

From server's perspective request ends here and subsequent request that gets List page is separate request. What it means it that people list is created anew without newly added person.

There is very simple fix with not so simple implications.

Add static keyword like this.

static List<PersonModel> people = new List<PersonModel>() { ... };

Now this list will be a static member of the class - not associated with given instance, but with the class itself. You can read more about what is static here. You should read Static members section carefully.

What is this not simple implication?

With this added your code becomes not thread-safe. Why? Imagine two requests happen at once, or very close to each other with two different PersonModel. Depending on implementation of List<T> in C# (whether it itself is thread-safe) you may end up in different situations:

  1. Both persons are added. Hurray!
  2. Only one person is added. How is that possible? Let's say that to add element to list program needs to resolve at which index to add new person before actually adding this person. In this short time between those two event another request (thread) might want to do exactly the same. So it resolves the same index (because previous thread did not add its person yet). So second thread wins and its person overwrites first one's person.
  3. Some Exception is thrown. I don't know if it happens with List<T>, but Dictionary<K, V> actually throws exceptions sometimes if accessed concurrently.

What to do to avoid the new issue?

If this is your learning project you might as well not do anything as this probably will never occur.

BUT if you want to learn OR this is not something to throw away you might want to use lock to make threads wait before previous finishes adding.

Add this as a field in your class (mind static keyword):

private static object _lock = new object()

And do adding to list like this:

lock(_lock) {
    people.Add(per);
}

Another options is to use thread-safe collection instead of List<T> like ConcurrentBag<T>. You can read more about them here.

GrayCat
  • 1,749
  • 3
  • 19
  • 30