Storing data between requests in dbcontext












6














I have a page with server side rendering using razor, where you can add a couple of elements from different lists, fill some fields and create a request from it on submit.



Each time an item is added/taken from any list, I send a post with submit button to a specific action, e.g. "CustomerSelected". I do this, because I need to recreate additional view components for the added item. In these methods I would like to add added objects to the db context, so on submit I can just say SaveChanges() and not have to assemble everything in the same method. But in .net core db context is per request and it is advisable to keep it that way. In this case how can I store these temporary entity objects between requests so later if someone decides to submit them I can say SaveChanges() or discard them otherwise?



I would like to have something like this:



public IActionResult CustomerAdded(int customerId)
{
var customer = _context.Customers.First(c => c.IdCustomer == customerId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Customers.Add(customer);
return View();
}

public IActionResult ItemAdded(int itemId)
{
var item = _context.Items.First(c => c.IdItem == itemId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Items.Add(item);
return View();
}

public IActionResult Submit()
{
_context.SaveChanges();
return View();
}


If this is not possible then I was thinking about adding individual elements in each method and save them there and onsubmit I would build the last final element. But if someone closes their browser without submitting then I have incomplete data laying in my database. I would have to run some kind of job to delete those and it seems to be too much for such a simple task.










share|improve this question

















This question has an open bounty worth +50
reputation from FCin ending in 3 hours.


This question has not received enough attention.
















  • Can you try it making those post methods void and prevent returning anything ? use ajax to run that method so it won't change page. and when user clicks at submit SaveChanges and return to your page. ?
    – Halil İbrahim
    Dec 28 '18 at 9:48










  • Have you tries this please: services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),ServiceLifetime.Singleton);
    – TanvirArjel
    Dec 28 '18 at 9:56










  • @TanvirArjel I don't want to set dbcontext to a singleton
    – FCin
    Dec 28 '18 at 10:00










  • @FCin Better solution you can stored the data on cache and retrieve on the submit method? After successful save changes remove the cache.
    – TanvirArjel
    Dec 28 '18 at 10:04






  • 1




    @FCin I have understood your requirement! Session and Cache is not the same thing! If you need any help you can share you code with the remote access like Team Viewer Visual Studio Live share.
    – TanvirArjel
    Dec 28 '18 at 10:11
















6














I have a page with server side rendering using razor, where you can add a couple of elements from different lists, fill some fields and create a request from it on submit.



Each time an item is added/taken from any list, I send a post with submit button to a specific action, e.g. "CustomerSelected". I do this, because I need to recreate additional view components for the added item. In these methods I would like to add added objects to the db context, so on submit I can just say SaveChanges() and not have to assemble everything in the same method. But in .net core db context is per request and it is advisable to keep it that way. In this case how can I store these temporary entity objects between requests so later if someone decides to submit them I can say SaveChanges() or discard them otherwise?



I would like to have something like this:



public IActionResult CustomerAdded(int customerId)
{
var customer = _context.Customers.First(c => c.IdCustomer == customerId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Customers.Add(customer);
return View();
}

public IActionResult ItemAdded(int itemId)
{
var item = _context.Items.First(c => c.IdItem == itemId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Items.Add(item);
return View();
}

public IActionResult Submit()
{
_context.SaveChanges();
return View();
}


If this is not possible then I was thinking about adding individual elements in each method and save them there and onsubmit I would build the last final element. But if someone closes their browser without submitting then I have incomplete data laying in my database. I would have to run some kind of job to delete those and it seems to be too much for such a simple task.










share|improve this question

















This question has an open bounty worth +50
reputation from FCin ending in 3 hours.


This question has not received enough attention.
















  • Can you try it making those post methods void and prevent returning anything ? use ajax to run that method so it won't change page. and when user clicks at submit SaveChanges and return to your page. ?
    – Halil İbrahim
    Dec 28 '18 at 9:48










  • Have you tries this please: services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),ServiceLifetime.Singleton);
    – TanvirArjel
    Dec 28 '18 at 9:56










  • @TanvirArjel I don't want to set dbcontext to a singleton
    – FCin
    Dec 28 '18 at 10:00










  • @FCin Better solution you can stored the data on cache and retrieve on the submit method? After successful save changes remove the cache.
    – TanvirArjel
    Dec 28 '18 at 10:04






  • 1




    @FCin I have understood your requirement! Session and Cache is not the same thing! If you need any help you can share you code with the remote access like Team Viewer Visual Studio Live share.
    – TanvirArjel
    Dec 28 '18 at 10:11














6












6








6


2





I have a page with server side rendering using razor, where you can add a couple of elements from different lists, fill some fields and create a request from it on submit.



Each time an item is added/taken from any list, I send a post with submit button to a specific action, e.g. "CustomerSelected". I do this, because I need to recreate additional view components for the added item. In these methods I would like to add added objects to the db context, so on submit I can just say SaveChanges() and not have to assemble everything in the same method. But in .net core db context is per request and it is advisable to keep it that way. In this case how can I store these temporary entity objects between requests so later if someone decides to submit them I can say SaveChanges() or discard them otherwise?



I would like to have something like this:



public IActionResult CustomerAdded(int customerId)
{
var customer = _context.Customers.First(c => c.IdCustomer == customerId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Customers.Add(customer);
return View();
}

public IActionResult ItemAdded(int itemId)
{
var item = _context.Items.First(c => c.IdItem == itemId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Items.Add(item);
return View();
}

public IActionResult Submit()
{
_context.SaveChanges();
return View();
}


If this is not possible then I was thinking about adding individual elements in each method and save them there and onsubmit I would build the last final element. But if someone closes their browser without submitting then I have incomplete data laying in my database. I would have to run some kind of job to delete those and it seems to be too much for such a simple task.










share|improve this question















I have a page with server side rendering using razor, where you can add a couple of elements from different lists, fill some fields and create a request from it on submit.



Each time an item is added/taken from any list, I send a post with submit button to a specific action, e.g. "CustomerSelected". I do this, because I need to recreate additional view components for the added item. In these methods I would like to add added objects to the db context, so on submit I can just say SaveChanges() and not have to assemble everything in the same method. But in .net core db context is per request and it is advisable to keep it that way. In this case how can I store these temporary entity objects between requests so later if someone decides to submit them I can say SaveChanges() or discard them otherwise?



I would like to have something like this:



public IActionResult CustomerAdded(int customerId)
{
var customer = _context.Customers.First(c => c.IdCustomer == customerId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Customers.Add(customer);
return View();
}

public IActionResult ItemAdded(int itemId)
{
var item = _context.Items.First(c => c.IdItem == itemId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Items.Add(item);
return View();
}

public IActionResult Submit()
{
_context.SaveChanges();
return View();
}


If this is not possible then I was thinking about adding individual elements in each method and save them there and onsubmit I would build the last final element. But if someone closes their browser without submitting then I have incomplete data laying in my database. I would have to run some kind of job to delete those and it seems to be too much for such a simple task.







c# asp.net-core asp.net-core-mvc entity-framework-core dbcontext






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Jan 3 at 9:47









Tetsuya Yamamoto

14.8k42040




14.8k42040










asked Dec 28 '18 at 9:24









FCinFCin

2,47521027




2,47521027






This question has an open bounty worth +50
reputation from FCin ending in 3 hours.


This question has not received enough attention.








This question has an open bounty worth +50
reputation from FCin ending in 3 hours.


This question has not received enough attention.














  • Can you try it making those post methods void and prevent returning anything ? use ajax to run that method so it won't change page. and when user clicks at submit SaveChanges and return to your page. ?
    – Halil İbrahim
    Dec 28 '18 at 9:48










  • Have you tries this please: services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),ServiceLifetime.Singleton);
    – TanvirArjel
    Dec 28 '18 at 9:56










  • @TanvirArjel I don't want to set dbcontext to a singleton
    – FCin
    Dec 28 '18 at 10:00










  • @FCin Better solution you can stored the data on cache and retrieve on the submit method? After successful save changes remove the cache.
    – TanvirArjel
    Dec 28 '18 at 10:04






  • 1




    @FCin I have understood your requirement! Session and Cache is not the same thing! If you need any help you can share you code with the remote access like Team Viewer Visual Studio Live share.
    – TanvirArjel
    Dec 28 '18 at 10:11


















  • Can you try it making those post methods void and prevent returning anything ? use ajax to run that method so it won't change page. and when user clicks at submit SaveChanges and return to your page. ?
    – Halil İbrahim
    Dec 28 '18 at 9:48










  • Have you tries this please: services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),ServiceLifetime.Singleton);
    – TanvirArjel
    Dec 28 '18 at 9:56










  • @TanvirArjel I don't want to set dbcontext to a singleton
    – FCin
    Dec 28 '18 at 10:00










  • @FCin Better solution you can stored the data on cache and retrieve on the submit method? After successful save changes remove the cache.
    – TanvirArjel
    Dec 28 '18 at 10:04






  • 1




    @FCin I have understood your requirement! Session and Cache is not the same thing! If you need any help you can share you code with the remote access like Team Viewer Visual Studio Live share.
    – TanvirArjel
    Dec 28 '18 at 10:11
















