Razor page: complex modeling binding not updating model - asp.net-core

So I'm trying to create a little tool where a user can select a set of days. I however need quite a complex model for extra data.
public class DayRange{
...
List<DaySelected> Days
...
}
public class DaySelected{
public bool Selected{ get; set;}
public string DayName {get; set;}
public DaySelected(string Day){
Selected = false;
DayName = day;
}
}
My Razorpage looks like this:
#Model DayRange
...
<form asp-action="RegisterSelection" asp-controller="DayRegister">
<table>
#foreach (var Day in Model.Days)
{
<tr>
<td>
<input asp-for=#Day.Selected />
</td>
</tr>
}
</table>
<button type="submit">Confirm</button>
</form>
My method Registerselection looks like this:
[HttpPost]
public IActionResult RegisterSelection(DayRange dr){
...
}
However, whenever I change any of textboxes, all of the selected bool values remain the same. Can anybody help me on my way? Thanks in advance!

Here is a demo to pass data to action correctly:
Model:
public class DayRange
{
public List<DaySelected> Days { get; set; }
}
public class DaySelected
{
public bool Selected { get; set; }
public string DayName { get; set; }
public DaySelected()
{
}
public DaySelected(string Day)
{
Selected = false;
DayName = Day;
}
}
View:
#Model DayRange
<form asp-action="RegisterSelection" asp-controller="DayRegister">
<table>
#{ var i = 0;}
#foreach (var Day in Model.Days)
{
<tr>
<td>
<input asp-for=#Day.Selected name="Days[#i].Selected" />
#Day.DayName
<input asp-for=#Day.DayName name="Days[#i].DayName" hidden />
</td>
</tr>
i ++;
}
</table>
<button type="submit">Confirm</button>
</form>
result:

Related

How to post back a List<> using asp.net razor pages

I have a written a sample program that shows how I create a Generic LIST and I send that to the VIEW and the VIEW renders the values correctly but when I do a POST the LIST is empty. So Console.WriteLine(TimeEntry.Count) is 0, was kind of expecting 2
public class TimeEntry
{
public DateTime entryTime { get; set; }
}
public class IndexModel : PageModel
{
[BindProperty(SupportsGet = true)]
public List<TimeEntry> TimeEntry { get; set; }
public void OnGet(List<TimeEntry> timeEntry)
{
TimeEntry _timeEntry = new TimeEntry();
_timeEntry.entryTime = DateTime.Now;
timeEntry.Add(_timeEntry);
_timeEntry = new TimeEntry();
_timeEntry.entryTime = DateTime.Now.AddMinutes(10);
timeEntry.Add(_timeEntry);
TimeEntry = timeEntry;
}
public void OnPost(List<TimeEntry> TimeEntry)
{
Console.WriteLine(TimeEntry.Count);
}
}
above is the server side script
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
<form class="form-horizontal" method="post">
<table>
#if (Model.TimeEntry != null)
{
#foreach (var _time in Model.TimeEntry)
{
<tr>
<td>
Entry Time: #_time.entryTime
</td>
</tr>
}
}
</table>
<button type="submit">Post</button>
</form>
You should add some hidden input fields in your form for each table row and bind them to the List<TimeEntry> like this:
<table>
#if (Model.TimeEntry != null)
{
#for (var i = 0; i < Model.TimeEntry.Count; i++)
{
<tr>
<td>
<input type="hidden" name="[#i].entryTime" />
Entry Time: #Model[i].entryTime
</td>
</tr>
}
}
</table>
https://www.learnrazorpages.com/razor-pages/model-binding#binding-complex-collections

Asp Core Filter & Search Data Using Drop Downs & Search Bars

