Reputation: 141
I have the following view:
@model dynamic
<form class="form-horizontal" id="formDynamicItem" action="/DynamicItem/SaveItem" method="post">
@Html.AntiForgeryToken()
<div class="col-xs-12 buttonBar">
<button type="submit" value="Save" name="submitButton" class="btn">Save</button>
<button type="submit" value="Cancel" name="submitButton" class="btn">Cancel</button>
</div>
<div class="col-lg-6">
<div class="ibox ">
<div class="ibox-content">
@{
foreach (var obj in Model)
{
var kvpObj = (KeyValuePair<string, object>)obj;
var entityProp = (EntityAttributeProperties)kvpObj.Value;
<div class="form-group">
@if (entityProp.IsHiddenField)
{
<input type="hidden" class="form-control" data-val="true" id="@kvpObj.Key" name="@kvpObj.Key" value="@entityProp.Value" />
}
else if (entityProp.IsFormField)
{
var isReadOnly = entityProp.IsReadonly ? "readonly" : "";
IHtmlString validationRules = Html.Raw(string.Empty);
if (entityProp.ValidationRules != null)
{
validationRules = entityProp.ValidationRules;
}
@Html.Label(entityProp.Name, new { @class = labelClass })
<div class="@controlClass">
@switch (@entityProp.Type)
{
//... many cases
default:
<input type="text" class="form-control" id="@kvpObj.Key" name="@kvpObj.Key" value="@entityProp.Value" @isReadOnly @validationRules />
break;
}
</div>
}
</div>
}
}
</div>
</div>
</div>
</form>
@section Scripts {
<script>
$("#formDynamicItem").validate();
</script>
}
And in the controller I get my values using FormCollection:
public ActionResult SaveItem(FormCollection form)
{
...
newValue = typeConverter.ConvertFromString(form[entityAttribute.Name]);
...
}
}
My question is the following:
How can I establish Server-side validation on such dynamic model? Can I use FormCollection somehow? Possibly build dynamic view model somehow? If anyone has experience in this please consider giving a suggestion(answer).
Update: Making Detail page with ViewModel insted of dynamic model
So, After much refactoring I appear to be again stuck with server-side validation:
So now I have this ViewModel:
public class DynamicItemViewModel
{
public Guid Id { get; set; }
public List<EntityAttributeProperties> Properties { get; set; }
}
this detail page:
@model ExactDistillation.Models.DynamicItem.DynamicItemViewModel
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
@using (Html.BeginForm("SaveItem", "DynamicItem", FormMethod.Post, new { @class = "form-horizontal", @id = "formDynamicItem" }))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(x => x.Id)
<div class="col-xs-12 buttonBar">
<button type="submit" value="Save" name="submitButton" class="btn btn-primary pull-right">Save</button>
<button type="submit" value="Cancel" name="submitButton" class="btn btn-default pull-right cancel">Cancel</button>
</div>
<div class="col-lg-6">
<div class="ibox float-e-margins">
<div class="ibox-title text-webwonders">
<h5>Item</h5>
</div>
<div class="ibox-content">
@{
for (int i = 0; i < Model.Properties.Count; i++)
{
@Html.EditorFor(m => Model.Properties[i], "EntityAttributeProperties", "Properties[" + i + "]")
}
}
</div>
</div>
</div>
}
</div>
</div>
And this is how I define EntityAttributeProperties page:
@model EntityAttributeProperties
<div class="form-group">
@if (Model.IsHiddenField)
{
@Html.HiddenFor(x => x.Value)
}
else if (Model.IsFormField)
{
@Html.Label(Model.Name, new { @class = "col-sm-5 col-md-4 col-lg-3" })
<div class="col-sm-7 col-md-8 col-lg-9">
@switch (Model.Type)
{
--- Many cases
default:
@Html.DynamicTextBoxFor(m => m.Value, null, Model.IsReadonly, Model.ValidationRules)
break;
}
</div>
}
</div>
EntityAttributesProperties looks the following way:
public class EntityAttributeProperties
{
public string Name { get; set; }
public string DisplayName { get; set; }
public object Value { get; set; }
public EntityAttributeDataTypeEnum Type { get; set; }
public short Order { get; set; }
public bool IsFormField { get; set; }
public bool IsReadonly { get; set; }
public bool IsHiddenField { get; set; }
public Dictionary<string,object> ValidationRules { get; set; }
}
So, I am trying to make server-side validation for Model, but I am stuck, because I don't find any elegant solution to my problem, just solutions in which I have to do a lot of hardcoding (which I don't like).
Here is how I receive the form Submit:
public ActionResult SaveItem(DynamicItemViewModel model)
{
List<EntityAttributeExtendedView> entityAttributes = GetItemEntityAttributes();
DataObject dataObject = _dbContext.DataObjectCollection.FirstOrDefault(x => x.Id == model.Id);
if (dataObject != null)
{
JObject json = JObject.Parse(dataObject.Value);
dynamic dynJson = JsonConvert.DeserializeObject(dataObject.Value);
// Possibly loop through all entity attributes and separately make a custom validation ?
// Or somehow create custom Model State for validation ?
}
return View("Detail", model);
}
I will appreciate any suggestion on how to approach the problem with server side validation.
Thanks.
Upvotes: 0
Views: 801
Reputation: 5847
I'm adding another Answer because the Question changed heavily.
A good approach is to use the IValidatableObject
Interface. So you add this Interface to your EntityAttributeProperties
class and have to override the Method Validate
. for simple validation like required fields, you use so called validation Attributes.
Your EntityAttributeProperties
class would be decorated like this:
public class EntityAttributeProperties : IValidatableObject
{
public string Name { get; set; }
[Required]
public object Value { get; set; }
...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (... /* some condition, e.g. specific EntityAttributeDataTypeEnum */)
{
// Do some validation
// some other random test
if (.../* something not right... */)
{
results.Add(new ValidationResult("your input was not valid!"));
}
}
return results;
}
}
You might need to also make your DynamicItemViewModel
and IValidatableObject
and loop through the Items, but sometimes MVC is smart enough to validate sub-items automatically, so you might need this:
public class DynamicItemViewModel : IValidatableObject
{
...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return Items.SelectMany(x => x.Validate(validationContext));
}
}
OK now in your Controller, you basically need to check your ModelState
. The automatically generated ModelState
Property contains all errors.
Upvotes: 1
Reputation: 5847
FormCollection
is a very raw form of data and cannot be validated easily. I'd suggest you should refactor your view to be able to use ViewModels, else you will have a hard time working with the Data.
I cannot show you the full way but give you some hints:
Items
(of type EntityAttributeProperties
). Let's call it MainViewModel
.@Html.EditoFor(x => x.Items)
to generate the correct HTML. ASP.NET MVC will use the Editor Templates for EntityAttributeProperties
typeEntityAttributeProperties.cshtml
in your View folders EditorTemplates
sub-folderentityProp.Type
switches, but be careful with ID generation, always use @Html.IdFor(...)
etc. instead of generating own IDs to keep type safe with your View ModelMainViewModel
. If everything went good, the Item's will be filled, even if you used different Controls (hidden fields, text fields, drop-downs...) to populate the ValuesFrom my perspective, only this MVC-safe approach will lead to success in this case
Upvotes: 2