ModelMetadata.Watermark and MVC View Models - asp.net-mvc

I'm struggling to display a watermark (so called placeholder) on my MVC3 form inputs.
There is already few posts down here talking including the quite focused one here:
Html5 Placeholders with .NET MVC 3 Razor EditorFor extension?
In these posts, advice is made to create tweaked html.TextBox templates.
In my case, my asset is that I should not need any editor template as I'm tweaking them inline.
Better than long talks, here is the relevant part of the actual code:
~/Models/myModel.cs
namespace myProject.Models {
public class myFormModel {
...
[Display(Name = "firstFieldName", Prompt = "firstFieldPrompt")]
public string firstFieldValue { get; set; }
...
}
}
~/Controllers/myFormSurfaceController.cs
namespace myProject.Controllers {
public class myFormSurfaceController : SurfaceController {
...
[ChildActionOnly]
public PartialViewResult myForm()
{
return PartialView("myPartialView", new myFormModel());
}
...
[HttpPost, ValidateAntiForgeryToken]
public ActionResult handleMyFormSubmit(myFormModel model) {...}
...
}
}
~/Views/myProject/Partial/myPartialView.cshtml
#model myFormModel
#{
using (Html.BeginUmbracoForm("handleMyFormSubmit", "myFormSurface", null, new Dictionary<string, object> { { "class", "myFormStyle" }, { "id", "myFormId" } }))
{
...
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", #placeholder = ViewData.ModelMetadata.Watermark })
...
}
}
Result is that the placeholder html tag is showing up correctly on my rendered webpage but is empty though Name tag is filled up correctly, even without DisplayName decoration set on my view model's property.
http://localhost/testpage
...
<input type="text" value="" placeholder="" name="firstFieldName" id="firstFieldName" class="myInputStyle">
...
What am I missing here ? I did try indeed to create both editor templates (MultilineText and String) in the correct folder (~/Views/Shared/EditorTemplates/) but I assume they are never called as I'm using "Html.TextBoxFor" and not "Html.TextBox"...
Other thing, if I remove "#placeholder = ViewData.ModelMetadata.Watermark" from the #Html.TextBoxFor call, I don't have any "placeholder" displayed on the rendered webpage. Which is good, this part of the call is definitively fine.
Thanks in advance for any help on that point...
Nicolas.
Edit:
What about if I create more variable in my model.
For instance:
public string firstFieldPrompt { get { return "bla"; } set { } }
and then
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", #placeholder = x => x.FirstFieldPrompt })
?

I realise this is an oldie, but you can use the ModelMetadata.FromLambdaExpression() method from within your view (without using templates), i.e.
#Html.TextBoxFor(x => x.firstFieldValue,
new {
#class = "myInputStyle",
#placeholder = ModelMetadata.FromLambdaExpression(x => x.firstFieldValue, ViewData).Watermark
})
Hope this helps someone :-)

