Use build in validation to validate dynamic created View - asp.net-mvc-4

I want to make a task system where the content data is able to expand dynamically (With different kinds of inputs (Text, Images, Dropdowns etc.). My plan is to create the following model, and with the help from some custom HtmlHelpers generate my view.
Model:
public class Task
{
public int TaskId { get; set; }
public string Name { get; set; }
public virtual List<TaskData> TaskData { get; set; }
}
public enum TaskDataType
{
TextType1, TextType2, E-mail, Image
}
public class TaskData
{
public int TaskDataId { get; set; }
public int TaskId { get; set; }
public TaskDataType Type { get; set; }
public string Name { get; set; }
[Required]
public string Data { get; set; }
public bool Required { get; set; }
public virtual Task Task { get; set; }
}
View:
Validation for the Task, works out of the box with the default HtmlHelpers, but when i try to make something like:
#if (Model.TaskData != null)
{
foreach (var taskData in Model.TaskData)
{
#Html.DisplayTextFor(td => taskData.Name)
#Html.TextBoxFor(td => taskData.Data)
#Html.ValidationMessageFor(td => taskData.Data)
}
}
In my View, the validation starts to go wrong, client site validation only validates the first input in the forech loop, and the ModelState in the controller does not contain any data regarding the TaskData fields.
So I have 2 quetions:
Is this the right path, or do I need to think of this in a different way?
If this is valid method of making what i want, is there a way to make the build in validation work?

You have to use for loop in order to post field values as well as get the in build validation work for you.
#if (Model.TaskData != null)
{
for(int i=0; i< Model.TaskData.Count; i++)
{
#Html.DisplayTextFor(model => model.TaskData[i].Name)
#Html.TextBoxFor(model => model.TaskData[i].Data)
#Html.ValidationMessageFor(model => model.TaskData[i].Data)
}
}

Related

Model Binding in Web API for .NET Core Type Mismatch

I have the following controller which is supposed to create a new object in the database:
[HttpPost]
public ActionResult<Panels> CreateNewPanel(Panels panel)
{
_context.Panels.Add(panel);
_context.SaveChanges();
return CreatedAtAction(nameof(GetPanelById), new { id = panel.ID }, panel);
}
It is receiving some JSON data, example:
{
"desc": "test5",
"frame": 2,
"aC240v": false
}
Which maps to the following model:
public class Panels
{
public int ID { get; set; }
public string Desc { get; set; }
public PanelFrames Frame { get; set; }
public bool AC240v { get; set; }
}
It works for the most part if "frame" isn't set, but if it is set to an integer like the code above it fails because it is type PanelFrames not an integer.
PanelFrames is another model that has a one to many relationship with Panels, each Panel can have only one PanelFrame so in the database this is recorded as simply an integer, the PanelFrames ID.
How do I reconcile this so that the integer (which is the PanelFrame ID) get's passed through the API and recorded in the database. The MS documentation doesn't seem to cover this, though it seems like it would be a pretty common occurrence, so I must not be understanding something, or doing something very wrong.
If you use EF Core one-to-many relationships and save the principle entity(PanelFrames) id,you just need to add a foreign key for your navigation property in your Panel model.Refer to my below demo:
1.Models
public class Panels
{
[Key]
public int ID { get; set; }
public string Desc { get; set; }
public int FrameID { get; set; }
[ForeignKey("FrameID")]
public PanelFrames Frame { get; set; }
public bool AC240v { get; set; }
}
public class PanelFrames
{
[Key]
public int PanelFramesID { get; set; }
public string Name { get; set; }
public List<Panels> Panels { get; set; }
}
2.In my case, I pass json data using postman, so I need to use [FromBody] on action parameters.
json:
{
"desc": "test5",
"frameid": 2,
"aC240v": false
}
Action:
[HttpPost]
public ActionResult<Panels> CreateNewPanel([FromBody]Panels panel)
Then a new Panel with FrameId would be added into database.
3.If you need to get panels with their Frame, just use Include method in action like
using Microsoft.EntityFrameworkCore;//Add necessary namespaces before
//...
var panels= _context.Panels
.Include(p => p.Frame)
.ToList();

Handling queries over related documents in RavenDB

I have a project where I have a set of forms:
public class Form
{
public string Id { get; set; }
public string Name { get; set; }
public IList<string> FieldValueIds { get; set; }
public string UserId { get; set; } // the user who completed the form.
public string FormTemplateId { get; set; }
}
Which each "implement" a form template selected at creation of the form.
public class FormTemplate
{
public string Id { get; set; }
public string Name { get; set; }
public IList<string> FieldIds { get; set; }
}
Which defines which fields are present within the form. Each field
public class FormField
{
public string Id { get; set; }
public string Name { get; set; }
public string Caption { get; set; }
public ValueType DataType { get; set; } // Enum specifying the type of data this field accepts.
}
Stores information about the field such as a description and what type it is expecting. Each FormField can be present in multiple FormTemplates with the values for the form being stored as FieldValue objects related to the Form itself.
public class FieldValue
{
public string Id { get; set; }
public string FieldId { get; set; }
public string ValueAsJsonString { get; set; }
}
Other objects include the User Object:
public class User
{
public string Id { get; set; }
public string Username { get; set; }
public string GivenNames { get; set; }
public string Surname { get; set; }
}
I would like to be able to perform a query to find all Forms completed by a user with a specified name, or all Forms where a field with name X has value Y and so forth.
I have looked into usage of indexes as specified in the documentation Indexing related documents, however the implementation as presented in the documentation threw a NotSupportedException when I implemented the example as follows:
class FormTemplates_ByFieldAndName : AbstractIndexCreationTask<FormTemplate>
{
public class Result
{
public string Name { get; set; }
public IList<string> FieldNames { get; set; }
}
public FormTemplates_ByFieldAndName()
{
Map = FormTemplates => from FormTemplate in FormTemplates
select new
{
Name = FormTemplate.Name,
FieldNames = FormTemplate.FieldIds.Select(x => LoadDocument<FormField>(x).Name)
};
}
}
// in code:
IList<FormTemplate> TestResults = session.Query<FormTemplates_ByFieldAndName.Result, FormTemplates_ByFieldAndName>()
.Where(x => x.Name == "TemplateName" || x.FieldNames.Contains("FieldName"))
.OfType<FormTemplate>()
.ToList();
As best as I can tell this was implemented correctly, however I have seen a suggestion to replace the .Contains with a .Any implementation instead. In lieu of this I have been experimenting with a different approach by applying successive .Where arguments. Like so:
var pre = session.Query<FormTemplates_ByFieldAndName.Result, FormTemplates_ByFieldAndName>();
var pr2 = pre.Where(x => x.Name == "TypeTest25");
List<FormTemplate> TestResults = pr2
.Where(x => x.FieldNames.Any(a => a == "field25"))
.OfType<FormTemplate>()
.OrderByScoreDescending()
.ToList();
Modifying the system to perform in a more factory oriented approach by applying successive filters based on a supplied string in a pre-specified format.
Is this the way I should be going for this implementation and if not what should I be changing? In particular if I am to proceed with the Indexing option how would I apply this technique to the nested relationship between Forms and FormFields through FormTemplates.
You seems to be trying to do this in a way that is mostly relational, but you don't have to.
Instead of trying to have a set of independent documents that each has part of the data, just store it all in a single document.
public class Form
{
public string Id { get; set; }
public string Name { get; set; }
public IList<FieldValue> FieldValues { get; set; }
public string UserId { get; set; } // the user who completed the form.
public string FormTemplateId { get; set; }
}
public class FieldValue
{
public string Id { get; set; }
// can store the value directly!
//public string ValueAsJsonString { get; set; }
public object Value {get; set; }
}
This will generate documents that looks like this:
{
"Id": "forms/1234",
"Name": "Tom",
"FieldValues": [
{
"Id": "FromValues/SchoolDistrictName",
"Value": "ABi195"
}
],
"UserId": "users/tom",
"FormTemplateId": "FromTemplate/1234"
}
Which is a much more natural way to model things.
At that point, you can use RavenDB's ability to index dynamic data, see the docs here:
https://ravendb.net/docs/article-page/3.5/Csharp/indexes/using-dynamic-fields

Looping inside razor

I have field name "GroupTitle" assigned to each Control. I want to loop through each element of Control assigned to particular group.
public class Groups
{
public virtual int Id { get; set; }
public virtual string GroupTitle { get; set; }
}
public class Controls
{
public int Id { get; set; } //Id
public string Name { get; set; } //name of control/element
public string ControlType { get; set; } // checkbox, radio button, textbox, time, date
public string Caption { get; set; } //caption/title/label
public string Content { get; set; } //in case of checkbox
public bool Mandatory { get; set; } //is mandatory to select or enter its value.
public string GroupTitle { get; set; } // there will be title at the top of controls if grouped together
//public List<SelectListItem> SelectOptions { get; set; } //select/dropdown options e.g. Pakistan, Uk for country dropdown
}
Below is my code . I am not sure how to access Model variable inside nested loop. This gives me error. Also it gives me error that Where clause does not exists.
#foreach (var groups in Model.Groups)
{
foreach (var row in Model.Controls.Where("GroupTitle ==", #groups.GroupTitle;))
{
}
}
Prove this:
#foreach (var groups in Model.Groups)
{
foreach (var row in Model.Controls.ToList().Where(x => x.GroupTitle == groups.GroupTitle))
{
}
}
I think this answers probably also applies to your case.

EditorForModel not working with ICollection MVC 4

We have the below model:
public class Sample
{
public int SampleId { get; set; }
public int ToTestId { get; set; }
public int Name { get; set; }
public virtual ICollection<SampleCondition> Child_SampleConditions { get; set; }
}
I am rendering them in my view as below:
#model Sample
#{
string cntlrName = ViewContext.RouteData.GetRequiredString("controller");
List<SampleCondition> sampleConditions = Model.Child_SampleConditions.ToList();
if (d2l.NullOrEmpty(sampleConditions))
{
Model.Child_SampleConditions .Add(new SampleCondition());
}
List<SampleCondition> sampleConditions = Model.Child_SampleConditions as List<SampleCondition>;
}
#Html.ValidationSummary(true)
#Html.d2_HiddenFor(O => O.SampleId)
<div class="#BOOTSTRAP.ROW">
#Html.ManyToOneFieldFor(Model, O => O.ToTestId, allowed: CrudFlag.Editable)
#Html.FieldFor(O => O.Name, allowed: CrudFlag.Editable)
#Html.FieldFor(O => sampleConditions[0].ConditionValue, allowed:CrudFlag.Editable)
</div>
Please look at the third filed in div, sampleConditions[0].ConditionValue. The html input field for this control is rendering with the name "[0].ConditionValue" instead of "Child_SampleConditions[0].ConditionValue". Thats why when I post this form, the Child_SampleConditions are not automatically binded to the model object into post method.
can somebody advise how to render ICollection input field names correctly so that they can automatically gets model binded.
NOTE: Please exclude the custom model binder options.
EDIT: I am providing some more information here.
In the ManyToOneFieldFor mehtod, the name retrieved using the below helper:
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
It just returns, [0].ConditionValue but not Child_SampleConditions[0].ConditionValue.
Please advise..
Thanks.
Instead of :
O => sampleConditions[0].ConditionValue
write:
O => O.Child_SampleConditions[0].ConditionValue
EDIT:
Create a ViewModel with IList as ICollection cannot be indexed:
public class SampleVM
{
public int SampleId { get; set; }
public int ToTestId { get; set; }
public int Name { get; set; }
public virtual IList<SampleCondition> Child_SampleConditions { get; set; }
}
Reference link:Cannot apply indexing with [] to an expression of type ICollection

Posting DropDownList value from View to Controller in MVC4

I have a MVC4 application and although I have get parameters for my DropDownList from the database, I encounter some kind of problems while posting the DropDownList value to the database. There is lots of samples for different approach, but I would like to apply a method without using an extra approach i.e. Ajax, Javascript, etc. On the other hand, I have run into "FormCollection" to pass data, but I am not sure if FormCollection is the best way in this scene. Here are some part of the view, controller and model I use:
View:
#using (Html.BeginForm("Add", "Product", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
<p>Product Type : #Html.DropDownListFor(m => m.SelectedLookupId, new SelectList(Model.Lookups.Where(x => x.LookupType == "Product Type"), "LookupID", "LookupValue"), "--- Select ---") </p>
Controller:
[HttpPost]
public ActionResult Add(Product product)
{
if (ModelState.IsValid)
{
product.ProductType = // ??? Cannot get the SelectedLookupId
...
repository.SaveProduct (product);
TempData["message"] = string.Format("{0} has been saved", product.Name);
return View("Completed");
}
else
{
//there is something wrong with the data values
return View(product);
}
}
ViewModel:
public class ProductViewModel
{
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Lookup> Lookups { get; set; } //Lookup for Product Types
public int SelectedLookupId { get; set; }
public Product Product { get; set; }
}
Thanks in advance for your helps.
Your action method should be receiving the view model, not the Product itself, like so:
[HttpPost]
public ActionResult Add(ProductViewModel productViewModel)
Unless I'm confused. But I assume the view snippet you posted above is from the Add view and that view's model is of type ProductViewModel. In your action method you are returning the Add view when the model state is invalid however you are passing a Product to that view. Again I may be confused because this should give you a runtime error that the types don't match.
Thanks for reply. Actually by using ViewModel rather than View, I have managed to solve the problem. On the other hand, after some research, I have applied another effective method in order to populate Dropdownlist without needing ViewModel. Furthermore with this example, I could use multiple foreign keys on the same Lookup table as shown below. Here is an an Applicant entity having 3 foreign keys and Lookup entity related to these keys. What I wanted to achieve with this example is exactly to use a Lookup table for only several Dropdownlist parameters i.e. Gender, Yes/No, Status,... due to no needing to create a table for the several parameters (these parameters are distinguished LookupType property on Lookup table). Here is the full example (I have shorted unrelated properties for brevity) below:
Applicant Entity:
public class Applicant
{
[Key]
public int ApplicantID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
// for using "Multiple foreign keys within same table using Fluent API"
public int? HasDoneAnyProject { get; set; }
public int? IsInterestedAnyProgramme { get; set; }
public int? InterestedProgrammeId { get; set; }
public virtual Lookup PrimaryLookup { get; set; }
public virtual Lookup SecondaryLookup { get; set; }
public virtual Lookup TertiaryLookup { get; set; }
}
Lookup Entity:
public class Lookup
{
[Key]
public int LookupID { get; set; }
public string LookupType { get; set; }
public string LookupValue { get; set; }
// for using "Multiple foreign keys within same table using Fluent API"
public virtual ICollection<Applicant> PrimaryLookupFor { get; set; }
public virtual ICollection<Applicant> SecondaryLookupFor { get; set; }
public virtual ICollection<Applicant> TertiaryLookupFor { get; set; }
}
DbContext:
public class EFDbContext : DbContext
{
public DbSet<Applicant> Applicants { get; set; }
public DbSet<Lookup> Lookups { get; set; }
//for using "Multiple foreign keys within same table using Fluent API"
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.PrimaryLookup)
.WithMany(a => a.PrimaryLookupFor)
.HasForeignKey(b => b.HasDoneAnyProject)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.SecondaryLookup)
.WithMany(a => a.SecondaryLookupFor)
.HasForeignKey(b => b.IsInterestedAnyProgramme)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.TertiaryLookup)
.WithMany(a => a.TertiaryLookupFor)
.HasForeignKey(b => b.InterestedProgrammeId)
.WillCascadeOnDelete(false);
}
}
Controller:
private void PopulateLookupsDropDownList(string lookupType, string foreignKey, object selectedLookups = null)
{
var lookupsQuery = repository.Lookups
.Select(x => x)
.Where(x => x.LookupType == lookupType)
.OrderBy(x => x.ParentLookupID).ToList();
ViewData[foreignKey] = new SelectList(lookupsQuery, "LookupID", "LookupValue", selectedLookups);
}
and for calling the Method for each of three Dropdownlist:
PopulateLookupsDropDownList("YesNo", "HasDoneAnyProject", applicant.HasDoneAnyProject);
PopulateLookupsDropDownList("YesNo", "IsInterestedAnyProgramme", applicant.IsInterestedAnyProgramme);
PopulateLookupsDropDownList("Programme", "InterestedProgrammeId", applicant.InterestedProgrammeId);
View: : Populating each of three Dropdownlist from the same Lookup table with different LookupType parameter:
<label>Has done any project before?</label>
#Html.DropDownList("HasDoneAnyProject", "---- Select ----")
<label>Are you interested in any programme?</label>
#Html.DropDownList("IsInterestedAnyProgramme", "---- Select ----")
<label>Interested programme name?</label>
#Html.DropDownList("InterestedProgrammeId", "---- Select ----")
I hope this approach will be useful for those who want to populate Dropdownlists from the same Lookup table. On the other hand, it is not only suitable for this, also can be used for populating Dropdownlists from different tables.
Regards.