Creating a form and binding to a class without a datatabase - c#

I'm new to learning MVC and could really use some help- as I'm trying to move on from ASP.NET Web Forms. I have a custom web API for all my database transactions that is very efficient and reliable. I've recoded it in .NET Core.
My problem is that I'm finding most of the model binding examples pertaining to MVC are composed of Entity Framework examples. I'm looking for help showing how to link a ViewModel to a Controller with get() post(form) actions. I need to see how to bind to a radio button list, etc...
I'm working with the class below, which has removed the database connections to simplify the answers/suggestions.
public class BuildSearch
{
//Bootstrap date entry
public DateTime StartDate { get; set; }
//Bootstrap date entry
public DateTime EndDate { get; set; }
//Need this bound to a radio button list
public List<GeoArea> GeoAreas { get; set; }
public BuildSearch()
{
GeoAreas = new List<GeoArea>();
// StartDate = DateTime.Parse(DateTime.Now.AddDays(-31).ToShortDateString());
// EndDate = DateTime.Parse(DateTime.Now.ToShortDateString());
GeoAreas.Add(new GeoArea { GeoAreaItem = "Region", Id = 0 });
GeoAreas.Add(new GeoArea { GeoAreaItem = "Manager1", Id = 1 });
GeoAreas.Add(new GeoArea { GeoAreaItem = "Manager2", Id = 2 });
}
}
public class GeoArea
{
public int Id { get; set; }
public string GeoAreaItem { get; set; }
}
I'm trying to create a view, that will display this data, and then allow for me to post back the user edits. I'm purposely looking to keep the example simple as once I figure out how to post back, with the updated data I can handle the pass off to a web API to do the work I need to be done. Just frustrated trying to figure out how I bind to this type of class.

For the radio buttons I would add a property to your BuildSearch class called GeoAreaId. The radio button selection will be model bound to this property on post back. Your BuildSearch class therefore becomes
public class BuildSearch
{
//Bootstrap date entry
public DateTime StartDate { get; set; }
//Bootstrap date entry
public DateTime EndDate { get; set; }
public int GeoAreaId { get; set; } //added field
//Need this bound to a radio button list
public List<GeoArea> GeoAreas { get; set; }
public BuildSearch()
{
GeoAreas = new List<GeoArea>();
// StartDate = DateTime.Parse(DateTime.Now.AddDays(-31).ToShortDateString());
// EndDate = DateTime.Parse(DateTime.Now.ToShortDateString());
GeoAreas.Add(new GeoArea { GeoAreaItem = "Region", Id = 0 });
GeoAreas.Add(new GeoArea { GeoAreaItem = "Manager1", Id = 1 });
GeoAreas.Add(new GeoArea { GeoAreaItem = "Manager2", Id = 2 });
}
public class GeoArea
{
public int Id { get; set; }
public string GeoAreaItem { get; set; }
}
}
Your get method in your controller will look something like this
public IActionResult Search()
{
var buildSearch = new BuildSearch();
return View(buildSearch);
}
Your view will need to look something like this
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#model BuildSearch
<form asp-controller="Home" asp-action="Search" method="post">
<label asp-for="StartDate">Start date</label>
<input asp-for="StartDate" />
<label asp-for="EndDate">End date</label>
<input asp-for="EndDate" />
<fieldset>
<legend>
GeoArea
</legend>
#foreach (var item in Model.GeoAreas)
{
<input type="radio" name="GeoAreaId" value="#item.Id" />
#item.GeoAreaItem
}
</fieldset>
<input type="submit" />
</form>
For the radio buttons note how the name attribute matches the new property GeoAreaId that I added to your BuildSearch class. This is important for the model binding to work.
Then your post method in your controller will need to look like this
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Search(BuildSearch buildSearch)
{
//...
}
To see what happens set a break point inside this method. Run the code, enter some values into the form and click submit. When the code has stopped inside hover over buildSearch and you will see the model binding has worked. The properties StartDate, EndDate and GeoAreaId will contain the values that you need from the form.

Here is an radio button example:
https://www.c-sharpcorner.com/article/radiobutton-in-asp-net-mvc/
here is a good example of getting your form data from the html form into the controller.
https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/controller-methods-views?view=aspnetcore-2.2
Getting the data into the viewmodel you will do something like this
public async Task<IActionResult> Index()
{
var movies = await _context.Movies.ToList();
if (movies == null)
{
return NotFound();
}
return View(movies);
}
You will then need the form to have a post action to you Action e.g.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (ModelState.IsValid)
{
//post to api here
return RedirectToAction(nameof(Index));
}
return View(movie);
}
You will have to pass the model or view model into the html class as
#model MvcMovie.Models.Movie
#{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4> etc etc

