LabelFor incorrect when using AllowHtml - asp.net-mvc

I'm using MVC 5.2.2.0, .net 4.5.1 and I'm seeing some odd behavior. I have a model like so:
public class Program
{
// .... Other Properties
[Display(Name = "Courses Description")]
[RichText]
[AllowHtml]
public string CoursesDescription { get; set; }
// .... Other Properties
}
RichText is a custom attribute:
[AttributeUsage(AttributeTargets.Property)]
public class RichText : Attribute
{
public RichText() { }
}
All it is used for is to tell the T4 template to add the wysi data-editor attribute to the TextAreaFor html helper.
My view has the following:
<div class="form-group">
#Html.LabelFor(model => model.CoursesDescription, htmlAttributes: new { #class = "control-label col-sm-3" })
<div class="col-sm-6">
#Html.TextAreaFor(model => model.CoursesDescription, 10, 20, new { #class = "form-control", data_editor = "wysi" })
#Html.ValidationMessageFor(model => model.CoursesDescription, "", new { #class = "text-danger" })
</div>
</div>
With the AllowHtml attribute, the View renders like this:
However, if I modify the model by removing the AllowHtml attribute, the view renders correctly.
public class Program
{
// .... Other Properties
[Display(Name = "Courses Description")]
[RichText]
public string CoursesDescription { get; set; }
// .... Other Properties
}
It is also worth pointing out that removing the RichText attribute doesn't change anything, the View still ignores the Display attribute.
Any ideas why AllowHtml is interfering with LabelFor?

Digging deeper into this, I noticed that my MVC project was using MVC 5.2.2.0, but I had inadvertently installed MVC 5.2.3.0 for my models project.
Downgrading the models project to 5.2.2.0 fixed the problem.

Related

How do I carry a complex object model through a POST request

