In my MVC view I need to display values in model object.
#model List<School.Model.Student_Model>
In a table I am displaying those student data.
#foreach (var item in Model)
{
<tr>
...
<td>#Html.DisplayFor(model => item.RegisterDate)</td>
</tr>
}
RegisterDate is nullable DateTime?
In my tabel I like to display message "date is not provided" if RegisterDate is null.
<td>#Html.DisplayFor(model => item.RegisterDate.HasValue?item.RegisterDate.HasValue.ToString():"-")</td>
if I use above it gives me following error
Exception Details: System.InvalidOperationException: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
I will be able to do this if I use separate If- else condition.
#if(item.RegisterDate.HasValue)
{
<td>#Html.DisplayFor(model => item.RegisterDate)</td>
}
else
{
<td>date is not provided</td>
}
Is there any way to do this?
You can define an empty template for null fields :
//EmptyTemplate.cshtml
#(Model == null ? "NULL" : Model)
And call it in your DisplayFor method :
#Html.DisplayFor(model => item.RegisterDate, "EmptyTemplate")
DisplayTemplates and EditorTemplates must be named based on model type's name (a MyClass model will require a MyClass.cshtml as DisplayTemplate). It works the same for nullable types, although you can't add ? or <> in a file name. Create a DisplayTemplate in the Views\Shared\DisplayTemplates folder named DateTime.cshtml :
#model DateTime?
#(Model.HasValue ? string.Format("{0:yyyy-MM-dd}", Model.Value) : "date is not provided")
It can be used in your view like this :
#Html.DisplayFor(model => item.RegisterDate)
You don't have to specify template's name, it will be found by convention.
Related
I am trying to pass a list of Objects from controller to View ,where the attributes of the objects are retrieved from database using LINQ. The MODEL Code is given below:
public class Department
{
[Key]
public int DepartmentId { get; set; }
[Required(ErrorMessage = "Enter Department Name")]
[DisplayName("Department Name")]
public string DepartmentName { get; set; }
}
The Controller Code is :
public ActionResult ShowDepartments()
{
var departmentList = db.Deapartment.Select(x => new
{
x.DepartmentId,
x.DepartmentName
}).ToList();
return View(departmentList);
}
And the View Code is:
#model List<PractiseMVC11_17_2017.Models.Department>
#{
ViewBag.Title = "ShowDepartments";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Show Departments</h2>
<div>
<table class="table table-bordered table-hover">
<tbody>
#foreach (var department in Model)
{
<tr>
<td>
#department.DepartmentId
</td>
<td>
#department.DepartmentName
</td>
</tr>
}
</tbody>
</table>
</div>
}
But when I run the project, it shows the following exception:
Exception Details: System.InvalidOperationException: The model item passed into the dictionary is of type 'System.Collections.Generic.List1[<>f__AnonymousType32[System.Int32,System.String]]', but this dictionary requires a model item of type 'System.Collections.Generic.List`1[PractiseMVC11_17_2017.Models.Department]'.
Shyju answer works because the object you are passing to the View happens to be the same as the database object. This will not always be the case (it's, in fact, better if it is not).
The correct way to handle this is to create a named object:
var departmentList = db.Deapartment.Select(x => new Department
{
x.DepartmentId,
x.DepartmentName
}).ToList();
(I'd go with the LINQ syntax, but the lambda syntax works just as well)
var departmentList =
(from x in db.Deapartment
select new Department
{
x.DepartmentId,
x.DepartmentName
}).ToList();
Your view is strongly typed to a collection of Department class objects. But in your action method, you are creating a list of new anonymous objects with this code
db.Deapartment.Select(x => new
{
x.DepartmentId,
x.DepartmentName
})
Here you are creating an anonymous object with the DepartmentId and DepartmentName property, for each item in the db.Deapartment collection. You are passing the result of the above code, which is a list of anonymous objects to the view. Your view now getting a different type than it is strongly typed to , hence you are getting the exception.
So simply pass ToList() on db.Deapartment and pass that to the view.
public ActionResult ShowDepartments()
{
var departmentList = db.Deapartment.ToList();
return View(departmentList);
}
Here departmentList variable will be a collection of Deapartment objects.
I'm running into an issue trying to use #Html.DropDownListFor().
I have a model with a navigation property on it:
public class Thing {
...
public virtual Vendor Vendor { get; set; }
}
In the controller I'm grabbing the vendor list to throw into the ViewBag:
public ActionResult Create() {
ViewBag.Vendors = Vendor.GetVendors(SessionHelper.CurrentUser.Unit_Id);
return View();
}
The html item in the view looks like this:
#Html.DropDownListFor(model => model.Vendor, new SelectList(ViewBag.Vendors, "Id", "Name"), "---- Select vendor ----")
#Html.ValidationMessageFor(model => model.Vendor)
The dropdown list is being rendered, and everything seems fine until I submit the form. The HttpPost Create method is returning false on the ModelState.IsValid and throwing a Model Error: The parameter conversion from type 'System.String' to type '...Models.Vendor' failed because no type converter can convert between these types.
If I let the page post through, I end up with a server error:
Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: items
After searching high and low I haven't been able to find a reason that the #Html.DropDownListFor() isn't properly auto-binding a Vendor object to the navigation property.
Any help would be greatly appreciated.
EDIT:
I ended up having to explicitly set the ForeignKey attributes so that I could directly access "Vendor_Id" then I changed the DropDownListFor to point to "Vendor_Id" instead of the navigation property. That seems to work.
I have found that the best way to do this is as follows. Change the controller to create the SelectListItems.
public ActionResult Create() {
ViewBag.Vendors = Vendor.GetVendors(SessionHelper.CurrentUser.Unit_Id)
.Select(option => new SelectListItem
{
Text = (option == null ? "None" : option.Name),
Value = option.Id.ToString()
});
return View();
}
Then modify the view as follows:
#Html.DropDownListFor(model => model.Vendor, (IEnumerable<SelectListItem>)ViewBag.Vendors, "---- Select vendor ----")
#Html.ValidationMessageFor(model => model.Vendor)
You have to cast the ViewBag.Vendors as (IEnumerable).
This keeps the views nice and neat. You could also move the code that gets the SelectListItems to your repo and put it in a method called something like GetVendorsList().
public IEnumerable<SelectListItem> GetVendorsList(int unitId){
return Vendor.GetVendors(unitId)
.Select(option => new SelectListItem
{
Text = (option == null ? "None" : option.Name),
Value = option.Id.ToString()
});
}
This would separate concerns nicely and keep your controller tidy.
Good luck
I have replied similar question in following stackoverflow question. The answer is good for this question too.
Validation for Navigation Properties in MVC (4) and EF (4)
This approach doesn't publish the SelectList in controller. I don't think publishing SelectList in controller is good idea, because this means we are taking care of view part in controller, which is clearly not the separation of concerns.
Could you help me, please.
I have a class:
public class Product
{
...
// NOT REQUIRED!
public virtual Category Category{ get; set; }
}
But when in a view I create
#Html.HiddenFor(model => model.Category.Id), or
#Html.Hidden("model.Category.Id", model => model.Category.Id)
razor adds validation attribute to this.
How to turn it off? (in model, in view)
How to turn off validation event if a property has the attribute [Required]?
I found out that this is not a razor problem, it is somewhere in MVC.
Even if I manage to pass "Category.Id" value = "" to the server, TryModelUpdate() will fail - it requires "Category.Id" to be set, but it's not required in my model.
Why is it so??!
I solved the same issue with an crutch like this:
#{ Html.EnableUnobtrusiveJavaScript(false); }
#Html.HiddenFor(t => t.Prop1)
#Html.HiddenFor(t => t.Prop2)
...
#{ Html.EnableUnobtrusiveJavaScript(true); }
Setup a hidden like:
#Html.Hidden("CategoryIdHidden", model => model.Category.Id)
And process the posted hidden value separate from the model binding stuff... I think the validation is UI specific, and not model specific, so it wouldn't validate the category ID.
Or, supply in the hidden a default value of "0". A value of "" probably won't evaluate correctly if the category.ID is of type int, hence its null, hence it errors.
HTH.
I am trying to list all users in a view but with no success. There are loads of questions here referring to the same problem and I have tried most of the replies, but for some reason I cannot get it to work. The following is the simplest implementation I could come up with but the error message is the same whatever method I use.
Compiler Error Message: CS1061: 'object' does not contain a definition for 'UserName' and no extension method 'UserName' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
#{
ViewBag.Title = "Admin";
}
<ul>
#foreach (var user in Membership.GetAllUsers())
{
<li>Name: #user.UserName </li>
}
</ul>
Any suggestions or advice would be greatly appreciated. Many Thanks
Use MembershipUser instead of var.
#foreach (MembershipUser user in Membership.GetAllUsers())
{
<li>Name: #user.UserName</li>
}
This being said I hope you realize that this is a total anti-MVC pattern. It's not the views responsibility to pull/fetch data from some stores. They should only use data that it is being passed to them from a controller action under the form of a strongly typed view model.
So here's the correct way to do this.
As always you start by defining a view model which will contain the data that you need to show in your view (in this case a list of usernames):
public class UserViewModel
{
public string Name { get; set; }
}
then a controller action:
public ActionResult Index()
{
var model = Membership
.GetAllUsers()
.Cast<MembershipUser>()
.Select(x => new UserViewModel
{
Name = x.UserName
});
return View(model);
}
then a corresponding strongly typed view:
#model IEnumerable<UserViewModel>
<ul>
#Html.DisplayForModel()
</ul>
and a display template for a given user which will be rendered for each item of the model collection (~/Views/Shared/DisplayTemplates/UserViewModel.cshtml):
#model UserViewModel
<li>Name: #Model.Name</li>
I have a FK in my Details ViewModel and when the View binds to the ViewModel I only get the FK back (expected). The FK is a reference to a simple ID/Name type table. I also have a Strongly typed List in the VM representing that FK-referenced table. I want to do something like
<div class="display-field">#Model.ManufacturersList.Find(x => x.ID == Model.softwaremanufacturerid))</div>
While this will return the the instance I want...I can't figure out how to get the "Name" attribute to display.
Sorry if this is more a Lamda question but thought I'd try all the same
Thanks
If .ManufacturersList.Find(x => x.ID == Model.softwaremanufacturerid) returns what you want, don't do it in the View. The View should only display data, while the model layer should really be doing the searching (.Find)
In your view model, add a string property for ManufacturerName
public string ManufacturerName { get; set; }
In your controller,
MyViewModel vm = new MyViewModel()
{
ManufacturerName = .ManufacturersList
.Find(x => x.ID == theFKAlreadyInTheViewModel)
};
return View(vm);
Then in your view,
#Model.ManufacturerName
OR, more simply, you could use the ViewBag
ViewBag.ManufacturerName = YourManufacturersList
.Find(x => x.ID == theFKAlreadyInTheViewModel);
Then in your View,
#ViewBag.ManufacturerName