I am working on an app where I have a requirement to be able to load object properties at runtime from a database. The customer wants to be able to add attributes to the database and have them show up in the app. I am accomplishing this by giving my model a list of Field objects that contain a name, a type, and a value. This works well for displaying and editing project properties, but I'm having trouble with validation in the editor view. Thanks for your help.
I want to be able to do this in my Edit action:
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Normal view:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
</fieldset>
}
What I need to do:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
for(i = 1 to n) {
<div class="editor-label">
#Html.LabelFor(model => model.Fields[i].Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Fields[i].Value)
#Html.ValidationMessageFor(model => model.Fields[i].Value)
</div>
}
</fieldset>
}
Model:
public class Movie
{
public Movie()
{
this.Fields = new List<Field>();
}
public List<Movie> Movies { get; set; }
}
Field Class:
public class Field
{
public string Name { get; set; }
public Type Type { get; set; }
public object Value { get; set; }
}
I believe something like this should work:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
#{int i = 0;}
#foreach(var field in model.Fields) {
var htmlFieldName = string.Format("Fields[{0}]", i);
<div class="editor-label">
<label for="#htmlFieldName">#field.Title</label>
</div>
<div class="editor-field">
#Html.EditorFor(model => field, null, htmlFieldName)
#Html.ValidationMessage(htmlFieldName)
</div>
}
</fieldset>
}
(Note that I made up how you're producing your label text, since using the actual value as the label didn't make sense to me).
The POST should end up with values like this:
ID=123
Fields[0]=Jaws
Fields[1]=VeggieTales
...
... and that should automatically bind to your Movie model, provided the model has, for example, a List<string> named Fields. If your model doesn't look like that, this should at least get you on the right track.
Update
In your comment, you explain that you are trying to produce an editor for an object. There are two major points of difficulty here:
MVC relies on the static type returned in the lambda expression you give to EditorFor to determine which kind of editor it should produce. To override this, you will need to provide a specific template name where my original suggestion shows you providing null:
#Html.EditorFor(model => field.Value, field.Type.Name, htmlFieldName + ".Value")
You'll probably need to tweak this to make it provide the right template name for types like Integer, but this should give you the general idea.
When posting back, there is no way for the server to know that Field[0] is an int, etc. You can either:
Provide hidden values to specify each type, and then use a custom model binder that can consume this information to build each Field based on the combined Type and Value.
Recreate the structure of the Movie object on the server side based on the Movie's ID, and then walk through each of it's Fields calling:
TryUpdateModel((dynamic)field, string.Format("Field[{0}]", i));
There are probably other options, but that's about all the time I'm willing to put into this today.
Related
Hope someone can help me. I am new to MVC, coming from a winforms/console/vb6background.
Apologies if this has already been answered, I am stuggling to understand how I can resolve the below issue.
I have a view model :
public class testvm
{
public int id { get; set; }
public DateTime date { get; set; }
public student studentID { get; set; }
public testvm() { }
public testvm (student s)
{
studentID = s;
}
}
I am pre-populating the student child object of this ViewModel before it is passed to the view.
Student Model :
public class student
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
The problem I have is when the model is returned to the create HTTP post method the student child object is blank.
The controller code :
// GET: testvms/Create
public ActionResult Create(int sId)
{
student a = db.students.Find(sId);
testvm b = new testvm(a);
return View(b);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,date,student")] testvm testvm)
{
if (ModelState.IsValid)
{
db.testvws.Add(testvm);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(testvm);
}
View code:
#model WebApplication2.Models.testvm
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>testvm</h4>
<hr />
#Html.HiddenFor(model => model.studentID.ID)
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The model object on the view is populated with the student information. When this is passed back to Create POST controller the student child object is null!
Can somebody please advise where I am going wrong or of the correct way to achieve this?
My application will have many forms that will all need to be pre-populated with student information. Each student will have many forms that will need to be filled out for them.
Many thanks in advance,
Rob
For every property in domain model (in your case testvm) you must have an EditorFor or Input element (like TextBoxFor or so) on your view(or HiddenFor for ID or other non user ui data).It may be a pain binding nested models in MVC as the DefaultModelBinder may not be able to bind whole object.However it would be safer approach to expose only the required properties on view like
#Html.HiddenFor(model => model.studentID.ID)
#Html.HiddenFor(model => model.studentID.Name)
and later on Controller Side
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(testvm testvm)
{
var originalobj=db.get //get fresh copy from data store
originalobj.Name=testvm.Name;
// ...other properties
//perform required operations on originalobj
}
you may use AutoMapper for this Purpose as
Mapper.CreateMap<testvm,testvm>();
originalobj=Mapper.Map<testvm,testvm>(testvm,originalobj);
you may find more information about Automapper on :
https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
Your property name is called studentId (even though standard C# property naming convention dictates that it should have been called StudentId):
public student studentID { get; set; }
But in your Bind attribute you seem to have specified some student property which doesn't really exist on your view model:
[Bind(Include = "id,date,student")]
So you probably want to get rid of this Bind attribute from your controller action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(testvm testvm)
{
...
}
Also note that you only have a hidden field for the student id inside your form:
#Html.HiddenFor(model => model.studentID.ID)
You don't have a corresponding hidden field for the student name property, so it will never be posted back to your controller action.
Your attribute [Bind(Include = "id,date,student")] should include the names of the properties that you want to be set, student isn't in your model, but studentID is, they have to match.
You don't have to explicitly specify all of the field names that you want to be bound to your model, by default they will be bound anyway unless you tell the binder NOT to bind it by using [Bind(Exclude = "id,date,student")]. Therefore as it currently stands, I'd recommend removing your Include attribute to ease maintenance unless there is an important reason for using it and simply ensure that the models that you bind to only include the values you need.
Secondly, you have to make sure that any values that you are posting back from a form in your view have the same parameter names and are structured the same as the ones that you want to be bound to the request model.
This:
#Html.HiddenFor(model => model.studentID.ID)
Is not the same as:
#Html.HiddenFor(model => model.studentID)
I'm having trouble understanding why my model is not passed along with its values to my controller when posting a form.
I have a view with a strongly typed model (UnitContract) that is being fetched from a webservice, that holds a set of values. In my action I'm trying to fetch int ID and bool Disabled fields that exists in my model. When debugging, I see that my model being passed from the form doesn't contain any values at all. What am I missing?
My view (UnitContract as strongly typed model):
...
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
My controller action:
[HttpPost]
public ActionResult EnableDisableUnit(UnitContract model)
{
var client = new UnitServiceClient();
if (model.Disabled)
{
client.EnableUnit(model.Id);
}
else
{
client.DisableUnit(model.Id);
}
return RedirectToAction("Index", model.Id);
}
Sounds like you need to add the fields from your model to your form. Assuming your view accepts a UnitContract model, then something like this should work:
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Disabled)
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
Now when you submit the form, it should submit the fields to your model.
The MVC framework will use the data from the form to create the model. As your form is essentially empty, there is no data to create the model from, so you get an object without any data populated.
The only data that is sent from the browser in the request when you post the form, is the data that is inside the form. You have to put the data for the properties in the model as fields in the form, so that there is something to populate the model with.
Look into using #Html.HiddenFor(). Put these in your form, and the data you want to see posted back to your controller should be there. For example, your form would look something like...
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.IsDisabled)
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
Let's say you have a model like this:
public class UnitContract
{
public int Id { get; set; }
public DateTime SignedOn { get; set; }
public string UnitName { get; set; }
}
Your view would look something like this:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>UnitContract</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.SignedOn)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SignedOn)
#Html.ValidationMessageFor(model => model.SignedOn)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UnitName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UnitName)
#Html.ValidationMessageFor(model => model.UnitName)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
In your controller:
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Edit(UnitContract unitContract)
{
// do your business here .... unitContract.Id has a value at this point
return View();
}
Hope this is helpful.
I am trying to abstract my view model from various types of views. The entire thing compiles without a issue but I am having issues with "reflecting" (formally known as unboxing) the data annotations.
I have an interface:
public interface IPerson
{
string FirstName { get;set;}
string LastName {get;set;}
}
And I have two class which implement the interface as such:
public class Employee : IPerson
{
[Required]
[Display(Description = "Employee First Name", Name = "Employee First Name")]
public string FirstName {get;set;}
[Required]
[Display(Description = "Employee Last Name", Name = "Employee Last Name")]
public string LastName {get;set;}
public int NumberOfYearsWithCompany {get;set;}
}
public class Client : IPerson
{
[Required]
[Display(Description = "Your first Name", Name = "Your first Name")]
public string FirstName {get;set;}
[Display(Description = "Your last Name", Name = "Your last Name")]
public string LastName {get;set;}
[Display(Description = "Company Name", Name = "What company do you work for?")]
public string CompanyName {get;set;}
}
Person Edit View: views/Person/Edit as such:
#model IPerson
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
</div>
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
</div>
Employee Edit View: views/Employee/Edit:
#model Employee
Html.RenderAction("Edit", "Person", new { person = Model });
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.CompanyName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CompanyName)
#Html.ValidationMessageFor(model => model.CompanyName)
</div>
</div>
where the PersonController is:
public ActionResult Edit(IPerson person)
{
return PartialView(person);
}
Everything compiles and renders fine. However, the data annotations are being lost.
So, Employee/Edit is coming out like:
FirstName [textfield]
LastName [textfield]
What Company do you work for? [textfield] Company Name is a required field
Is there anyway of unboxing those data annotations for the concrete class?
Side note
I tried explicitly casting the IPerson to Employee as such:
#model IPerson
#{
var employee = (Employee)Model;
}
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => employee.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => employee.FirstName)
#Html.ValidationMessageFor(model => employee.FirstName)
</div>
</div>
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => employee.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => employee.LastName)
#Html.ValidationMessageFor(model => employee.LastName)
</div>
</div>
Doing this made the first name required but didn't take the display properties from the label.
Update
After much discussion as to whether this is or is not unboxing, I have not yet found a simple solution of grabbing the data annotation from the (more basic) concrete class. It would really defeat the goal of simplicity to use reflection in view (or helper) to get at the data annotations of the concrete class.
We have a few views that essentially are the same but have slightly different required fields and display names. It would be REALLY convenient if I could just pass a view model into an interfaced view and it would figure out the required fields and display properties. If anyone has figured out a way to do this it would be greatly appreciated.
I had the same problem with the TextBoxFor helper not generating the correct markup validation.
The way I was able to solve it was to use the TextBox helper instead of the TextBoxFor helper.
Here is the partial snippet which worked for me
#model Interfaces.Models.EntryPage.ICustomerRegisterVM
<p>
#Html.ValidationMessageFor(model => model.Department)
#Html.TextBox(Html.NameFor(model => model.Department).ToString(), Model.Department)
</p>
As you can see I used the Html.NameFor helper to generate the correct name from the expression and then passed in the property. Using this approach MVC was able to successfully generate the correct unobtrusive validation markup for the concrete class which implemented the interface which is referenced as the viewmodel.
I have not tried this approach for LabelFor or other helpers. But I hope that the result would be the same.
Please note the Html.NameFor helper is available in MVC5
You're specifying a model (IPerson) which has no data attributes when you're calling the PersonController.Edit action. The default metadata provider will only pick up the data attributes defined explicitly on the specified type (in this case, IPerson) or those that are inherited. You can share a metadata class or interface or copy the data annotations attributes to the interface.
However, I think you may want to redesign how this works a bit (for example, calling RenderAction to include another view into the current view is a code smell).
I would create a partial view for Person. Then, you can create a partial view for each type of person (Client, etc.). You can then add any additional markup, and include your Person view by using #Html.Partial("Person", Model).
You might also want to use a base class Person instead of an interface, otherwise it'll get tricky overriding the data attributes for FirstName and LastName.
public abstract class Person
{
public virtual string FirstName {get;set;}
public virtual string LastName {get;set;}
}
public class Employee : Person
{
[Required]
[Display(Description = "Employee First Name", Name = "Employee First Name")]
public override string FirstName {get;set;}
[Required]
[Display(Description = "Employee Last Name", Name = "Employee Last Name")]
public override string LastName {get;set;}
public int NumberOfYearsWithCompany {get;set;}
}
public class Client : Person
{
[Required]
[Display(Description = "Your first Name", Name = "Your first Name")]
public override string FirstName {get;set;}
[Display(Description = "Your last Name", Name = "Your last Name")]
public override string LastName {get;set;}
[Display(Description = "Company Name", Name = "What company do you work for?")]
public string CompanyName {get;set;}
}
Views/Shared/Person.cshtml
#model Person
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
</div>
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
</div>
Views/Employees/Edit.cshtml
#model Employee
#Html.Partial("Person", Model);
<div class="clear paddingbottomxxsm">
<div class="editor-label">
#Html.LabelFor(model => model.CompanyName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CompanyName)
#Html.ValidationMessageFor(model => model.CompanyName)
</div>
</div>
Controllers/EmployeesController.cs
public class EmployeesController : Controller
{
public ActionResult Edit(int id)
{
var model = GetEmployee(id); // replace with your actual data access logic
return View(model);
}
}
I found this post struggling with the exact same issue.
(On a side note: I had already implemented my own DataAnnotationsModelMetadataProvider following this sample to be able to access custom attributes inside my editor templates:
http://weblogs.asp.net/seanmcalinden/archive/2010/06/11/custom-asp-net-mvc-2-modelmetadataprovider-for-using-custom-view-model-attributes.aspx. Ensure to not miss the step in application start if you want to use your own DataAnnotationsModelMetadataProvider for this problem)
So after almost giving up on getting this to work I decided to debug my CreateMetadata override to see what I could get hold of there. I also found this post:
Obtain containing object instance from ModelMetadataProvider in ASP.NET MVC
This in combination with some reflection of the DataAnnotationsModelMetadataProvider class lead me to the following solution:
public class MyModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
//If containerType is an interface, get the actual type and the attributes of the current property on that type.
if (containerType != null && containerType.IsInterface)
{
object target = modelAccessor.Target;
object container = target.GetType().GetField("container").GetValue(target);
containerType = container.GetType();
var propertyDescriptor = this.GetTypeDescriptor(containerType).GetProperties()[propertyName];
attributes = this.FilterAttributes(containerType, propertyDescriptor, Enumerable.Cast<Attribute>((IEnumerable)propertyDescriptor.Attributes));
}
var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
//This single line is for the "sidenote" in my text above, remove if you don't use this:
attributes.OfType<MetadataAttribute>().ToList().ForEach(x => x.Process(modelMetadata));
return modelMetadata;
}
}
So now I can have an EditorTemplate that has an interface type as the model and then use different implementations of it to be able to have different field names and validation rules via data annotations. I'm using this for a form that takes three different addresses; home address, work address and invoice address. The user interface for these groups of inputs are exactly the same but the validation rules differ.
This is of course a bit of a convention based solution saying that this behaviour should always apply when the editor template model is an interface. If you have existing editor templates where the model is an interface type that it self has data annotations, this solution will of course break that. For my case we are just starting up our usage of MVC and for now this convention will work. It would be interesting to maybe send a combination of the attributes from the interface and the actual type to the base-implementation but I'll save that experiment for later.
Please also let me know if you're a reader that is aware of some serious flaw with this solution.
I am quite new to MVC but have been making steady progress however I have recently hit a problem that I can't seem to overcome despite reading a number of similar posts on similar topics.
I have a model as follows (simplified for brevity)
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Address Address { get; set; }
I have a strongly typed view as follows:
#model JFS.Data.Model.Supplier
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Supplier Address</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Address.AddressLine1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.AddressLine1)
#Html.ValidationMessageFor(model => model.Address.AddressLine1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Address.Country)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.Country)
#Html.ValidationMessageFor(model => model.Address.Country)
</div>
</fieldset>
And I have a shared EditorTemplate for the Country field as follows:
#model JFS.Data.Model.Address
#using System.Globalization
#Html.DropDownListFor(o => o.Country, GetCountries(Model), "Please select")
#functions
{
private static IEnumerable<SelectListItem> GetCountries(object country)
{
var regions = CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Select(cultureInfo => new RegionInfo(cultureInfo.LCID))
.OrderBy(r => r.EnglishName)
.Distinct()
.ToList();
return new SelectList(regions, "TwoLetterISORegionName", "EnglishName", country);
}
}
I understand the problem but not sure how best to overcome it, any advice would be very much appreciated.
Though your particular scenario was already answered by #ProNotion, I have had this issue before when you are passing null into your View or Templates. It may help someone else coming this way.
In your main view replace:
#Html.EditorFor(model => model.Address.Country)
with:
#Html.EditorFor(model => model.Address)
By the way if you are writing a shared editor template for an Address model you probably want to include the other properties as well such as AddressLine1 in this template.
You need to just pass the Address property:
#Html.EditorFor(model => model.Address)
You are trying to pass the Address properties which are strings, whereas the requirement of type is Address.
I have a model with this property:
[AllowHtml]
[DisplayName("Widget for Table")]
[StringLength(1000, ErrorMessage = "Maximum chars 1000")]
[DataType(DataType.Html)]
public object TableWidget { get; set; }
And here is the create methods in controller:
//
// GET: /Admin/Table/Create
public ActionResult Create(int id)
{
Season season = _seasonRepository.GetSeason(id);
var table = new Table
{
SeasonId = season.SeasonId
};
return View(table);
}
//
// POST: /Admin/Table/Create
[HttpPost]
public ActionResult Create(Table a)
{
if (ModelState.IsValid)
{
_tableRepository.Add(a);
_tableRepository.Save();
return RedirectToAction("Details", "Season", new { id = a.SeasonId });
}
return View();
}
And last here is my view:
#model Stridh.Data.Models.Table
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name) #Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TableURL)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TableURL) #Html.ValidationMessageFor(model => model.TableURL)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.SortOrder)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SortOrder) #Html.ValidationMessageFor(model => model.SortOrder)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TableWidget)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TableWidget) #Html.ValidationMessageFor(model => model.TableWidget)
</div>
<div class="editor-label invisible">
#Html.LabelFor(model => model.SeasonId)
</div>
<div class="editor-field invisible">
#Html.EditorFor(model => model.SeasonId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I add a "normal" message without html everything is saved OK, but when saving it says A potentially dangerous Request.Form...
Another strange thing is that I got this [AllowHtml] to work in another model class. I cant find why this is causing me troubble. Need your help. :-)
The way you are using AllowHtml should work. Make sure that you are not accessing the HttpRequest.Form collection anywhere else in your code (controller, filter, etc) as this will trigger ASP.NET Request Validation and the error you are seeing. If you do want access to that variable then you should access it via the following code.
using System.Web.Helpers;
HttpRequestBase request = .. // the request object
request.Unvalidated().Form;
I get the same problem and i solve it with the help of this post.
If you are on .net 4.0 make sure you add this in your web.config
<httpRuntime requestValidationMode="2.0" />
Inside the <system.web> tags
I had the same problem. My model class is named "GeneralContent" and has the property "Content". In my action method i used attribute like this:
public ActionResult Update(GeneralContent content)
when i renamed content argument to cnt, everything works well. I think MVC is confused when some attribude of model class has the same name as the argument in action method.
I also had this issue. I could not get a model property marked with [AllowHtml] to actually allow HTML, and instead encountered the same error you describe. My solution ended up being to mark the Controller action that accepts the posted model with the [ValidateInput(false)] attribute.
The answer that #marcind put me on the right track but my issue was that I was passing the FormCollection into the Controller method, so changing this...
public ActionResult Edit(MyClass myClass, FormCollection collection)
To this...
public ActionResult Edit(MyClass myClass)
Solved the problem.
Subsequently, I was able to access the heck out of the form collection with code like this without issue.
foreach (var key in Request.Form.AllKeys)
{
...
}
So, it was the passing the form collection parameter that caused the problem, not merely accessing the form collection.