I have the following entity models:
public class AssetLabel
{
public string QRCode { get; set; }
public string asset { get; set; }
public virtual IEnumerable<Conversation> Conversations { get; set; }
}
public class Conversation
{
public int ID { get; set; }
public virtual AssetLabel AssetLabel{ get; set; }
public string FinderName { get; set; }
public string FinderMobile { get; set; }
public string FinderEmail { get; set; }
public ConversationStatus Status{ get; set; }
public IEnumerable<ConversationMessage> Messages { get; set; }
}
public class ConversationMessage
{
public int ID { get; set; }
public DateTime MessageDateTime { get; set; }
public bool IsFinderMessage { get; set; }
public virtual Conversation Conversation { get; set; }
}
public enum ConversationStatus { open, closed };
public class FinderViewModel : Conversation
{/*used in Controllers->Found*/
}
My MVC application will prompt for a QRCode on a POST request. I then validate this code exists in the database AssetLabel and some other server-side logic is satisfied. I then need to request the user contact details to create a new Conversation record.
Currently I have a GET to a controller action which returns the first form to capture the Code. If this is valid then I create a new FinderViewModel, populate the AssetLabel with the object for the QRCode and return a view to consume the vm and show the fields for the Name, Mobile and Email.
My problem is that although the AssetLabel is being passed to the view as part of the FinderViewModel and I can display fields from the AssetLabel; graphed object the AssetLabel does not get passed back in the POST. I know I could modify the FinderViewModel so that it takes the Conversation as one property and set up the QRCode as a separate property that could be a hidden field in the form and then re-find the the AssetLabel as part of the processing of the second form but this feels like a lot of work seeing as I have already validated it once to get to the point of creating the second form (this is why I am moving away from PHP MVC frameworks).
The first question is HOW?, The second question is am I approaching this design pattern in the wrong way. Is there a more .NETty way to persist the data through multiple forms? At this point in my learning I don't really want to store the information in a cookie or use ajax.
For reference the rest of the code for the 1st form POST, 2nd view and 2nd form POST are shown below (simplified to eliminate irrelevant logic).
public class FoundController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Found
public ActionResult Index()
{
AssetLabel lbl = new AssetLabel();
return View(lbl);
}
[HttpPost]
public ActionResult Index(string QRCode)
{
if (QRCode=="")
{
return Content("no value entered");
}
else
{
/*check to see if code is in database*/
AssetLabel lbl = db.AssetLables.FirstOrDefault(q =>q.QRCode==QRCode);
if (lbl != null)
{
var vm = new FinderViewModel();
vm.AssetLabel = lbl;
vm.Status = ConversationStatus.open;
return View("FinderDetails", vm);
}
else
{/*Label ID is not in the database*/
return Content("Label Not Found");
}
}
}
[HttpPost]
public ActionResult ProcessFinder(FinderViewModel vm)
{
/*
THIS IS WHERE I AM STUCK! - vm.AssetLabel == NULL even though it
was passed to the view with a fully populated object
*/
return Content(vm.AssetLabel.QRCode.ToString());
//return Content("Finder Details posted!");
}
FinderView.cshtml
#model GMSB.ViewModels.FinderViewModel
#{
ViewBag.Title = "TEST FINDER";
}
<h2>FinderDetails</h2>
#using (Html.BeginForm("ProcessFinder","Found",FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Finder Details</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.AssetLabel)
<div class="form-group">
#Html.LabelFor(model => model.FinderName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FinderName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FinderName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FinderMobile, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FinderMobile, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FinderMobile, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FinderEmail, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FinderEmail, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FinderEmail, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
Rendered HTML snippet for AssetLabel
<input id="AssetLabel" name="AssetLabel" type="hidden"
value="System.Data.Entity.DynamicProxies.AssetLabel_32653C4084FF0CBCFDBE520EA1FC5FE4F22B6D9CD6D5A87E7F1B7A198A59DBB3"
/>
You cannot use #Html.HiddenFor() to generate a hidden output for a complex object. Internally the method use .ToString() to generate the value (in you case the output is System.Data.Entity.DynamicProxies.AssetLabel_32653C4084FF0CBCFDBE520EA1FC5FE4F22B6D9CD6D5A87E7F1B7A198A59DBB3 which cannot be bound back to a complex object)
You could generate a form control for each property of the AssetLabel - but that would be unrealistic in your case because AssetLabel contains a property with is a collection of Conversation which in turn contains a collection of ConversationMessage so you would need nested for loops to generate an input for each property of Conversation and ConversationMessage.
But sending a whole lot of extra data to the client and then sending it all back again unchanged degrades performance, exposes unnecessary details about your data and data structure to malicious users, and malicious users could change the data).
The FinderViewModel should just contain a property for QRCode (or the ID property of AssetLabel) and in the view
#Html.HiddenFor(m => m.QRCode)
Then in the POST method, if you need the AssetLabel, get it again from the repository just as your doing it in the GET method (although its unclear why you need to AssetLabel in the POST method).
As a side note, a view model should only contain properties that are needed in the view, and not contain properties which are data models (in in your case inherit from a data model) when editing data. Refer What is ViewModel in MVC?. Based on your view, it should contain 4 properties FinderName, FinderMobile, FinderEmail and QRCode (and int? ID if you want to use it for editing existing objects).
Thanks Stephen. The QRCode is the PK on AssetLabel and the FK in Conversation so it needs to be tracked through the workflow. I was trying to keep the viewModel generic so that is can be used for other forms rather than tightly coupling it to this specific form and I was trying to pass the AssetLabel around as I have already done a significant amount of validation on it's state which I didn't want to repeat. I worked out what I need to do - If you use #Html.Hidden(model => model.AssetLabel.QRCode) then the form field name becomes AssetLabel_QRCode and is automatically mapped to the correct place in the POST viewmodel. To promote code reuse and avoid any rework later I have created this logic in a display template with the fields defined as hidden and then #Html.Partial() using the overload method that allows me to define the model extension to the form names
#Html.Partial
(
"./Templates/Assetlabel_hidden",
(GMSB.Models.AssetLabel)(Model.AssetLabel),
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo()
{
HtmlFieldPrefix = "AssetLabel"
}
}
)
But you are absolutely right, this exposes additional fields and my application structure. I think I will redraft the viewModel to only expose the necessary fields and move the AssetLabel validation to a separate private function that can be called from both the initial POST and the subsequent post. This does mean extra code in the controller as the flat vm fields need to be manually mappped to the complex object graph.

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

MVC. Code first field with HiddenInput Attribute has a visible Label generated