Can you try it making those post methods void and prevent returning anything ? use ajax to run that method so it won't change page. and when user clicks at submit SaveChanges and return to your page. ?
– Halil İbrahim
Dec 28 '18 at 9:48




Can you try it making those post methods void and prevent returning anything ? use ajax to run that method so it won't change page. and when user clicks at submit SaveChanges and return to your page. ?
– Halil İbrahim
Dec 28 '18 at 9:48












Have you tries this please: services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),ServiceLifetime.Singleton);
– TanvirArjel
Dec 28 '18 at 9:56




Have you tries this please: services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),ServiceLifetime.Singleton);
– TanvirArjel
Dec 28 '18 at 9:56












@TanvirArjel I don't want to set dbcontext to a singleton
– FCin
Dec 28 '18 at 10:00




@TanvirArjel I don't want to set dbcontext to a singleton
– FCin
Dec 28 '18 at 10:00












@FCin Better solution you can stored the data on cache and retrieve on the submit method? After successful save changes remove the cache.
– TanvirArjel
Dec 28 '18 at 10:04




@FCin Better solution you can stored the data on cache and retrieve on the submit method? After successful save changes remove the cache.
– TanvirArjel
Dec 28 '18 at 10:04




1




1




@FCin I have understood your requirement! Session and Cache is not the same thing! If you need any help you can share you code with the remote access like Team Viewer Visual Studio Live share.
– TanvirArjel
Dec 28 '18 at 10:11




@FCin I have understood your requirement! Session and Cache is not the same thing! If you need any help you can share you code with the remote access like Team Viewer Visual Studio Live share.
– TanvirArjel
Dec 28 '18 at 10:11












3 Answers
3






active

oldest

votes


















3














It's not good idea to use server resources to track changes in such scenarios. In scenarios like shopping basket, list or batch editing it's better track changes at client-side.



Your requirement to get Views generated at server-side doesn't mean you need to track changes in DbContext. Get the index view and create view from server, but track changes on client. Then to save, post all data to the server to save changes based on the tracking info that you have.



The mechanism for client-side change tracking depends to the requirement and the scenario, for example you can track changes using html inputs, you can track changes using cookie, you can track changes using javascript objects in browser memory like angular scenarios.



Here is this post I'll show an example using html inputs and model binding. To learn more about this topic, take a look at this article by Phill Haack: Model Binding To A List.



Example



In the following example I describe a list editing scenario for a list of customers. To make it simple, I suppose:




  • You have a list of customers which you are going to edit at client. You may want to add, edit or delete items.

  • When adding new item, the row template for new row should come from server.

  • When deleting, you mark an item as deleted by clicking on a checkbox on the row.

  • When adding/editing you want to show validation errors near the cells.

  • You want to save changes at the end, by click on Save button.


To implement above scenario Then you need to create following models, actions and views:



Trackable<T> Model



This class is a model which helps us in client side tracking and list editing:



public class Trackable<T>
{
public Trackable() { }
public Trackable(T model) { Model = model; }
public Guid Index { get; set; } = Guid.NewGuid();
public bool Deleted { get; set; }
public bool Added { get; set; }
public T Model { get; set; }
}


Customer Model



The customer model:



public class Customer
{
[Display(Name ="Id")]
public int Id { get; set; }

[StringLength(20, MinimumLength = 1)]
[Required]
[Display(Name ="First Name")]
public string FirstName { get; set; }

[StringLength(20, MinimumLength = 1)]
[Required]
[Display(Name ="Last Name")]
public string LastName { get; set; }

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


Index.cshtml View



The Index view is responsible to render List<Trackable<Customer>>. When rendering each record, we use RowTemplate view. The same view which we use when adding new item.



In this view, we have a submit button for save and a button for adding new rows which calls Create action using ajax.



Here is Index view:



@model IEnumerable<Trackable<Customer>>
<h2>Index</h2>
<form method="post" action="Index">
<p>
<button id="create">New Customer</button>
<input type="submit" value="Save All">
</p>
<table class="table" id="data">
<thead>
<tr>
<th>
Delete
</th>
<th>
@Html.DisplayNameFor(x => x.Model.FirstName)
</th>
<th>
@Html.DisplayNameFor(x => x.Model.LastName)
</th>
<th>
@Html.DisplayNameFor(x => x.Model.Email)
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
await Html.RenderPartialAsync("RowTemplate", item);
}
</tbody>
</table>
</form>

@section Scripts{
<script>
$(function () {
$('#create').click(function (e) {
e.preventDefault();
$.ajax({
url: 'Create',
method: 'Get',
success: function (data) {
$('#data tbody tr:last-child').after(data);
},
error: function (e) { alert(e); }
});
});
});
</script>
}


RowTemplate.cshtml View



This view is responsible to render a customer record. In this view, we first render the Index in a hidden, then set a prefix [index] for the fields and then render the fields, including index again, added, deleted and model id:



Here is RowTemplate View:



@model Trackable<Customer>
<tr>
<td>
@Html.HiddenFor(x => x.Index)
@{Html.ViewData.TemplateInfo.HtmlFieldPrefix = $"[{Model.Index}]";}
@Html.HiddenFor(x => x.Index)
@Html.HiddenFor(x => x.Model.Id)
@Html.HiddenFor(x => x.Added)
@Html.CheckBoxFor(x => x.Deleted)
</td>
<td>
@Html.EditorFor(x => x.Model.FirstName)
@Html.ValidationMessageFor(x => x.Model.FirstName)
</td>
<td>
@Html.EditorFor(x => x.Model.LastName)
@Html.ValidationMessageFor(x => x.Model.LastName)
</td>
<td>
@Html.EditorFor(x => x.Model.Email)
@Html.ValidationMessageFor(x => x.Model.Email)
</td>
</tr>


CustomerController



public class CustomerController : Controller
{
private static List<Customer> list;
}


It will have the following actions.



[GET] Index Action



In this action you can load data from database and shape it to a List<Trackable<Customer>> and pass to the Index View:



[HttpGet]
public IActionResult Index()
{
if (list == null)
{
list = Enumerable.Range(1, 5).Select(x => new Customer()
{
Id = x,
FirstName = $"A{x}",
LastName = $"B{x}",
Email = $"A{x}@B{x}.com"
}).ToList();
}
var model = list.Select(x => new Trackable<Customer>(x)).ToList();
return View(model);
}


[GET] Create Action



This action is responsible to returning new row template. It will be called by a button in Index View using ajax:



[HttpGet]
public IActionResult Create()
{
var model = new Trackable<Customer>(new Customer()) { Added = true };
return PartialView("RowTemplate", model);
}


[POST] Index Action