Related

C# MVC Post form not passing ID from view to controller

I am new to MVC. I work on an auction application. On the auction site, there should be a form for making a bid. I have a problem passing the auction parameter to the controller
My models:
public class Auctions
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string title { get; set; }
(..) some other fields
public List<Bid> bids = new List<Bid>();
}
public class BiddingViewModel
{
public Auctions auctionToSend { get; set; }
public double bid { get; set; }
}
My view:
#model BiddingViewModel
#using(Html.BeginForm("CreateBid", "Auction", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.HiddenFor(model=>model.auctionToSend)
#Html.EditorFor(model => model.bid)
<input type="submit" value="Make it work" />
}
and my controller:
[AllowAnonymous]
public ActionResult AuctionPage(int id)
{
var tmp = _context.Auctions.FirstOrDefault(i => i.ID == id);
BiddingViewModel bvm = new BiddingViewModel
{
auctionToSend = tmp,
bid = -1
};
return View(bvm);
}
[Authorize]
[HttpPost]
public async Task<ActionResult> CreateBid(BiddingViewModel bvm)
{
//After filling the form from view, the bvm.auctionToSend is null, whereas the bvm.bid value is visible
return RedirectToAction("AuctionList", "Auction");
}
My problem is that the auction data (perfectly visible in the view) is not sent back to the controller. I checked the internet and it showed me some naming-conflicts' solutions, so I made sure the naming is different, but this didn't fix my problem.
auctionToSend is a complex object and your use of #Html.HiddenFor(model=>model.auctionToSend) is generating
<input type="hidden" name="auctionToSend" value="yourAssembly.Auctions" ... />
If you just need the ID of the Auctions, then use
#Html.HiddenFor(m => m.auctionToSend.ID)
otherwise you need to generate a hidden input for each property of Auctions but that would be inefficient, particularly as Auctions contains a property which is a collection, so if you need the Auctions object in the POST method, better to just get it again based on the ID value your submitting.
As a side note, you really should be using a view model with just properties for the double Bid and int AuctionID

How to correctly submit checkbox values to controller in ASP.NET MVC

I'm working on ASP.NET MVC web application, and I need to submit List<ProdColor> to Controller using checkboxs. Here is my code
Model
public partial class ProdColor
{
public int ProdColor_ID { get; set; }
public Nullable<int> P_ID { get; set; }
public Nullable<int> Color_ID { get; set; }
public virtual ProdctModelView ProdctModelView { get; set; }
}
public class ProdctModelView
{
public ProdctModelView()
{
this.ProductColors = new HashSet<ProdColor>();
}
public int P_ID { get; set; }
public string P_name { get; set; }
public virtual ICollection<ProdColor> ProductColors { get; set; }
}
Controller
public ActionResult Create()
{
ViewBag.colorlist = db.Colors.OrderBy(m => m.Color_name).ToList();
return View();
}
[HttpPost]
public ActionResult Create(ProdctModelView product, List<ProdColor> ProductColors)
{
Product prod = new Product();
//Save new product
db.Products.Add(prod);
db.SaveChanges();
foreach (var color in ProductColors)
{
color.P_ID = prod.P_ID;
db.ProdColors.Add(color);
}
db.SaveChanges();
return RedirectToAction("Index");
}
View
#model mvc4test.Models.ProdctModelView
#using (Html.BeginForm("Create", "CP_Product", FormMethod.Post))
{
#for (int i = 0; i < ViewBag.colorlist.Count; i++)
{
<input type="checkbox" id="#ViewBag.colorlist[i].Color_name" name="[#i].Color_ID" value="#ViewBag.colorlist[i].Color_id"/>
}
<input type="submit" value="Save" />
}
The problem is when submitting the checkboxes without selecting the first one, the value of List<ProdColor> become Null. So how should I get the correct values at the Controller.
You manually creating checkboxes with indexers. Unchecked checkboxes do not post back a value, so if any of the checkboxes are unchecked, you get non-consecutive indexers so model binding fails.
Your model (view model) needs to include a boolean property (say) public bool IsSelected { get; set; } so that in the view you can use the #Html.CheckBoxFor() method to strongly bind to your model.
#for (int i = 0; i < Model.ColorList.Count; i++)
{
#Html.HiddenFor(m => m.ColorList[i].Color_id)
#Html.CheckBoxFor(m => m.ColorList[i].IsSelected)
#Html.LabelFor(m => m.ColorList[i].IsSelected, Model.ColorList[i].Color_name)
}
The CheckBoxFor() method generates a checkbox with value="true" and an associated hidden input with value="false". If the checkbox is checked, both true and false are posted, but only the first (true) value is bound. If the checkbox is unchecked, the only false is posted.
Then in the POST method, you can get the ID's of the selected items using (say)
var selectedColors = product.ColorList.Where(c => c.IsSelected).Select(c => c.Color_id);
Note that you do not need a parameter in your POST method for List<ProdColor> ProductColors since parameter ProdctModelView product already contains all those values.

