Using ModelMetadata how do you call DisplayFor and have the metadata used - asp.net-mvc-2

This question is based on using the Asp.Net MVC tabular layout display template from Phil Haack http://haacked.com/archive/2010/05/05/asp-net-mvc-tabular-display-template.aspx
The problem is in the Html.DisplayFor(m => propertyMetadata.Model). This displays the data from the property just fine, however it doesn't use any of the data annotations that may be present. I'm mostly thinking the DataType annotation here for example DataType.Date. This annotation correctly outputs a short date when used with DisplayFor, but not when the property is reflected from the ModelMetadata as in this example. (It shows a full DateTime)
<% for(int i = 0; i < Model.Count; i++) {
var itemMD = ModelMetadata.FromLambdaExpression(m => m[i], ViewData); %>
<tr>
<% foreach(var property in properties) { %>
<td>
<% var propertyMetadata = itemMD.Properties
.Single(m => m.PropertyName == property.PropertyName); %>
<%= Html.DisplayFor(m => propertyMetadata.Model) %>
</td>
<% } %>
</tr>
An example model would be an
public class TableModel
{
[UIHint("Table")]
public PeriodModel[] Periods { get; set; }
}
public class PeriodModel
{
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Date From")]
[DataType(DataType.Date)]
public DateTime DateFrom { get; set; }
[DisplayName("Date To")]
[DataType(DataType.Date)]
public DateTime DateTo { get; set; }
}
So how would this be changed to get the full metadata behaviour of DisplayFor?

Seems this is a simple one, you just have to change:
Html.DisplayFor(m => propertyMetadata.Model)
to
Html.DisplayFor(m => propertyMetadata.Model, propertyMetadata.TemplateHint)

I've been struggling with this myself for a few hours and this is the only hit I found on Google. But the answer is wrong.
propertyMetadata.TemplateHint is always null in my case.
But this works:
Html.DisplayFor(m => propertyMetadata.Model, propertyMetadata.DataTypeName)

you can check #Html.ValueFor(m => propertyMetdata.Model) instead of DisplayFor.

Related

controller post action not able catch list of objects

It may duplicate question, i have searched all over but couldn't satisfied, so i am posting here question.
I have object as (generated from entity framework),
public partial class Usp_Sel_NotEnteredStringResources_Result
{
public string name { get; set; }
public Nullable<int> Value { get; set; }
public int StringResourceId { get; set; }
public Nullable<int> LanguageId { get; set; }
}
and view i have created as,
#{
ViewBag.Title = "Resource Entry Languagewise";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>ResourceEntry</h2>
#using (Html.BeginForm("ResourceEntry", "LangResource", FormMethod.Post))
{
<fieldset>
<table>
#for (int i = 0; i < Model.ToList().Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => Model.ElementAt(i).name)</td>
<td>#Html.TextBoxFor(m => Model.ElementAt(i).Value)</td>
#Html.HiddenFor(m => Model.ElementAt(i).LanguageId)
#Html.HiddenFor(m => Model.ElementAt(i).name)
#Html.HiddenFor(m => Model.ElementAt(i).StringResourceId)
</tr>
}
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
And Controller as,
[HttpPost]
public ActionResult ResourceEntry(List<Usp_Sel_NotEnteredStringResources_Result> list)
{
// here getting the null value for list
// ??????
return View(list);
}
After submitting the form controller gets null value for list, what is wrong with code??
You cannot use .ElementAt(). If you inspect the html your generating you will see that the name attribute has no relationship to your model.
You model needs to implemet IList<Usp_Sel_NotEnteredStringResources_Result> and use a for loop
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => m[i].name)</td>
<td>
#Html.TextBoxFor(m => m.[i]Value)
// note you hidden inputs should be inside a td element
#Html.HiddenFor(m => m[i].LanguageId)
....
</td>
</tr>
}
Alternative if the model is IEnumerable<Usp_Sel_NotEnteredStringResources_Result>, you can use an EditorTemplate (refer [Post an HTML Table to ADO.NET DataTable for more details on how the name attributes must match your model property names, and the use of an EditorTemplate)

Why when I pass a Lambda expressin Func thats give me an error

When I pass call a Generic Method that return A Func and pass in parameter of the Where, that's dosen't work. (System.InvalidOperationException: Internal .NET Framework Data Provider error 1025.)
The error is when I want to get the Role information.
For the Role, I need to perform a Where Clause Expression EX: (p => p.LangID == 1)
This code dosen't Work
In the repository
public Func<T, bool> GetLmbLang<T>() where T:class,IBaseGenericTxt
{
int lang = -1;
lang = Convert.ToInt32(HttpContext.Current.Session["Language"]);
return (p => p.LangID == lang);
}
In the controller
var ViewModel = _db.Contacts.Where(a=> a.IsActive == true).Select(a => new ContactListViewModel {
ContactID = a.ContactID,
ContactName = a.ContactName,
Role = a.ContactType.ContactTypeTexts.Where(repGeneric.GetLmbLang<ContactTypeText>()).Select(af => af.Txt).FirstOrDefault(),
CompanyType = a.Supplier.SupplierName,
Addr = a.Address ,
Email = a.ContactEmail,
Phone = a.ContactPhone
}).ToList();
for (int i = 0; i < ViewModel.Count(); i++)
{
Response.Write(ViewModel.ElementAt(i).ContactID + "<br />");
}
This code WORK
int lang = -1;
lang = Convert.ToInt32(Session["Language"]);
var ViewModel = _db.Contacts.Where(a=> a.IsActive == true).Select(a => new ContactListViewModel {
ContactID = a.ContactID,
ContactName = a.ContactName,
Role = a.ContactType.ContactTypeTexts.Where(p => p.LangID == lang).Select(af => af.Txt).FirstOrDefault(),
CompanyType = a.Supplier.SupplierName,
Addr = a.Address ,
Email = a.ContactEmail,
Phone = a.ContactPhone
}).ToList();
for (int i = 0; i < ViewModel.Count(); i++)
{
Response.Write(ViewModel.ElementAt(i).ContactID + "<br />");
}
My ContactListViewModel
public class ContactListViewModel
{
public int ContactID { get; set; }
public string ContactName { get; set; }
public string Role { get; set; }
public string Company { get; set; }
public string CompanyType { get; set; }
public Address Addr { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
My List View
..... Inherits="System.Web.Mvc.ViewPage<List<mvcinfosite.ViewModels.ContactListViewModel>>" %>
<table class="genTable">
<% for (int i = 0;i < Model.Count; i++) { %>
<tr>
<td>
<%: Html.ActionLink(item.ContactName, "Edit", new { id=item.ContactID }) %>
</td>
<td>
<%: item.Role %>
</td>
<td>
<%: item.Company %>
</td>
<td>
<%: item.CompanyType %>
</td>
<td>
<%: GlobalHelper.GetAddress(item.Addr) %>
</td>
<td>
<%: item.Email %>
</td>
<td>
<%: item.Phone %>
</td>
</tr>
<% } %>
</table>
As naasking points out, you need to use an Expression of a Func instead of a straight Func:
public Expression<Func<T, bool>> GetLmbLang<T>() where T:class,IBaseGenericTxt
{
int lang = -1;
lang = Convert.ToInt32(HttpContext.Current.Session["Language"]);
return (p => p.LangID == lang);
}
Edit
Ah, yes, well the problem is that your function doesn't actually know what class it's working with at compile time: it only knows that it's a class, and it implements IBaseGenericTxt. So when you say p.LangId, that part of the expression is calling IBaseGenericTxt.LangId, and not ContactTypeText.LangId.
You'll need to build your own expression tree in order to get this to work right. Something like this:
var paramExpr = Expression.Parameter(typeof(T), "p");
return Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Property(paramExpr, "LangId"),
Expression.Constant(lang)),
paramExpr);
Edit 2
Two things:
Because LINQ to Entities will try to take anything in a query expression and convert it to a SQL statement, you have to be careful not to go calling methods in the middle of your query. You'll want to call the GetLmbLang method first and store its value in a variable to use in the query.
As you point out in your comment, because the ContactTypeTexts property does not implement IQueryable, this gets particularly tricky. You have three options as far as I can tell:
Create your entire select statement as an expression tree. This is very annoying and error-prone.
Use Joe Albari's LinqKit to "Compile" and "Expand" your query. LinqKit will traverse the expression tree and build a new tree wherein your query Expression is converted to its equivalent Func.
Go back to your data context rather than using the ContactTypeTexts property.
Personally, I would probably go with the last option, like this:
var lambdaLang = repGeneric.GetLmbLang<ContactTypeText>();
var ViewModel = _db.Contacts
.Where(a=> a.IsActive == true)
.Select(a => new ContactListViewModel {
ContactID = a.ContactID,
ContactName = a.ContactName,
Role = _db.ContactTypeTexts
.Where(ct => ct.ContactType.Contacts.Any(
c => c.ContactId == a.ContactId)
.Where(lambdaLang)
.Select(af => af.Txt).FirstOrDefault(),
CompanyType = a.Supplier.SupplierName,
Addr = a.Address ,
Email = a.ContactEmail,
Phone = a.ContactPhone
}).ToList();
The latter code works because the C# compiler converts it into an expression tree, ie. System.Linq.Expression, whereas your original code was compiled as a Func. Linq to SQL as currently designed cannot process the Func, only expression trees.

Data is empty in Post method asp.net mvc 2

I`m trying to display classes that have properties of type some custom class inside it.
Model:
public class ComplexParent
{
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
public class SimpleChild
{
public int Id { get; set; }
public string ChildName { get; set; }
public string ChildDescription { get; set; }
}
Controller:
public ActionResult Testify(int id)
{
ComplexParent par = new ComplexParent();
par.First = new SimpleChild() { Id = id };
par.Second = new SimpleChild()
{
Id = id + 1,
ChildName = "Bob",
ChildDescription = "Second"
};
return View("Testify", par);
}
[HttpPost]
public ActionResult Testify(ComplexParent pComplexParent)
{
return View("Testify", pComplexParent);
}
View:
<% using (Html.BeginForm())
{%>
<fieldset>
<legend>Fields</legend>
<%: Html.EditorFor(x => x.First) %>
<br />
<%: Html.EditorFor(x => x.Second.ChildName)%>
<br/>
<br/>
<br/>
<% Html.RenderPartial("SimpleChild", Model.First); %>
<p>
<input type="submit" value="Watch me :-)" />
</p>
</fieldset>
<% } %>
When it comes to Get it works just fine, I can see all the data. But on post pComplexParent parameter is empty (both properties of a complex class are nulls). Probably Im missing something here, but could not get this to work ...
Small addition: view part that only shows editor for name makes Second child not null and name is set to Bob. But I dont understand how to make it just with EditorFor or DisplayFor methods.
UPDATE: Thanks to Darin Dimitrov, who kindly went throught all my code and found what caused this problem. The exact problem was that if you are using display template, asp.net mvc 2 doesnt post any values back and if whole template is has nothing to post back object is null. I still thinking of the way how to get the data, even if you don`t want to edit it. But using editor template does the thing and I have all objects filled with proper data now.
Your view is a bit of a mess. You are using editor templates along with partials for the first child. It is not very clear what fields are included inside the form. I would recommend you using only editor templates:
Model:
public class ComplexParent
{
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
public class SimpleChild
{
public int Id { get; set; }
public string ChildName { get; set; }
public string ChildDescription { get; set; }
}
Controller:
[HandleError]
public class HomeController : Controller
{
public ActionResult Testify(int id)
{
var par = new ComplexParent();
par.First = new SimpleChild() { Id = id };
par.Second = new SimpleChild()
{
Id = id + 1,
ChildName = "Bob",
ChildDescription = "Second"
};
return View(par);
}
[HttpPost]
public ActionResult Testify(ComplexParent pComplexParent)
{
return View(pComplexParent);
}
}
View:
<% using (Html.BeginForm()) { %>
<%: Html.EditorFor(x => x.First) %>
<%: Html.EditorFor(x => x.Second) %>
<input type="submit" value="Watch me :-)" />
<% } %>
Editor template for SimpleChild (~/Views/Home/EditorTemplates/SimpleChild.ascx):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SomeNs.Models.SimpleChild>" %>
<%: Html.HiddenFor(x => x.Id) %>
<%: Html.EditorFor(x => x.ChildName) %>
<%: Html.EditorFor(x => x.ChildDescription) %>
Now if you want to have different editor templates for the two child properties you could either specify the editor template name when including it:
<%: Html.EditorFor(x => x.First, "FirstChildEditor") %>
which corresponds to ~/Views/Home/EditorTemplates/FirstChildEditor.ascx or use an [UIHint] attribute at your model:
public class ComplexParent
{
[UIHint("FirstChildEditor")]
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
My recommendation is not to use Html.RenderPartial for generating input fields because their names will be hardcoded and won't bind properly depending on your objects hierarchy.

In MVC2, how do I validate fields that aren't in my data model?

I am playing with MVC2 in VS 2010 and am really getting to like it. In a sandbox application that I've started from scratch, my database is represented in an ADO.NET entity data model and have done much of the validation for fields in my data model using Scott Guthrie's "buddy class" approach which has worked very well.
However, in a user registration form that I have designed and am experimenting with, I'd like to add a 'confirm email address' or a 'confirm password' field. Since these fields obviously wouldn't exist in my data model, how would I validate these fields client side and server side?
I would like to implement something like 'Html.ValidationMessageFor', but these fields don't exist in the data model. Any help would be greatly appreciated.
I use view models. I don't create the data model instance to persist until the view model is valid.
Below is a simple example. Notice that some of the properties are data models, but the validation properties only exist on this view model.(the base isn't pertinent here)
public class ProblemAddToDepartmentProductView : ViewModel
{
public Problem Problem { get; set; }
public IList<Product> AllProducts { get; set; }
public IList<Department> AllDepartments { get; set; }
public string ProblemId { get; set; }
public string ProblemName { get; set; }
[DisplayName("Choose the product:")]
[Required(ErrorMessage = "Select the Product.")]
public string SelectedProduct { get; set; }
public SelectList GetProducts()
{
var selectList = new SelectList(AllProducts, "Id", "Name");
return selectList;
}
[DisplayName("Choose the department using this problem for that product:")]
[Required(ErrorMessage = "Select the Department.")]
public string SelectedDepartment { get; set; }
public SelectList GetDepartments()
{
var selectList = new SelectList(AllDepartments, "Id", "Name");
return selectList;
}
internal class ProductSelect
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
It will also help to see it wired on the page:
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(x => x.SelectedProduct) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(x => x.SelectedProduct, Model.GetProducts(),"--Select One--") %>
<%= Html.ValidationMessageFor(x => x.SelectedProduct)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(x => x.SelectedDepartment) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(x => x.SelectedDepartment, Model.GetDepartments(),"--Select One--") %>
<%= Html.ValidationMessageFor(x => x.SelectedDepartment)%>
</div>
<p>
<input type="submit" value="Add Selected" />
</p>
</fieldset>
I also do this so the model will have these values if the validation fails, to pull back in the needed data for the drop downs:
p.ProblemId) %>
<%= Html.HiddenFor(p => p.ProblemName) %>
Client-Side:
Using Javascript Validation
and/or
Server-Side:
Validate in Controller (Using FormCollection) - or
Create "CustomViewModel" Class that encapsulates all validation strongly type your view - or
You could add two string properties to your Model. Doing so will allow you to populate an instance of your model in a Controller and validate appropriately in your Model/s...

Html.EditorFor does not render anything if I use a custom object?

It seems like it's not possible to edit custom object anymore after I upgraded to asp.net mvc 2 rc 2? I use this approach http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html with a custom object like this:
My model has just one property but inherits from an abstract base class
public class Page : ContentItem {
[DataType(DataType.MultilineText)]
public virtual string MainIntro { get;set; } // This property render correct
[DisplayFormat(NullDisplayText="(null value)")]
public virtual DetailCollection Tags { get; set; }
}
My controller looks like this
public ActionResult Edit(string pagePath) {
var page = _repository.GetByUrlSegment(pagePath);
return View(page.EditViewName, new DashboardModel(page, RootPages));
}
And my view looks like this
<% using (Html.BeginForm("update","Dashboard", FormMethod.Post, new { name = "editForm" } )) %>
<% { %>
<div>
<%=Html.EditorFor(model => model.CurrentItem) %>
<div class="editor-button">
<input type="submit" value="Save" />
</div>
</div>
<% } %>
Perhaps it would be better to expose this to the view as a space-separated string and exclude the collection from being displayed in the view. Alternatively, you might be able to define a specific template for how you want to display a collection. It's not clear to me how MVC would be able to determine what to display otherwise.
Try something like:
[ShowForDisplay(false)]
[ShowForEdit(false)]
public virtual DetailCollection Tags { get; set; }
public virtual string TagList
{
get
{
if (tags == null) return "(null value)";
// assumes DetailCollection implements IEnumerable<string>
return string.Join( " ", tags.Select( t => t).ToArray() );
}
set
{
tags = new DetailCollection( value.Split( new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries )
.Select( s => s.Trim() ) );
}
}