This action is responsible for receiving the tracked item from client and save them. The model which it receives is List<Trackable<Customer>>. It first strips the validation error messages for deleted rows. Then removes those which are both deleted and added. Then checks if model state is valid, tries to apply changes on data source.



Items having Deleted property as true are deleted, items having Added as true and Deleted as false are new items, and rest of items are edited. Then without needing to load all items from database, just using a for loop, call db.Entry for each item and set their states and finally save changes.



[HttpPost]
public IActionResult Index(List<Trackable<Customer>> model)
{
//Cleanup model errors for deleted rows
var deletedIndexes = model.
Where(x => x.Deleted).Select(x => $"[{x.Index}]");
var modelStateDeletedKeys = ModelState.Keys.
Where(x => deletedIndexes.Any(d => x.StartsWith(d)));
modelStateDeletedKeys.ToList().ForEach(x => ModelState.Remove(x));

//Removing rows which are added and deleted
model.RemoveAll(x => x.Deleted && x.Added);

//If model state is not valid, return view
if (!ModelState.IsValid)
return View(model);

//Deleted rows
model.Where(x => x.Deleted && !x.Added).ToList().ForEach(x =>
{
var i = list.FindIndex(c => c.Id == x.Model.Id);
if (i >= 0)
list.RemoveAt(i);
});

//Added rows
model.Where(x => !x.Deleted && x.Added).ToList().ForEach(x =>
{
list.Add(x.Model);
});

//Edited rows
model.Where(x => !x.Deleted && !x.Added).ToList().ForEach(x =>
{
var i = list.FindIndex(c => c.Id == x.Model.Id);
if (i >= 0)
list[i] = x.Model;
});

//Reditect to action index
return RedirectToAction("Index");
}