The reason you get an empty watermark is that in your case (i.e. not using templates) ViewData actually refers to myFormModel (not myFormModel.firstFieldValue); you are essentially retrieving the watermark of your view model. Since models can't have watermarks ([Display] can't be applied to classes) ViewData.ModelMetadata.Watermark will always be empty for views.
As far as I can see, your only option here (if you don't want to use templates) is doing the watermark inline:
#Html.TextBoxFor(x => x.firstFieldValue, new { #class = "myInputStyle", placeholder = "Your watermark text here" })
By the way, if want to use templates, you need to use the templated helpers #Html.EditorFor() and #Html.DisplayFor(). #Html.TextBoxFor() is just the strongly-typed version of #Html.TextBox(). It is not templated.

Related

How to configure an MVC dropdown depending on which view calls it

I have two views, BatchReceipt and Receipt which utilise the same model. Until now they have used the same display template of ReceiptType. But I want to have one exclude certain items and the other to have the full list (so essentially a second .cshtml display template called ReceiptTypeFull). How do I configure each of these views in Visual Studio to utilise the different Display Templates?
Some additions to show the code being used:
I have file ReceiptType.cshtml being used as a DisplayTemplate which contains the following to setup the receipt dropdown
#using Clinton.Web.Helpers.EnumHelpers
#{
var item = EnumsHelper.GetNameFromEnumValue(Model);
}
I want to use a different DisplayTemplate, call it ReceiptTypeFull.cshtml
#using Clinton.Web.Helpers.EnumHelpersFull
#{
var item = EnumsHelper.GetNameFromEnumValue(Model);
}
#item
The difference is in calling the enumhelper or the enumhelperfull to vary the query populating the dropdown. My problem is that I cannot see how to redirect the view to use the different enumhelper/displaytemplate/
Thanks
I think I understand what you are getting at. You want to control which template is used for an Enum in the view.
I will explain using editor templates but it works the same way if you use display templates. You should be able to follow and apply for your scenario.
The idea is to use this overload of the editor html helper.
public static MvcHtmlString Editor(this HtmlHelper html, string expression, string templateName);
It is called like this
#Html.Editor("{property name}", "{template name}").
Below is an example to show it being used.
Suppose we have this enum
public enum MyItems
{
Item1 = 1,
Item2 = 2,
Item3 = 3
}
This helper
public static class MyEnumHelper
{
public static List<MyItems> GetAllItems()
{
return new List<MyItems>()
{
MyItems.Item1,
MyItems.Item2,
MyItems.Item3
};
}
public static List<MyItems> GetSomeItems()
{
return new List<MyItems>()
{
MyItems.Item1,
MyItems.Item2
};
}
}
This controller
public class HomeController : Controller
{
public ActionResult AllItems()
{
return View();
}
public ActionResult SomeItems()
{
return View();
}
}
We have these 2 editor templates, which are put in views/shared/editortemplates
First one called MyItems.cshtml which is the all one
#model MyItems?
#{
var values = MyEnumHelper.GetAllItems().Cast<object>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.ToString(),
Value = v.ToString()
});
}
#Html.DropDownList("", values)
Second one called MyItems2.cshtml which is the some one
#model MyItems?
#{
var values = MyEnumHelper.GetSomeItems().Cast<object>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.ToString(),
Value = v.ToString()
});
}
#Html.DropDownList("", values)
Then in the AllItems.cshtml to get the MyItems.cshtml template called we need
#model MyItemsViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.MyItem)
<submit typeof="submit" value="submit"/>
}
And in the SomeItems.cshtml to get some of the items by calling MyItems2.cshtml we use
#model MyItemsViewModel
#using (Html.BeginForm())
{
#Html.Editor("MyItem", "MyItems2") #* this bit answers your question *#
<submit typeof="submit" value="submit" />
}

Working with dynamic editor template

I am trying to build a web site that lets user to create new violation.Each violation has its own input area.For instance, if user wants to create a new Ramp Width Violation, he/she should provide width or for Ramp Slope Violation slope should be provided as input.
Taking into account those requirements , I decided to have a dynamic property inside my view.
My dynamic object property can be boolean , integer or double according to violation type.
Here is my class that includes dynamic property.Other properties are removed for brevity.
public class CreateViolationViewModel
{
public string DynamicName { get; set; }
public object DynamicValue { get; set; }
}
Main View
#Html.LabelFor(model => model.DynamicName, #Model.DynamicName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => x.DynamicValue, "violationDynamics_" + Model.DynamicValue.GetType().Name)
</div>
#Html.HiddenFor(model => model.DynamicName, new {#value=Model.DynamicName })
EditorTemplate For Integer(violationDynamics_Int32.cshtml)
#{
Layout = null;
}
#model int
#Html.TextBoxFor(x => x, new { #class = "form-control", #type = "number" })
#Html.Hidden("ModelType", Model.GetType())
EditorTemplate For Double(violationDynamics_Double.cshtml)
#{
Layout = null;
}
#model double
#Html.TextBoxFor(x => x, new { #class = "form-control", #type = "number" })
#Html.Hidden("ModelType", Model.GetType())
Controller
[HttpPost]
public ActionResult Create(CreateViolationViewModel createViolationViewModel)
{
}
When the page is loaded it renders the related editor templates.The problem is that when the form is posted back dynamic value comes as a array of string as shown below.
I know I need something like model binder but haven't found the solution yet.By the way I take this approach from the previous answer here
So questions are ;
1- How can I make this approach work for post backs?
2- Is there any other recommend or easy approach to accomplish my task ?
Thanks

MVC5: Can #Html.TextBoxFor (or EditorFor) get its MaxLength from the Model's Data Annotation?

