I am having two datepickers fields on my form, and i would like to validate that the to date is greater than from date.
Is there any validation attribute in MVC5 which I can use to achieve this?
I would also like this to work on client side, can some body please help in enabling client side validation in MVC?
Many Thanks
Edit: Created the custom attribute, but client side validation not working.
public class ValidateToDateAttribute : ValidationAttribute, IClientValidatable
{
public string errorMessageKey { get; private set; }
public ValidateToDateAttribute(string errorMessageKey)
{
this.errorMessageKey = errorMessageKey;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var viewModel = (TransactionViewModel)validationContext.ObjectInstance;
if (viewModel.ToDate.CompareTo(viewModel.FromDate) < 0)
{
return new ValidationResult(new ResourceManager(typeof(ValidationErrorMessages)).GetString(errorMessageKey));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "validatetodate",
ErrorMessage = new ResourceManager(typeof(ValidationErrorMessages)).GetString(errorMessageKey)
};
yield return modelClientValidationRule;
}
}
}
Bundle.Config
bundles.Add(new ScriptBundle("~/bundles/jqueryvalidation").Include(
"~/Scripts/jquery.validate.unobtrusive.min.js",
"~/Scripts/jquery.unobtrusive-ajax.min.js"));
View Model
[Display(ResourceType = typeof(DisplayLabelText), Name = "FromDate")]
public DateTime FromDate { get; set; }
[Display(ResourceType = typeof(DisplayLabelText), Name = "ToDate")]
[ValidateToDate("ToDateMustBeGreater")]
public DateTime ToDate { get; set; }
in View:
<div class="col-sm-7 margin-top-10">
<div class="col-sm-12">
#Html.LabelFor(m => m.FromDate, new { #class = "col-sm-3 form-group control-label" })
<div class="col-sm-8">
#Html.TextBoxFor(m => m.FromDate, "{0:MMM dd yyyy}", new { #class = "datepicker", disabled = "disabled" })
</div>
</div>
<div class="col-sm-12">
#Html.LabelFor(m => m.ToDate, new { #class = "col-sm-3 form-group control-label" })
<div class="col-sm-8">
#Html.TextBoxFor(m => m.ToDate, "{0:MMM dd yyyy}", new { #class = "datepicker", disabled = "disabled" })
#Html.ValidationMessageFor(m => m.ToDate)
</div>
</div>
<button type="submit" class="apply-filter-button">Apply Filter</button>
</div>
I figured it out, I was missing the unobtrusive java script code:
please see the below, it might be helpful for some one.
public class ValidateToDateAttribute : ValidationAttribute, IClientValidatable
{
/// <summary>
/// Initializes a new instance of the <see cref="ValidateToDateAttribute"/> class.
/// </summary>
/// <param name="errorMessageKey">The error message key.</param>
public ValidateToDateAttribute(string errorMessageKey, string otherProperty)
{
this.ErrorMessageKey = errorMessageKey;
this.FromDate = otherProperty;
}
/// <summary>
/// Gets from date.
/// </summary>
/// <value>
/// From date.
/// </value>
public string FromDate { get; private set; }
/// <summary>
/// Gets the error message key.
/// </summary>
/// <value>
/// The error message key.
/// </value>
public string ErrorMessageKey { get; private set; }
/// <summary>
/// When implemented in a class, returns client validation rules for that class.
/// </summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <returns>
/// The client validation rules for this validator.
/// </returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "validatetodate",
ErrorMessage = new ResourceManager(typeof(ValidationErrorMessages)).GetString(this.ErrorMessageKey),
};
modelClientValidationRule.ValidationParameters.Add("other", this.FromDate);
yield return modelClientValidationRule;
}
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var viewModel = (TransactionViewModel)validationContext.ObjectInstance;
if (viewModel.ToDate.CompareTo(viewModel.FromDate) < 0)
{
return new ValidationResult(new ResourceManager(typeof(ValidationErrorMessages)).GetString(this.ErrorMessageKey));
}
}
return ValidationResult.Success;
}
}
JavaScript:
<script type="text/javascript">
jQuery.validator.addMethod('greaterThan', function (value, element, params) {
if (!/Invalid|NaN/.test(new Date(value))) {
//return new Date(value) > new Date($("input[name='FromDate']").val());
return Date.parse(value) > Date.parse($(params).val());
}
return isNaN(value) && isNaN($(fromDate)) || (parseFloat(value) > parseFloat($("input[name='FromDate']").val()));
}, '');
// and an unobtrusive adapter
jQuery.validator.unobtrusive.adapters.add('validatetodate', ["other"], function (options) {
options.rules['greaterThan'] = "#" + options.params.other;
options.messages['greaterThan'] = options.message;
});
Related
I am trying to create a dynamic select builder that includes also nested class.
Basically I have entity class DB then DTO like in the following classes
/// <summary>
/// Base entity class
/// </summary>
public abstract class BaseEntity
{ }
/// <summary>
/// EF Schema entity
/// </summary>
public class AMOS_AMOSUSER : BaseEntity
{
public decimal USERID { get; set; }
public decimal? SUPERIORID { get; set; }
public decimal? EMPLOYEEID { get; set; }
public virtual AMOS_AMOSUSER SUPERIOR { get; set; }
public string LOGINID { get; set; }
public string NAME { get; set; }
public string COMMENT1 { get; set; }
public decimal ACCOUNTDISABLED { get; set; }
public DateTime? LASTLOGIN { get; set; }
}
/// <summary>
/// DTO base for all dtos
/// </summary>
public abstract class DTO_BASE
{
}
/// <summary>
/// DTO amos user base
/// </summary>
public class DTO_AMOSUSER_BASE : DTO_BASE
{
public decimal USERID { get; set; }
public string LOGINID { get; set; }
public string NAME { get; set; }
}
/// <summary>
/// DTO AMOS User
/// </summary>
public class DTO_AMOSUSER : DTO_AMOSUSER_BASE
{
public decimal? SUPERIORID { get; set; }
public virtual DTO_AMOSUSER_BASE SUPERIOR { get; set; }
}
Here below code for testing and creating dynamic select.
public static class TestDynamicSelect
{
public static void TestDynamicSelectDTO()
{
List<AMOS_AMOSUSER> users = new List<AMOS_AMOSUSER>();
PopulateUsers(users);
var q = users.AsQueryable().Select(DynamicSelectGenerator2<AMOS_AMOSUSER, DTO_AMOSUSER>(typeof(DTO_AMOSUSER).GetProperties().Select(p => p.Name)));
var cc = q.ToArray();
foreach (var item in cc)
{
Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item));
}
Console.ReadLine();
}
public static Expression<Func<T, TSelect>> DynamicSelectGenerator1<T, TSelect>(string fields)
{
return DynamicSelectGenerator2<T, TSelect>(fields.Split(','));
}
/// <param name="fields">
/// Format1: "Field1"
/// Format2: "Nested1.Field1"
/// Format3: "Field1:Field1Alias"
/// </param>
public static Expression<Func<T, TSelect>> DynamicSelectGenerator2<T, TSelect>(IEnumerable<string> fields)
{
string[] EntityFields;
if (fields == null || fields.Count() == 0)
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
else
EntityFields = fields.ToArray();
// input parameter "x"
var xParameter = Expression.Parameter(typeof(T), "x");
// new statement "new Data()"
var xNew = Expression.New(typeof(TSelect));
// create initializers
var bindings = EntityFields
.Select(x =>
{
string[] xFieldAlias = x.Split(':');
string field = xFieldAlias[0];
string[] fieldSplit = field.Split('.');
if (fieldSplit.Length > 1)
{
// original value "x.Nested.Field1"
Expression exp = xParameter;
foreach (string item in fieldSplit)
exp = Expression.PropertyOrField(exp, item);
// property "Field1"
PropertyInfo member2 = null;
if (xFieldAlias.Length > 1)
member2 = typeof(TSelect).GetProperty(xFieldAlias[1]);
else
member2 = typeof(T).GetProperty(fieldSplit[fieldSplit.Length - 1]);
// set value "Field1 = x.Nested.Field1"
var res = Expression.Bind(member2, exp);
return res;
}
// property "Field1"
var mi = typeof(T).GetProperty(field);
PropertyInfo member;
if (xFieldAlias.Length > 1)
member = typeof(TSelect).GetProperty(xFieldAlias[1]);
else
member = typeof(TSelect).GetProperty(field);
// original value "x.Field1"
if (member != null)
{
if (mi != null)
{
if (typeof(BaseEntity).IsAssignableFrom(mi.PropertyType))
{
List<string> props = new List<string>();
mi.PropertyType.GetProperties().ToList().ForEach(p => {
if (member.PropertyType.GetProperties().Any(c => c.Name.Equals(p.Name, StringComparison.Ordinal)))
props.Add(p.Name);
});
Type ex = typeof(TestDynamicSelect);
MethodInfo mm = ex.GetMethod("DynamicSelectGenerator2");
MethodInfo miConstructed = mm.MakeGenericMethod(mi.PropertyType, member.PropertyType);
var expr = (LambdaExpression)miConstructed.Invoke(null, new object[] { props });
// Probably here is not correct
return Expression.Bind(member, Expression.TypeAs(expr, member.PropertyType));
}
else
{
var xOriginal = Expression.Property(xParameter, mi);
return Expression.Bind(member, xOriginal);
}
}
}
//return Expression.Constant(GetDefault(mi.PropertyType));
throw new Exception("No property to bind");
}
);
var b = bindings.Where(x => x != null && xNew.Type.GetProperties().Any(p => p.Name.Equals(x.Member.Name, StringComparison.OrdinalIgnoreCase)));
// initialization "new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var xInit = Expression.MemberInit(xNew, b);
// expression "x => new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var lambda = Expression.Lambda<Func<T, TSelect>>(xInit, xParameter);
return lambda;
}
}
I can't populate correctly the nested property SUPERIOR which is always null.
Basically what I am trying to do is to populate DTO classes from an Entity class using property name comparison.
Please help.
I have this model:
public class FieldMapViewModel
{
public int Id { get; set; }
[Range(1, int.MaxValue, ErrorMessageResourceName = "RangeErrorMessage", ErrorMessageResourceType = typeof(Resources))] public int FeedId { get; set; }
[Range(1, int.MaxValue, ErrorMessageResourceName = "RangeErrorMessage", ErrorMessageResourceType = typeof(Resources))] public int FieldId { get; set; }
[Required(ErrorMessageResourceName = "RequiredErrorMessage", ErrorMessageResourceType = typeof(Resources)), StringLength(100, ErrorMessageResourceName = "StringLengthErrorMessage", ErrorMessageResourceType = typeof(Resources))] public string Path { get; set; }
}
As you can see, the Path is required and the FieldId and FeedId have a range of 1 to int.MaxValue.
So, in my controller, this line should return a BadRequest
if (!ModelState.IsValid) return BadRequest(ModelState);
But it isn't.
The model I have passed looks like this:
The ModelState should fail for both FieldId and Path in this case.
Here is the full code:
/// <summary>
/// Creates a new fieldMap
/// </summary>
/// <param name="fieldMap">The fieldMap</param>
/// <returns></returns>
[HttpPost]
[ProducesResponseType(typeof(FieldMap), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync(FieldMapViewModel fieldMap)
{
if (fieldMap == null) return BadRequest();
if (!ModelState.IsValid) return BadRequest(ModelState);
var request = ModelFactory.Create(fieldMap);
_fieldMapService.Create(request);
await _fieldMapService.SaveChangesAsync();
return Created(nameof(Get), new Sxp.Web.ActionResult<FieldMap>(request, string.Format(Resources.EntityCreated, "fieldMap")));
}
Can anyone tell me why it isn't being picked up?
I have a controller action that receives a complex object as a parameter, I need the OutputCache to vary by one of the properties of this complex object. Is this possible? How?
if you have a model like
public class person{
public string Name {get;set;}
public string location {get;set;}
}
and in the (strongly typed)view you have a form
#model Person
#Html.BeginForm(){
#Html.TextBoxFor(x=>x.Name)
#Html.TextBoxFor(x=>x.location)
}
and you submit the form to an ActionResult savePerson, with varying signature like
public ActionResult savePerson(Person p){
// p.Name
// p.location
}
or
public ActionResult savePerson(string Name, string location){
}
therefore i think if you annotate the ActionResult like
[OutputCache(Duration=3600, VaryByParam="Name")]
public ActionResult savePerson(Person p)
{
//
return View();
}
it will do for you, or if you have a complex model like
public class person{
public string Name {get;set;}
public Location loc {get;set;}
}
public class Location{
public string address
}
try
[OutputCache(Duration=3600, VaryByParam="Person.Location.address")]
public ActionResult savePerson(Person p)
{
//
return View();
}
I had the same requirement as above and came up with a slightly different approach
The class
/// <summary>
/// This class is used to encapsulate search filters for monitor graphs
/// </summary>
public class DatacarMonitorSearchCriteriaModel
{
public int? SynergyCode { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime EndDate { get; set; }
/// <summary>
/// Filter to apply
/// </summary>
public IEnumerable<int> Countries { get; set; }
public DatacarMonitorSearchCriteriaModel()
{
Countries = new List<int>();
}
}
OutputCacheComplexAttribute
/// <summary>
/// <para>
/// An instance of this class mimic the behaviour of OutputCacheAttribute but for complex objects.
/// </para>
/// <para>
/// It allows to cache the output of any action that takes complex objects
/// </para>
/// </summary>
public class OutputCacheComplexAttribute : OutputCacheAttribute
{
private readonly Type[] _types;
private string _cachedKey;
/// <summary>
/// Initializes a new instance of the <see cref="OutputCacheComplexAttribute"/> class.
/// </summary>
/// <param name="types">Types that this attribute will lookup for in QueryString/Form data and store values in cache.</param>
/// <exception cref="System.ArgumentOutOfRangeException">type;type cannot be null</exception>
public OutputCacheComplexAttribute(params Type[] types)
{
if (types == null)
{
throw new ArgumentOutOfRangeException("type", "type cannot be null");
}
_types = types;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
StringBuilder sbCachedKey = new StringBuilder();
if (filterContext.HttpContext.Request.Url != null)
{
string path = filterContext.HttpContext.Request.Url.PathAndQuery;
IDictionary<string, object> parameters = filterContext.ActionParameters;
//we need to compute a cache key which will be used to store the action output for later retrieval
//The cache key scheme is
// {url}:{key 1}:{value};[{key 2}:{value 2}[; ... {key n}:{value n}]];
// where :
// - url is the url of the action that will be executed
// - key n is the name of the n-th parameter
// - value n is the value of the n-th parameter as json string.
foreach (KeyValuePair<string, object> kv in parameters)
{
var kv1 = kv;
if (kv.Value != null && _types.AtLeastOnce(t => t.IsInstanceOfType(kv1.Value)))
{
sbCachedKey = sbCachedKey.AppendFormat("{0}:{1};",kv.Key,
JsonConvert.SerializeObject(kv.Value, Formatting.None, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}));
}
}
_cachedKey = String.Format("{0}:{1}:{2}", GetType().Name, path, sbCachedKey.ToString());
}
if (!String.IsNullOrWhiteSpace(_cachedKey) && filterContext.HttpContext.Cache[_cachedKey] != null)
{
filterContext.Result = (ActionResult)filterContext.HttpContext.Cache[_cachedKey];
}
else
{
base.OnActionExecuting(filterContext);
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (!String.IsNullOrWhiteSpace(_cachedKey))
{
filterContext.HttpContext.Cache.Add(_cachedKey, filterContext.Result, null,
DateTime.UtcNow.AddSeconds(Duration), Cache.NoSlidingExpiration,
CacheItemPriority.Default, null);
}
base.OnActionExecuted(filterContext);
}
}
Attribute usage
[OutputCacheComplex(typeof(DatacarMonitorSearchCriteriaModel), Duration = OutputCacheDurationInSeconds, Location = OutputCacheLocation.Server)]
public async Task<JsonNetResult<DatacarMonitorDetailModel>> ReadMonitorDetailsJson([DataSourceRequest] DataSourceRequest request, DatacarMonitorSearchCriteriaModel criteria)
{
//some really complicated code here
}
with this new attribute, you can specify which type[s] to use for caching and the cache key will be computed based on values of each its properties.
For object, just that work fine:
[OutputCache(VaryByParam = "*", Duration = 60)]
The following code is working just fine but I have a few questions realated to it's design.
BlogEntry.cs
public class BlogEntry : EntityBase
{
/// <summary>
/// Gets or sets the blog entry comments.
/// </summary>
public virtual ICollection<BlogEntryComment> BlogEntryComments { get; set; }
}
BlogEntryComment.cs
public class BlogEntryComment : EntityBase//, IValidatableObject
{
/// <summary>
/// Gets or sets the comment.
/// </summary>
[StringLength(2500)]
[Required(ErrorMessageResourceName = "Comment", ErrorMessageResourceType = typeof(Validation))]
[AllowHtml]
public string Comment { get; set; }
/// <summary>
/// Gets or sets the blog entry id.
/// </summary>
public Guid BlogEntryId { get; set; }
/// <summary>
/// Gets or sets the blog entry.
/// </summary>
public virtual BlogEntry BlogEntry { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// Gets or sets the author.
/// </summary>
public virtual User User { get; set; }
}
BlogController.cs
[HttpPost]
public virtual ActionResult PostComment(Guid id, BlogEntryComment blogEntryComment)
{
var blogEntry = this.BlogEntryService.GetById(id);
if (blogEntry == null)
{
if (Request.IsAjaxRequest())
return Json(new { success = false, message = "Blog entry not found" });
return new HttpNotFoundWithViewResult("NotFound");
}
var user = UserService.GetByUsername(User.Identity.Name);
if (user == null)
{
if (Request.IsAjaxRequest())
return Json(new { success = false, message = "Unknown user!" });
return new HttpUnauthorizedResult();
}
if (!ModelState.IsValid)
{
var errorModel = new BlogEntryDetail()
{
BlogEntry = blogEntry,
HideNewCommentsForm = false
};
if (this.Request.IsAjaxRequest())
{
return PartialView(MVC.Blog.Views._CommentsControl, errorModel);
}
else
{
errorModel.RelatedBlogEntries = this.BlogEntryService.GetRelatedBlogEntries(false, blogEntry, 3).ToArray();
return View(errorModel);
}
}
blogEntryComment.User = user;
blogEntryComment.BlogEntry = blogEntry;
this.BlogEntryCommentService.Add(blogEntryComment);
var model = new BlogEntryDetail()
{
BlogEntry = blogEntry,
HideNewCommentsForm = true
};
if (this.Request.IsAjaxRequest())
{
return PartialView(MVC.Blog.Views._CommentsControl, model);
}
else
{
model.RelatedBlogEntries = this.BlogEntryService.GetRelatedBlogEntries(false, blogEntry, 3).ToArray();
return View(model);
}
}
BlogEntryService.cs
public class BlogEntryService : GenericEntityService<BlogEntry>, IBlogEntryService
{
/// <summary>
/// Initializes a new instance of the <see cref="BlogEntryService"/> class.
/// </summary>
/// <param name="unitOfWork">The <see cref="IUnitOfWork"/>.</param>
public BlogEntryService(IUnitOfWork unitOfWork)
: base(unitOfWork.BlogEntries, unitOfWork)
{
}
GenericEntityService.cs
public abstract class GenericEntityService<T> : IGenericEntityService<T> where T : MVCBlog.Core.Entities.EntityBase
{
/// <summary>
/// Initializes a new instance of the <see cref="GenericEntityService<T>"/> class.
/// </summary>
/// <param name="repository">The <see cref="MVCBlog.Core.Repository.IRepository{T}"/>.</param>
/// <param name="unitOfWork">The <see cref="IUnitOfWork"/>.</param>
protected GenericEntityService(IRepository<T> repository, IUnitOfWork unitOfWork)
{
this.Repository = repository;
this.UnitOfWork = unitOfWork;
}
}
The comment should be added to the database with a method of BlogEntryService.AddComment(..) or with it's own BlogEntryCommentService.Add(..) as with current implementation?
I'm validating User and BlogEntry in the controller, this validation should be part of the service layer? for example [Service].AddComment(Guid blogEntryId, string username, string comment)
Any other idea to improve the desing or code?
Acording to SRP (Single Response Principal) it should be BlogEntryCommentService.Add(..).
I suppose, this is not servicelayer validation.
I'm having some trouble getting my ViewModel to return a non-null object in the base model property in my controller's Create action postback. I currently have another page that is doing the exact same operations on another model that has almost the same properties and that form is working perfectly, so I feel like I'm missing something basic, though I can't place what is wrong.
Here's my ViewModel with my Base class:
public class SystemFailureActionViewModel
{
/// <summary>
/// View model class for adding and modifying SystemFailureActions
/// </summary>
public SystemFailureAction action { get; set; }
//properties
public int TypeID { get; set; }
public string TypeDescription { get; set; }
public bool Assigned { get; set; }
public SystemFailureActionViewModel() { }
public SystemFailureActionViewModel(SystemFailureAction action)
{
this.action = action;
}
//Collections for views
public IEnumerable<SelectListItem> EditSystemFailiureTypesList { get { return ModelListProvider.FilteredSystemFailureTypeList; } }
public IEnumerable<SelectListItem> DetailsSystemFailiureTypesList { get { return ModelListProvider.FullSystemFailureTypeList; } }
}
[MetadataType(typeof(SystemFailureActionMetadata))]
public partial class SystemFailureAction
{
private class SystemFailureActionMetadata
{
/// <summary>
/// ID for this failure action
/// </summary>
public int ID { get; set; }
/// <summary>
/// Action Description
/// </summary>
[Required]
[StringLength(200)]
[DisplayName("Action Description")]
public string Description { get; set; }
}
}
Here's my Controller Add and Add Postback methods:
public ActionResult Add()
{
SystemFailureAction action = new SystemFailureAction();
action.Description = "";
populateSystemFailureActionData(action);
return PartialView("Form", new SystemFailureActionViewModel(action));
}
[HttpPost]
public ActionResult Add(SystemFailureActionViewModel viewModel, string[] selectedTypes, FormCollection collection)
{
SystemFailureAction newAction = viewModel.action;
if (!ModelState.IsValid)
{
populateSystemFailureActionData(newAction);
return PartialView("Form", new SystemFailureActionViewModel(newAction));
}
try
{
//Insert the new failure action type
context.SystemFailureActions.InsertOnSubmit(newAction);
context.SubmitChanges();
//Insert the failure type mappings
updateSystemFailureAssociationData(newAction, selectedTypes);
context.SubmitChanges();
//Return the new data
populateSystemFailureActionData(newAction);
return PartialView("Done", new SystemFailureActionViewModel(newAction));
}
catch (Exception ex)
{
ModelState.AddModelError("", ex.Message);
populateSystemFailureActionData(newAction);
return PartialView("Form", new SystemFailureActionViewModel(newAction));
}
}
And finally here is my form, it is being loaded into a Jquery dialog and the postback is being done via Ajax.
#using (Ajax.BeginForm(new AjaxOptions
{
HttpMethod = "Post",
UpdateTargetId = "formDialog",
InsertionMode = InsertionMode.Replace,
OnSuccess = "onDialogDone()"
}))
{
#Html.ValidationSummary(true)
<div>
<div class="editor-label">
#Html.LabelFor(model => model.action.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.action.Description)
</div>
</div>
<div class="categoryFieldSet">
<fieldset>
<legend>Mechanical System Failure Categories</legend>
<div class="editor-field">
<table>
<tr>
#{
int count = 0;
List<ManageMAT.ViewModels.SystemFailureActionViewModel> types = ViewBag.Types;
foreach (var type in types)
{
if (count++ % 3 == 0)
{
#: </tr> <tr>
}
#: <td>
<input type="checkbox"
id="selectedTypes + #type.TypeID"
name="selectedTypes"
value="#type.TypeID"
#(Html.Raw(type.Assigned ? "checked=\"checked\"" : "")) />
<label for="selectedTypes + #type.TypeID">#type.TypeDescription</label>
#:</td>
}
#:</tr>
}
</table>
</div>
</fieldset>
</div>
}
If you're wondering what the ViewBag.Types logic is in the form it is related to this question I asked earlier.
Edit:
I checked the ModelState error and the exception I'm getting is
"The parameter conversion from type 'System.String' to type Models.SystemFailureAction' failed because no type converter can convert between these types."
I also removed the logic that adds the Failure types and I'm still receiving the same issue. So it appears the problem is coming from mapping the Viewmodel.action.Description to the action.
The problem is the following property on your SystemFailureActionViewModel:
public SystemFailureAction action { get; set; }
In ASP.NET MVC action and controller are kinda reserved words. They are part of every route. And if you have a property called this way it conflicts with the string value which is pointing to the action name and which obviously cannot be bound to a complex SystemFailureAction type.
So to fix the problem simply rename this property on your view model:
public SystemFailureAction FailureAction { get; set; }