I can't seem to find how to build a proper solution for a one to many checkbox.
So what do i have :
I have an article and an admin user can set user rights to that article.
So in my article right page you have an overview with all the users.
My article:
public partial class Artikel
{
public Artikel()
{
this.ArtikelLinks = new HashSet<ArtikelLink>();
this.ArtikelRights = new HashSet<ArtikelRight>();
}
public int id { get; set; }
public string code { get; set; }
public string naam { get; set; }
public virtual ICollection<ArtikelLink> ArtikelLinks { get; set; }
public virtual ICollection<ArtikelRight> ArtikelRights { get; set; }
}
My rights class
public partial class ArtikelRight
{
public int id { get; set; }
public System.Guid userId { get; set; }
public int artikelId { get; set; }
public bool hasRight { get; set; }
public virtual Artikel Artikel { get; set; }
}
How do i build my view? i tried several ways but i can't seem to save my data.
this is my rights view at the moment:
#using (Html.BeginForm()) {
<table width="90%" align="center" class="table table-striped">
<thead>
<tr>
<th align="left">Gebruiker</th>
<th align="left">Heeft toegang</th>
</tr>
</thead>
#Html.EditorFor(x => Model.ArtikelRights, "ArtikelRight")
</table>
<br />
<div class="pull-right">
<input type="submit" value="Opslaan" class="btn btn-sm beige" />
</div>
}
And this is my partial Artikel right view:
#model IEnumerable<GtiProducts.Models.ArtikelRight>
#foreach (var item in Model) {
<tr>
<td>
#Membership.GetUser(item.userId).UserName
</td>
<td>
#Html.EditorFor(x => item.hasRight)
</td>
</tr>
}
My save action is:
public ActionResult Rights(Artikel art)
{
repo.SaveChanges();
return View(art);
}
When i debug my art.ArtikelRights is null.
How can i fix this and what's the best solution to do this with the entity framework?
Rename the partial to make it an EditorTemplate- /Views/Shared/EditorTemplates/ArtikelRight.cshtml, then make the following modifications
#model GtiProducts.Models.ArtikelRight // not IEnumerable
<tr>
<td>
#Html.CheckBoxFor(m => m.hasRight)</td>
#Html.LabelFor(m => m.hasRight, "....") // the users name?
#Html.HiddenFor(m > m.id) // or what ever property you need to identify the ArtikelRight.cshtml
</td>
</tr>
Then in the main view
<tbody>
#Html.EditorFor(x => Model.ArtikelRights)
</tbody>
This will correctly name you controls with indexers so they can be bound to your model on post back, for example
<input type="checkbox" name="ArtikelRights[0].hasRight" ...>
<input type="checkbox" name="ArtikelRights[1].hasRight" ...>
Side note: You should not be using #Membership.GetUser(item.userId).UserName in the view. Instead create a view model with only those properties you need in the view (which appears to be id and hasright, and include a property for the users display name (and populate it in the controller).
Related
I want to allow the user to download the report, when it is approved by a supervisor. At the moment, I'm working with manager account, where he can check many reports and change their state to either verified or denied, but I don't understand why the report states enum list is not displaying, even though it is shown in the console.
HTML code
Model:
public class Report
{
public int ID { get; set; }
[Display(Name = "Report Name")]
public string reportName { get; set; }
public virtual User reportManager { get; set; }
[Display(Name = "State")]
public ReportState reportState { get; set; }
public byte[] reportData { get; set; }
}
public enum ReportState
{
Accepted,
Pending,
Denied
}
Controller:
public async Task<IActionResult> Index()
{
ViewBag.Reports = await _context.Reports.ToListAsync();
ViewBag.ReportStates = new SelectList(Enum.GetNames(typeof(ReportState)));
return View();
}
#model variable_pay_system.Models.Report
#{
ViewData["Title"] = "Reports";
}
<div class="container">
<h5>Reports</h5>
<table>
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.reportName)
</th>
<th>
#Html.DisplayNameFor(model => model.reportState)
</th>
</tr>
</thead>
<tbody>
#foreach (Report report in ViewBag.Reports)
{
<tr>
<td>
#Html.DisplayFor(modelItem => report.reportName)
</td>
<td>
<select asp-for="#report.reportState" asp-items="Html.GetEnumSelectList<ReportState>()"></select>
</td>
</tr>
}
</tbody>
</table>
If you are going to use enums in View, you can show these enums as a list. and I recommend using it by printing it to a dropdown. EnumDropDownListFor()
Model :
public enum ReportState
{
Accepted,
Pending,
Denied
}
View:
#Html.EnumDropDownListFor(model => model.ReportState)
Using Tag Helper (ASP.NET MVC 6):
<select asp-for="#Model.SelectedValue" asp-items="Html.GetEnumSelectList<ReportState>()">
Ok so the problem was because, I was missing this code at the end.
<script>
$(document).ready(function () {
$('select').formSelect();
});
I'm a bit confused because I thought this a very straight-forward thing, it's possibly something simple tripping me up.
I have a view:
#model IEnumerable<CarViewModel>
#using (Html.BeginForm("SummarySaveAll", "VroomVroom", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
<table>
<thead>
<tr>
<th width="1">
#Html.DisplayNameFor(model => model.Driver)
</th>
<th width="1">
#Html.DisplayNameFor(model => model.Colour.Name)
</th>
</tr>
</thead>
<tbody>
#foreach (var element in Model)
{
<tr>
<td width="1">
#Html.DisplayFor(m => element.Driver)
</td>
<td width="1">
#Html.DropDownListFor(m => element.Colour, element.Colours, "Unknown")
</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save Changes" class="btn" />
#Html.ActionLink("Cancel Changes", "Index", null, new { #class = "btn" })
</div>
</div>
}
and the list/enumerable of CarViewModel is supposed to bounce back to the VroomVroom controller, action SummarySaveAll which it does - but the viewmodel on the page doesn't get passed back to it:
[HttpPost]
public ActionResult SummarySaveAll(IEnumerable<CarViewModel> summaries)
{
// Want to do stuff with summaries but it's always null
return View();
}
I tried to encapsulate the List in another ViewModel and cycle through elements using a for i loop but that wouldn't pass back to the controller either.
Surely it's possible to send a List or IEnumerable of models back to a controller?
My CarVM:
public class CarViewModel
{
[MaxLength(150)]
[Display(AutoGenerateField = true, Name = "Entered By")]
public string Driver { get; set; }
[Display(AutoGenerateField = true)]
public Colour Colour { get; set; }
[Key]
[Display(AutoGenerateField = false)]
public int Id { get; set; }
[Display(AutoGenerateField = false)]
public bool IsDeleted { get; set; } = false;
public IEnumerable<SelectListItem> Colours { get; set; }
public CarViewModel() { }
public CarViewModel(Model CarModel summaryModel, CarPropertyCollection propertyCollection)
{
Driver = summaryModel.Driver;
Id = summaryModel.Id;
IsDeleted = summaryModel.IsDeleted;
Colour = summaryModel.Colour == null ? null :
propertyCollection.Colours.Where(x => x.Id == summaryModel.Colour.Id).FirstOrDefault();
Colours = propertyCollection.Colours.Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.Name });
}
}
}
Must stress that Colour is a custom class but only has Id and Name properties
Colours doesn't relate to a specific car, it relates to cars in general, so rather than using a collection as your view model, create a wrapper:
class EditCarsViewModel
{
public IEnumerable<SelectListItem> Colours { get; set; }
public IList<CarViewModel> Cars { get; set; }
}
Then your view:
#model EditCarsViewModel
#for (int i = 0; i < Model.Cars.Length; i++)
{
<td>
#Html.DropDownListFor(m => Model.Cars[i].Colour, Model.Colours, "Unknown")
</td>
}
Any other CarViewModel properties will need their own input as well. HiddenFor can be used if they should be readonly:
#model EditCarsViewModel
#for (int i = 0; i < Model.Cars.Length; i++)
{
#Html.HiddenFor(m => Model.Cars[i].Id)
#Html.HiddenFor(m => Model.Cars[i].Driver)
<!-- etc. -->
<td>
#Html.DropDownListFor(m => Model.Cars[i].Colour.Id, Model.Colours, "Unknown")
</td>
}
And your controller:
[HttpPost]
public ActionResult SummarySaveAll(EditCarViewModel model)
{
// model.Cars should be populated
return View();
}
Note that an indexable collection, such as IList<T> should be used, as the form field names need to include the index to differentiate the items.
Edit by OP
The Colour class consists of a [Key] int Id property and a string Name property. For DropDownList items I had to make sure the Id property was specified on the m => Model.Cars[i].Colour.Id line otherwise that particular prop was coming back as null even though other items were coming through fine.
try
[HttpPost]
public ActionResult SummarySaveAll(IList<CarViewModel> summaries)
{
// Want to do stuff with summaries but it's always null
return View(summaries);
}
I've also added this model as a param for your view
This how you do it:
First my View which posts back to a controller named Home and an action named ListView:
#model List<MyModel>
#{
ViewData["Title"] = "Using a list as model";
}
<h1>#ViewData["Title"]</h1>
#using (Html.BeginForm("ListView", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
<table>
<thead>
<tr>
<th width="1">
Name
</th>
<th width="1">
Description
</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td width="1">
#Html.DisplayFor(m => Model[i].Name)
</td>
<td width="1">
#Html.TextBoxFor(m => Model[i].Description)
</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save Changes" class="btn" />
#Html.ActionLink("Cancel Changes", "Index", null, new { #class = "btn" })
</div>
</div>
}
Notice how I used an indexer to render the controls [i]
This is my model:
public class MyModel
{
public string Name { get; set; }
public string Description { get; set; }
}
This is my controller action:
[HttpPost]
public IActionResult ListView(IEnumerable<MyModel> model)
{
return View(model);
}
And this is the result:
CustomCssFields is null when going back to controller. I already set a constructor but it is still the same. I found some similar question MVC Model with a list of objects as property but it almost has the same code as mine.
MODEL
public class CompanyModelView
{
public CompanyModelView()
{
CustomCssFields = new List<CompanyCssReferenceModelView>();
}
[BsonId]
public string _id { get; set; }
[BsonElement("CompanyName")]
[DisplayName("Company Name")]
public string CompanyName { get; set; }
[BsonElement("CompanyCode")]
[DisplayName("Company Code")]
public string CompanyCode { get; set; }
[BsonElement("CompanyConnectionString")]
[DisplayName("Company Connection String")]
public string CompanyConnectionString { get; set; }
[BsonElement("CargowiseVersion")]
[DisplayName("Cargowise Version")]
public string CargoWiseVersion { get; set; }
[BsonElement("CustomCssFields")]
[UIHint("CustomCssFields")]
public IList<CompanyCssReferenceModelView> CustomCssFields { get; set; }
}
public class CompanyCssReferenceModelView
{
[BsonElement("FieldName")]
public CssOptionEnum FieldName;
[BsonElement("FieldValue")]
public string FieldValue;
}
VIEW
here is the view.
#model WebTrackerModels.CompanyModels.CompanyModelView
<form asp-action="SaveCompany" enctype="multipart/form-data">
<div class="form-group">
<table class="table">
<thead>
<tr>
<th>
Field Name
</th>
<th>
Field Value
</th>
</tr>
</thead>
<tbody>
#{
for (int i = 0; i < Model.CustomCssFields.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(model => model.CustomCssFields[i].FieldName)
</td>
<td>
#Html.TextBoxFor(model => model.CustomCssFields[i].FieldValue, new { #class = "form-control", type = "color", value = Model.CustomCssFields[i].FieldValue })
</td>
</tr>
}
}
</tbody>
</table>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-success" />
</div>
</form>
I am trying to load a partial view of inside a tab but its not showing data.
I am using the following code can I not just do a loop using razor code this is in a partial view which I wish to load in from another view
#model IEnumerable<solitude.models.ProductImages>
#{
ViewData["Title"] = "ProductPicturesList";
Layout = "~/Views/Shared/_LoginAdminLte.cshtml";
}
<h2>ProductPicturesList</h2>
<table class="table">
<thead>
<tr>
<th>
Picture Title
</th>
<th>
Image
</th>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
</tr>
<td>
<a asp-action="Edit" asp-route-id="#item.ProductID">Edit</a> |
<a asp-action="Details" asp-route-id="#item.ProductID">Details</a> |
<a asp-action="Delete" asp-route-id="#item.ProductID">Delete</a>
</td>
}
</tbody>
</table>
Its cause in the main list I am using a view model but I want to show a list of pictures above the form upload what would my best way of doing this be as obv it is not returning anyresults I am using a controller for my main page.
#model solitude.models.Models.ViewModels.ProductImageVm
#*
For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
*#
#Html.PartialAsync("_ProductPicturesList.cshtml")
<div class="form-group">
<form asp-controller="Products" asp-action="FileUpload" asp-route-returnurl="#ViewData["ReturnUrl"]" enctype="multipart/form-data" method="post" class="form-horizontal" role="form">
<input asp-for="Title" />
<input asp-for="ProductId" type="hidden" />
<input asp-for="Image" />
<input type="submit" />
</form>
Edit 2
My Product Images as a class should this be changed
public class ProductImages
{
[Key]
public int ProductImageId { get; set; }
public int ProductID { get; set; }
public string ProductImageTitle { get; set; }
public string ProductImageUploadUrl { get; set; }
public string ProductImageRealPath { get; set; }
public string ServerIpAddress { get; set; }
public string ProductImageAltTag { get; set; }
public int DisplayOrder { get; set; }
public string Image { set; get; }
}
}
Your partial view is strongly typed to a collection of ProductImages. But in your main view when you are calling this partial view, you are not passing the model (which is the collection of ProductImage objects) to this partial view. If you are not explcitily passing the model, it will try to use the model for the parent view. In your case your parent view is strongly ProductImageVm view model class. So it is not maching with what the partial view is expecting.
The solution is to pass a valid collection of ProductImages. If your view model has a collection property of that type you can do that
#await Html.PartialAsync("_ProductPicturesList.cshtml",Model.Images)
Assuming Images of type IEnumerable<solitude.models.ProductImages>
Ideally it is not a great idea to mix entity classes with view models. So i would create a view model class for the ProductImage partial view and use that as the property
public class ProductImg
{
public string Title { set;get;}
public string FileName { set;get;}
// or path as needed
}
public class EditProductImageVm
{
public string Title { set;get;} //for the new item
public IFormFile Image { set;get; } //for the new item
public IEnumerable<ProductImg> Images { set;get;}
}
Now make sure main view is not strongly typed to EditProductImageVm and your partial view is strongly typed to IEnumerable<ProductImg>. Also you need to await the call to PartialAsync method
#model YourNameSpaceGoesHere.EditProductImageVm
<div>
#await Html.PartialAsync("_ProductPicturesList.cshtml",Model.Images);
</div>
<form asp-controller="Products" asp-action="FileUpload" enctype="multipart/form-data"
method="post" >
<input asp-for="Title" />
<input asp-for="Image" />
<input type="submit" />
</form>
And your partial view will be
#model IEnumerable<YourNameSpaceGoesHere.ProductImg>
<h3>Images</h3>
<table class="table table-condensed table-bordered">
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
<!-- adjust the below img src path as needed -->
<img src="#Url.Content("~/uploads/"+item.FileName)"/>
</td>
</tr>
}
</table>
I'm trying to handle various Model creations in one View / Action.
When s/o clicks on "New Computer" the Type ViewModel receives a new object of type Computer. The View switches through the different types (in this case Computer) and creates the View based on the passed in model.
This worked fine but when submitting the form (calling the CreateComputer Action) not the whole Computer-object gets transmitted - to be precise the Computer-related properties (the both enums) gets lost. I think this is because the Name of the dropdown's do not start with Hardware, but are called "FormType" and "PerformanceType" instead, is there any proper solution for this case?
Clarification:
The Model "Computer" inherits from "Hardware". Because I'd like to handle all creations, updates etc. with one VM / View the ViewModel has a Property called Hardware of type Hardware. Unfortunately when using the VM as Action parameter the property Hardware is always of type Hardware (although sometimes it should be of type Computer). Additionally, my approach works (partially) - the modelbinder seems to be smart enough to bind to the nested type Hardware.
What do I want?
In the Action I want the Method to be able to determine if the passed in parameter is of type Hardware or of type Computer (inherits form Hardware) and let the Controller do it's stuff for the determined model. In other words: I want to avoid to create different Actions for different Models such as "CreateComputer(Computer computer)" and "CreateGeneric(Hardware hardware)".
Models:
Computer:
namespace UserChange.com.Models
{
public class Computer : Hardware
{
public PerformanceType PerformanceType { get; set; }
public FormType FormType { get; set; }
public override HardwareType GetHardwareType()
{
return HardwareType.Computer;
}
public override string ToString()
{
return $"Computer: {base.ToString()}";
}
}
}
Monitor:
using System.ComponentModel.DataAnnotations;
namespace UserChange.com.Models
{
public class Monitor : Hardware
{
[Required]
public short Size { get; set; }
[Required]
public MonitorColor MonitorColor { get; set; }
public override HardwareType GetHardwareType()
{
return HardwareType.Monitor;
}
public override string ToString()
{
return "Monitor: " + base.ToString();
}
}
}
Hardware:
[Table("NEOV_Hardware")]
public class Hardware : BaseModel
{
[Required]
[StringLength(50)]
public string Description { get; set; }
public bool IsVisible { get; set; }
public virtual HardwareType GetHardwareType()
{
return HardwareType.Generic;
}
public override string ToString()
{
return Description;
}
}
Controller:
[HttpPost]
public ActionResult CreateComputer(Computer hardware)
{
var uow = new UnitOfWork(new ApplicationContext());
uow.HardwareRepository.Add(hardware);
uow.Save();
return RedirectToAction("Index");
}
View:
#using UserChange.com.Models
#using UserChange.dal.Repository.Interface
#model UserChange.ViewModels.NewHardwareViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#{
switch (Model.Hardware.GetHardwareType()) {
case HardwareType.Computer:
<h2>New Computer</h2>
using (Html.BeginForm("CreateComputer", "Hardware", FormMethod.Post)) {
<table class="table table-hover table-bordered table-striped table-condensed">
<tr>
<td>Model name:</td>
<td>#Html.TextBoxFor(o => o.Hardware.Description)</td>
</tr>
<tr>
<td>Form factor:</td>
<td>#Html.EnumDropDownListFor(o => (o.Hardware as Computer).FormType)</td>
</tr>
<tr>
<td>Performance type:</td>
<td>#Html.EnumDropDownListFor(o => (o.Hardware as Computer).PerformanceType)</td>
</tr>
<tr>
<td>Comment:</td>
<td>#Html.TextAreaFor(o => o.Hardware.Comment)</td>
</tr>
</table>
<input type="submit" value="Submit" class="btn btn-primary" />
}
break;
case HardwareType.Monitor:
<h2>New Monitor</h2>
using (Html.BeginForm("CreateMonitor", "Hardware", FormMethod.Post)) {
<table class="table table-hover table-bordered table-striped table-condensed">
<tr>
<td>Model name:</td>
<td>#Html.TextBoxFor(o => o.Hardware.Description)</td>
</tr>
<tr>
<td>Size:</td>
<td>#Html.TextBoxFor(o => (o.Hardware as Monitor).Size)</td>
</tr>
<tr>
<td>Color:</td>
<td>#Html.EnumDropDownListFor(o => (o.Hardware as Monitor).MonitorColor)</td>
</tr>
<tr>
<td>Comment:</td>
<td>#Html.TextAreaFor(o => o.Hardware.Comment)</td>
</tr>
</table>
<input type="submit" value="Submit" class="btn btn-primary" />
}
break;
case HardwareType.Generic:
<h2>New Generic device</h2>
using (Html.BeginForm("CreateGeneric", "Hardware", FormMethod.Post)) {
<table class="table table-hover table-bordered table-striped table-condensed">
<tr>
<td>Model name:</td>
<td>#Html.TextBoxFor(o => o.Hardware.Description)</td>
</tr>
<tr>
<td>Comment:</td>
<td>#Html.TextAreaFor(o => o.Hardware.Comment)</td>
</tr>
</table>
<input type="submit" value="Submit" class="btn btn-primary" />
}
break;
}
}
** Edit 1: ** - Missing VM
public class NewHardwareViewModel : BaseViewModel
{
public Hardware Hardware { get; set; }
public NewHardwareViewModel(Hardware hardware, bool isUserAuthorized) : base(isUserAuthorized)
{
Hardware = hardware;
}
public NewHardwareViewModel(){ }
}