It seems like there should be a way of specifying a Model annotation like:
[Display(Name="State")]
[MaxLength(2)]
public string State { get; set; }
so that when it is used in an EditorFor or TextBoxFor, like:
#Html.EditorFor(model => model.State)
or
#Html.EditorFor(model => model.State)
the generation of the html would set the input field's maxlength to 2. Right now, I need to do this:
#Html.TextBoxFor(model => model.State, new { maxlength = 2 })
Since this violates the DRY principle, I'm wondering if there is already a built in way to have this flow from the data annotations to the input field?
There's an answer here that describes a way to get hold of additional metadata values within the view. With this in mind, you can do something like this...
Annotate your model:
[AdditionalMetadata("MaxLength", 30)]
public string State { get; set; }
Define a custom editor template (String.cshtml):
#{
string maxLength = ViewData.ModelMetadata.AdditionalValues.ContainsKey("MaxLength")
? ViewData.ModelMetadata.AdditionalValues["MaxLength"].ToString() : null;
}
#Html.TextBox("", Model, new { maxlength = maxLength })
Then just do:
#Html.EditorFor(m => m.State)
This might need some tweaking and could be improved by defining a custom attribute instead of just using AdditionalMetadata but it should get you started.
I tend to wrap up calls to the model's additional metadata in a custom HTML helper, too.
Here is what I did to get around this.
Created a js file to handle adding in the required field marker and maxlength attribute:
var formUtility = function () {
return {
AddMaxLength: function () {
$("input[data-val-length-max]").each(function () {
$(this).attr("maxlength", $(this).attr("data-val-length-max"));
});
},
ShowRequiredFields: function () {
$("input[data-val-required]").each(function () {
$(this).prev().prepend("<span>* </span>");
});
}
}
}();
After linking it on the page where I need it, just call it like this:
<script>
formUtility.AddMaxLength();
formUtility.ShowRequiredFields();
</script>
This is not currently supported, you can vote for this feature.
I think by using ContainerType and PropertyName properties of ModelMetadata you can obtain the PropertyInfo, and from that query for the MaxLength attribute, all this in a custom editor template.
Use
[StringLength(2)]
instead of
[MaxLength(2)]

Post a form with multiple partial views

I'm currently trying to post a form composed of two strongly typed views. This question is similar but it doesn't have an answer:
MVC 3 Razor Form Post w/ Multiple Strongly Typed Partial Views Not Binding
When I submit form the model submitted to the controller is always null. I've spent a couple of hours trying to get this to work. This seems like it should be simple. Am I missing something here? I don't need to do ajax just need to be able to post to the controller and render a new page.
Thanks
Here's my view code:
<div>
#using (Html.BeginForm("TransactionReport", "Reports", FormMethod.Post, new {id="report_request"}))
{
ViewContext.FormContext.ValidationSummaryId = "valSumId";
#Html.ValidationSummary(false, "Please fix these error(s) and try again.", new Dictionary<string, object> { { "id", "valSumId" } });
#Html.Partial("_ReportOptions", Model.ReportOptions);
#Html.Partial("_TransactionSearchFields", new ViewDataDictionary(viewData) { Model = Model.SearchCriteria });
}
Here's the code in the controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TransactionReport(TransactionReportRequest reportRequest)
{
var reportInfo = new List<TransactionReportItem>();
if (ModelState.IsValid)
{
var reportData = _reportDataService.GetReportData(Search.MapToDomainSearchCriteria(reportRequest.SearchCriteria));
if (reportData!=null)
{
reportInfo = reportData.ToList();
}
return View(reportInfo);
}
return View(reportInfo);
}
The partial views themselves are pretty irrelevant since all they are doing is biding and displaying their models.
Partials are not the way to go here. You are looking for EditorTemplates, these are made for what you want. This case, your properties will be nicely bound to your model (that you will submit).
Your main View will have this form (note that you only have to use EditorFor instead of Partial; in this case, you probably will need to put that viewData parameter in the ViewBag or so):
#using (Html.BeginForm("TransactionReport", "Reports", FormMethod.Post, new {id="report_request"}))
{
ViewContext.FormContext.ValidationSummaryId = "valSumId";
#Html.ValidationSummary(false, "Please fix these error(s) and try again.", new Dictionary<string, object> { { "id", "valSumId" } });
#Html.EditorFor(model => model.ReportOptions);
#Html.EditorFor(model = Model.SearchCriteria });
}
Now you only have to drag your partials to the folder ~/Shared/EditorTemplates/ and rename them to match the model name they are the editor templates for.
In the ~/Shared/EditorTemplates/ folder, make a new "view", example "SearchCriteria.cshtml". Inside, put as "model" the type of class you which to create an editor template for. Example (example class has properties Name and OtherCriteria):
#model MyNamespace.SearchCriteria
<ul>
<!-- Note that I also use EditorFor for the properties; this way you can "nest" editor templates or create custom editor templates for system types (like DateTime or String or ...). -->
<li>#Html.LabelFor(m => m.Name): #Html.EditorFor(m => m.Name)</li>
<li>#Html.LabelFor(m => OtherCriteria): #Html.EditorFor(m => m.OtherCriteria</li>
</ul>
Some good reading about them:
https://www.exceptionnotfound.net/asp-net-mvc-demystified-display-and-editor-templates/
https://www.hanselman.com/blog/ASPNETMVCDisplayTemplateAndEditorTemplatesForEntityFrameworkDbGeographySpatialTypes.aspx
You should add prefix to the PartialView's fields. That will let binding data correctly.
So instead:
#Html.Partial("_ReportOptions", Model.ReportOptions);
Use:
#Html.Partial("_ReportOptions", Model.ReportOptions, new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ReportOptions" }})
I agree with #Styxxy and #Tony, Editor Templates are the better solution. However, your problem is that that you are feeding a sub-model to the partial views. Thus, when the partial view renders it doesn't know that it's part of a larger model and does not generate the correct name attributes.
If you insist on using Partials rather than Editor Templates, then I suggest only passing the Model to the partials, then having each partial do Model.Whatever.Foo and it will generate the correct name attributes for binding.
Try using EditorTemplates instead of Partials http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/.
#Html.Partial("_ReportOptions", Model.Contact, new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo()
{
HtmlFieldPrefix = "Contact"
}
})
)
#Html.Partial("_TransactionSearchFields", Model.SearchCriteria, new
ViewDataDictionary()
{
TemplateInfo = new TemplateInfo()
{
HtmlFieldPrefix = "SearchCriteria"
}
})