share|improve this answer































    0














    What about dynamic form(s) with javascript and using type="hidden" or visibility
    and then sending everything at once



    Or using TempData with redirects and reusing that data in other views(form) as input type="hidden"



    Flow:



    Form1 ->



    Controller's Method saves data in TempData and Redirects to Form2 View / Or ViewData and return Form2 View? ->



    Form2 has TempData inserted into the form under hidden inputs ->



    Submit both at once






    share|improve this answer































      -1














      Cookie !



      public class HomeController : Controller
      {

      public string Index()
      {

      HttpCookie cookie = Request.Cookies["message"];
      Message message = null;
      string json = "";

      if (cookie == null)
      {
      message = new Message();
      json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
      cookie = new HttpCookie("message", json);
      }
      Response.Cookies.Add(cookie);
      return json;
      }

      public string CustomerAdded(int id)
      {
      HttpCookie cookie = Request.Cookies["message"];
      Message message = null;
      string json = "";

      if (cookie == null || string.IsNullOrEmpty(cookie.Value))
      {
      message = new Message();
      json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
      cookie = new HttpCookie("message", json);
      }
      else
      {
      json = cookie.Value;
      message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
      }

      if (message.Customers == null) message.Customers = new List<int>();
      if (message.Items == null) message.Items = new List<int>();

      if (!message.Customers.Contains(id))
      {
      message.Customers.Add(id);
      }


      json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
      cookie = new HttpCookie("message", json);

      Response.Cookies.Add(cookie);

      return json;
      }


      public string ItemAdded(int id)
      {
      HttpCookie cookie = Request.Cookies["message"];
      Message message = null;
      string json = "";

      if (cookie == null || string.IsNullOrEmpty(cookie.Value))
      {
      message = new Message();
      json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
      cookie = new HttpCookie("message", json);
      }
      else
      {
      json = cookie.Value;
      message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
      }
      if (message.Customers == null) message.Customers = new List<int>();
      if (message.Items == null) message.Items = new List<int>();

      if (!message.Items.Contains(id))
      {
      message.Items.Add(id);
      }

      json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
      cookie = new HttpCookie("message", json);

      Response.Cookies.Add(cookie);

      return json;
      }

      public string Submit()
      {
      HttpCookie cookie = Request.Cookies["message"];
      Message message = null;
      string json = "";

      if (cookie == null || string.IsNullOrEmpty(cookie.Value))
      {
      return "no data";
      }
      else
      {
      json = cookie.Value;
      message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
      }

      Response.Cookies["message"].Value = "";
      Response.Cookies["message"].Expires = DateTime.Now.AddDays(-1);

      return "Submited";

      }
      }


      Example links




      • http://localhost:58603/Home/CustomerAdded/1


      • http://localhost:58603/Home/CustomerAdded/2


      • http://localhost:58603/Home/Submit



      enter image description hereenter image description hereenter image description hereenter image description here






      share|improve this answer























        Your Answer






        StackExchange.ifUsing("editor", function () {
        StackExchange.using("externalEditor", function () {
        StackExchange.using("snippets", function () {
        StackExchange.snippets.init();
        });
        });
        }, "code-snippets");

        StackExchange.ready(function() {
        var channelOptions = {
        tags: "".split(" "),
        id: "1"
        };
        initTagRenderer("".split(" "), "".split(" "), channelOptions);

        StackExchange.using("externalEditor", function() {
        // Have to fire editor after snippets, if snippets enabled
        if (StackExchange.settings.snippets.snippetsEnabled) {
        StackExchange.using("snippets", function() {
        createEditor();
        });
        }
        else {
        createEditor();
        }
        });

        function createEditor() {
        StackExchange.prepareEditor({
        heartbeatType: 'answer',
        autoActivateHeartbeat: false,
        convertImagesToLinks: true,
        noModals: true,
        showLowRepImageUploadWarning: true,
        reputationToPostImages: 10,
        bindNavPrevention: true,
        postfix: "",
        imageUploader: {
        brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
        contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
        allowUrls: true
        },
        onDemand: true,
        discardSelector: ".discard-answer"
        ,immediatelyShowMarkdownHelp:true
        });


        }
        });














        draft saved

        draft discarded


















        StackExchange.ready(
        function () {
        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53956235%2fstoring-data-between-requests-in-dbcontext%23new-answer', 'question_page');
        }
        );

        Post as a guest















        Required, but never shown

























        3 Answers
        3






        active

        oldest

        votes








        3 Answers
        3






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes









        3














        It's not good idea to use server resources to track changes in such scenarios. In scenarios like shopping basket, list or batch editing it's better track changes at client-side.



        Your requirement to get Views generated at server-side doesn't mean you need to track changes in DbContext. Get the index view and create view from server, but track changes on client. Then to save, post all data to the server to save changes based on the tracking info that you have.



        The mechanism for client-side change tracking depends to the requirement and the scenario, for example you can track changes using html inputs, you can track changes using cookie, you can track changes using javascript objects in browser memory like angular scenarios.



        Here is this post I'll show an example using html inputs and model binding. To learn more about this topic, take a look at this article by Phill Haack: Model Binding To A List.



        Example



        In the following example I describe a list editing scenario for a list of customers. To make it simple, I suppose:




        • You have a list of customers which you are going to edit at client. You may want to add, edit or delete items.

        • When adding new item, the row template for new row should come from server.

        • When deleting, you mark an item as deleted by clicking on a checkbox on the row.

        • When adding/editing you want to show validation errors near the cells.

        • You want to save changes at the end, by click on Save button.


        To implement above scenario Then you need to create following models, actions and views:



        Trackable<T> Model



        This class is a model which helps us in client side tracking and list editing:



        public class Trackable<T>
        {
        public Trackable() { }
        public Trackable(T model) { Model = model; }
        public Guid Index { get; set; } = Guid.NewGuid();
        public bool Deleted { get; set; }
        public bool Added { get; set; }
        public T Model { get; set; }
        }


        Customer Model



        The customer model:



        public class Customer
        {
        [Display(Name ="Id")]
        public int Id { get; set; }

        [StringLength(20, MinimumLength = 1)]
        [Required]
        [Display(Name ="First Name")]
        public string FirstName { get; set; }

        [StringLength(20, MinimumLength = 1)]
        [Required]
        [Display(Name ="Last Name")]
        public string LastName { get; set; }

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


        Index.cshtml View



        The Index view is responsible to render List<Trackable<Customer>>. When rendering each record, we use RowTemplate view. The same view which we use when adding new item.



        In this view, we have a submit button for save and a button for adding new rows which calls Create action using ajax.



        Here is Index view:



        @model IEnumerable<Trackable<Customer>>
        <h2>Index</h2>
        <form method="post" action="Index">
        <p>
        <button id="create">New Customer</button>
        <input type="submit" value="Save All">
        </p>
        <table class="table" id="data">
        <thead>
        <tr>
        <th>
        Delete
        </th>
        <th>
        @Html.DisplayNameFor(x => x.Model.FirstName)
        </th>
        <th>
        @Html.DisplayNameFor(x => x.Model.LastName)
        </th>
        <th>
        @Html.DisplayNameFor(x => x.Model.Email)
        </th>
        </tr>
        </thead>
        <tbody>
        @foreach (var item in Model)
        {
        await Html.RenderPartialAsync("RowTemplate", item);
        }
        </tbody>
        </table>
        </form>

        @section Scripts{
        <script>
        $(function () {
        $('#create').click(function (e) {
        e.preventDefault();
        $.ajax({
        url: 'Create',
        method: 'Get',
        success: function (data) {
        $('#data tbody tr:last-child').after(data);
        },
        error: function (e) { alert(e); }
        });
        });
        });
        </script>
        }


        RowTemplate.cshtml View



        This view is responsible to render a customer record. In this view, we first render the Index in a hidden, then set a prefix [index] for the fields and then render the fields, including index again, added, deleted and model id:



        Here is RowTemplate View:



        @model Trackable<Customer>
        <tr>
        <td>
        @Html.HiddenFor(x => x.Index)
        @{Html.ViewData.TemplateInfo.HtmlFieldPrefix = $"[{Model.Index}]";}
        @Html.HiddenFor(x => x.Index)
        @Html.HiddenFor(x => x.Model.Id)
        @Html.HiddenFor(x => x.Added)
        @Html.CheckBoxFor(x => x.Deleted)
        </td>
        <td>
        @Html.EditorFor(x => x.Model.FirstName)
        @Html.ValidationMessageFor(x => x.Model.FirstName)
        </td>
        <td>
        @Html.EditorFor(x => x.Model.LastName)
        @Html.ValidationMessageFor(x => x.Model.LastName)
        </td>
        <td>
        @Html.EditorFor(x => x.Model.Email)
        @Html.ValidationMessageFor(x => x.Model.Email)
        </td>
        </tr>


        CustomerController



        public class CustomerController : Controller
        {
        private static List<Customer> list;
        }


        It will have the following actions.



        [GET] Index Action



        In this action you can load data from database and shape it to a List<Trackable<Customer>> and pass to the Index View:



        [HttpGet]
        public IActionResult Index()
        {
        if (list == null)
        {
        list = Enumerable.Range(1, 5).Select(x => new Customer()
        {
        Id = x,
        FirstName = $"A{x}",
        LastName = $"B{x}",
        Email = $"A{x}@B{x}.com"
        }).ToList();
        }
        var model = list.Select(x => new Trackable<Customer>(x)).ToList();
        return View(model);
        }


        [GET] Create Action



        This action is responsible to returning new row template. It will be called by a button in Index View using ajax:



        [HttpGet]
        public IActionResult Create()
        {
        var model = new Trackable<Customer>(new Customer()) { Added = true };
        return PartialView("RowTemplate", model);
        }


        [POST] Index Action



        This action is responsible for receiving the tracked item from client and save them. The model which it receives is List<Trackable<Customer>>. It first strips the validation error messages for deleted rows. Then removes those which are both deleted and added. Then checks if model state is valid, tries to apply changes on data source.



        Items having Deleted property as true are deleted, items having Added as true and Deleted as false are new items, and rest of items are edited. Then without needing to load all items from database, just using a for loop, call db.Entry for each item and set their states and finally save changes.



        [HttpPost]
        public IActionResult Index(List<Trackable<Customer>> model)
        {
        //Cleanup model errors for deleted rows
        var deletedIndexes = model.
        Where(x => x.Deleted).Select(x => $"[{x.Index}]");
        var modelStateDeletedKeys = ModelState.Keys.
        Where(x => deletedIndexes.Any(d => x.StartsWith(d)));
        modelStateDeletedKeys.ToList().ForEach(x => ModelState.Remove(x));

        //Removing rows which are added and deleted
        model.RemoveAll(x => x.Deleted && x.Added);

        //If model state is not valid, return view
        if (!ModelState.IsValid)
        return View(model);

        //Deleted rows
        model.Where(x => x.Deleted && !x.Added).ToList().ForEach(x =>
        {
        var i = list.FindIndex(c => c.Id == x.Model.Id);
        if (i >= 0)
        list.RemoveAt(i);
        });

        //Added rows
        model.Where(x => !x.Deleted && x.Added).ToList().ForEach(x =>
        {
        list.Add(x.Model);
        });

        //Edited rows
        model.Where(x => !x.Deleted && !x.Added).ToList().ForEach(x =>
        {
        var i = list.FindIndex(c => c.Id == x.Model.Id);
        if (i >= 0)
        list[i] = x.Model;
        });

        //Reditect to action index
        return RedirectToAction("Index");
        }





        share|improve this answer




























          3














          It's not good idea to use server resources to track changes in such scenarios. In scenarios like shopping basket, list or batch editing it's better track changes at client-side.



          Your requirement to get Views generated at server-side doesn't mean you need to track changes in DbContext. Get the index view and create view from server, but track changes on client. Then to save, post all data to the server to save changes based on the tracking info that you have.



          The mechanism for client-side change tracking depends to the requirement and the scenario, for example you can track changes using html inputs, you can track changes using cookie, you can track changes using javascript objects in browser memory like angular scenarios.



          Here is this post I'll show an example using html inputs and model binding. To learn more about this topic, take a look at this article by Phill Haack: Model Binding To A List.



          Example



          In the following example I describe a list editing scenario for a list of customers. To make it simple, I suppose:




          • You have a list of customers which you are going to edit at client. You may want to add, edit or delete items.

          • When adding new item, the row template for new row should come from server.

          • When deleting, you mark an item as deleted by clicking on a checkbox on the row.

          • When adding/editing you want to show validation errors near the cells.

          • You want to save changes at the end, by click on Save button.


          To implement above scenario Then you need to create following models, actions and views:



          Trackable<T> Model



          This class is a model which helps us in client side tracking and list editing:



          public class Trackable<T>
          {
          public Trackable() { }
          public Trackable(T model) { Model = model; }
          public Guid Index { get; set; } = Guid.NewGuid();
          public bool Deleted { get; set; }
          public bool Added { get; set; }
          public T Model { get; set; }
          }


          Customer Model



          The customer model:



          public class Customer
          {
          [Display(Name ="Id")]
          public int Id { get; set; }

          [StringLength(20, MinimumLength = 1)]
          [Required]
          [Display(Name ="First Name")]
          public string FirstName { get; set; }

          [StringLength(20, MinimumLength = 1)]
          [Required]
          [Display(Name ="Last Name")]
          public string LastName { get; set; }

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


          Index.cshtml View



          The Index view is responsible to render List<Trackable<Customer>>. When rendering each record, we use RowTemplate view. The same view which we use when adding new item.



          In this view, we have a submit button for save and a button for adding new rows which calls Create action using ajax.



          Here is Index view:



          @model IEnumerable<Trackable<Customer>>
          <h2>Index</h2>
          <form method="post" action="Index">
          <p>
          <button id="create">New Customer</button>
          <input type="submit" value="Save All">
          </p>
          <table class="table" id="data">
          <thead>
          <tr>
          <th>
          Delete
          </th>
          <th>
          @Html.DisplayNameFor(x => x.Model.FirstName)
          </th>
          <th>
          @Html.DisplayNameFor(x => x.Model.LastName)
          </th>
          <th>
          @Html.DisplayNameFor(x => x.Model.Email)
          </th>
          </tr>
          </thead>
          <tbody>
          @foreach (var item in Model)
          {
          await Html.RenderPartialAsync("RowTemplate", item);
          }
          </tbody>
          </table>
          </form>

          @section Scripts{
          <script>
          $(function () {
          $('#create').click(function (e) {
          e.preventDefault();
          $.ajax({
          url: 'Create',
          method: 'Get',
          success: function (data) {
          $('#data tbody tr:last-child').after(data);
          },
          error: function (e) { alert(e); }
          });
          });
          });
          </script>
          }


          RowTemplate.cshtml View



          This view is responsible to render a customer record. In this view, we first render the Index in a hidden, then set a prefix [index] for the fields and then render the fields, including index again, added, deleted and model id:



          Here is RowTemplate View:



          @model Trackable<Customer>
          <tr>
          <td>
          @Html.HiddenFor(x => x.Index)
          @{Html.ViewData.TemplateInfo.HtmlFieldPrefix = $"[{Model.Index}]";}
          @Html.HiddenFor(x => x.Index)
          @Html.HiddenFor(x => x.Model.Id)
          @Html.HiddenFor(x => x.Added)
          @Html.CheckBoxFor(x => x.Deleted)
          </td>
          <td>
          @Html.EditorFor(x => x.Model.FirstName)
          @Html.ValidationMessageFor(x => x.Model.FirstName)
          </td>
          <td>
          @Html.EditorFor(x => x.Model.LastName)
          @Html.ValidationMessageFor(x => x.Model.LastName)
          </td>
          <td>
          @Html.EditorFor(x => x.Model.Email)
          @Html.ValidationMessageFor(x => x.Model.Email)
          </td>
          </tr>


          CustomerController



          public class CustomerController : Controller
          {
          private static List<Customer> list;
          }


          It will have the following actions.



          [GET] Index Action



          In this action you can load data from database and shape it to a List<Trackable<Customer>> and pass to the Index View:



          [HttpGet]
          public IActionResult Index()
          {
          if (list == null)
          {
          list = Enumerable.Range(1, 5).Select(x => new Customer()
          {
          Id = x,
          FirstName = $"A{x}",
          LastName = $"B{x}",
          Email = $"A{x}@B{x}.com"
          }).ToList();
          }
          var model = list.Select(x => new Trackable<Customer>(x)).ToList();
          return View(model);
          }


          [GET] Create Action



          This action is responsible to returning new row template. It will be called by a button in Index View using ajax:



          [HttpGet]
          public IActionResult Create()
          {
          var model = new Trackable<Customer>(new Customer()) { Added = true };
          return PartialView("RowTemplate", model);
          }


          [POST] Index Action



          This action is responsible for receiving the tracked item from client and save them. The model which it receives is List<Trackable<Customer>>. It first strips the validation error messages for deleted rows. Then removes those which are both deleted and added. Then checks if model state is valid, tries to apply changes on data source.



          Items having Deleted property as true are deleted, items having Added as true and Deleted as false are new items, and rest of items are edited. Then without needing to load all items from database, just using a for loop, call db.Entry for each item and set their states and finally save changes.



          [HttpPost]
          public IActionResult Index(List<Trackable<Customer>> model)
          {
          //Cleanup model errors for deleted rows
          var deletedIndexes = model.
          Where(x => x.Deleted).Select(x => $"[{x.Index}]");
          var modelStateDeletedKeys = ModelState.Keys.
          Where(x => deletedIndexes.Any(d => x.StartsWith(d)));
          modelStateDeletedKeys.ToList().ForEach(x => ModelState.Remove(x));

          //Removing rows which are added and deleted
          model.RemoveAll(x => x.Deleted && x.Added);

          //If model state is not valid, return view
          if (!ModelState.IsValid)
          return View(model);

          //Deleted rows
          model.Where(x => x.Deleted && !x.Added).ToList().ForEach(x =>
          {
          var i = list.FindIndex(c => c.Id == x.Model.Id);
          if (i >= 0)
          list.RemoveAt(i);
          });

          //Added rows
          model.Where(x => !x.Deleted && x.Added).ToList().ForEach(x =>
          {
          list.Add(x.Model);
          });

          //Edited rows
          model.Where(x => !x.Deleted && !x.Added).ToList().ForEach(x =>
          {
          var i = list.FindIndex(c => c.Id == x.Model.Id);
          if (i >= 0)
          list[i] = x.Model;
          });

          //Reditect to action index
          return RedirectToAction("Index");
          }





          share|improve this answer


























            3












            3








            3






            It's not good idea to use server resources to track changes in such scenarios. In scenarios like shopping basket, list or batch editing it's better track changes at client-side.



            Your requirement to get Views generated at server-side doesn't mean you need to track changes in DbContext. Get the index view and create view from server, but track changes on client. Then to save, post all data to the server to save changes based on the tracking info that you have.



            The mechanism for client-side change tracking depends to the requirement and the scenario, for example you can track changes using html inputs, you can track changes using cookie, you can track changes using javascript objects in browser memory like angular scenarios.



            Here is this post I'll show an example using html inputs and model binding. To learn more about this topic, take a look at this article by Phill Haack: Model Binding To A List.



            Example



            In the following example I describe a list editing scenario for a list of customers. To make it simple, I suppose:




            • You have a list of customers which you are going to edit at client. You may want to add, edit or delete items.

            • When adding new item, the row template for new row should come from server.

            • When deleting, you mark an item as deleted by clicking on a checkbox on the row.

            • When adding/editing you want to show validation errors near the cells.

            • You want to save changes at the end, by click on Save button.


            To implement above scenario Then you need to create following models, actions and views:



            Trackable<T> Model



            This class is a model which helps us in client side tracking and list editing:



            public class Trackable<T>
            {
            public Trackable() { }
            public Trackable(T model) { Model = model; }
            public Guid Index { get; set; } = Guid.NewGuid();
            public bool Deleted { get; set; }
            public bool Added { get; set; }
            public T Model { get; set; }
            }


            Customer Model



            The customer model:



            public class Customer
            {
            [Display(Name ="Id")]
            public int Id { get; set; }

            [StringLength(20, MinimumLength = 1)]
            [Required]
            [Display(Name ="First Name")]
            public string FirstName { get; set; }

            [StringLength(20, MinimumLength = 1)]
            [Required]
            [Display(Name ="Last Name")]
            public string LastName { get; set; }

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


            Index.cshtml View



            The Index view is responsible to render List<Trackable<Customer>>. When rendering each record, we use RowTemplate view. The same view which we use when adding new item.



            In this view, we have a submit button for save and a button for adding new rows which calls Create action using ajax.



            Here is Index view:



            @model IEnumerable<Trackable<Customer>>
            <h2>Index</h2>
            <form method="post" action="Index">
            <p>
            <button id="create">New Customer</button>
            <input type="submit" value="Save All">
            </p>
            <table class="table" id="data">
            <thead>
            <tr>
            <th>
            Delete
            </th>
            <th>
            @Html.DisplayNameFor(x => x.Model.FirstName)
            </th>
            <th>
            @Html.DisplayNameFor(x => x.Model.LastName)
            </th>
            <th>
            @Html.DisplayNameFor(x => x.Model.Email)
            </th>
            </tr>
            </thead>
            <tbody>
            @foreach (var item in Model)
            {
            await Html.RenderPartialAsync("RowTemplate", item);
            }
            </tbody>
            </table>
            </form>

            @section Scripts{
            <script>
            $(function () {
            $('#create').click(function (e) {
            e.preventDefault();
            $.ajax({
            url: 'Create',
            method: 'Get',
            success: function (data) {
            $('#data tbody tr:last-child').after(data);
            },
            error: function (e) { alert(e); }
            });
            });
            });
            </script>
            }


            RowTemplate.cshtml View



            This view is responsible to render a customer record. In this view, we first render the Index in a hidden, then set a prefix [index] for the fields and then render the fields, including index again, added, deleted and model id:



            Here is RowTemplate View:



            @model Trackable<Customer>
            <tr>
            <td>
            @Html.HiddenFor(x => x.Index)
            @{Html.ViewData.TemplateInfo.HtmlFieldPrefix = $"[{Model.Index}]";}
            @Html.HiddenFor(x => x.Index)
            @Html.HiddenFor(x => x.Model.Id)
            @Html.HiddenFor(x => x.Added)
            @Html.CheckBoxFor(x => x.Deleted)
            </td>
            <td>
            @Html.EditorFor(x => x.Model.FirstName)
            @Html.ValidationMessageFor(x => x.Model.FirstName)
            </td>
            <td>
            @Html.EditorFor(x => x.Model.LastName)
            @Html.ValidationMessageFor(x => x.Model.LastName)
            </td>
            <td>
            @Html.EditorFor(x => x.Model.Email)
            @Html.ValidationMessageFor(x => x.Model.Email)
            </td>
            </tr>


            CustomerController



            public class CustomerController : Controller
            {
            private static List<Customer> list;
            }


            It will have the following actions.



            [GET] Index Action



            In this action you can load data from database and shape it to a List<Trackable<Customer>> and pass to the Index View:



            [HttpGet]
            public IActionResult Index()
            {
            if (list == null)
            {
            list = Enumerable.Range(1, 5).Select(x => new Customer()
            {
            Id = x,
            FirstName = $"A{x}",
            LastName = $"B{x}",
            Email = $"A{x}@B{x}.com"
            }).ToList();
            }
            var model = list.Select(x => new Trackable<Customer>(x)).ToList();
            return View(model);
            }


            [GET] Create Action



            This action is responsible to returning new row template. It will be called by a button in Index View using ajax:



            [HttpGet]
            public IActionResult Create()
            {
            var model = new Trackable<Customer>(new Customer()) { Added = true };
            return PartialView("RowTemplate", model);
            }


            [POST] Index Action



            This action is responsible for receiving the tracked item from client and save them. The model which it receives is List<Trackable<Customer>>. It first strips the validation error messages for deleted rows. Then removes those which are both deleted and added. Then checks if model state is valid, tries to apply changes on data source.



            Items having Deleted property as true are deleted, items having Added as true and Deleted as false are new items, and rest of items are edited. Then without needing to load all items from database, just using a for loop, call db.Entry for each item and set their states and finally save changes.



            [HttpPost]
            public IActionResult Index(List<Trackable<Customer>> model)
            {
            //Cleanup model errors for deleted rows
            var deletedIndexes = model.
            Where(x => x.Deleted).Select(x => $"[{x.Index}]");
            var modelStateDeletedKeys = ModelState.Keys.
            Where(x => deletedIndexes.Any(d => x.StartsWith(d)));
            modelStateDeletedKeys.ToList().ForEach(x => ModelState.Remove(x));

            //Removing rows which are added and deleted
            model.RemoveAll(x => x.Deleted && x.Added);

            //If model state is not valid, return view
            if (!ModelState.IsValid)
            return View(model);

            //Deleted rows
            model.Where(x => x.Deleted && !x.Added).ToList().ForEach(x =>
            {
            var i = list.FindIndex(c => c.Id == x.Model.Id);
            if (i >= 0)
            list.RemoveAt(i);
            });

            //Added rows
            model.Where(x => !x.Deleted && x.Added).ToList().ForEach(x =>
            {
            list.Add(x.Model);
            });

            //Edited rows
            model.Where(x => !x.Deleted && !x.Added).ToList().ForEach(x =>
            {
            var i = list.FindIndex(c => c.Id == x.Model.Id);
            if (i >= 0)
            list[i] = x.Model;
            });

            //Reditect to action index
            return RedirectToAction("Index");
            }





            share|improve this answer














            It's not good idea to use server resources to track changes in such scenarios. In scenarios like shopping basket, list or batch editing it's better track changes at client-side.



            Your requirement to get Views generated at server-side doesn't mean you need to track changes in DbContext. Get the index view and create view from server, but track changes on client. Then to save, post all data to the server to save changes based on the tracking info that you have.



            The mechanism for client-side change tracking depends to the requirement and the scenario, for example you can track changes using html inputs, you can track changes using cookie, you can track changes using javascript objects in browser memory like angular scenarios.



            Here is this post I'll show an example using html inputs and model binding. To learn more about this topic, take a look at this article by Phill Haack: Model Binding To A List.



            Example



            In the following example I describe a list editing scenario for a list of customers. To make it simple, I suppose:




            • You have a list of customers which you are going to edit at client. You may want to add, edit or delete items.

            • When adding new item, the row template for new row should come from server.

            • When deleting, you mark an item as deleted by clicking on a checkbox on the row.

            • When adding/editing you want to show validation errors near the cells.

            • You want to save changes at the end, by click on Save button.


            To implement above scenario Then you need to create following models, actions and views:



            Trackable<T> Model



            This class is a model which helps us in client side tracking and list editing:



            public class Trackable<T>
            {
            public Trackable() { }
            public Trackable(T model) { Model = model; }
            public Guid Index { get; set; } = Guid.NewGuid();
            public bool Deleted { get; set; }
            public bool Added { get; set; }
            public T Model { get; set; }
            }


            Customer Model



            The customer model:



            public class Customer
            {
            [Display(Name ="Id")]
            public int Id { get; set; }

            [StringLength(20, MinimumLength = 1)]
            [Required]
            [Display(Name ="First Name")]
            public string FirstName { get; set; }

            [StringLength(20, MinimumLength = 1)]
            [Required]
            [Display(Name ="Last Name")]
            public string LastName { get; set; }

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


            Index.cshtml View



            The Index view is responsible to render List<Trackable<Customer>>. When rendering each record, we use RowTemplate view. The same view which we use when adding new item.



            In this view, we have a submit button for save and a button for adding new rows which calls Create action using ajax.



            Here is Index view:



            @model IEnumerable<Trackable<Customer>>
            <h2>Index</h2>
            <form method="post" action="Index">
            <p>
            <button id="create">New Customer</button>
            <input type="submit" value="Save All">
            </p>
            <table class="table" id="data">
            <thead>
            <tr>
            <th>
            Delete
            </th>
            <th>
            @Html.DisplayNameFor(x => x.Model.FirstName)
            </th>
            <th>
            @Html.DisplayNameFor(x => x.Model.LastName)
            </th>
            <th>
            @Html.DisplayNameFor(x => x.Model.Email)
            </th>
            </tr>
            </thead>
            <tbody>
            @foreach (var item in Model)
            {
            await Html.RenderPartialAsync("RowTemplate", item);
            }
            </tbody>
            </table>
            </form>

            @section Scripts{
            <script>
            $(function () {
            $('#create').click(function (e) {
            e.preventDefault();
            $.ajax({
            url: 'Create',
            method: 'Get',
            success: function (data) {
            $('#data tbody tr:last-child').after(data);
            },
            error: function (e) { alert(e); }
            });
            });
            });
            </script>
            }


            RowTemplate.cshtml View



            This view is responsible to render a customer record. In this view, we first render the Index in a hidden, then set a prefix [index] for the fields and then render the fields, including index again, added, deleted and model id:



            Here is RowTemplate View:



            @model Trackable<Customer>
            <tr>
            <td>
            @Html.HiddenFor(x => x.Index)
            @{Html.ViewData.TemplateInfo.HtmlFieldPrefix = $"[{Model.Index}]";}
            @Html.HiddenFor(x => x.Index)
            @Html.HiddenFor(x => x.Model.Id)
            @Html.HiddenFor(x => x.Added)
            @Html.CheckBoxFor(x => x.Deleted)
            </td>
            <td>
            @Html.EditorFor(x => x.Model.FirstName)
            @Html.ValidationMessageFor(x => x.Model.FirstName)
            </td>
            <td>
            @Html.EditorFor(x => x.Model.LastName)
            @Html.ValidationMessageFor(x => x.Model.LastName)
            </td>
            <td>
            @Html.EditorFor(x => x.Model.Email)
            @Html.ValidationMessageFor(x => x.Model.Email)
            </td>
            </tr>


            CustomerController



            public class CustomerController : Controller
            {
            private static List<Customer> list;
            }


            It will have the following actions.



            [GET] Index Action



            In this action you can load data from database and shape it to a List<Trackable<Customer>> and pass to the Index View:



            [HttpGet]
            public IActionResult Index()
            {
            if (list == null)
            {
            list = Enumerable.Range(1, 5).Select(x => new Customer()
            {
            Id = x,
            FirstName = $"A{x}",
            LastName = $"B{x}",
            Email = $"A{x}@B{x}.com"
            }).ToList();
            }
            var model = list.Select(x => new Trackable<Customer>(x)).ToList();
            return View(model);
            }


            [GET] Create Action



            This action is responsible to returning new row template. It will be called by a button in Index View using ajax:



            [HttpGet]
            public IActionResult Create()
            {
            var model = new Trackable<Customer>(new Customer()) { Added = true };
            return PartialView("RowTemplate", model);
            }


            [POST] Index Action



            This action is responsible for receiving the tracked item from client and save them. The model which it receives is List<Trackable<Customer>>. It first strips the validation error messages for deleted rows. Then removes those which are both deleted and added. Then checks if model state is valid, tries to apply changes on data source.



            Items having Deleted property as true are deleted, items having Added as true and Deleted as false are new items, and rest of items are edited. Then without needing to load all items from database, just using a for loop, call db.Entry for each item and set their states and finally save changes.



            [HttpPost]
            public IActionResult Index(List<Trackable<Customer>> model)
            {
            //Cleanup model errors for deleted rows
            var deletedIndexes = model.
            Where(x => x.Deleted).Select(x => $"[{x.Index}]");
            var modelStateDeletedKeys = ModelState.Keys.
            Where(x => deletedIndexes.Any(d => x.StartsWith(d)));
            modelStateDeletedKeys.ToList().ForEach(x => ModelState.Remove(x));

            //Removing rows which are added and deleted
            model.RemoveAll(x => x.Deleted && x.Added);

            //If model state is not valid, return view
            if (!ModelState.IsValid)
            return View(model);

            //Deleted rows
            model.Where(x => x.Deleted && !x.Added).ToList().ForEach(x =>
            {
            var i = list.FindIndex(c => c.Id == x.Model.Id);
            if (i >= 0)
            list.RemoveAt(i);
            });

            //Added rows
            model.Where(x => !x.Deleted && x.Added).ToList().ForEach(x =>
            {
            list.Add(x.Model);
            });

            //Edited rows
            model.Where(x => !x.Deleted && !x.Added).ToList().ForEach(x =>
            {
            var i = list.FindIndex(c => c.Id == x.Model.Id);
            if (i >= 0)
            list[i] = x.Model;
            });

            //Reditect to action index
            return RedirectToAction("Index");
            }






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Jan 6 at 3:23

























            answered Jan 3 at 13:29









            Reza AghaeiReza Aghaei

            64.7k851156




            64.7k851156

























                0














                What about dynamic form(s) with javascript and using type="hidden" or visibility
                and then sending everything at once



                Or using TempData with redirects and reusing that data in other views(form) as input type="hidden"



                Flow:



                Form1 ->



                Controller's Method saves data in TempData and Redirects to Form2 View / Or ViewData and return Form2 View? ->



                Form2 has TempData inserted into the form under hidden inputs ->



                Submit both at once






                share|improve this answer




























                  0














                  What about dynamic form(s) with javascript and using type="hidden" or visibility
                  and then sending everything at once



                  Or using TempData with redirects and reusing that data in other views(form) as input type="hidden"



                  Flow:



                  Form1 ->



                  Controller's Method saves data in TempData and Redirects to Form2 View / Or ViewData and return Form2 View? ->



                  Form2 has TempData inserted into the form under hidden inputs ->



                  Submit both at once






                  share|improve this answer


























                    0












                    0








                    0






                    What about dynamic form(s) with javascript and using type="hidden" or visibility
                    and then sending everything at once



                    Or using TempData with redirects and reusing that data in other views(form) as input type="hidden"



                    Flow:



                    Form1 ->



                    Controller's Method saves data in TempData and Redirects to Form2 View / Or ViewData and return Form2 View? ->



                    Form2 has TempData inserted into the form under hidden inputs ->



                    Submit both at once






                    share|improve this answer














                    What about dynamic form(s) with javascript and using type="hidden" or visibility
                    and then sending everything at once



                    Or using TempData with redirects and reusing that data in other views(form) as input type="hidden"



                    Flow:



                    Form1 ->



                    Controller's Method saves data in TempData and Redirects to Form2 View / Or ViewData and return Form2 View? ->



                    Form2 has TempData inserted into the form under hidden inputs ->



                    Submit both at once







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Jan 3 at 13:49

























                    answered Jan 3 at 13:41









                    JoeltyJoelty

                    11010




                    11010























                        -1














                        Cookie !



                        public class HomeController : Controller
                        {

                        public string Index()
                        {

                        HttpCookie cookie = Request.Cookies["message"];
                        Message message = null;
                        string json = "";

                        if (cookie == null)
                        {
                        message = new Message();
                        json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                        cookie = new HttpCookie("message", json);
                        }
                        Response.Cookies.Add(cookie);
                        return json;
                        }

                        public string CustomerAdded(int id)
                        {
                        HttpCookie cookie = Request.Cookies["message"];
                        Message message = null;
                        string json = "";

                        if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                        {
                        message = new Message();
                        json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                        cookie = new HttpCookie("message", json);
                        }
                        else
                        {
                        json = cookie.Value;
                        message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                        }

                        if (message.Customers == null) message.Customers = new List<int>();
                        if (message.Items == null) message.Items = new List<int>();

                        if (!message.Customers.Contains(id))
                        {
                        message.Customers.Add(id);
                        }


                        json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                        cookie = new HttpCookie("message", json);

                        Response.Cookies.Add(cookie);

                        return json;
                        }


                        public string ItemAdded(int id)
                        {
                        HttpCookie cookie = Request.Cookies["message"];
                        Message message = null;
                        string json = "";

                        if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                        {
                        message = new Message();
                        json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                        cookie = new HttpCookie("message", json);
                        }
                        else
                        {
                        json = cookie.Value;
                        message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                        }
                        if (message.Customers == null) message.Customers = new List<int>();
                        if (message.Items == null) message.Items = new List<int>();

                        if (!message.Items.Contains(id))
                        {
                        message.Items.Add(id);
                        }

                        json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                        cookie = new HttpCookie("message", json);

                        Response.Cookies.Add(cookie);

                        return json;
                        }

                        public string Submit()
                        {
                        HttpCookie cookie = Request.Cookies["message"];
                        Message message = null;
                        string json = "";

                        if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                        {
                        return "no data";
                        }
                        else
                        {
                        json = cookie.Value;
                        message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                        }

                        Response.Cookies["message"].Value = "";
                        Response.Cookies["message"].Expires = DateTime.Now.AddDays(-1);

                        return "Submited";

                        }
                        }


                        Example links




                        • http://localhost:58603/Home/CustomerAdded/1


                        • http://localhost:58603/Home/CustomerAdded/2


                        • http://localhost:58603/Home/Submit



                        enter image description hereenter image description hereenter image description hereenter image description here






                        share|improve this answer




























                          -1














                          Cookie !



                          public class HomeController : Controller
                          {

                          public string Index()
                          {

                          HttpCookie cookie = Request.Cookies["message"];
                          Message message = null;
                          string json = "";

                          if (cookie == null)
                          {
                          message = new Message();
                          json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                          cookie = new HttpCookie("message", json);
                          }
                          Response.Cookies.Add(cookie);
                          return json;
                          }

                          public string CustomerAdded(int id)
                          {
                          HttpCookie cookie = Request.Cookies["message"];
                          Message message = null;
                          string json = "";

                          if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                          {
                          message = new Message();
                          json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                          cookie = new HttpCookie("message", json);
                          }
                          else
                          {
                          json = cookie.Value;
                          message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                          }

                          if (message.Customers == null) message.Customers = new List<int>();
                          if (message.Items == null) message.Items = new List<int>();

                          if (!message.Customers.Contains(id))
                          {
                          message.Customers.Add(id);
                          }


                          json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                          cookie = new HttpCookie("message", json);

                          Response.Cookies.Add(cookie);

                          return json;
                          }


                          public string ItemAdded(int id)
                          {
                          HttpCookie cookie = Request.Cookies["message"];
                          Message message = null;
                          string json = "";

                          if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                          {
                          message = new Message();
                          json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                          cookie = new HttpCookie("message", json);
                          }
                          else
                          {
                          json = cookie.Value;
                          message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                          }
                          if (message.Customers == null) message.Customers = new List<int>();
                          if (message.Items == null) message.Items = new List<int>();

                          if (!message.Items.Contains(id))
                          {
                          message.Items.Add(id);
                          }

                          json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                          cookie = new HttpCookie("message", json);

                          Response.Cookies.Add(cookie);

                          return json;
                          }

                          public string Submit()
                          {
                          HttpCookie cookie = Request.Cookies["message"];
                          Message message = null;
                          string json = "";

                          if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                          {
                          return "no data";
                          }
                          else
                          {
                          json = cookie.Value;
                          message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                          }

                          Response.Cookies["message"].Value = "";
                          Response.Cookies["message"].Expires = DateTime.Now.AddDays(-1);

                          return "Submited";

                          }
                          }


                          Example links




                          • http://localhost:58603/Home/CustomerAdded/1


                          • http://localhost:58603/Home/CustomerAdded/2


                          • http://localhost:58603/Home/Submit



                          enter image description hereenter image description hereenter image description hereenter image description here






                          share|improve this answer


























                            -1












                            -1








                            -1






                            Cookie !



                            public class HomeController : Controller
                            {

                            public string Index()
                            {

                            HttpCookie cookie = Request.Cookies["message"];
                            Message message = null;
                            string json = "";

                            if (cookie == null)
                            {
                            message = new Message();
                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);
                            }
                            Response.Cookies.Add(cookie);
                            return json;
                            }

                            public string CustomerAdded(int id)
                            {
                            HttpCookie cookie = Request.Cookies["message"];
                            Message message = null;
                            string json = "";

                            if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                            {
                            message = new Message();
                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);
                            }
                            else
                            {
                            json = cookie.Value;
                            message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                            }

                            if (message.Customers == null) message.Customers = new List<int>();
                            if (message.Items == null) message.Items = new List<int>();

                            if (!message.Customers.Contains(id))
                            {
                            message.Customers.Add(id);
                            }


                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);

                            Response.Cookies.Add(cookie);

                            return json;
                            }


                            public string ItemAdded(int id)
                            {
                            HttpCookie cookie = Request.Cookies["message"];
                            Message message = null;
                            string json = "";

                            if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                            {
                            message = new Message();
                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);
                            }
                            else
                            {
                            json = cookie.Value;
                            message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                            }
                            if (message.Customers == null) message.Customers = new List<int>();
                            if (message.Items == null) message.Items = new List<int>();

                            if (!message.Items.Contains(id))
                            {
                            message.Items.Add(id);
                            }

                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);

                            Response.Cookies.Add(cookie);

                            return json;
                            }

                            public string Submit()
                            {
                            HttpCookie cookie = Request.Cookies["message"];
                            Message message = null;
                            string json = "";

                            if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                            {
                            return "no data";
                            }
                            else
                            {
                            json = cookie.Value;
                            message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                            }

                            Response.Cookies["message"].Value = "";
                            Response.Cookies["message"].Expires = DateTime.Now.AddDays(-1);

                            return "Submited";

                            }
                            }


                            Example links




                            • http://localhost:58603/Home/CustomerAdded/1


                            • http://localhost:58603/Home/CustomerAdded/2


                            • http://localhost:58603/Home/Submit



                            enter image description hereenter image description hereenter image description hereenter image description here






                            share|improve this answer














                            Cookie !



                            public class HomeController : Controller
                            {

                            public string Index()
                            {

                            HttpCookie cookie = Request.Cookies["message"];
                            Message message = null;
                            string json = "";

                            if (cookie == null)
                            {
                            message = new Message();
                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);
                            }
                            Response.Cookies.Add(cookie);
                            return json;
                            }

                            public string CustomerAdded(int id)
                            {
                            HttpCookie cookie = Request.Cookies["message"];
                            Message message = null;
                            string json = "";

                            if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                            {
                            message = new Message();
                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);
                            }
                            else
                            {
                            json = cookie.Value;
                            message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                            }

                            if (message.Customers == null) message.Customers = new List<int>();
                            if (message.Items == null) message.Items = new List<int>();

                            if (!message.Customers.Contains(id))
                            {
                            message.Customers.Add(id);
                            }


                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);

                            Response.Cookies.Add(cookie);

                            return json;
                            }


                            public string ItemAdded(int id)
                            {
                            HttpCookie cookie = Request.Cookies["message"];
                            Message message = null;
                            string json = "";

                            if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                            {
                            message = new Message();
                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);
                            }
                            else
                            {
                            json = cookie.Value;
                            message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                            }
                            if (message.Customers == null) message.Customers = new List<int>();
                            if (message.Items == null) message.Items = new List<int>();

                            if (!message.Items.Contains(id))
                            {
                            message.Items.Add(id);
                            }

                            json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
                            cookie = new HttpCookie("message", json);

                            Response.Cookies.Add(cookie);

                            return json;
                            }

                            public string Submit()
                            {
                            HttpCookie cookie = Request.Cookies["message"];
                            Message message = null;
                            string json = "";

                            if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                            {
                            return "no data";
                            }
                            else
                            {
                            json = cookie.Value;
                            message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
                            }

                            Response.Cookies["message"].Value = "";
                            Response.Cookies["message"].Expires = DateTime.Now.AddDays(-1);

                            return "Submited";

                            }
                            }


                            Example links




                            • http://localhost:58603/Home/CustomerAdded/1


                            • http://localhost:58603/Home/CustomerAdded/2


                            • http://localhost:58603/Home/Submit



                            enter image description hereenter image description hereenter image description hereenter image description here







                            share|improve this answer














                            share|improve this answer



                            share|improve this answer








                            edited Jan 4 at 10:18

























                            answered Jan 4 at 10:06









                            Mohamed ElrashidMohamed Elrashid

                            9931518




                            9931518






























                                draft saved

                                draft discarded




















































                                Thanks for contributing an answer to Stack Overflow!


                                • Please be sure to answer the question. Provide details and share your research!

                                But avoid



                                • Asking for help, clarification, or responding to other answers.

                                • Making statements based on opinion; back them up with references or personal experience.


                                To learn more, see our tips on writing great answers.





                                Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                                Please pay close attention to the following guidance:


                                • Please be sure to answer the question. Provide details and share your research!

                                But avoid



                                • Asking for help, clarification, or responding to other answers.

                                • Making statements based on opinion; back them up with references or personal experience.


                                To learn more, see our tips on writing great answers.




                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function () {
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53956235%2fstoring-data-between-requests-in-dbcontext%23new-answer', 'question_page');
                                }
                                );

                                Post as a guest















                                Required, but never shown





















































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown

































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown







                                Popular posts from this blog

                                Monofisismo

                                Angular Downloading a file using contenturl with Basic Authentication

                                Olmecas