I would like users to search through pets using a search bar or drop down lists. I tried using viewbag and asp tag helpers but I keep getting errors. Below is a picture of what i'm going for. Any help is appreciated.
Model
public class Reptile
{
public int ReptileId { get; set; }
public string Name { get; set; }
public string Age { get; set; }
[Display(Name ="Reptile's Image")]
public byte[] Image { get; set; }
[Display(Name ="Food Requirements")]
public string FoodReq { get; set; }
[Display(Name="Habitat Requiremtns")]
public string HabitatReq { get; set; }
public string Gender { get; set; }
public string Type { get; set; }
public string Size { get; set; }
public string Color { get; set; }
[Display(Name="Recent Checkup")]
public bool RecentCheckup { get; set; }
public bool Trained { get; set; }
public bool Neutered { get; set; }
public bool Declawed { get; set; }
[Display(Name = "Good With Other Reptiles")]
public bool GoodWithRept { get; set; }
[Display(Name = "Good With Kids")]
public bool GoodWithKids { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int ApplicationUserId { get; set; }
}
Controller
public async Task<IActionResult> Index(string searchString)
{
var reptiles = from r in _context.Reptiles
select r;
if (!string.IsNullOrEmpty(searchString))
{
reptiles = reptiles.Where(r => r.Type.Contains(searchString));
}
return View(await reptiles.ToListAsync());
}
View
<form asp-controller="Reptiles" asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
Search By Type: <input type="text" name="SearchString" />
<input type="submit" value="Filter" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
I've been trying to follow the docs here Tutorial: Add sorting, filtering, and paging - ASP.NET MVC with EF Core. Not having any luck though.
Here is a simple demo to show how to use searchstring:
Controller:
public IActionResult Index(string searchString)
{
IEnumerable<Reptile> list = new List<Reptile> { new Reptile { Type = "t1", Name= "Reptile1" }, new Reptile { Type = "t2", Name = "Reptile2" }, new Reptile { Type = "t3", Name = "Reptile3" } };
ViewData["CurrentFilter"] = searchString;
if (!String.IsNullOrEmpty(searchString))
{
list = list.Where(s => s.Name.Contains(searchString));
}
return View(list);
}
View:
Find by name:
|
Back to Full List
<table>
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Type)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
<input type="text" asp-for="#item.Type">
</td>
</tr>
}
</tbody>
</table>
result:
Okay, I figured out how to use select to filter the reptile page by using the data users already added to the database from the properties in the model. I had to create a view model and add the Reptile model to it.
View Model
public class ReptileGenderViewModel
{
public Reptile Reptile { get; set; }
public List<Reptile> reptiles;
public SelectList genders;
public string reptileGender { get; set; }
}
Reptile Controller
public async Task<IActionResult> Index(string searchString, string reptileGender)
{
IQueryable<string> genderQuery = from g in _context.Reptiles
orderby g.Gender
select g.Gender;
var reptiles = from r in _context.Reptiles
select r;
if (!string.IsNullOrEmpty(searchString))
{
reptiles = reptiles.Where(r => r.Type.Contains(searchString));
}
if (!string.IsNullOrEmpty(reptileGender))
{
reptiles = reptiles.Where(g => g.Gender == reptileGender);
}
var reptileGenderVM = new ReptileGenderViewModel();
reptileGenderVM.genders = new SelectList(await genderQuery.Distinct().ToListAsync());
reptileGenderVM.reptiles = await reptiles.ToListAsync();
return View(reptileGenderVM);
}
View
<select asp-for="reptileGender" asp-items="Model.genders">
<option value="">All</option>
</select>

#Helpers In ASP.NET Core