How to pass data to the view in mvc asp.net?

let me ask the question first.
Where is the correct place to call a function that load a list of values to be display on a view?
I create a controller like this
public ActionResult Create()
{
SeaModel newSea = new SeaModel();
return View("Season/CreateSea", newSea);
}
//I not quite sure if this should go here or in another place
partial class seaDataContext
{
public List<string> getSeaSettings()
{
var seaSettings = from p in settings
where p.setting == "periods"
select p.value;
return seaSettings.ToList<string>();
}
}
The model is like
public class SeaModel
{
[Required(ErrorMessage="*")]
[Display(Name = "Period Name")]
public string periods { get; set; }
}
Which create a view like
#using (Html.BeginForm()) {
#Html.ValidationSummary(true, "Please correct the following errors.")
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
#Html.LabelFor(model => model.periods)
</div>
<div class="editor-field">
#Html.Select(model => model.periods, ****My doubt comes here****)
#Html.ValidationMessageFor(model => model.periods)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
so, How and where do I pass the return of getSeaSettings() to the view?
Thanks
best practice is to make a Selectlist in your Model for this dropdown.
however you also can use the more easy option: using ViewData
public ActionResult Create()
{
SeaModel newSea = new SeaModel();
ViewData["myDropDown"] = new SelectList(listOfObjects, "valueOfTheObjectLikeID", "NameYouWantToShowInDropdown");
return View("Season/CreateSea", newSea);
}
then:
#Html.Select(model => model.periods, ViewData["myDropDown"] as SelectList)
dont forget in your [HttpPost] method to also fill in the viewdata if you'r validation fails, so the dropdown can be rebuilt.
You need to look at repository pattern. Have a look at this tutorial at asp.net site
http://www.asp.net/mvc/tutorials/creating-model-classes-with-linq-to-sql-cs
Stefanvds's approach was what I used to do.
But I found out there is a better way using additionalViewData.
Use this EditorFor HTML Helper extension method.
http://msdn.microsoft.com/en-us/library/ff406462.aspx
Instead of passing Select List Items into ViewData in the Controller, you do this in your View.
Pass in your list items as an anonymous object for the additionalViewData parameter.
Important thing is to use the same name as your Property Name.
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
);
Of course, you are passing in a View Model object.
public class MyViewModel
{
public int MyPropertyName;
public IList<SelectListItem> ListItemsForMyPropertyName;
}
EditorFor method uses your existing Editor View Templates.
So you don't need to specify CSS class names and HTML attributes again like when you use the Html.DropDown( ) method.
For example,
//------------------------------
// additionalViewData
//------------------------------
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
)
//------------------------------
// traditional approach requires to pass your own HTML attributes
//------------------------------
#Html.DropDown(
"MyPropertyName",
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
//------------------------------
// DropDownListFor still requires you to pass in your own HTML attributes
//------------------------------
#Html.DropDownListFor(
m => m.MyPropertyName,
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
That is why I like the additionalViewData approach more.
Because, the HTML code rendered relies on the Editor Templates completely.
Also, using specialized View Models make your code cleaner and easier to maintain.
Hope it helps.

Resources