Using MVC5 I Have the following defined in the model
[HiddenInput(DisplayValue = false)]
public DateTime Created { get; set; }
I do not want this field to appear on any of the MVC generated views. However I am getting a label generated for this hidden field when scaffolding the views, as shown below.
What is the correct way to use this attribute so that the field and it's label are not output?
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
will be rendered as
<input id="Id" name="Id" type="hidden" />
when using Html.EditorForModel() or Html.EditorFor(m => m.Id)
Check what is render in your UI, type="hidden" or something else.
You can simply use #Html.HiddenFor inside view as shown :-
Model :-
public DateTime Created { get; set; }
View :-
#Html.HiddenFor(model=>model.Created)
I know this is an old post but I'm experiencing the same thing. My view is set up as
<div class="form-group row">
#Html.LabelFor(model => model.Summary.LeaID, htmlAttributes: new { #class = "col-form-label col-md-3" })
<div class="col-md-9">
#Html.EditorFor(model => model.Summary.LeaID, new { htmlAttributes = new { #class = "form-control"}})
#Html.ValidationMessageFor(model => model.Summary.LeaID, "", new { #class = "text-danger" })
</div>
</div>
And my Model is
[HiddenInput(DisplayValue = false)]
public string LeaID { get; set; }
When the view renders, I get the label (LabelFor) yet the EditorFor is hidden correctly.
If your only problem is that the label is showing, then annotate the model property with a Display attribute:
[HiddenInput(DisplayValue = false)]
[Display(Name ="")]
public string LeaID { get; set; }
If you do that, the label will not show but still you will have white space on the screen for where the LeaID segment was supposed to appear. Alternatively, you can manually change the view as Kartikeya Khosla has suggested.

How to add "required" attribute to mvc razor viewmodel text input editor