Form submission in partial views in MVC

I am developing a simple mvc application . The code is as follows:
Model .cs:
public class CustomModel
{
public IEnumerable<lang> lstlang { get; set; }
public IEnumerable<org> lstOrg { get; set; }
}
public class lang
{
public int langid { get; set; }
public string langName { get; set; }
}
public class org
{
public int orgId { get ;set;}
public string orgName { get; set; }
}
Controller.cs
public Action Index()
{
// Get data from database and fill the model
var model = new CustomModel();
return View(model);
}
public Action Partial()
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
[HttpPost]
public Action Partial(FormCollection frm, CustomModel model)
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
Index.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="button" id="btn" />
#Html.RenderPartial("Partial", model)
Partial.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="submit" id="submit" />
The thing is, when I click the submit button in the Partial.cshtml page, and examine the model in httppost method in public Action Partial(FormCollection frm, CustomModel model), the model contains null for both lists lstlang and lstOrg, but the formcollection[0] will give the selected textbox value.
What am I missing, or is this the right way of using partial views?
Don't use FirstOrDefault(). If you want to post something back to the front end with collections, you'll need to use indexing.
Public class CustomModel
{
public ICollection<lang> lstlang { get; set; }
public ICollection<org> lstOrg { get; set; }
}
#HTML.textboxfor(x=>x.lang[0].id);

Issue with editing data and model binding

