I am struggling to determine if a Model passed to a View actually has any records.
The code below loops through the parent recordset and passes a parent parameter to a child recordset. I've tried if (Model.Content != null) but that doesn't seems to work, the code just thinks there are records, when actually there aren't.
Can somebody please review the code below and let me know what I am doing wrong?
<ul class="nav sf-menu clearfix">
#foreach (var navigation in Model.Navigation)
{
if (Model.Content != null)
{
#Html.SubMenuLink(navigation.Title, navigation.Action, navigation.Controller)
#Html.Raw("<ul>")
foreach (var content in Model.Content.Where(c => c.NavigationId == navigation.Id))
{
if (string.IsNullOrEmpty(content.Url))
{
if (string.IsNullOrEmpty(content.Content1))
{
}
else
{
#Html.MenuLink(content.Title, "Home/Article/" + content.Id + "/" + ToFriendlyUrl(content.Title), "Home");
}
}
else
{
#Html.MenuLink(content.Title, content.Url, "Home");
}
}
#Html.Raw("</ul>")
#Html.Raw("</li>")
}
else
{
#Html.MenuLink(navigation.Title, navigation.Action, navigation.Controller)
}
}
</ul>
Any help would be much appreciated :-)
NavigationViewModel
namespace WebApplication1.Models
{
public class NavigationViewModel
{
public List<Navigation> Navigation { get; set; }
public List<Content> Content { get; set; }
}
}
HomeController
public ActionResult Navigation()
{
var navigationModel = new NavigationViewModel();
navigationModel.Navigation = (from m in db.Navigations where (m.Main == true) orderby m.Position select m).ToList();
navigationModel.Content = (from n in db.Contents where (n.Active == true) orderby n.Position select n).ToList();
return View(navigationModel);
}
It may have empty list items. Better check the Count property.
if (Model.Content != null && Model.Content.Count>0)
Assuming Model.Content may be the type of IList or Array
If Content property is of List or Array type then do like this:
if(Model.Content != null && Model.Content.Count > 0)
{
//do something
}
if its a IEnumerable of some type then:
if(Model.Content != null && Model.Content.Count() > 0)
{
//do something
}
and if you are sure that Model.Content will not be passed null from action method, then you can use Any():
if(Model.Content.Any())
{
//do something
}
Simply you can do something like this
In your C# class introduce new property named called Empty.
public class YourClass
{
public bool Empty
{
get
{
return ( ColumnID== 0 )
}
}
}
Then in your Razor view you can use this Empty property for check weather model has values or not
#if (Model.Empty)
{
#*what ever you want*#
}
else
{
#*what ever you want*#
}
You could just use the Linq Method Any()
if (Model.Content.Any())
{
}
Edit: After second look. That if statement may not be right Model.Content inside the loop will always execute the same way. Are you use it isnt something like navigation.Content.Any()
My issue was that the Model.Content was initially being passed all records to the view so records did actually exist before the Where clause foreach (var content in Model.Content.Where(c => c.NavigationId == navigation.Id)) to find related Content records based on NavigationID.
I have amended my code as follows:
<ul class="nav sf-menu clearfix">
#foreach (var navigation in Model.Navigation)
{
int records = Model.Content.Count(c => c.NavigationId == navigation.Id);
if (records > 0)
{
#Html.SubMenuLink(navigation.Title, navigation.Action, navigation.Controller)
#Html.Raw("<ul>")
foreach (var content in Model.Content.Where(c => c.NavigationId == navigation.Id))
{
if (string.IsNullOrEmpty(content.Url))
{
if (string.IsNullOrEmpty(content.Content1))
{
}
else
{
#Html.MenuLink(content.Title, "Home/Article/" + content.Id + "/" + ToFriendlyUrl(content.Title), "Home");
}
}
else
{
#Html.MenuLink(content.Title, content.Url, "Home");
}
}
#Html.Raw("</ul>")
#Html.Raw("</li>")
}
else
{
#Html.MenuLink(navigation.Title, navigation.Action, navigation.Controller)
}
}
</ul>
I'm not sure this is the most elegant or efficient way to achieve the end goal, any suggestions to make the code more efficient I am happy to listen.
#if (!Model.Any())
{
Response.Redirect("Create");
}
If model empty then return empty default content will give you what do you expected
if (!model.Any())
return Content("");
Related
I have this code :
0#Model.Work.Phone
When I use phone like 0#Model.Work.Phone, the # sign is not highlighted. And this looks like this in browser : 0#Model.Work.Phone . I mean the code is displayed instead of the phone number.
When I put a space like this :
0 #Model.Work.Phone
The # sign is higlighted but I want 0 and # to be next to each other. How can I achieve this? Thanks.
On Razor (at least on 2.0 or up) you can use an explicit expression:
0#(Model.Work.Phone)
As an alternative you can use the direct string.format call as offered by Gypsy Spellweaver
Another alternative is using an Razor delegate:
#{
Func<dynamic, object> phoneformat = (item) =>
{
// if we have a string
if (item is String && !String.IsNullOrEmpty(item))
{
// check if the first is not a 0
if (item[0] != '0')
{
// add it
item = String.Format("0{0}", item);
}
}
else if(item is Int32)
{
/// ints never have leading 0, so add it
item = String.Format("0{0}", item);
}
return item;
};
}
0#(Model.Work.Phone) <br/>
0#(Model.Work.PhoneInt)
#phoneformat(Model.Work.Phone) <br/>
#phoneformat(Model.Work.PhoneInt)
Here is the Model I used:
public class Work
{
public string Phone { get; set; }
public int PhoneInt { get; set; }
}
And the Controller that fills it:
public ActionResult Index()
{
var model = new MyModel();
model.Work = new Work {Phone = "612345678", PhoneInt = 612345678};
return View(model);
}
The rendered html content looks like this:
0612345678 <br/>
0612345678
0612345678 <br/>
0612345678
I have two drop down lists. Niether of them have a relation ship with each other. But I need to filter one drop down list based on the chosen value of another drop down list.
I can filter it in code. When I debug I can see the filtered results on the property. However when I run the app, it does not work. Here is my code so far:
private BindingList<Commodity> _AllocationCommodities;
[Browsable(false)]
public BindingList<Commodity> AllocationCommodities
{
get
{
if (_AllocationCommodities == null)
{
_AllocationCommodities = new BindingList<Commodity>();
ChangeCommodities();
}
return _AllocationCommodities;
}
}
private SourceEntity _SourceEntity;
[ImmediatePostData]
[Association("SourceEntity-LimitAllocations")]
[RuleRequiredField("RuleRequiredField_LimitAllocation_SourceEntity", DefaultContexts.Save)]
public SourceEntity SourceEntity
{
get
{
return _SourceEntity;
}
set
{
//New Code
if (SetPropertyValue<SourceEntity>("SourceEntity", value))
{
if (IsSaving || IsLoading) return;
ChangeCommodities();
}
}
}
private Commodity _Commodity;// This is the drop down to be filtered
[ImmediatePostData]
[DataSourceProperty("AllocationCommodities")] //// This Attribute Should filter Commodities
[RuleRequiredField("RuleRequiredField_LimitAllocation_Commodity", DefaultContexts.Save)]
public Commodity Commodity
{
get
{
return _Commodity;
}
set
{
SetPropertyValue("Commodity", ref _Commodity, value);
if (Commodity.Oid != Guid.Empty)
AllocationVolumeUnits.Reload();
}
}
private void ChangeCommodities()
{
if (!this.IsLoading && _SourceEntity != null)
{
_AllocationCommodities.RaiseListChangedEvents = false;
_AllocationCommodities.Clear();
OperandValue[] _params;
System.Collections.Generic.List<CMSBOD.SourceCommodity> _sc = new System.Collections.Generic.List<SourceCommodity>();
BindingList<Commodity> _Commodities = new BindingList<Commodity>();
foreach (SourceCommodityEntity _tempSCE in _SourceEntity.SourceCommodityEntities)
{
if (_tempSCE.SourceCommodity != null)
_sc.Add(_tempSCE.SourceCommodity);
}
foreach (SourceCommodity _tempSC in _sc)
{
if (_tempSC.Commodity != null && !_Commodities.Contains<Commodity>(_tempSC.Commodity) && _tempSC.Commodity.IsActive)
_Commodities.Add(_tempSC.Commodity);
}
_AllocationCommodities.RaiseListChangedEvents = true;
_AllocationCommodities = _Commodities;///This is where I can see the filtered list when debugging.
}
}
You can find a DataSourceCriteria useful in this scenario, instead of DataSourceProperty.
Assuming you have collection properties that associates Commodity back to SourceCommodityEntity, you can use this criteria:
[DataSourceCriteria("IsActive And SourceCommodities[SourceCommodityEntities[SourceEntity = '#SourceEntity'] ]")]
Even if its designed to be a 1x1 assocation, you can find that associations can be useful for filtering purposes.
I have list of objects in my coming from controller.
it looks like this
{ Driver = System.Data.Entity.Driver_Driver1, Statuss = NotConfirmed }
{ Driver = System.Data.Entity.Driver_Driver2, Statuss = NotConfirmed }
please note that Driver is a complex type object.
Controller:
var Drivers = _db.Drivers.Where(x => x.DriverCompanyID == id).Where(d => d.CanWorkIn.Where(f => f.CompanyToDriveFor.CompanyID == OwnCompany.CompanyID).Any())
.Select(x => new
{
Driver = x,
Statuss = x.CanWorkIn.FirstOrDefault().Status.ToString()
}).ToList();
ViewBag.ListOfDrivers = Drivers;
return PartialView("_DriverList");
My Model
public class DriverViewItem
{
public Driver Driver { get; set; }
public string Statuss { get; set; }
}
My View
#model List<MyApp.web.Models.DriverViewItem>
and this last bit does not work. model declaration.
First create a strongly typed class with the properties you require. I've called mine DriverViewItem.
Then in your controller change the select to select this DriverViewItem and parse the list as a model to the view.
var Drivers = _db.Drivers.Where(x => x.DriverCompanyID == id).Where(d => d.CanWorkIn.Where(f => f.CompanyToDriveFor.CompanyID == OwnCompany.CompanyID).Any())
.Select(x => new DriverViewItem()
{
Driver = x,
Statuss = x.CanWorkIn.FirstOrDefault().Status
}).ToList();
return PartialView("_DriverList", Drivers);
In the view you will need to tell the view to expect your model you can do this with:
#model List<DriverViewItem>
Then you can iterate through the items like so:
#foreach(DriverViewItem item in Model)
{
<div>
<p>#item.Driver.{what ever property}</p>
<p>#item.Statuss</p>
</div>
}
This is a much cleaner way than parsing data using the ViewBag.
It would be better to use the model instead to pass this kind of data. But to answer the question directly, in the controller, assign it as an array of items to the viewbag
ViewBag.Data = {
new { Driver = System.Data.Entity.Driver_Driver1, Status = NotConfirmed },
new { Driver = System.Data.Entity.Driver_Driver2, Status = NotConfirmed }
}
And in the markup:
#{
if (ViewBag.Data != null){
foreach (var item in ViewBag.Data) {
//show the item in the view
}
}
}
Well I have some view model for, let's say, product:
public class ProductsAddModel
{
public IEnumerable<string> Names {get;set;}
}
I need to add several product names at a time but if there already exists product with one of the inputed names i want to add model error for Names and highlight the proper input in view. It basically looks like:
[HttpPost]
public ActionResult(ProductsAddModel model)
{
if(ModelState.IsValid)
{
var existedProducts = productRepository.AllProducts;
if(model.Names.Any(n => existedProducts.Select(p => p.Name).Contains(n)))
{
ModelState.AddModelError("Names", "Error");
return View(model);
}
}
}
But in this case all Names inputs will be highlighted in view. How can I highlight specific name input - input which causes error. I have several solutions such ass past error Name with view data to view add add error class to text input if it's value matches error name. Something like:
var match = existedProducts.Select(p => p.Name).FirstOrDefault(n => model.Names.Contains(n));
if(match != null)
{
ModelState.AddModelError("Names", "Error");
ViewBag.ErrorName = match.Name;
return View(model);
}
And in view:
for (int i; i<Model.NumberOfInputs; i++)
{
var value = Model.Names != null && Model.Names.Count() < index ? Model.Names.ToArray()[index] : string.Empty;
var errorClass = value.Equals(ViewBag.ErrorName) ? "error" : string.Empty;
<input type="text" name="Names" value="#value" class="#errorClass" />
}
... or create partial view for each input, save error name to temp data (not to view data to be able to sent this value through actions) with unique key, create render action for each input partial view, generate input id (or some custom data attribute) and temp data error key by the same pattern, check is there error for this input in temp data and add model error for "Names" if there is. Something like below:
var match = existedProducts.Select(p => p.Name).FirstOrDefault(n => model.Names.Contains(n));
if(match != null)
{
ModelState.AddModelError("Names", "Error");
var key = string.Format("Names-{0}", match.Name);
TempData[key] = "Error"; // or just true
return View(model);
}
View:
for (int i; i<Model.NumberOfInputs; i++)
{
var value = Model.Names != null && Model.Names.Count() < index ? Model.Names.ToArray()[index] : string.Empty;
Html.RenderAction("RenderInputAction", new {name = value});
}
And additional action I was talking about:
public PartialViewResult RenderInputAction(string name)
{
var key = string.Format("Names-{0}", name);
if(TempData[key])
{
ModelState.AddModelError("Names", "Error");
}
return PartialView("NameInput", name);
}
and partial view itself:
<input type="text" name="Names" value="#Model" />
As you can see these are robust and strange approaches. I just want to know is there some proper and simpler way in MVC framework to handle such complex models and their model state errors? Maybe some prefix for name attribute to catch specific input or something like that. I'm just not even sure what to google to find the answer. Thanks in advance.
Nice question! I wasn't sure.. so I tried it. Here's what I found.
My test had a model like this:
public class TestModel {
public IList<string> Items { get; set; }
public TestModel()
{
Items = new List<string>() { "Simon", "Whitehead" };
}
}
..a View like this:
#model MvcApplication1.Models.TestModel
#using (Html.BeginForm())
{
for (var i = 0; i < Model.Items.Count; i++)
{
<p>
#Html.TextBoxFor(x => Model.Items[i])
</p>
<p>#Html.ValidationMessageFor(x => Model.Items[i])</p>
}
<input type="submit" />
}
This renders the names Items[0] and Items[1]. Basically, you add a model error with the same names:
[HttpPost]
[ActionName("Index")]
public ActionResult Index_Post(TestModel model)
{
for (int i = 0; i < model.Items.Count; i++)
{
if (string.IsNullOrEmpty(model.Items[i]))
{
// empty name is Items[i]
ModelState.AddModelError(string.Format("Items[{0}]", i), "Required");
return View(model);
}
}
return null;
}
Works fine for me. Individual boxes are required when blank. The result is:
I've been struggling to research an answer to this question as I cannot come up with the correct search terms.
Basically I have 2 IEnumerable<T>'s in my controller, below is the code for the attempt I made.
Controller:
IEnumerable<Room> allRooms = RoomHelper.FindAllRooms();
foreach (var room in allRooms)
{
IEnumerable<Bunk> associatedBunks = RoomHelper.FindAssociatedBunksByRoom(room);
if (associatedBunks.Count() > 0)
{
ViewData["Room_"+room.RoomId] = associatedBunks;
}
}
And I'm trying to send them to the view in a way that I can do two foreach loops that will cycle through one set of data (in this case the Room objects and will then using the Room.RoomId key cycle through another IEnumerable which contains the associated data.
My view looks like this but is showing parse errors:
#foreach (var room in ViewBag.Rooms)
{
<h2>#room.RoomName</h2>
#if (ViewData["Room_" + room.RoomId].Count() > 0)
{
<ol>
#foreach (var bunk in ViewData["Room_" + room.RoomId])
{
<li>#bunk.BunkId</li>
}
</ol>
}
}
The end result I'm looking for in the HTML is something like:
<h2>Room 1</h2>
<ol>
<li>Bunk 1</li>
<li>Bunk 2</li>
</ol>
<h2>Room 2</h2>
<ol>
<li>Bunk 3</li>
<li>Bunk 4</li>
</ol>
What is the best practice in ASP.NET MVC 4 with EF5 to achieve this kind of result when passing "multidimensional" (is this multidimensional?) data?
Don't rely on ViewData. Store the data that you want to pass on to your view in a proper ViewModel:
public class RoomViewModel
{
List<Room> Rooms { get; set;}
...
}
Store your data in one of those.
Your Controller method then returns an instance of it:
public RoomViewModel GetRooms(int someParameter)
{
RoomViewModel result = new RoomViewModel();
result.Rooms = RoomHelper.Something(someParameter);
...
return result;
}
Your View declares its model on top:
#model MyApplication.ViewModels.RoomViewModel
and hence you use it in your View.
<h2>#Model.Rooms.Count rooms found</h2>
etc.
Use a code block in your view instead of adding an '#' in front of each C# statement:
#{
foreach (var room in ViewBag.Rooms)
{
#Html.Raw("<h2>" + room.RoomName + "</h2>");
if (ViewData["Room_" + room.RoomId].Count() > 0)
{
#Html.Raw("<ol>");
foreach (var bunk in ViewData["Room_" + room.RoomId])
{
#Html.Raw("<li>" + bunk.BunkId + "</li>");
}
#Html.Raw("</ol>");
}
}
}
You should avoid the use of #HtmlRaw("") as far as possible as it has a XSS vulnerability. But this should put you on the right track.
As per description given by you it seems that Bunk is associated with rooms. If that's the case then Bunk may have some id for pointing to room it belongs. Now you can create a ViewModel like this
public class BunkViewModel:Bunk
{
public BunkViewModel()
{
Mapper.CreateMap<Bunk,BunkViewModel>();
}
//I'm assuming that you already have some id in bunk to point to room it belongs.
//But writing it here to make it clear
public int RoomId { get; set; }
public string RoomName { get; set; }
//Use AutoMapper to Map
public static BunkViewModel Map(Bunk source)
{
return Mapper.Map<Bunk,BunkViewModel>(source);
}
}
Now in your controller
public ActionResult ActionName()
{
var result = new List<BunkViewModel>();
var rooms = RoomHelper.FindAllRooms();
var bunks = BunkHelper.GetAllBunks();
foreach(var bunk in bunks)
{
var bunkViewModel = BunkViewModel.Map(bunk);
var room = rooms.FirstorDefault(r=>room.RoomId == bunk.RoomId);
bunkViewModel.RoomId = room.RoomId; //No need to set if you already have this id in bunk
bunkViewModel.RoomName = room.RoomName;
result.Add(bunkViewModel);
}
return View(result.);
}
Now in your view you can do like this
#model List<MyApplication.ViewModels.RoomViewModel>
#foreach(var bunk in Model)
{
//Print it here as you want..
}