I have the following MVC 5 Razor HTML helper:
#Html.TextBoxFor(m => m.ShortName,
new { #class = "form-control", #placeholder = "short name"})
I need this field to be required (i.e. have a red outline when user navigates out without putting a value inn). In a WebForms HTML 5 I could just say <input type="text" required /> to have this effect.
What is the proper syntax to accomplish this in a Razor syntax?
You can use the required html attribute if you want:
#Html.TextBoxFor(m => m.ShortName,
new { #class = "form-control", placeholder = "short name", required="required"})
or you can use the RequiredAttribute class in .Net. With jQuery the RequiredAttribute can Validate on the front end and server side. If you want to go the MVC route, I'd suggest reading Data annotations MVC3 Required attribute.
OR
You can get really advanced:
#{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = new Dictionary<string, object>(
Html.GetUnobtrusiveValidationAttributes(ViewData.TemplateInfo.HtmlFieldPrefix));
attributes.Add("class", "form-control");
attributes.Add("placeholder", "short name");
if (ViewData.ModelMetadata.ContainerType
.GetProperty(ViewData.ModelMetadata.PropertyName)
.GetCustomAttributes(typeof(RequiredAttribute), true)
.Select(a => a as RequiredAttribute)
.Any(a => a != null))
{
attributes.Add("required", "required");
}
#Html.TextBoxFor(m => m.ShortName, attributes)
}
or if you need it for multiple editor templates:
public static class ViewPageExtensions
{
public static IDictionary<string, object> GetAttributes(this WebViewPage instance)
{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = new Dictionary<string, object>(
instance.Html.GetUnobtrusiveValidationAttributes(
instance.ViewData.TemplateInfo.HtmlFieldPrefix));
if (ViewData.ModelMetadata.ContainerType
.GetProperty(ViewData.ModelMetadata.PropertyName)
.GetCustomAttributes(typeof(RequiredAttribute), true)
.Select(a => a as RequiredAttribute)
.Any(a => a != null))
{
attributes.Add("required", "required");
}
}
}
then in your templates:
#{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = this.GetAttributes();
attributes.Add("class", "form-control");
attributes.Add("placeholder", "short name");
#Html.TextBoxFor(m => m.ShortName, attributes)
}
Update 1 (for Tomas who is unfamilar with ViewData).
What's the difference between ViewData and ViewBag?
Excerpt:
So basically it (ViewBag) replaces magic strings:
ViewData["Foo"]
with magic properties:
ViewBag.Foo
On your model class decorate that property with [Required] attribute. I.e.:
[Required]
public string ShortName {get; set;}
A newer way to do this in .NET Core is with TagHelpers.
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro
Building on these examples (MaxLength, Label), you can extend the existing TagHelper to suit your needs.
RequiredTagHelper.cs
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System.Linq;
namespace ProjectName.TagHelpers
{
[HtmlTargetElement("input", Attributes = "asp-for")]
public class RequiredTagHelper : TagHelper
{
public override int Order
{
get { return int.MaxValue; }
}
[HtmlAttributeName("asp-for")]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
if (context.AllAttributes["required"] == null)
{
var isRequired = For.ModelExplorer.Metadata.ValidatorMetadata.Any(a => a is RequiredAttribute);
if (isRequired)
{
var requiredAttribute = new TagHelperAttribute("required");
output.Attributes.Add(requiredAttribute);
}
}
}
}
}
You'll then need to add it to be used in your views:
_ViewImports.cshtml
#using ProjectName
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper "*, ProjectName"
Given the following model:
Foo.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace ProjectName.Models
{
public class Foo
{
public int Id { get; set; }
[Required]
[Display(Name = "Full Name")]
public string Name { get; set; }
}
}
and view (snippet):
New.cshtml
<label asp-for="Name"></label>
<input asp-for="Name"/>
Will result in this HTML:
<label for="Name">Full Name</label>
<input required type="text" data-val="true" data-val-required="The Full Name field is required." id="Name" name="Name" value=""/>
I hope this is helpful to anyone with same question but using .NET Core.
I needed the "required" HTML5 atribute, so I did something like this:
<%: Html.TextBoxFor(model => model.Name, new { #required = true })%>
#Erik's answer didn't fly for me.
Following did:
#Html.TextBoxFor(m => m.ShortName, new { data_val_required = "You need me" })
plus doing this manually under field I had to add error message container
#Html.ValidationMessageFor(m => m.ShortName, null, new { #class = "field-validation-error", data_valmsg_for = "ShortName" })
Hope this saves you some time.

MVC 5 scaffolding not emitting bootstrap classes for basic EF derived data

First off, I'm a newcomer to MVC and ASP.NET so apologies if I'm missing something simple.
I'm working on a code first MVC 5 application, for the sake of brevity lets say I have two Models defined like this:
public class Platform
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "You must select a manufacturer")]
[DisplayName("Manufacturer")]
public int ManufacturerId { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
[Required(ErrorMessage="You must enter a name for the platform")]
[StringLength(50, ErrorMessage="Reduce length to 50 characters or less")]
public string Name { get; set; }
}
and
public class Manufacturer
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage="You must enter a name for the manufacturer")]
[StringLength(50, ErrorMessage="Reduce length to 50 characters or less")]
public string Name { get; set; }
public virtual ICollection<Platform> Platforms { get; set; }
}
When creating 'MVC 5 Controller with views, using Entity Framework' scaffolding for these models all the CRUD actions are working correctly. The form elements on the other hand are unformatted, missing the form-control class that Bootstrap wants.
I can work around this per type by adding an EditorTemplate with something like #Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { #class = "form-control" }) in it, but surely the scaffolding for a Bootstrap MVC 5 project should be emitting valid code already? Looking at various MVC 5 tutorials they seem to be working correctly with proper styling, so I'm left somewhat confused as to what I'm doing wrong.
For reference, the output in the Create.cshtml file for each model (clipped to the important form elements for brevity) is:
<div class="form-group">
#Html.LabelFor(model => model.ManufacturerId, "ManufacturerId", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("ManufacturerId", String.Empty)
#Html.ValidationMessageFor(model => model.ManufacturerId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
and
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
Why is the ManufacturerId being specified as the label text for the drop down when a DisplayName is set in the model?
Why is the drop down using DropDownList() instead of the strongly typed DropDownListFor()?
Thank you in advance.
It looks like MVC 5.1 will have a workaround for the form-control problem with Bootstrap 3, as stated here. It will be possible to pass styles for EditorFor.
Sounds like you did what I did - upgrade Bootstrap 2.x to 3.0 or 3.0.1. The Microsoft project templates are based on Bootstrap 2.x not 3.x. You can read about others bumping into on
GitHub.
I also got a reply from Rick Anderson of Microsoft who wrote several of the Tutorials on www.asp.net and he confirmed this...
Great catch. Actually, some of the images in my tutorial are from Beta and RC that are using bootstrap v2, not V3, so our images will differ. Look at the views for your account controller to see "form-control". You can download my complete project and compare with your project.
Regarding your other question, I ran into the same issue and think it may be a bug. Certain annotations appear to get ignored. Try adding a [ForeignKey()] annotation to Platform.ManufacturerId and see if it affects the field label.

Resources