I started working with asp.net and I have encountered a problem when I try to edit multiple values from a table. I have a bookmark tables which is connected to another tag table, with an 1 : N relationship. My problem is when I want to edit already existing tags associated with an existing url. I can display them on the page but when I try to post the edited data I don't know how to pick it up in the controller. So far I have managed to send them back as a string but I doubt that is the solution since I have to edit all the data again later. I want to replace the existing values in the Tag table with the edited data. Here are my model and controller code snippets.
Bookmark model:
public int id { get; set; }
public string url { get; set; }
public virtual ICollection<Tag> tags { get; set; }
Tag model:
public int id { get; set; }
public string name { get; set; }
public virtual Bookmark bookmark { get; set; }
public string user { get; set; }
Controller:
public ActionResult Edit(int id)
{
var editBookmark = adc.Bookmarks.Single(x => x.id == id);
var query_where2 = from a in adc.Tags
where a.bookmark.id == id
select a;
BookmarkTag bkTag = new BookmarkTag();
bkTag.bookmark = new List<Bookmark>();
bkTag.bookmark.Add(editBookmark);
bkTag.tag = query_where2.ToList();
return View(bkTag.tag);
}
//
// POST: /SavedBookmark/Edit/5
[HttpPost]
public ActionResult Edit(int id, ICollection<FormCollection> tag)
{
try
{
return View();
}
catch
{
return View();
}
Html code:
#using (Html.BeginForm("edit", "SavedBookmark"))
{
#Html.AntiForgeryToken()
if (Model != null) {
var aa= Model.First();
#Html.TextBox("test2", aa.bookmark.url);
List<BookIT2.Models.Tag> allTags = new List<BookIT2.Models.Tag>();
allTags = Model.ToList();
for (int i = 0; i < allTags.Count; i++)
{
if (!allTags[i].name.IsEmpty())
{
#Html.TextBox(allTags[i].name, allTags[i].name);
#Html.Hidden(allTags[i].id.ToString(), allTags[i].id);
#Html.Hidden(allTags[i].user, allTags[i].user)
#Html.Hidden(allTags[i].bookmark.id.ToString(), allTags[i].bookmark.id.ToString())
}
}
#Html.Label("Additional tag")
#Html.TextBox("additionalTag")
<input type="submit" value="edit" />
}
In short: I can't get any values in the http post ICollection, it's always null.
Here is the updated code:
#using (Html.BeginForm("edit", "SavedBookmark"))
{
#Html.AntiForgeryToken()
if (Model != null)
{
for (int i = 0; i < Model.tag.Count; i++)
{
if (!Model.tag[i].name.IsEmpty()) {
#Html.Hidden(Model.tag[i].id.ToString(), Model.tag[i].id);
#Html.Label("name");
#Html.TextBox(Model.tag[i].name, Model.tag[i].name);
#Html.Hidden(Model.tag[i].bookmark.id.ToString(), Model.tag[i].bookmark.id);
#Html.Hidden(Model.tag[i].user, Model.tag[i].user);
}
}
#Html.TextBox(Model.bookmark.id.ToString(), Model.bookmark.url);
<input type="submit" value="edit" />
}
}
Model class:
public class TestBookmark
{
public Bookmark bookmark{get; set;}
public List<Tag> tag {get; set;}
}
[HttpPost]
public ActionResult Edit(TestBookmark edit)
{}
Don't really understand why you're doing it this way. I would like to suggest you totally different approach.
First:
Create a class with all the fields you want in your view.
Second:
Use this class as the MODEL in your View
Third:
In the controller, in the POST function user your class as the only one parameter of that function.

Can't get all my ViewModel properties back after the postback -MVC Partial Views

Can't get all my ViewModel properties back after the postback (After user entered some values on HttpPost)
There are numerous questions here related to losing data or getting nulls after the postback
I tried some of them and played around on my case, I think the scenario is a bit different,
Using a PartialView or Editor Templates(except a list property), Always the returned result properties are null.
In partialView approach always all the properties are null, I think maybe I missed a piece.
In the "custom editor template approach for the type", I'll have just "EnteredNums" List returned. (Maybe because these are what the template have EditorFor for them, but what is the solution here if that's the case?)
Don't know weather if it's important here or not, the application also uses Unity. I don't think it be the problem here.
The HttpGet Passed model is the same as HttpPost : DataVm
The name of the action is also the same : ProcessEnteredData
=================== Controller and action
[HttpPost]
public ActionResult ProcessEnteredData(DataVm vm)
{
if (ModelState.IsValid)
{
foreach (NumType num in vm.EnteredNums)
{
int i1 = num.Score1;
int i2 = num.Score2;
string profTitle = vm.Profile.Title;
Repository.Context.EnteredNums.Add(num);
}
return RedirectToAction("ShowTable");
}
else
{
return View(vm);
}
}
==============
The Partial View of Custom Editor Template are similar :
#model xxxx.NumType
#Html.LabelFor(m => m.TheTitle)
#Html.TextBoxFor(m => m.Score1)
#Html.TextBoxFor(m => m.Score2)
#Html.HiddenFor(m => m.Profile)
// Profile or ProfileId - Just used to see could it bring the property back or not as a test
============
NumType Model
[Key]
public int NumTypeId { get; set; }
[ForeignKey("Profile")]
[Required]
public int ProfileId { get; set; }
public int Score1 { get; set; }
public int Score2 { get; set; }
public int BoxId { get; set; }
public Box Box { get; set; } // something not important here
public virtual Profile Profile { get; set; }
============
The ViewModel
public class DataVm
{
public Profile Profile { get; set; }
public string TheTitle { get; set; }
public List<NumType> EnteredNums { get; set; }
// In the Editor template approach it's the only item with data and others are null
public List<Box> Boxes { get; set; }
}
=========
View for PartialView approach :
#model xxxx.DataVm
#using (Html.BeginForm("ProcessEnteredData", "Profile", FormMethod.Post))
{
#Html.AntiForgeryToken()
Model.EnteredNums = new List<NumType>();
foreach(var box in Model.Boxes)
{
NumType num = new NumType();
num.Profile = Model.Profile;
num.Box = box;
int iCount = Model.EnteredNums.Count;
Model.EnteredNums.Add(num);
#Html.Partial("NumView", Model.EnteredNums[iCount]);
}
<input type="submit" value="Do Process" />
}
===================
View for Editor for approach :
// instead of #Html.Partial :
#Html.EditorFor(m => m.EnteredNums[iCount]);

Categories