I am using ASP.NET MVC 5 for a project, and while I like the model binding capabilities of MVC, I do not find myself using them much because I am using javascript MVVM systems, and I find that it is hard to do these with the model binder.
But I do find myself re-using HTML code a lot for specific inputs that need to be replicated across multiple places.
Is there any way to use the #Html.EditorFor etc and instead of specifying a property, just call upon a specific template to use it like a small inline snippets engine?
Update
Hey guys, I went with the suggestion to use Partial Views, but I changed it up a bit. I'd like to post my solution on StackOverflow for feedback, as I want to know if this is a good practice or not, but for now I am accepting the answer.
You can use your own user interface.
do the following steps:
go to Views\Shared
and create [EditorTemplates] folder.
then create your own *.cshtml file for each type or UIHint attributes
for example Create Int32.cshtml as follow for int type:
#model int?
#{
int i;
if (!Model.HasValue)
{
i = 0;
}
else
{
i = Model.Value;
}
var htmlAttributes = new RouteValueDictionary();
if (ViewBag.#class != null)
{
htmlAttributes.Add("class", "form-control " + ViewBag.#class);
}
else
{
htmlAttributes.Add("class", "form-control");
}
if (ViewBag.placeholder != null)
{
htmlAttributes.Add("placeholder", ViewBag.placeholder);
}
}
<div class="form-group#(Html.ValidationErrorFor(m => m, " has-error"))">
#Html.LabelFor(m => m, new { #class = "control-label" })
<div class="controls">
#Html.TextBox(
"",
ViewData.TemplateInfo.FormattedModelValue,
htmlAttributes)
#Html.ValidationMessageFor(m => m, null, new { #class = "help-block" })
</div>
</div>
You could use partial views for that.
Related
I find myself writing this a whole lot in my views:
<div class="form-group">
#Html.LabelFor(x => x.City)
#Html.EditorFor(x => x.City)
#Html.ValidationMessageFor(x => x.City)
</div>
I'd really like to put this in a Partial _Field.cshtml, something like this:
#model //what model type???
<div class="form-group">
#Html.LabelFor(Model)
#Html.EditorFor(Model)
#Html.ValidationMessageFor(Model)
</div>
That could then be called by:
#Html.Partial("_Field", x => x.City)
What would the #model type in my partial be if I wanted to accomplish something like this?
UPDATE This works, but I'd rather use a partial for ease of changing the template:
public static MvcHtmlString Field<TModel, TItem>(this HtmlHelper<TModel> html, Expression<Func<TModel, TItem>> expr)
{
var h = "<div class='form-group'>";
h += $"{html.LabelFor(expr)}{html.EditorFor(expr)}{html.ValidationMessageFor(expr)}";
h += "</div>";
return MvcHtmlString.Create(h);
}
That's not possible. However, what you want is very similar to editor templates. Essentially, you just create a view in Views/Shared/EditorTemplates named after one of the following conventions:
A system or custom type (String.cshtml, Int32.cshtml, MyAwesomeClass.cshtml, etc.)
One of the members of the DataType enum (EmailAddress.cshtml, Html.cshtml, PhoneNumber.cshtml, etc.). You would then apply the appropriate DataType attributes to your properties:
[DataType(DataType.EmailAdress)]
public string Email { get; set; }
Any thing you want, in conjunction with the UIHint attribute:
[UIHint("Foo")]
public string Foo { get; set; }
Which would then correspond to a Foo.cshtml editor template
In your views, then, you simply use Html.EditorFor:
#Html.EditorFor(x => x.City)
Then, for example, you could have Views/Shared/EditorTemplates/String.cshtml as:
<div class="form-group">
#Html.Label("", new { #class = "control-label" })
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { #class = "form-control" })
#Html.ValidationMessage("")
</div>
(The empty quotes are placeholders. Razor will automatically fill in the appropriate property name, thankfully.)
Calling EditorFor, then, will print all of this, rather than just the default text input. You can take this much further, as well. I have some articles on my blog that goes into greater detail, if you're interested.
UPDATE
It's worth mentioning a few features of EditorFor:
You can pass a template directly to the call, meaning you can customize what template is used on the fly and per instance:
#Html.EditorFor(x => x.City, "MyCustomEditorTemplate")
You can pass additionalViewData. The members of this anonymous object are added to the ViewData dynamic dictionary. Potentially, you could use this to branch within your editor template to cover additional scenarios. For example:
#Html.EditorFor(x => x.City, new { formGroup = false })
Then in your editor template:
#{ var formGroup = ViewData["formGroup"] as bool? ?? true; }
#if (formGroup)
{
<!-- Bootstrap form group -->
}
else
{
<!-- Just the input -->
}
Visually speaking, on one of my MVC Views I have about 20 fields in standard vertical order, with the first 8 or so having optional [Create] boxes in same <div> group over on the right.
My default tab order currently hits my first dropdown, then goes right to [Create], down to the next, then right, etc. What I would like to do set the TAB order to where it goes straight down my various fields and leave the [Create] boxes as optional for the user (or at the end of the tab order). While there seems to be a lot of discussion on this with a quick search, there seems to be inconsistent answers; a lot of them seemingly from a couple years ago regarding setting TAB Order in an EditorFor() but being forced to use Custom Editor Templates or switch to TextBoxFor()?
Hoping someone can weigh in on this. The below somewhat details my fields:
(8 of these DropDownListFor()):
#Html.DropDownListFor(model => model.STATUS_ID, (SelectList)ViewBag.Model_List, htmlAttributes: new { #class = "form-control dropdown", #id = "selectStatus" })
(12 of these EditorFor()):
#Html.EditorFor(model => model.NOTE, new { htmlAttributes = new { #class = "form-control" } })
To set the tab order, all you need to do is be able to add an extra attribute, tabindex to the generated field. That's easy enough with something like TextBoxFor or DropDownListFor, since they actually take an htmlAttributes parameter specifically for this purpose:
#Html.TextBoxFor(m => m.Foo, new { tabindex = 1 })
In the past, the same could not be said for EditorFor. Since it's a "templated" helper, the editor template, not the method call, effects what's generated. You can see this in the definition of EditorFor, as there's no htmlAttributes param like the other helpers have, but rather additionalViewData.
Starting with MVC 5.1, Microsoft made it possible to pass additional HTML attributes to EditorFor, via a specially named ViewData key, "htmlAttributes". As a result, you can achieve the same thing as when using something like TextBoxFor, although it's a little more verbose:
#Html.EditorFor(m => m.Foo, new { htmlAttributes = new { tabindex = 1 } })
See, you're still actually passing additionalViewData here, but that additional view data contains an anonymous object keyed to htmlAttributes. The built-in editor templates, then, know how to utilize ViewData["htmlAttributes"] to add additional attributes to the generated element. However, this only applies to the default editor templates because Microsoft has specifically programmed them to use this. As soon as you add your own custom editor templates, you're right back to where you started.
There's a number of ways you could approach this with custom editor templates. First, you could just pass the tab index directly as view data, and utilize that in your template:
#Html.EditorFor(m => m.Foo, new { tabindex = 1 })
Then, in your editor template:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { tabindex = ViewData["tabindex"]})
Second, you could mimic EditorFor's behavior with the default templates:
#Html.EditorFor(m => m.Foo, new { htmlAttributes = new { tabindex = 1 } })
Then, in your editor template:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, ViewData["htmlAttributes"])
However, that option doesn't allow you to have "default" attributes. It's an all or nothing approach. To truly be able to utilize ViewData["htmlAttributes"] as the built-in editor templates do, you'll need to combine the default attributes with the passed in ones, first, and then pass the whole shebang to htmlAttributes. I've got a blog post that discusses that in depth, but TL;DR: you'll need the following extension:
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
public static partial class HtmlHelperExtensions
{
public static IDictionary<string, object> MergeHtmlAttributes(this HtmlHelper helper, object htmlAttributesObject, object defaultHtmlAttributesObject)
{
var concatKeys = new string[] { "class" };
var htmlAttributesDict = htmlAttributesObject as IDictionary<string, object>;
var defaultHtmlAttributesDict = defaultHtmlAttributesObject as IDictionary<string, object>;
RouteValueDictionary htmlAttributes = (htmlAttributesDict != null)
? new RouteValueDictionary(htmlAttributesDict)
: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject);
RouteValueDictionary defaultHtmlAttributes = (defaultHtmlAttributesDict != null)
? new RouteValueDictionary(defaultHtmlAttributesDict)
: HtmlHelper.AnonymousObjectToHtmlAttributes(defaultHtmlAttributesObject);
foreach (var item in htmlAttributes)
{
if (concatKeys.Contains(item.Key))
{
defaultHtmlAttributes[item.Key] = (defaultHtmlAttributes[item.Key] != null)
? string.Format("{0} {1}", defaultHtmlAttributes[item.Key], item.Value)
: item.Value;
}
else
{
defaultHtmlAttributes[item.Key] = item.Value;
}
}
return defaultHtmlAttributes;
}
}
And then you'll need to add the following to top of your custom editor templates:
#{
var defaultHtmlAttributesObject = new { type = "date", #class = "form-control" };
var htmlAttributesObject = ViewData["htmlAttributes"] ?? new { };
var htmlAttributes = Html.MergeHtmlAttributes(htmlAttributesObject, defaultHtmlAttributesObject);
}
You'd change the defaultHtmlAttributesObject variable depending on what attributes the generated input should have by default for that particular template.
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"
}
})
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.
I know there was few similar questions here about DropDownListFor, but neither helped me...
I use Entity Framework as ORM in my project. There's EF model called "Stete". Stete has Foreign on EF model called "Drustva"
Now I'm trying to make a form for editing the data, for Stete model. I managed to display everything, including Stete.Drustva.Naziv property, but I can't get this last property in my handler method [HttpPost]. It always return 0, no matter what I select in drop down list.
Here's the code:
DrustvaController:
public static IEnumerable<SelectListItem> DrustvaToSelectListItemsById(this KnjigaStetnikaEntities pEntities, int Id)
{
IEnumerable<Drustva> drustva = (from d in pEntities.Drustva
select d).ToList();
return drustva.OrderBy(drustvo => drustvo.Naziv).Select(drustvo => new SelectListItem
{
Text = drustvo.Naziv,
Value = drustvo.Id.ToString(),
Selected = (drustvo.Id == Id)? true : false
});
}
SteteController:
private IEnumerable<SelectListItem> privremenaListaDrustava(int Id)
{
using (var ctx = new KnjigaStetnikaEntities())
{
return ctx.DrustvaToSelectListItemsById(Id);
}
}
public ActionResult IzmijeniPodatkeStete(Int32 pBrojStete)
{
PretragaStetaModel psm = new PretragaStetaModel();
ViewData["drustva"] = privremenaListaDrustava(psm.VratiStetuPoBrojuStete(pBrojStete).Drustva.Id);
ViewData.Model = new Models.Stete();
return View("EditView", (Stete.Models.Stete)psm.GetSteta(pBrojStete));
}
EditView:
<div class="editor-label">
<%: Html.Label("Društvo") %>
</div>
<div class="editor-field">
<%: Html.DropDownListFor(m => m.Drustva.Naziv, ViewData["drustva"] as IEnumerable<SelectListItem>) %>
<%: Html.ValidationMessageFor(model => model.FKDrustvo) %>
</div>
I am sorry for not translating names of the objects into english, but they hardly have appropriate translation. If necessary, I can try creating similar example...
Did you include Html.BeginForm or Ajax.BeginForm in your view markup? That is a common oversight that can cause the behavior you are referring to. I can't tell from the code you pasted in your question.Cheers.