Custom view control for MVC doesn't work? - asp.net-mvc

I have the following custom view control in MVC. However, it doesn't work at all.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime?>" %>
<%=Html.TextBox("", (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty), new { #class = "timePicker" }) %>
And this is where I use it from, and how:
<div class="editor-field">
#Html.EditorFor(model => model.StartTime)
#Html.ValidationMessageFor(model => model.StartTime)
</div>
The model looks like this:
[Bind()]
[Table("DailyReports", Schema = "Actives")]
public class DailyReport
{
[Key()]
[Display(AutoGenerateField = false, AutoGenerateFilter = false)]
public int ID { get; set; }
[DisplayName("Starttidspunkt")]
public DateTime? StartTime { get; set; }
[DisplayName("Sluttidspunkt")]
public DateTime? EndTime { get; set; }
[DisplayName("Time-rapporter")]
public virtual ICollection<HourlyReport> HourlyReports { get; set; }
public DailyReport()
{
}
}
However, a simple textfield just shows up, when in reality, I expect the view user control to show up, since the type is DateTime.
Any suggestions on how to solve this?

I'm assuming that you're correctly placing your template in the EditorTemplates folder, and that you're naming it after the correct type (ie DateTime.aspx)
Beause you're using a nullable type, you need to specify the template name manually.
<%: Html.EditorFor(model => model.StartTime, "NullableDateTimeTemplate" )%>
Or, you can check the model metadata to determine if the type is nullable.
<% if (ViewData.ModelMetadata.IsNullableValueType) { %>
<%= Html.TextBox("", (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty),
new { #class = "timePicker" }) %>
<% } else { %>
<%= Html.TextBox("", Model.ToShortDateString(), new { #class = "timePicker" }) %>
<% } %>

Related

MVC Partial View throwing error on dropdownfor

I have a partial view I want to display as a search interface. Every time it tells me I get the error
There is no ViewData item of type IEnumerable that has the key resource_type_id.
I have tried so many things to make this work but nothing helps.
This is my view calls the partialview
#model IEnumerable<NewSAMACentral2._0.ViewModel.MemberResourcesViewModel.MemberResource>
#{
ViewBag.Title = "Add Resource To Folder";
}
<h2>Add Resource To Folder</h2>
<div>
#{Html.Partial("SearchResource", new NewSAMACentral2._0.ViewModel.MemberResourcesViewModel.ResourceSearch());}
</div>
#using (Ajax.BeginForm("InsertAttendee", "Meetings", new AjaxOptions { HttpMethod = "POST" }))
{
if (Model.Any())
{
}
}
This is my partialview
#model NewSAMACentral2._0.ViewModel.MemberResourcesViewModel.ResourceSearch
#using (Ajax.BeginForm("AddAttendee", "Meetings", new AjaxOptions { UpdateTargetId = "AddAttendee", HttpMethod = "POST" }))
{
<div class="form-group">
<label for="keyword">Keyword(s): </label>#Html.TextBox("keyword", null, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(model => Model.resource_type_id)
#Html.DropDownListFor(model => Model.resource_type_id, Model.resource_type, "-- Select --", new { #class = "form-control" })
</div>
<div class="form-group">
<label for="author">Author(s): </label>#Html.TextBox("author", null, new { #class = "form-control" })
</div>
<div class="form-group">
<input type="submit" name="submit" value="Search" />
</div>
}
This is the controller that never seems to get called...
public PartialViewResult SearchResource()
{
var results = new MemberResourcesViewModel.ResourceSearch();
results.resource_type = db.Resource_Types.Select(s => new SelectListItem
{
Text = s.name,
Value = s.id.ToString()
}).Distinct().OrderBy(x => x.Text);
return PartialView(results);
}
This is the ViewModel
namespace NewSAMACentral2._0.ViewModel
{
public class MemberResourcesViewModel
{
public IEnumerable<MemberResource> MemberResourceResult;
public MemberResource memberResource;
public class MemberResource
{
public ResourceSearch resource_search { get; set; }
[Key]
public int Id { get; set; }
[DisplayName("Resource Title")]
public string title { get; set; }
public IEnumerable<SelectListItem> resource_type { get; set; }
[DisplayName("Resource Type")]
public string resource_type_id { get; set; }
[DisplayName("Keyword")]
public string keyword { get; set; }
[DisplayName("Author")]
public string author { get; set; }
[DisplayName("Subject Type")]
public string subject_type { get; set; }
[DisplayName("Industry")]
public string industry { get; set; }
[DisplayName("Description")]
public string description { get; set; }
}
public class ResourceSearch
{
[DisplayName("Author")]
public string author { get; set; }
public IEnumerable<SelectListItem> resource_type { get; set; }
[DisplayName("Resource Type")]
public string resource_type_id { get; set; }
[DisplayName("Keyword")]
public string keyword { get; set; }
}
}
}
You have to be carefull with capital letters and put model not Model:
#Html.LabelFor(model => model.resource_type_id)
#Html.DropDownListFor(model => model.resource_type_id, model.resource_type, "-- Select --", new { #class = "form-control" })
On your main View Action (not partial view action result):
var types = db.Resource_Types.Distinct().OrderBy(x => x.Text).ToList();
SelectList typeList = new SelectList(types, "ID", "Name");
ViewBag.Types = typelist;
All that was done there was pull your objects from the DataBase. Then we turned that into a Select list with 'ID' as the value field and 'Name' as the text field. Then we put that select list in a viewbag to be used by our view.
Next In your partial view:
#Html.DropDownListFor(model => model.resource_type_id, new SelectList(ViewBag.Types, "value", "text"), "-- Select --", new { #class = "form-control" })
The only difference in this HTML is its pulling values from the select list so you never have to worry about it even hitting the partial view controller. I also changed the capital 'M' in model to a lowercase because not needed
Remember to put the code for the ViewBag in your main actionresult, not the partial view action result.
As Stephen commented below all you really need is :
#Html.DropDownListFor(model => model.resource_type_id, (SelectList)ViewBag.Types, "-Select-", ...)
Your error occurs because the value of Model.resource_type is null when used inside the DropDownListFor() method.
In your main view you use Html.Partial() to render a partial view named SearchResource.cshtml, passing it a new instance of your class ResourceSearch. But ResourceSearch does not have a default constructor which initializes the resource_type property so its null, hence the exception.
Your naming conventions and use of nested models make it difficult to understand, and you have not shown the GET method for the main view, but I suspect you are wanting to actually call the SearchResource() method on your controller which will return the partial view of the form. In which case you need to use
#{Html.RenderAction("SearchResource")}
which will call the method and return its partial. Since that method initializes a new instance of ResourceSearch and populates its resource_type property, it will no longer be null
Note you should also consider applying the [ChildActionOnly] attribute to the method so it cant be called by the user entering the url in the address bar.

asp.net mvc. Bind ViewModel to items in a collection

I can bind to properties in the ViewModel fairly simply like so:
<%=Html.RadioButtonFor(m => m.isCool, true%>cool<br/>
<%=Html.RadioButtonFor(m => m.isCool, false)%>not cool<br/>
but what if I wanted to bind to collection items within the ViewModel. I'm not sure if it's possible. Here's what I have:
My ViewModel:
namespace MvcApplication3.ViewModels
{
public class cpApprovalViewModel
{
// My internal class
public class PersonImage
{
public bool isApproved { get; set; }
public bool isProFilePic { get; set; }
public string uriId { get; set; }
public string urlPath { get; set; }
}
public string displayName { get; set; }
public bool isCool { get; set; }
public List<PersonImage> personImages { get; set; }
}
}
My Controller:
public class cpApprovalController : Controller
{
//
// GET: /cpApproval/
public ActionResult Index()
{
cpApprovalViewModel viewModel = new cpApprovalViewModel();
viewModel.displayName = "jon doe";
viewModel.personImages = new List<cpApprovalViewModel.PersonImage>()
{
new cpApprovalViewModel.PersonImage(){isApproved = false, uriId = "1", isProFilePic = false, urlPath="someImagePath1.jpg" },
new cpApprovalViewModel.PersonImage(){isApproved = false, uriId = "2", isProFilePic = false, urlPath="someImagePath2.jpg" },
new cpApprovalViewModel.PersonImage(){isApproved = false, uriId = "3", isProFilePic = false, urlPath="someImagePath2.jpg" }
};
return View(viewModel);
}
//cpApprovalViewModel viewModel
[HttpPost]
public void formReceiver(cpApprovalViewModel viewModel)
{
// This is where I'd like to get access to the changed personImages (approved/disapproved )
}
}
My View:
<%: Model.displayName %><br /><br />
<% using (Html.BeginForm("formReceiver", "cpApproval", FormMethod.Post )){ %>
<% foreach (var anImage in Model.personImages){ %>
<%: Html.RadioButtonFor(model => model.personImages.Single(i => i.uriId == anImage.uriId), true, new { id = anImage.uriId })%> Approve <br />
<%: Html.RadioButtonFor(model => model.personImages.Single(i => i.uriId == anImage.uriId), false, new { id = anImage.uriId })%> Deny <br />
<hr />
<% } %>
<input type="submit" value="Save" />
<%} %>
I'm getting the following error:
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
Am I trying to do something impossible here? I hope this makes sense. Thanks.
Yes - what you're trying to do is possible.
I think your problem is that you're trying to get the relevant image again using .Single, when you already have the item in the foreach loop.
Try changing:
<%: Html.RadioButtonFor(model => model.personImages.Single(i => i.uriId == anImage.uriId), true, new { id = anImage.uriId })%> Approve <br />
To:
<%: Html.RadioButtonFor(model => anImage, true, new { id = anImage.uriId })%> Approve <br />
More info on model-binding to a Collection here.

Roles Provider - AccountModel

I'm adding the Roles provider to the built in AccountModel but having some problems adding GetAllRoles in my view using the Register View Model.
View Model from AccountModel
public class RegisterModel
{
UserName, Email Etc....
[Required]
[DisplayName("AllRoles")]
public SelectList AllRoles { get; set; }
}
Roles Service added to AccountModel
public interface IRolesService
{
SelectList GetAllRoles();
}
public class RolesService : IRolesService
{
public SelectList GetAllRoles()
{
var AllRoles = new SelectList(Roles.GetAllRoles());
return AllRoles;
}
}
Register View Page Inherits RegisterModel
Form...
<div class="editor-label">
<%= Html.LabelFor(m => m.ConfirmPassword) %>
</div>
<div class="editor-field">
<%= Html.PasswordFor(m => m.ConfirmPassword) %>
<%= Html.ValidationMessageFor(m => m.ConfirmPassword) %>
</div>
<%= Html.DropDownListFor(m => m.AllRoles)%>
I'm not sure how to populate the DropDown list with all the Roles from the View Model.
Any help would be really great!!
I think you need properties for the selected role and the full list of roles. The list of roles will be used to populate the dropdown, the selected role will be populated on post with the selected value.
public class RegisterModel
{
UserName, Email Etc....
[Required]
[DisplayName("Role")]
public string Role { get; set; }
[ScaffoldColumn(false)]
public SelectList AllRoles { get; set; }
}
...
public ActionResult Register()
{
var roleService = new RoleService();
var model = new RegisterModel
{
AllRoles = roleService.GetAllRoles(),
// Role = "User" if you want to choose a default
}
return View( model );
}
<div class="editor-label">
<%= Html.LabelFor(m => m.ConfirmPassword) %>
</div>
<div class="editor-field">
<%= Html.PasswordFor(m => m.ConfirmPassword) %>
<%= Html.ValidationMessageFor(m => m.ConfirmPassword) %>
</div>
<%= Html.DropDownListFor(m => m.Role, Model.AllRoles, "--select--", null )%>

Model binding form to a List using dropdown. binding failing 1 way for dropdown

I have an action called Sessies. In this action i am creating 'Sessies' objects from a form. if they don't exist i add them in the DB, if there are already Sessies objects connected to the 'Reeksen' object, i load the 'Sessies' into the form so that they can be edited. so i have a create and edit in 1 and the same form.
Also, a 'Reeksen' has a predefined number of 'Sessies' which can not be changed. so i let the user make all 'Sessies' in one time (cos the amount of sessies will be from 1 to 10)
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<MVC2_NASTEST.Models.FlatSessie>>" %>
...
<h2>
Sessies</h2>
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<% for (int i = 0; i < Model.Count; i++) { %>
<%= Html.HiddenFor(model => model[i].Ses_ID)%>
<%= Html.HiddenFor(model => model[i].Ses_Rks_ID)%>
<div class="editor-label">
<%= Html.LabelFor(model => model[i].Ses_Nummer)%>
</div>
<div class="editor-field">
<%= Html.HiddenFor(model => model[i].Ses_Nummer)%>
<%= Html.Label(Model[i].Ses_Nummer.ToString())%>
</div>
....
<div class="editor-label">
<%= Html.LabelFor(model => model[i].Ses_LG_ID)%>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model[i].Ses_LG_ID, MVC2_NASTEST.MvcApplication.lesgeverList(), "Selecteer een lesgever...")%>
<%= Html.ValidationMessageFor(model => model[i].Ses_LG_ID)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model[i].Ses_Lpl_ID)%>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(model => model[i].Ses_Lpl_ID, (ViewData["lesplist"] as List<List<SelectListItem>>)[i], "Selecteer een lesplaats...")%>
<%= Html.ValidationMessageFor(model => model[i].Ses_Lpl_ID)%>
</div>
<% } %>
<p>
<input type="submit" value="Create" />
</p>
in my aspx i use a for loop which goes over the List (a FlatSessie is a Sessie flattened strings.)
namespace MVC2_NASTEST.Models {
public partial class FlatSessie {
public int Ses_ID { get; set; }
public int Ses_Nummer { get; set; }
public string Ses_Datum { get; set; }
public string Ses_Beginuur { get; set; }
public string Ses_Einduur { get; set; }
public int Ses_Lpl_ID { get; set; }
public int Ses_Rks_ID { get; set; }
public int Ses_LG_ID { get; set; }
}
}
so, in my code it goes like this:
int antses = m.Mod_AantalSessies.Value;
List<List<SelectListItem>> lpllst = new List<List<SelectListItem>>(antses);
List<FlatSessie> sl = new List<FlatSessie>(antses);
Reeksen rks = _db.Reeksens.First(r => r.Rks_ID == id)
...
List<Sessies> sesl = rks.Sessies.ToList();
for (int i = 0; i < antses; i++) {
sl.Add(Mapper.Map<Sessies, FlatSessie>(sesl[i]));
lpllst.Add(MvcApplication.lesplaatsList(schooljaarparam, sesl[i].Ses_Lpl_ID));
}
...
ViewData["lesplist"] = lpllst;
ViewData["lglist"] = MvcApplication.lesgeverList();
return View(sl);
and the lesgeverlist() method
public static List<SelectListItem> lesgeverList() {
NASDataContext _db = new NASDataContext();
var lesg = (from l in _db.Lesgevers
where l.LG_Naam != "leeg"
orderby l.LG_Naam
select l).ToSelectList(m => m.LG_Naam + " " + m.LG_Vnaam, m => m.LG_ID.ToString(), m => m.LG_ID < -1);
return lesg.ToList();
}
now the problem:
this all works brilliantly. the List goes to the ASPX, i get the form as much times as there are items in the List, and postback works also, the parsing goes and everything. so all is good except for 1 point: the dropdowns.
usually in MVC i don't set any selected value for a SelectList or for a List because they dont need it, in the Edit page, MVC sets those selected items itself on binding.
now however, with the form in the Foreach loop, all fields get filled besides the dropdown boxes, these do not receive their 'initial value'.
however when i set an item in the List as selected, it does get selected in the form. (as seen from the ViewData["lesplist"]) but when i send a normal List with no selected value, the model binder does not propagate it's given value for that field to the selectedvalue of the dropdown.
however, when i do a form submit, and i return the view (because of validation failed) the dropdowns DO keep their value.
Is this fixable, or is this just a flaw in MVC2?
DropDownListFor not binding on Edit View with repeating items (List<T>)
there is the answer :)

Validating a SelectList in ASP.NET MVC 2 with Data Annotations

I'm trying to use the built in ASP.NET MVC 2 client side validation on a Select List like the following:
private SelectList _CategoryList;
[Required(ErrorMessage = "Category Required")]
[System.ComponentModel.DataAnnotations.Range(1, double.MaxValue, ErrorMessage = "Please Select A Category")]
[DisplayName("Category")]
public SelectList CategoryList
{
get
{
return new SelectList(Categories, "CatID", "CatFullName"); ;
}
set
{
_CategoryList = value;
}
}
However it's not working...if the default value which is 0 is selected the validation message does not appear and the page progresses as though it's validated. Thoughts?
Ok so I found the answer in an answer to a slightly different question. So I'm posting my complete code here, which extends on Scott Guthries ASP.NET MVC 2 Validation post: http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
My ViewModel:
public class Person
{
[Required(ErrorMessage="First Name Required")]
[StringLength(50,ErrorMessage="Must be under 50 characters")]
public string FirstName { get; set; }
[Required(ErrorMessage="Last Name Required")]
[StringLength(50, ErrorMessage = "Must be under 50 characters")]
public string LastName { get; set; }
[Required(ErrorMessage="Age Required")]
[Range(1,120,ErrorMessage="Age Must be between 0 and 120")]
public int Age { get; set; }
[Required(ErrorMessage="Email Required")]
public string Email { get; set; }
public IEnumerable<SelectListItem> FavoriteColor { get; set; }
[Range(0, 6, ErrorMessage = "Out of range")]
public int SelectedFavColor { get; set; }
}
My Color class:
public class Colors
{
public int ColorID { get; set; }
public string ColorName { get; set; }
}
My list helper extensions stolen from Rob Connery, who stole it from someone else:
public static class ListExtensions
{
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T> action)
{
foreach (var item in collection) action(item);
return collection;
}
public static SelectList ToSelectList<T>(this IEnumerable<T> collection)
{
return new SelectList(collection, "Key", "Value");
}
public static SelectList ToSelectList<T>(this IEnumerable<T> collection, string selectedValue)
{
return new SelectList(collection, "Key", "Value", selectedValue);
}
public static SelectList ToSelectList<T>(this IEnumerable<T> collection,
string dataValueField, string dataTextField)
{
return new SelectList(collection, dataValueField, dataTextField);
}
public static SelectList ToSelectList<T>(this IEnumerable<T> collection,
string dataValueField, string dataTextField, string selectedValue)
{
return new SelectList(collection, dataValueField, dataTextField, selectedValue);
}
}
My Controller Code (yes it could be refactored to be more DRY):
public ActionResult Create()
{
Person newFriend = new Person();
IList<Colors> colorslist = new List<Colors>();
colorslist.Add(new Colors { ColorID = -1, ColorName = "Please Select Color" });
colorslist.Add(new Colors { ColorID = 1, ColorName = "Red" });
colorslist.Add(new Colors { ColorID = 2, ColorName = "Green" });
colorslist.Add(new Colors { ColorID = 3, ColorName = "Blue" });
newFriend.FavoriteColor = colorslist.ToSelectList("ColorID","ColorName","-1");
return View(newFriend);
}
[HttpPost]
public ActionResult Create(Person friendToCreate, FormCollection collection)
{
friendToCreate.SelectedFavColor = Convert.ToInt32(collection["SelectedFavColor"]);
if (ModelState.IsValid)
{
return Redirect("/");
}
IList<Colors> colorslist = new List<Colors>();
colorslist.Add(new Colors { ColorID = -1, ColorName = "Please Select Color" });
colorslist.Add(new Colors { ColorID = 1, ColorName = "Red" });
colorslist.Add(new Colors { ColorID = 2, ColorName = "Green" });
colorslist.Add(new Colors { ColorID = 3, ColorName = "Blue" });
friendToCreate.FavoriteColor = colorslist.ToSelectList("ColorID", "ColorName");
return View(friendToCreate);
}
My page markup:
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(model => model.FirstName) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.FirstName) %>
<%= Html.ValidationMessageFor(model => model.FirstName) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.LastName) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.LastName) %>
<%= Html.ValidationMessageFor(model => model.LastName) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.Age) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.Age) %>
<%= Html.ValidationMessageFor(model => model.Age) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.Email) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(model => model.Email) %>
<%= Html.ValidationMessageFor(model => model.Email) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(model => model.FavoriteColor) %>
</div>
<div class="editor-field">
<%= Html.DropDownList("SelectedFavColor", Model.FavoriteColor, -1)%>
<%= Html.ValidationMessageFor(model => model.SelectedFavColor) %>
</div>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
<% } %>
When I work with my ViewModel, I'd have a property CategoryId and put my range validator on that, not the dropdown. The Selectlist just provides the data - you validate against the model.
[Required(ErrorMessage = "Category Required")]
[System.ComponentModel.DataAnnotations.Range(1, double.MaxValue, ErrorMessage = "Please Select A Category")]
[DisplayName("Category")]
public int CategoryId {get;set;}
On the view I'd have my dropdown with the id for my category but the list from my Categories:
<%= Html.DropDownList("CategoryId", (SelectList)Model.Categories, "(Select)")%>
when your data posts back to the server, you should observe that the class contains the id value.
I don't think it has to do with DataAnnotations because it happens without them as well when you have a model bound to an entity with a non-nullable and you try to put an invalid value in. What I have done is to send along ModelState["XXXX"].Value.AttemptedValue from the form and validate against that instead of the property in the entity. I wonder if validating against the raw form data entirely instead of just the problem items is more appropriate.
A similar reply: ASP.NET MVC: DataAnnotations - Show an error message indicating that a field must be numeric
I also posed a similar question: ASP.NET MVC. Validation fails on dropdown no matter the value

Resources