I would like to know if there is a way to access 2 rows of a model table in my view for Edit. As I would like to edit one of the record only in the view, I cannot load a list that contains the 2 methods at the GET edit controller method. Is there any way that allows my to query for the next record inside my view?
My controller methods
// GET: Record/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Record record = db.Records.Find(id);
if (record == null)
{
return HttpNotFound();
}
return View(record);
}
// POST: Record/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "RecordID,BeforeDate,AfterDate,ShiftIDBefore,ShiftIDAfter,EmpAID,EmpBID,Reason,Self_interchange,Status")] Record record, string button)
{
//Codes for modifying the record
}
My view
#model Interchangesys.Models.Record
#{
ViewBag.Title = "Edit";
}
<h2>Process interchange record</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.RecordID)
<dt>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" name="button" value="Accept" class="btn btn-success btn-lg" style="width:200px;height:50px;font-size: 25px;" />
<input type="submit" name="button" value="Reject" class="btn btn-danger btn-lg" style="width:200px;height:50px;font-size: 25px;" />
</div>
</div>
</dt>
<!--rest of the codes that shows the content of record with the same id and content of record with the id +1-->
}
Thanks in advance.
Well, if I understand you correctly, you just want to show info about two records in your view.
You could pass the the next record to be available in your View via ViewBag like this:
Record record = db.Records.Find(id);
Record next = db.Records.Find(id + 1);
ViewBag.Next = next;
return(record);
And then in your view you access it like:
#{
Record next = (Record)ViewBag.Next;
}
And finally, to use it in a view, you would do something like this:
#Html.Hidden("NextRecordID", next.RecordID) // You can't use #Html.HiddenFor
Alternatively, you could pass both records via ViewBag, like this:
Record record = db.Records.Find(id);
Record next = db.Records.Find(id + 1);
ViewBag.Record = record
ViewBag.Next = next;
return(); // There will be no model for your view.
And then prefix the fields in the view like this:
#{
Record record = (Record)ViewBag.Record;
Record next = (Record)ViewBag.Next;
}
#Html.Hidden("record.RecordID", record.RecordID)
#Html.Hidden("next.RecordID", next.RecordID)
So you get them both in your Edit function like this:
public ActionResult Edit([Bind(Prefix = "record", Include = "...")] Record record, [Bind(Prefix = "next", Include = "...")] Record next, string button)
Related
I want to create a view where I select "checkbox" an Item and then click the button "Next" which will send a post request with the Id of the selected Item so I can use its Id to redirect to an action that requires an Id
This is the View
#model IEnumerable<Game>;
#{
ViewBag.Title = "Game Select List";
}
<form method="post">
<div class="card-body sm">
<div class="card-deck">
#foreach (var game in Model)
{
var photoPath = "~/image/" + (game.PhotoUrl ?? "noImage.png");
<div id="crta" class="card m-3" style="min-width: 18rem; max-width:30%;">
<div class="card-header">
<h3>#game.Name</h3>
<h1 hidden>#game.Id</h1>
</div>
<img class="card-img-top " height="400" title="#game.Name"
src="#photoPath" asp-append-version="true">
<div class="card-footer">
<label asp-for="#game.IsSelected"></label>
<input type="checkbox" value="#game.IsSelected" asp-for="#game.IsSelected"/>
</div>
</div>
}
</div>
<button type="submit">Next</button>
</div>
</form>
And this is the action
[HttpGet]
public IActionResult SelectGame()
{
var model = gameRepository.GetAllGames();
return View(model);
}
[HttpPost]
public IActionResult SelectGame(int id)
{
var game = gameRepository.GetById(id);
if(game==null)
{
return View("NotFound");
}
if(ModelState.IsValid)
{
if(game.IsSelected==true)
{
return RedirectToAction("details", new { id = game.Id });
}
}
return View(game);
}
It returns null.
I tried
<a asp-controller="home" asp-action="details" asp-route-id="#game.id">Select</a>
But I don't like how each item will get its "select button" I don't know how to call the game.id outside the foreach loop to use it instead of the post request!
The final result would be as this: You click a button then a modal with the game list will get call then you select a game from it and then you click next which will redirect you to the next modal(<= this view needs an id)
I want to create a view where I select "checkbox" an Item and then
click the button "Next" which will send a post request with the Id of
the selected Item so I can use its Id to redirect to an action that
requires an Id
For this situation, you can trigger a js event when you click the submit button. In this event, you can add a hidden input control, assign the name to the id(the same as the parameter name in SelectGame action), and assign the value to the id of the currently selected picture Id.
I don't know how to call the game.id outside the foreach loop to use
it instead of the post request!
You can add a custom attribute data-id to each checkbox and assign it to #game.Id, so that in jquery, you can cycle through all the checkboxes to determine which one is selected, and get id value from the selected checkbox data-id attribute to hidden input control value.
And it should be noted that you should ensure that the user can only select one of the checkboxes, not multiple selections, otherwise the last id in the selected picture will always be passed.
In your post SelectGame action, the last sentence of return View(game); will be an error, because the game is currently a single data of Game object, and the view received by the SelectGame corresponding model is a list object:IEnumerable<Game>.
Just try the following code, which solve all these issues:
[HttpPost]
public IActionResult SelectGame(int id)
{
var game = gameRepository.GetById(id);
if(game==null)
{
return View("NotFound");
}
if(ModelState.IsValid)
{
if(game.IsSelected==true)
{
return RedirectToAction("details", new { id = game.Id });
}
}
List<Game> games = new List<Game>() { game };
return View(games);
}
View:
#model IEnumerable<Game>;
#{
ViewBag.Title = "Game Select List";
}
<form method="post">
<div class="card-body sm">
<div class="card-deck">
#foreach (var game in Model)
{
var photoPath = "~/OrderImage/" + (game.PhotoUrl ?? "noImage.png");
<div id="crta" class="card m-3" style="min-width: 18rem; max-width:30%;">
<div class="card-header">
<h3>#game.Name</h3>
<h1 hidden>#game.Id</h1>
</div>
<img class="card-img-top " height="400" title="#game.Name"
src="#photoPath" asp-append-version="true">
<div class="card-footer">
<label asp-for="#game.IsSelected"></label>
<input type="checkbox" asp-for="#game.IsSelected" data-id="#game.Id" />
</div>
</div>
}
</div>
<button type="submit" onclick='submitform()'>Next</button>
</div>
</form>
#section Scripts{
<script>
$('input[type="checkbox"]').on('change', function () {
if ($(this).is(':checked')) {
$('input[type="checkbox"]').not(this).prop('checked', false);//allow only one checkbox can be selected.
}
});
function submitform() {
var id = 0;
$('input[type="checkbox"]').each(function () {
if ($(this).is(':checked')) {
id = $(this).attr("data-id");// get selected image Id to variable id.
}
});
$('<input type="hidden" name="id"/>').val(id).appendTo('form'); // add hidden input to form to pass selected image Id.
$("form").submit();
}
</script>
}
Here is the test result:
I am looping through an IEnumerable of my model:
#model IEnumerable<Testing.Models.ProductItem>
#{
ViewBag.Title = "Buy Products";
}
<div class="row">
#foreach (var product in Model)
{
using (Html.BeginForm())
{
#Html.HiddenFor(Model => product)
... More Controls and stuff...
<input type="submit" value="Add To Kart" class="btn btn-info">
}
}
</div>
and on submit I want to pass the selected instance of my model back to my controller:
[HttpPost]
public ActionResult Index(ProductItem product)
{
... Do Stuff ...
return View();
}
However I have tried a few things but always seem to be getting null passed into the controller... Please could someone please help point me in the right direction?
EDIT
I dont actually need to the full model instance as I can get this within the controller from the ID - so I have tried the following:
#model IEnumerable<Testing.Models.ProductItem>
#{
ViewBag.Title = "Buy Products";
}
<div class="row">
#foreach (var product in Model)
{
using (Html.BeginForm())
{
#Html.HiddenFor(Model => product.ID)
#Html.TextBox("qty", "1", htmlAttributes: new { #style = "width: 30px;" })
... More Controls and stuff...
<input type="submit" value="Add To Kart" class="btn btn-info">
}
}
</div>
which posts to the controller:
[HttpPost]
public ActionResult Index([Bind(Include = "ID")] int? ID, [Bind(Include = "qty")] int? qty)
{
return null;
}
The textbox is not part of the model as it is user input - this value is passed nicely into the actions parameter, however I am still getting a null for the ID in the HiddenFor control. Is this to do with the naming of the control? I dont seem to be able to add a name to the HiddenFor control.
I know this puts a different light on the original question but I am hoping you may still be able to help.
I take the note about the BeginForm being inside the loop - creating for each item in the list... Is there an easy alternative to this (note I haven't tried anything yet).
It sounds like you're trying to use HiddenFor on a complex type and that won't work. You'll need to use a property of ProductItem like ProductId or something like that, which will most likely be an int or Guid.
Now that you have cleared up the complex binding to a simple field, you'll notice that your name is being set to product.id and that is why it is always null in your controller. You can't override the name attribute with Hidden for, so you'll want to change your code to:
#foreach (var product in Model)
{
using (Html.BeginForm())
{
#Html.Hidden("ID", product.ID)
#Html.TextBox("qty", "1", htmlAttributes: new { #style = "width: 30px;" })
<input type="submit" value = "Add To Kart" class="btn btn-info">
}
}
I have managed to arrive at my desired functionality (rightly or wrongly) with the following:
#model List<ShoppingKartTest.Models.ProductItem>
#{
ViewBag.Title = "Buy Products";
}
#foreach (var item in Model)
{
using (Html.BeginForm())
{
<input type="hidden" value="#item.ID" name="ID" />
#Html.TextBox("qty", "1", new { #style = "width: 30px;" })
<input type="submit" value="Add To Kart" class="btn btn-info">
}
}
Which correctly submits the Hidden ID and the contents of the Textbox to the Controller Action:
[HttpPost]
public ActionResult Index(int ID, int qty)
{
//... Do stuff with parameters...
return View();
}
I would be interested to hear any comments on this. I know that I was told above that I shouldn't have my BeginForm within the loop... But it just works for me.
Instead of Model => product.Id, try p=> product.Id
#model IEnumerable<Testing.Models.ProductItem>
#{
ViewBag.Title = "Buy Products";
}
<div class="row">
using (Html.BeginForm())
{
#foreach (var product in Model)
{
#Html.HiddenFor(p => product.ID)
#Html.TextBox("qty", "1", htmlAttributes: new { #style = "width:
30px;" })
... More Controls and stuff...
}
<input type="submit" value="Add To Kart" class="btn btn-info">
}
</div>
I'm writing an MVC app which ends up accessing a SQL database. On my edit page, I previously had every item available to be edited that is in the model. Recently I was asked to no longer allow the user to edit the primary keys. I did a quick change to change the primary key fields (in this example, there are 2 of them) from an EditorFor to a DisplayFor. The new view is this:
#model App.Data.Item
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Item</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
<strong>ID:</strong>
<div class="col-md-10">
<p>#Html.DisplayFor(model => model.ID)</p>
</div>
</div>
<div class="form-group">
<strong>ID2:</strong>
<div class="col-md-10">
<p>#Html.DisplayFor(model => model.ID2)</p>
</div>
</div>
<div class="form-group">
<strong>Description:</strong>
<div class="col-md-10">
<p>#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)</p>
</div>
</div>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-primary">Submit <i class="fa fa-caret-right"></i></button>
</div>
</div>
</div>
}
It used to work with the full editing. Now the data is displayed properly, as expected. However, when submit is pressed, Null values are passed back to the controller for the values that are displayed.
These are the edit functions in my controller. ItemService.Edit() just saves the data to the server. It works correctly.
[Authorize]
public ActionResult Edit(string id)
{
if (id == null)
{
//return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
string[] vals = id.Split('|');
ItemAttribute itemAttribute = itemAttributeService.Find(int.Parse(vals[0]), vals[1]);
if (itemAttribute == null)
{
return HttpNotFound();
}
return View(itemAttribute);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult Edit([Bind(Include = "ID,ID2,Description")]
Item item)
{
if (item.Description == null)
{
ModelState.AddModelError("Description", "Description cannot be null.");
}
if (ModelState.IsValid)
{
itemService.Edit(item);
return RedirectToAction("../Home/Index/");
}
return View(item);
}
Lastly, my data model:
public class Item
{
public int ID { get; set; }
public string ID2 { get; set; }
public string Description { get; set; }
}
Why is the data no longer being passed back to the second function, and how do I get it to pass correctly so that I can save it to the database?
You need to have an input element generated for the items that you want returned. Currently, you are only displaying two of your model elements and have no associated input with them. As a result, they will not be POSTed back to the server.
To get them to post to the server and not "editable" from a textbox, add a Html.HiddenFor() helper for each of the items that you need returned.
<div class="form-group">
<strong>ID:</strong>
<div class="col-md-10">
<p>#Html.DisplayFor(model => model.ID)</p>
<p>#Html.HiddenFor(model => model.ID)</p>
</div>
</div>
<div class="form-group">
<strong>ID2:</strong>
<div class="col-md-10">
<p>#Html.DisplayFor(model => model.ID2)</p>
<p>#Html.HiddenFor(model => model.ID)</p>
</div>
</div>
However, keep in mind that anyone can edit the HTML using Firebug or Chrome console tools and submit any value that they want for any field, even if you did not want to change it. Be sure that when you are persisting the changes to the database, you are NOT including these fields as part of the update.
Try this, just before this line of code:
#Html.DisplayFor(model => model.ID)
put in this for debugging:
#Html.LabelFor(model => model.ID)
Tell us what you see...
If you see the label then check your controller, in particular the parameter it takes on the post. It should take and Item of type ITEM per your model.
Before the controller receives the data MVC has to try to populate the data... It converts name/value pairs to model types with values secretly. If you don't see any data after you are in the controller it's usually because the names were not found!
Form-
#using IEnumerable<Myapplication.Models.CardModel>
#foreach(var item in Model)
{
<form method="post" action="/Upload/EditCard/?cardID=#item.cardID" enctype="multipart/form-data">
<h3>
Change Title-
</h3>
<div class="display-field">
#Html.HiddenFor(m => item.cardTitle)
#Html.TextBoxFor(cardTitle => item.cardTitle)
</div>
<img src="#item.cardFilePath" />
<input type="submit">
</form>
}
Method-
[HttpPost]
public void EditCard(CardModel card, HttpPostedFileBase file) {}
Where in form I am sending values through this form, and cardID is sent in form's url parameter.
For other value like cardTitle is coming null in EditCard Method.
How do I get this value using repeater?
However when data is not IEnumerable type , then I was able to send value through form directly as-
#using Myapplication.Models.CardModel
<form method="post" action="/Upload/EditCard/?cardID=#Model.cardID" enctype="multipart/form-data">
<h3>
Change Title-
</h3>
<div class="display-field">
#Html.HiddenFor(m => m.cardTitle)
#Html.TextBoxFor(m=> m.cardTitle)
</div>
<img src="#Model.cardFilePath" />
<input type="submit">
</form>
}
but In case of repeater values don't come at method. As soon as i change values of title, or anything else, The values comes old one.
From the pictures-
And server code-
As you can see from 1st picture that, Form is editable for one record from the list. As soon as I change something in i.e. Title to --> 2nd Upload to 2nd Uploadsssss
Then this values is null at server side. I mean this form don't send values of it.
Note-
However I can send values in URL through parameters. But if I do change in something like Title or aboutCard model value. The form keeps sending only those values which has come by default while the form was rendered.
Rather than using foreach use for loop. To apply indexing you would need to convert Model to List
#{ var list=Model.ToList();)
#for(var i = 1;i <= list.Count();i++)
{
<form method="post" action="/Upload/EditCard/?cardID=#list[i].cardID" enctype="multipart/form-data">
<h3>
Change Title-
</h3>
<div class="display-field">
#Html.HiddenFor(m => list[i].cardTitle)
#Html.TextBoxFor(m=> list[i].cardTitle)
</div>
<img src="#list[i].cardFilePath" />
<input type="submit">
</form>
}
Update..
I have tested same but doesnt work. I have created workaround check if that helps.
In controller get title using cardId
var title=Request["[" + (model.cardTitle)+ "].cardTitle"];
Same thing can be done for other properties for model.
Note that i have changed for loop, index now starts with 1
You need to change IEnumerable to IList, and then you'll be able to bind Model correctly by index.
See this working exaple.
View Page
#model IList<MvcApplication3.Models.CardModel>
#using (Html.BeginForm("EditCard", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => Model[i].CardID)
#Html.TextBoxFor(cardTitle => Model[i].CardTitle)
<img src="#Model[i].CardFilePath" />
<input type="file" name="files">
}
<br>
<input type="submit" value="Upload File to Server">
}
Controller
[HttpPost]
public void EditCard(IList<CardModel> cm, IEnumerable<HttpPostedFileBase> files)
{
string strfile = string.Empty;
string cardTitle = string.Empty;
if (files != null)
{
foreach (var file in files) //loop through to get posted file
{
strfile = file.FileName;
}
}
if (cm != null)
{
foreach (var item in cm) //loop through to get CardModel fields
{
cardTitle = item.CardTitle;
}
}
}
I dont know if this helps in solving your problem. I tried this and it works really well.
The Model
public class CardFileModel
{
public int CardId { get; set; }
public string Name { get; set; }
public HttpPostedFileBase File { get; set; }
}
Index Action
public ActionResult Index()
{
List<CardFileModel> cards = new List<CardFileModel>
{
new CardFileModel{ CardId =1 , Name="Card1" },
new CardFileModel{ CardId =2 , Name="Card2" }
};
return View(cards);
}
Home View
#model List<MVC3Stack.Models.CardFileModel>
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
#for (int i = 0; i < Model.Count; i++)
{
using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div>
<div>
#Html.TextBoxFor(x => x[i].CardId)
</div>
<div>
#Html.TextBoxFor(x => x[i].Name)
</div>
#Html.TextBoxFor(x => x[i].File, new { type = "file" })
<input type="submit" value="submit" id="submit" />
</div>
}
}
index post action
[HttpPost]
public ActionResult Index(List<CardFileModel> card)
{
//Access your card file from the list.
var name = card.FirstOrDefault().Name;
return View();
}
And here are the results in the quick watch
Hope this helps.
I am using asp.net mvc5 app and I have partialView which I am using for both add and delete operations and I have separate actionResult to deal each requested according that is fire by jQuery Ajax functions.... I got figure problem when I try to edit record. it pick right record and display in screen in HttpGet actionResult but when I submit record with updated field, it doesn't update that in database and is because it doesn't send recordID {PK} back to action in controller unless I use hidden value for primary key in view... however with this I can't create new record!
partial View
#model DatabaseLayer.TableMappings.FeeZone
<script>
function CancelPage() {
$.ajax({
type: "POST",
url: "/Qualification/FeeZoneHome",
dataType: 'json',
success: function (jsonData) {
window.location = jsonData.redirectUrl;
},
error: function (error) {
}
});
}
</script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>FeeZone</h4>
<hr />
#Html.ValidationSummary(true)
<!--<div class="form-group">
<div class="col-md-10">
#Html.HiddenFor(model => model.FeeZoneID)
</div>
</div>-->
<div class="form-group">
#Html.LabelFor(model => model.FeeZoneDescription, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FeeZoneDescription)
#Html.ValidationMessageFor(model => model.FeeZoneDescription)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default">
<input type="button" value="Cancel" class="btn btn-default" onclick="CancelPage();" />
</div>
</div>
</div>
}
controller Method
[HttpGet]
public ActionResult EditFreeZoneByID(string FreeZoneID)
{
int numID = Convert.ToInt32(FreeZoneID);
return PartialView("Partial_CreateNewFreeZone", Q_UOF.GetFreeZoneByID(numID));
}
[HttpPost]
public ActionResult EditFreeZoneByID(FeeZone obj)
{
try
{
if (ModelState.IsValid)
{
Q_UOF.EditSubject(obj);
}
}
catch (DataException)
{
ModelState.AddModelError("FeeZone", "Unable to Save Changes.");
}
return RedirectToAction("FreeZone");
}
You need to send the Primary Key value from your form to your Action method so that it can update the proper record. For this you definitly need to keep the ID in the form in a hidden field.
I am not sure what you mean by "You can not create new record if you keep the hidden variable". I do not think it is going to be a problem. You can use the same HttpPost action method to save New record and update an existing record. You may simply check the value of your primary key property and If it is 0, Add a new record , else update existing record
[HttpPost]
public ActionResult EditFreeZoneByID(FeeZone obj)
{
if(obj.FreeZoneID==0)
{
//This is a new record. Save it
}
else
{
//Update an existing record now.
}
// to do : Return somethign valid or redirect
}