I want to use Razor helper in .net core
I use this code
#{
void DrawRow(CategoryModel child, int parentNo)
{
#<text>
<tr style="display: none;">
<td><span class="treegrid-indent"></span><span class="treegrid-expander"></span>#child.Name</td>
</td>
</tr>
</text>;
}
}
but when use this , get error
The "#" character must be followed by a ":", "(", or a C# identifier. If you intended to switch to markup, use an HTML start tag,
You can achieve same requirement using a view component in ASP.NET Core application, like below.
ViewComponent class
[ViewComponent(Name = "DrawRow")]
public class DrawRowComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(DrawRowModel model)
{
return View(model);
}
}
ViewComponent Razor view
#model DrawRowModel
#*display: none;*#
<tr style="">
<td>
<span class="treegrid-indent"></span>
<span class="treegrid-expander"></span>
#Model.child.Name
</td>
</tr>
Model Class(es)
public class CategoryModel
{
public string Name { get; set; }
}
public class DrawRowModel
{
public CategoryModel child { get; set; }
public int parentNo { get; set; }
}
Invoke view component in Test View page
#{
ViewData["Title"] = "Test";
var model = new DrawRowModel { child = new CategoryModel { Name = "Category" }, parentNo = 0 };
}
<h1>Test</h1>
#for (int i = 1; i < 6; i++)
{
model.child.Name = "Category" + i.ToString();
model.parentNo = i;
<table>
#await Component.InvokeAsync("DrawRow", model)
</table>
}
Test Result

Data Annotation for currency format not working

