I want to create a new attribute TipAttribute that I can use to display helpful tips to my users.
public class EditPersonModel {
[Display(Name = "Full Name")]
[Tip(Message = "Enter the person's full name.")]
public string Name { get; set; }
}
So from my View I would do
<div class="editor-field">
#Html.InputFor(m => m.Name)
#Html.TipFor(m => m.Name)
</div>
and it would render
<div class="editor-field">
<input id="Name" name="Name" type="text" value="">
<div class="tip">Enter the person's full name.</div>
</div>
I know that I would need to write an Extension method for HtmlHelper, but I have no idea what should go inside!
public static MvcHtmlString TipFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) {
???
}
I've started looking at the implementation of DisplayAttribute within Mvc3 Source Code, but it's very complex and doesn't look like providing my own custom attribute is very easy/pluggable. Is there a standard MVC3-way to do this?
Thank you!
Assuming your tip attribute was made metadata aware:
public class TipAttribute: Attribute, IMetadataAware
{
public string Message { get; set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["tip"] = Message;
}
}
you could:
public static class HtmlExtensions
{
public static IHtmlString TipFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var tip = metaData.AdditionalValues["tip"] as string;
var div = new TagBuilder("div");
div.AddCssClass("tip");
div.SetInnerText(tip ?? string.Empty);
return new HtmlString(div.ToString());
}
}
Related
I am performing Remote Validation in ASP.NET MVC3,but its not working means it not calling the action as I am seeing in Chrome java script Console.Here is my Code:
Model:
public class Genre
{
public int GenreId { get; set; }
[Remote("GenreNameExist","Genre",ErrorMessage="Genre name already Exists!!!")]
public string Name { get; set; }
public String Description { get; set; }
public List<Album> Albums { get; set; }
}
Controller:
public ActionResult GenreNameExist(Genre genre)
{
var gen = db.Genres.Where(x=>x.Name==genre.Name).FirstOrDefault();
if (gen == null)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
else {
return Json(string.Format("{0} is not available.", Name), JsonRequestBehavior.AllowGet);
}
}
and View:
#Html.ValidationSummary(true);
<div class="editor-label">
#Html.LabelFor(x => x.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(x => x.Name)
#Html.ValidationMessageFor(x=>x.Name)
</div>
Thanks in Advance!!!
You need to add:
var gen = db.Genres.Where(x=>x.Name==Name).FirstOrDefault();
so it will execute.
In the controller pass the class Genre so it maps to that class and then add if(ModelState.IsValid) so it'll perform validation against that class. Or add a ViewModel class.
Update:
Do you have the scripts in the right order?
<script src="/Scripts/jquery-1.7.2.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.min.js" type="text/javascript"></script>
My model is as follows:
public class testCreateModel
{
public string s1 { get; set; }
public SelectList DL { get; set; }
public testCreateModel()
{
Dictionary<string, string> items = new Dictionary<string, string>();
items.Add("1", "Item 1");
items.Add("2", "Item 2");
DL = new SelectList(items, "Key", "Value");
}
}
My initiating actions is:
public ActionResult testCreate()
{
testCreateModel model = new testCreateModel();
return View(model);
}
My Razor view (irrelevant parts deleted) is:
#model Tasks.Models.testCreateModel
#using (Html.BeginForm()) {
<fieldset>
<legend>testCreateModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.s1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.s1)
</div>
<div class="editor-label">
Select an item:
</div>
<div class="editor-field">
#Html.DropDownList("dropdownlist", (SelectList)Model.DL)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
The post back action is:
public ActionResult testCreate(testCreateModel model, FormCollection collection)
{
if (ModelState.IsValid)
{
Console.WriteLine("SelectedValue: ",model.DL.SelectedValue);
Console.WriteLine("FormCollection:", collection["dropdownlist"]);
// update database here...
}
return View(model);
}
On post back, model.DL.SelectedValue is null. (However, the selected item can be obtained from FormCollection, but that is besides the point). The DL object is still properly populated otherwise, Immediate Window output as follows:
model.DL
{System.Web.Mvc.SelectList}
base {System.Web.Mvc.MultiSelectList}: {System.Web.Mvc.SelectList}
SelectedValue: null
model.DL.Items
Count = 2
[0]: {[1, Item 1]}
[1]: {[2, Item 2]}
model.DL.SelectedValue
null
Q1: How can I make use of the SelectedValue property instead?
Now, if in the Razor view I change the name of the Html SELECT tag to DL (ie same as the property name in the model):
#Html.DropDownList("DL", (SelectList)Model.DL)
I get an exception:
No parameterless constructor defined for this object.
Stack Trace:
[MissingMethodException: No parameterless constructor defined for this object.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache) +98
System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache) +241
System.Activator.CreateInstance(Type type, Boolean nonPublic) +69
System.Web.Mvc.DefaultModelBinder.CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) +199
System.Web.Mvc.DefaultModelBinder.BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult
...
Q2: Why?
Thanks.
MVC will return just the value of the selected option in your POST, so you need a property to contain the single value that returns.
As a good advice, try setting SelectLists through ViewBag, that helps keep your ViewModels clean from data that needs to populate the form.
So your example could be solved like this:
public class testCreateModel
{
public string s1 { get; set; }
public int SelectedValue { get; set; }
}
and in your View just do this:
#Html.DropDownList("SelectedValue", (SelectList)ViewBag.DL)
prior to populating ViewBag.DL in your GET action.
As for your Q2, the default ModelBinder requires that all types to bind to have a default constructor (so that the ModelBinder can create them)
An answer has been selected, but look at how I did it. Below is code how I normally do it when populating a drop down. It is very simplistic, I suggest you use it as a base to build your drop downs.
At the top of my view I specify my view model:
#model MyProject.ViewModels.MyViewModel
On my view I have a drop down list that displays all the banks that a user can select from:
<table>
<tr>
<td><b>Bank:</b></td>
<td>
#Html.DropDownListFor(
x => x.BankId,
new SelectList(Model.Banks, "Id", "Name", Model.BankId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.BankId)
</td>
</tr>
</table>
I always have a view model for a view, I never pass a domain object directly to the view. In this case my view model will contain a list of banks that will be populated from the database:
public class MyViewModel
{
// Other properties
public int BankId { get; set; }
public IEnumerable<Bank> Banks { get; set; }
}
My bank domain model:
public class Bank
{
public int Id { get; set; }
public string Name { get; set; }
}
Then in my action method I create an instance of my view model and populate the banks list from the database. Once this is done then I return the view model to the view:
public ActionResult MyActionMethod()
{
MyViewModel viewModel = new ViewModel
{
// Database call to get all the banks
// GetAll returns a list of Bank objects
Banks = bankService.GetAll()
};
return View(viewModel);
}
[HttpPost]
public ActionResult MyActionMethod(MyViewModel viewModel)
{
// If you have selected an item then BankId would have a value in it
}
I hope this helps.
I'm trying to make use of the extended HTML Helper DisplayFor in this View:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcCms.Web.ViewModels.SubscriptionsViewModel>" %>
<% using (Html.BeginForm("TrainingSubscription", "Account", FormMethod.Post))
{ %>
<%: Html.DisplayFor(m => m.Subscriptions) %>
<input type="submit" value="Save" />
<% } %>
with the following ViewModel
namespace MvcCms.Web.ViewModels
{
public class SubscriptionsViewModel
{
public string TrainingId { get; set; }
public string Subject { get; set; }
public IEnumerable<SubscriptionViewModel> Subscriptions { get; set; }
public SubscriptionsViewModel(string TrainingId, string Subject, IEnumerable<SubscriptionViewModel> Subscriptions)
{
this.TrainingId = TrainingId;
this.Subject = Subject;
this.Subscriptions = Subscriptions;
}
}
public class SubscriptionViewModel
{
public string ContactId { get; set; }
public string FullName { get; set; }
public bool Subscribed { get; set; }
public SubscriptionViewModel(string ContactId, string FullName, bool Subscribed)
{
this.ContactId = ContactId;
this.FullName = FullName;
this.Subscribed = Subscribed;
}
}
}
It's giving me this error
The type arguments for method
'System.Web.Mvc.Html.DisplayExtensions.DisplayFor(System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)'
cannot be inferred from the usage. Try specifying the type arguments explicitly
I can't figure what's wrong. Note that I'm able to access the Model in a strongly typed manner with IntelliSense popping up in the view. However, IntelliSense is not popping up when I'm typing the lambda-expression.
I got it working now, the problem was that the project still compiled with .NET v3.5 instead of v4.0, see:
https://stackoverflow.com/a/7142200/1232507
Here are my model.
public class InfoModel
{
public NameModel Name { get; set; }
public string Phone { get; set; }
}
public class NameModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public NameModel(string first, string last)
{
this.FirstName = first;
this.LastName = last;
}
}
Then I have a partial View just for displaying names as follows
#model MyTestApp.Models.NameModel
#Html.LabelFor( m => m.LastName)
#Html.TextBoxFor( m => m.LastName)
<br />
#Html.LabelFor( m => m.FirstName)
#Html.TextBoxFor( m => m.FirstName)
Then there is a view for ShowInfo
#model MyTestApp.Models.InfoModel
#using (#Html.BeginForm())
{
#Html.Partial("ShowName", Model.Name)
<br />
#Html.LabelFor( m => m.Phone)
#Html.TextBoxFor(m => m.Phone)
<br />
<input type="submit" value="Submit Info" />
}
Now user submit any info, following controller method is called
[HttpPost]
public ActionResult ShowInfo(InfoModel model)
{
...
}
Problem is when i inspect the value of model, phone is fine but name is null. Any ideas how to make it work?
The DefaultModelBinder class uses Activator.CreateInstance(typeToCreate) internally to create the model classes. Your NameModel class dosn't have a default constructor so the DefaultModelBinder can't instantiate it. So if you add the default constructor it should work.
EDIT
It won't work Partial view you need to use an EditorTemplate instead:
Create a folder under your view folder with the name EditorTemplates and put your ShowName.cshtml there add in your main view use:
#using(Html.BeginForm())
{
#Html.EditorFor(m => m.Name, "ShowName")
...
The DefaultModelBinder works if the Model class has a default constructor and the properties have get and set. In all other cases it doesn't work.
Example
public class Product
{
public int id;
public string name;
}
doesn't work.
public class Product
{
public int id {get; set;}
public string name {get; set;}
public Product()
{
}
}
works.
i have a model SiteMapModel that have a object VirtualFolderModel inside him.
public class SiteMapModel
{
public SiteMapModel(DataRow data)
{
SMF_ID = Convert.ToInt32(data["SMF_ID"]);
SMF_VF_ID = Convert.ToInt32(data["SMF_VF_ID"]);
VirtualFolder = new VirtualFolderModel(data);
}
public VirtualFolderModel VirtualFolder;
public int SMF_ID { get; set; }
public int SMF_VF_ID { get; set; }
}
public class VirtualFolderModel
{
public VirtualFolderModel(DataRow data)
{
VF_ID = Convert.ToInt32(data["VF_ID"]);
}
public int VF_ID { get; set; }
}
in my controller i pass the model to a view.
public ActionResult Edit(int id)
{
SiteMapData smd = new SiteMapData();
SiteMapModel smm = new SiteMapModel(smd.GetFolderData((int)id, 15));
return View(smm);
}
how to use it in my view?
<div>
<span class="editor-label">
#Html.Label("Title")
</span>
#Html.TextBox("SMF_Name")
#Html.ValidationMessage("SMF_Name")
<span class="editor-label">
#Html.Label("VF_ID")
</span>
#Html.TextBox("VF_ID")
#Html.ValidationMessage("VF_ID")
<input type="submit" value="Save" />
</div>
the #Html.TextBox("VF_ID") don't work
At the top of your view add this:
#ModelType SitemapModel
Edit: For C# please use:
#model SitemapModel
Doing that will simply tell your view what kind of model is given at runtime. In this case, it's an object of type SitemapModel.
In your view you can reference to it to model.SMF_ID