In my ASP.NET MVC Core web project on VS2015, the following model is displaying data as, e.g., 15481 instead of $15,481 even though I'm using [DisplayFormat] below:
Models:
public class State
{
[Key]
public int StateId { get; set; }
[Column(TypeName ="varchar(40)")]
public string StateName { get; set; }
[Column(TypeName = "char(2)")]
public string StateCode { get; set; }
}
public class Sales
{
[Key]
public int SalesId { get; set; }
public int? FiscalYear { get; set; }
[DisplayFormat(DataFormatString = "{(0:C0)}")]
public float? SaleAmount { get; set; }
public int StateId { get; set; }
public State State { get; set; }
}
ModelView:
public class StatesSalesViewModel
{
[HiddenInput]
public int StateId { get; set; }
[Display(Name ="State")]
public string StateName { get; set; }
public int? FiscalYear { get; set; }
[DisplayFormat(DataFormatString = "{(0:C0)}")]
public float? SaleAmount { get; set; }
}
Controller:
public async Task<IActionResult> FYSales(List<StatesSalesViewModel> model, string GO, int currentlySelectedIndex, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
ViewBag.YearsList = Enumerable.Range(1996, 29).Select(g => new SelectListItem { Value = g.ToString(), Text = g.ToString() }).ToList();
if (!string.IsNullOrEmpty(GO))
{
var qryVM = from s in _context.States
join g in _context.Sales on s.StateId equals g.StateId
where g.FiscalYear == currentlySelectedIndex
select new StatesSalesViewModel() {StateId = s.StateId, StateName = s.StateName, SaleAmount = g.SaleAmount , FiscalYear = currentlySelectedIndex };
return View(qryVM.ToList());
}
}
View:
#model IList<mProject.Models.StatesSalesViewModel>
<div class="row">
<div class="col-md-12">
<form asp-controller="StatesSales" asp-action="getSales" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post">
#{
IEnumerable<SelectListItem> yearsList = (IEnumerable<SelectListItem>)ViewBag.YearsList;
var currentlySelectedIndex = 0; // Currently selected index (usually will come from model)
}
<strong>Select a Post Year</strong>
<h6>Choose a year o begin:</h6>
<label>Year:</label><select asp-for="#currentlySelectedIndex" asp-items="yearsList"></select><input type="submit" class="btn btn-default" name="GO" value="GO" />
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
<th>Fiscal Year</th>
<th>State</th>
<th>Sales</th>
</tr>
</thead>
<tbody>
#for (int i=0; i< Model.Count(); i++)
{
<tr>
<td>#Html.HiddenFor(r => r[i].StateID)</td>
<td>#Html.HiddenFor(r => r[i].FYSalesID)</td>
<td>
#Html.TextBoxFor(r => r[i].FiscalYear)
</td>
<td>
#Html.TextBoxFor(r => r[i].StateName)
</td>
<td>
#Html.TextBoxFor(r => r[i].SaleAmount)
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
A [DisplayFormat] attribute is only respected when using #Html.DisplayFor() or #Html.EditorFor(). It is ignored when using TextBoxFor().
In addition, if you wanted to use it with #Html.EditorFor(r => r[i].SaleAmount) you need to modify the attribute to include the ApplyFormatInEditMode property
[DisplayFormat(DataFormatString = "{0:C0}", ApplyFormatInEditMode = true)]
public float? SaleAmount { get; set; }
however that would be of little use to you, because although it would display in the textbox correctly, it will not bind back to you float property unless you were also to create a custom model binder which converted (say) "$15,481" back to a float
The currency annotation can be used. However it is just telling MVC which display or editor template to use. As We said current template uses the system currency. You would have to provide custom editor template or display template and some other way to determine the currency symbol to display.Look here at how to provide your own implementations
Try using this
[DisplayFormat(DataFormatString = "{0:C0}")]
Example
public class Sales
{
[Key]
public int SalesId { get; set; }
[DisplayFormat(DataFormatString = "{0:C0}")]
public float? SaleAmount { get; set; }
}
Check here for more details

MVC - Display multiple Controller results in one view

I have a controller in my MVC sire that has a couple of methods along the lines of the following:
public ActionResult _GetServiceStatus()
{
...
}
public ActionResult _GetEventLogErrors()
{
...
}
Each of these methods references a different class type that I have stored in my Model, Service and Event respectively. These can be seen below:
public class Service
{
public string Name { get; set; }
public string Status { get; set; }
}
public class Event
{
public string Source { get; set; }
public string EntryType { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
}
What I want to do is to show the results of these methods on a single view. I already have this working for the Services check, with the results displaying correctly, but I cannot find how to add in the Event results.
What I have currently in my view is below:
#{
ViewBag.Title = "Monitoring";
}
#model IEnumerable<SystemMonitoringTool.Models.Monitoring.Service>
<div style="width:25%">
<span>
Services
</span>
<table id="services" style="border:solid; border-width:2px; width:100%">
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(model => item.Name)
</td>
<td>
#Html.DisplayFor(model => item.Status)
</td>
</tr>
}
</table>
</div>
Can someone help me find a way to display these side by side on the same view? Will be happy to provide more info if needed.
Two options:
Use child actions. Instead of directly going to one of these individual controller actions, instead add an action like:
public ActionResult Monitoring()
{
return View();
}
You'll notice this action doesn't do much. It's just rendering a view. Then you'll need to move your HTML for services/events into partial views. For example, _GetServiceStatus.cshtml and _GetEventLogErrors.cshtml, where the model for each will be a collection of your Service or Event types, respectively. Finally, in your Monitoring.cshtml view (based on the action name above), you'll add:
#Html.Action("_GetServiceStatus")
#Html.Action("_GetEventLogErrors")
This view doesn't need a model, because it's not directly working with anything.
Use a view model that encapsulates your two collections:
public class MonitoringViewModel
{
public List<Service> Services { get; set; }
public List<Event> Events { get; set; }
}
Then you'll still need a unifying action. But here, you'll populate both lists. Basically, you'll just be moving your two existing actions into one:
public ActionResult Monitoring()
{
var model = new MonitoringViewModel
{
Services = /* code to retrieve services */,
Events = /* code to retrieve events */
}
return View(model);
}
Then, you can iterate through each list independently to build your HTML:
Monitoring.cshtml (again, based on the action name)
#model Namespace.To.MonitoringViewModel
...
<table id="services" style="border:solid; border-width:2px; width:100%">
#foreach (var item in Model.Services) { // notice the change here
<tr>
<td>
#Html.DisplayFor(model => item.Name)
</td>
<td>
#Html.DisplayFor(model => item.Status)
</td>
</tr>
}
</table>