blazor dynamic forms add validation without model class - c#

im learning blazor and wanted to build some small dynamic form generator
i known that there is already something like VxFormGenerator but wanted to learn by myself a bit - at least for simple forms purposes.
so i have it like this:
DynamicFormsComponent:
<EditForm Model = "#Params" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator/>
#if(Params != null ) #foreach (var field in Params.FormFields)
{
<div class="mb-3">
<label for= "#field.Id">#field.Label :</label>
#switch (field.Type)
{
case FormFieldType.Text:
{
<InputText id="#field.Id" #bind-Value="#field.StrValue" placeholder="#field.PlaceHolder" class="form-control"></InputText>
break;
}
case FormFieldType.Number:
{
<InputNumber id="#field.Id" #bind-Value="#field.IntValue" placeholder="#field.PlaceHolder" class="form-control"> ></InputNumber>
break;
}
case FormFieldType.Date:
{
<InputDate id="#field.Id" #bind-Value="#field.DateValue" placeholder="#field.PlaceHolder" class="form-control"></InputDate>
break;
}
default:
{
break;
}
}
</div>
}
<ValidationSummary></ValidationSummary>
<button type="submit" class="btn btn-primary">#Params?.SendButtonText</button>
public partial class DynamicFormComponent:ComponentBase
{
[Parameter]
public DynamicFormParams Params { get; set; } = new DynamicFormParams();
[Parameter]
public EventCallback<DynamicFormParams> OnValidSubmitCallback { get; set; }
void OnValidSubmit()
{
Console.WriteLine("onValidSubmit");
if (OnValidSubmitCallback.HasDelegate ) OnValidSubmitCallback.InvokeAsync(Params);
//NavigationManager.navigateto.....
}
}
public class DynamicFormParams
{
public List<DynamicFormField> FormFields { get; set; } = new List<DynamicFormField>();
public string FormTitle { get; set; } = string.Empty;
public string SendButtonText { get; set; } = "Send";
}
public class DynamicFormField
{
public string? Label { get; set; }
public string Id { get; set; } = Guid.NewGuid().ToString();
public string PlaceHolder { get; set; } = string.Empty;
public FormFieldType? Type { get; set; }
public string? StrValue { get; set; }
public int? IntValue { get; set; }
public DateTime? DateValue { get; set; }
}
public enum FormFieldType
{
Text,
Number,
Date
}
so the usage would be
<DynamicFormComponent Params="#p" OnValidSubmitCallback=#onChildFormSubmit ></DynamicFormComponent>
DynamicFormParams p = new DynamicFormParams()
{
FormTitle = "test form Title",
SendButtonText = "Wyƛlij",
FormFields = new List<DynamicFormField>()
{
new DynamicFormField()
{
Label="testLabelStr",
Id="anyid-notGuId",
StrValue="a",
PlaceHolder="asdadsad",
Type=FormFieldType.Text
},
new DynamicFormField()
{
Label="testLabelInt",
Type=FormFieldType.Number,
PlaceHolder="enter nr"
},
new DynamicFormField()
{
Label="testLabelDate",
Type=FormFieldType.Date,
DateValue=DateTime.Parse("2021-04-01")
}
}
};
private void onChildFormSubmit(DynamicFormParams pp)
{
Console.WriteLine("from local variable");
Console.WriteLine(JsonSerializer.Serialize(p));
Console.WriteLine("from event arg");
Console.WriteLine(JsonSerializer.Serialize(pp));
}
and the question is:
how with this approach i can use form validation ?
i have no clasic'model' so probably would need something like add 'list of validators ' to my DynamicFormField class
and somehow force DataAnnotationsValidator to use this list ? is it possible withoud clasic 'model' and 'data annotations attributes' on it ?
thanks and regards

The only way to validate form without a model is to use the Blazorise validation system. https://blazorise.com/docs/components/validation.
PS. I'm a Blazorise creator.

Related

Dynamically binding input-text to class/object propertys using Blazor

Im trying to build a dynamic list of input field for properties inside a class using Blazor but cant figur out how to bind/link the content of a input box to a property of a class. (the class have can have a large number of public props, not only Name and Description as in the below example, they are not always of the type "string")
lets say that I have this class/model:
public class customer{
public string Name { get; set; }
public int Age { get; set; }
public string Description { get; set; }
}
I got this blazor component (updateC.razor):
#inherits CLogic
#if (nfo != null)
{
#foreach (var obj in nfo)
{
<input type="text" class="form-control"
bind=#SCustomer.GetType().GetProperty(obj.ToString())/>
}
}
and finally Clogic:
public class Clogic: ComponentBase{
[Parameter]
public Customer SCustomer { get; set; } = new Customer();
[Parameter]
public PropertyInfo[] nfo { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
nfo = SCustomer.GetType().GetProperties();
StateHasChanged();
}
}
}
This is suppose to bind changes made in each input field to the correct property in the current instance of SCustomer (when input is made it is suppose to update the correct property of the class/object). This is not working, values inside of SCustomer are not changed after input are done. I'm guessing that i'm going about this completely wrong but can't seem to figure out how to make this work and can't find any examples doing this.
#foreach (var propertyInfo in nfo)
{
<input type="text" class="form-control"
value="#propertyInfo.GetValue(SCustomer)"
#onchange="#((ChangeEventArgs __e) =>
propertyInfo.SetValue(SCustomer, __e.Value.ToString()))" />
}
#foreach(propertyInfo in nfo)
{
<label>#propertyInfo.Name</label>
<input type="text" value="#propertyInfo.GetValue(SCustomer)" #onchange="#((ChangeEventArgs __e) =>
propertyInfo.SetValue(SCustomer,Convert.ChangeType(__e.Value,
propertyInfo.PropertyType,null))"/>
}
I finally found a way of doing this, here is my solution:
I created a helper class:
public class PropHolder
{
[Parameter]
public PropertyInfo info { get; set; }
[Parameter]
public string type { get; set; }
public PropHolder(PropertyInfo nfo, string propType)
{
info = nfo;
type = propType;
}
}
then i created a dictionary of this class and some cheking functions (this is inside of Clogic)
[Parameter]
public Dictionary<int, PropHolder> Props{ get; set; }
public void GetAllProps()
{
Props = new Dictionary<int, PropHolder>();
//nfo = SCustomer.GetType().GetProperties();
int Cid = 0;
foreach (PropertyInfo pif in SCustomer.GetType().GetProperties())
{
Props[Cid] = new PropHolder(pif, pif.PropertyType.Name);
Cid++;
}
}
public string CheckName(PropHolder propertyInfo)
{
if (propertyInfo.GetType() == typeof(PropHolder))
{
return propertyInfo.type;
}
else
{
return propertyInfo.GetType().Name.ToString();
}
}
public PropertyInfo getInfo(PropHolder propertyInfo)
{
if (propertyInfo.GetType() == typeof(PropHolder))
{
return propertyInfo.info;
}
else
{
return null;
}
}
and finally im able to loop over the keys of my dictionary and bind all values correctly (got lots of help figuring this out from the answere given by: "agua from mars")
here is the updateC.razor content:
#if (Props != null)
{
#foreach (int key in Props.Keys)
{
var pinfo = Props[key];
#if (CheckName(pinfo) == "String")
{
<input type="text" class="form-control" value=#(getInfo(pinfo).GetValue(SCustomer)) #onchange="#((ChangeEventArgs __e) => getInfo(pinfo).SetValue(SCustomer, __e.Value.ToString()))" />
}
}
}
this gives me a input box for every prop that is of the type String, if additional types are to be handled it can now easily be added. This is probably not the best sulution but it do work. I will update the answere if any better working solutions are posted.
I solved this using generics. This lets each input type pass on a generic type which defines what type it wants to use.
<input #bind="Name.Value" />
<input #bind="Age.Value" />
<div><code>Name</code>: #Name.Value</div>
<div><code>Age</code>: #Age.Value</div>
#code {
private FormField<string> Name { get; set; } = new FormField<string>();
private FormField<int> Age { get; set; } = new FormField<int>();
public class Form
{
public ICollection<IFormField> Fields { get; set; }
}
public interface IFormField
{
public int ControlType { get; set; }
}
public class FormField<T> : IFormField
{
public int ControlType { get; set; }
public T Value { get; set; }
}
}
Here it is on BlazorFiddle: https://blazorfiddle.com/s/wen1g26q

Issue While Add View Model Object

My view model as
public class StudViewModel
{
public StudLineViewModel()
{
StudLine = new List<CreateReqLineViewModel>();
}
public StudheaderViewModel StudHeader { get; set; }
public List<StudLineViewModel> StudLine { get; set; }
}
public class StudheaderViewModel
{
public int Roll_no { get; set; }
public DateTime? birthday_date { get; set; }
}
public class StudLineViewModel
{
public int Sub_Id { get; set; }
public double marks { get; set; }
}
var MainVMObj = new StudViewModel();
var StudHeaderObj = new StudheaderViewModel ();
StudHeaderObj.Roll_no =1;
StudHeaderObj.birthday_date =null;
MainVMObj.StudHeader = StudHeaderObj ;
MainVMObj.StudLine =null;
return Partialview("StudInfo",MainVMObj );
Code in View
#Html.TextBoxFor(model => model.StudHeader.birthday_date ,new { #class = "InputText", onclick = "javascript:NewCssCal (this.Id,'ddMMMyyyy','arrow','','','','future')" })
expected Result
1. birth day assign null but it show as default date like 01-Jan-0001.
I was debug i found issue at
MainVMObj.StudHeader = StudHeaderObj -->assign object value it make null value to default value. so birth date from null to default(01-01-001)
Please provide the solution
Editors Give the Ans not edit the Post

How to print PartialView

i was looking for this and tried to fix for couple of days but with no sucess. What wrong im doing in this code? I want to print _Champions
and _SearchEngine as PartialView in Home/Index but it show error.
My HomeController
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult _SearchEngine()
{
SearchLeagueModel searchLeagueModel = new SearchLeagueModel();
searchLeagueModel.MainSelector = "champions";
return PartialView("_SearchEngine", searchLeagueModel);
}
public ActionResult _SearchEngine(SearchLeagueModel searchLeagueModel)
{
if (searchLeagueModel.MainSelector == "champions")
{
return RedirectToAction("_Champions", searchLeagueModel);
}
else return View();
}
[HttpPost]
public ActionResult _Champions(SearchLeagueModel searchLeagueModel)
{
string chooser = searchLeagueModel.MainSelector;
string selector;
if (searchLeagueModel.MainSelector != null)
{
selector = "filter[name]=" + searchLeagueModel.MainSelector;
}
else
{
selector = "";
}
WebRequest request = WebRequest.Create("https://api.pandascore.co/lol/" + chooser + "?" + selector + "&token=mytoken);
WebResponse response = request.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
string responseFromServer = reader.ReadToEnd();
List<ChampionsModel> champions = JsonConvert.DeserializeObject<List<ChampionsModel>>(responseFromServer);
return PartialView("_Champions", champions);
}
//other views
}
My IndexModelView
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace LeagueAPI.Models
{
public class IndexModelView
{
public IndexModelView()
{
ChampionsList = new ChampionsModel();
Searcher = new SearchLeagueModel();
}
public ChampionsModel ChampionsList { get; set; }
public SearchLeagueModel Searcher { get; set; }
}
public class ChampionsModel
{
public List<string> videogame_versions { get; set; }
public double spellblockperlevel { get; set; }
public double spellblock { get; set; }
public string name { get; set; }
public double mpregenperlevel { get; set; }
public double mpregen { get; set; }
public double mpperlevel { get; set; }
public double mp { get; set; }
public double movespeed { get; set; }
public string image_url { get; set; }
public int id { get; set; }
public double hpregenperlevel { get; set; }
public double hpregen { get; set; }
public double hpperlevel { get; set; }
public double hp { get; set; }
public double critperlevel { get; set; }
public double crit { get; set; }
public string big_image_url { get; set; }
public double attackspeedperlevel { get; set; }
public object attackspeedoffset { get; set; }
public double attackrange { get; set; }
public double attackdamageperlevel { get; set; }
public double attackdamage { get; set; }
public double armorperlevel { get; set; }
public double armor { get; set; }
}
}
My _Champions.cshtml PartialView(made as PartialView)
#using LeagueAPI.Models
#model System.Collections.Generic.List<ChampionsModel>
<fieldset>
<div class="d-flex p-3 w-auto h-auto">
#foreach (var element in #Model)
{
<div class="p-3 flex-lg-wrap">
<div class="card p-2" style="width: 15rem ;">
<img class="card-img-top" src="#Html.DisplayFor(model => element.big_image_url)">
<div class="card-body p-0 m-0">
<h5 class="card-title p-0 m-0 text-center">#Html.DisplayFor(model => element.name)</h5>
<p class="card-text p-0 m-0">DMG/LVL: #Html.DisplayFor(model => element.attackdamageperlevel)</p>
<p class="card-text p-0 m-0">ARMOR/LVL: #Html.DisplayFor(model => element.armorperlevel)</p>
<p class="card-text p-0 m-0">MOVEMENT SPEED: #Html.DisplayFor(model => element.movespeed)</p>
<p class="card-text p-0 m-0">ATTACK RANGE: #Html.DisplayFor(model => element.attackrange)</p>
<a href="/Home/Details"
class="btn btn-primary m-1 "
OnClick="GreetingBtn_Click">More details</a>
</div>
</div>
</div>
}
</div>
</fieldset>
Index View
#model LeagueAPI.Models.IndexModelView
#Html.Action("_SearchEngine", Model.Searcher)
#Html.Action("_Champions", Model.ChampionsList)
Everything else looks standard, in _Layout i have #RenderBody()
My second question is about _Champions Controller. Why when i put IndexModelView indexModelView = new IndexModelView() as parametr and bring necessary changes in code to this ActionResult, the MainSelector is null in that case.
Im still learning, if You can explain whats wrong here i will be thankful. cheers
Try this java Script:-
<script>
function printContent(el){
var restorepage = document.body.innerHTML;
var printcontent = document.getElementById(el).innerHTML;
document.body.innerHTML = printcontent;
window.print();
document.body.innerHTML = restorepage;
}
</script>
I would say instead of ActionResults rather use PartialViewResult on both actions _Champions and _SearchEngine
[HttpPost]
public PartialViewResult _Champions(SearchLeagueModel searchLeagueModel)
{
return PartialView("_Champions", champions);
}
And then from the index view Render those action like below:
#model LeagueAPI.Models.IndexModelView
<div>
#Html.RenderAction("_Champions");
#Html.RenderAction("_SearchEngine");
</div>

ViewModel is passing null values to controller on submit

My view :
#using (Html.BeginForm("", "", FormMethod.Post, new { id = "responseEntryForm" }))
{
<div id="SiteDeployment" class="tabcontent">
#Html.HiddenFor(m=>m.Sections,Model.Sections)
#for (int index = 0; index < Model.Sections.Count; index++)
{
<fieldset class="leftFieldset">
<div class="inputFieldDiv">
<label class="GroupHeadings"> #Model.Sections[index].Name</label><br />
#for (int subIndex = 0; subIndex < Model.Sections[index].SubSections.Count; subIndex++)
{
<div style="width:100%">
<div class="SubGroups">
#Model.Sections[index].SubSections[subIndex].Name
</div>
<div class="subEntries">
#for (int subsubIndex = 0; subsubIndex < Model.Sections[index].SubSections[subIndex].QuestionsList.Count; subsubIndex++)
{
<div class="subSections">
<label class="StrucQuestions"> #Model.Sections[index].SubSections[subIndex].QuestionsList[subsubIndex].Question</label>
#Html.DropDownListFor(m => m.Sections[index].SubSections[subIndex].QuestionsList[subsubIndex].Response, new SelectList(Model.ResponseList, "Value", "Text"), new { #class = "strucType", #id = "ddl_" + subIndex + Model.Sections[index].SubSections[subIndex].QuestionsList[subsubIndex].QuestionID })
</div>
}
</div>
</div>
}
</div>
</fieldset>
}
calling my controller and passing form data as below using ajax call:
function submitResponses() {
$.ajax({
url: '#Url.Action("SaveResponsesData", "Dashboard")',
datatype: 'json',
type: "POST",
data: $('#responseEntryForm').serialize(),
success: function (result) {
if (result == "T ") {
alert("Save is successful");
}
}
});
}
and my controller is like below:
[HttpPost]
public ActionResult SaveResponsesData(ResponseEntryViewModel objResponseEntryViewModel)
{
// ViewBag.SelectedType = TypeValue.ToUpper();
return View();
}
My ViewModel looks like below:
public class ResponseEntryViewModel
{
public int TypeID { get; set; }
public string TypeName { get; set; }
public string CompletedBy { get; set; }
public string CompletedOn { get; set; }
public int User_ID { get; set; }
public List<SectionDataModel> Sections = new List<SectionDataModel>();
public IEnumerable<SelectListItem> ResponseList { get; set; }
public class SectionDataModel
{
public int SectionID { get; set; }
public string Name { get; set; }
public string Status { get; set; }
public int TypeId { get; set; }
public List<SubSectionModel> SubSections = new List<SubSectionModel>();
}
public class SubSectionModel
{
public int SubSectionID { get; set; }
public string Name { get; set; }
public string Status { get; set; }
public int SectionId { get; set; }
public List<QuestionModel> QuestionsList = new List<QuestionModel>();
}
public class QuestionModel
{
public int SubSectionID { get; set; }
public int QuestionID { get; set; }
public string Question { get; set; }
public bool Response { get; set; }
}
}
In my view model ResponseEntryViewModel , i have a list of sections which has a list of sub sections which further has a list of questions and user is entering responses to those questions from my view.
When I click on submit. My viewModel is not having any values and Sections count is 0.
Any suggestions?
I'm not sure if this is the issue but it could be.
datatype
should be
dataType
Easiest would be just to remove dataType attribute completely. You are sending form-encoded data, not JSON there.
If you want to send JSON to server, you would have to use [FromBody] attribute:
[HttpPost]
public ActionResult SaveResponsesData([FromBody] ResponseEntryViewModel objResponseEntryViewModel)
{
// ...
return View();
}
However, you don't have data in JSON format. For JSON, normally you use JavaScript object and call JSON.stringify(data) function on it. JQuery's serialize() uses different format, for form-posting - Encode a set of form elements as a string for submission.
Example of this formatting would be: single=Single&multiple=Multiple&multiple=Multiple3&check=check2&radio=radio1
You might also have to set contentType: "application/x-www-form-urlencoded"
I think it would be a lot easier if you make a separete model for saving the response, something like, I hope you understand what I trying to show:
public class SaveResponseModel
{
public int SubSectionID { get; set; }
public int QuestionID { get; set; }
public bool Response { get; set; }
}
[HttpPost]
public ActionResult SaveResponsesData(List<SaveResponseModel> responses)
{
// ViewBag.SelectedType = TypeValue.ToUpper();
return View();
}
.cshtml:
for each Sections, SubSection, Question...{
<input name="responses.Index" type="hidden" value="#item.QuestionID" />
<input name="responses[#item.QuestionID].QuestionID" type="hidden" value="#item.QuestionID" />
<input name="responses[#item.QuestionID].SubSectionID" type="hidden" value="#item.SubSectionID" />
#Html.DropDownList((string)string.Format("responses[{0}].Response", #item.QuestionID), new SelectList(Model.ResponseList, "Value", "Text"), new { #class = "strucType", #id = "ddl_" + subIndex + Model.Sections[index].SubSections[subIndex].QuestionsList[subsubIndex].QuestionID })
}

list<classtype> model update in C# using linq

I have a model structure as illustrate below.
public class GuideLineSectionsViewModel
{
public GuideLineSectionsViewModel()
{
SectionsSet = new List<SectionViewModel>();
}
public string Title { get; set; }
public List<SectionViewModel> SectionsSet { get; set; }
}
public class SectionViewModel
{
public SectionViewModel()
{
SectionsSet = new List<SectionViewModel>();
QuestionsSet = new List<QuestionViewModel>();
ProblemsSet = new List<ProblemViewModel>();
GoalsSet = new List<GoalViewModel>();
BarriersSet = new List<BarriersViewModel>();
QuestionReferencesSet = new List<QuestionReferenceViewModel>();
}
public string Heading { get; set; }
public List<SectionViewModel> SectionsSet { get; set; }
public List<QuestionViewModel> QuestionsSet { get; set; }
public List<ProblemViewModel> ProblemsSet { get; set; }
public List<GoalViewModel> GoalsSet { get; set; }
public List<BarriersViewModel> BarriersSet { get; set; }
public List<QuestionReferenceViewModel> QuestionReferencesSet { get; set; }
}
public class ProblemViewModel
{
public string Text { get; set; }
public bool Identified { get; set; }
public List<GoalViewModel> GoalsSet { get; set; }
public List<QuestionReferenceViewModel> QuestionReferencesSet { get; set; }
}
Now Based on the condition I need to update the every list value of the ProblemViewModel using linq.Below is the condition
public GuideLineSectionsViewModel FindGuidelineType(GuideLineSectionsViewModel guidelineSectionModel)
{
//GuideLineSectionsViewModel result = new GuideLineSectionsViewModel();
string title = guidelineSectionModel.Title;
int count = Regex.Matches(title, "Low Intensity").Count;
if (count > 0)
{
}
return guidelineSectionModel;
}
The guidelineSectionModel.Title will contain the text as "some value : Low Intensity". So i used the regx to filter the text. Is there other way i can directly check the condition in linq. and update the model model.
I want to update list value of ProblemViewModelmodel property value public bool Identified to "true"
Currently it contain only False value.
Please can anyone help me to solve the issue.
Have a look at following method. I could not put LINQ but I think this answer can solve your purpose. Again Some classes structure are missing in your question so you may need to put that in following method.
GuideLineSectionsViewModel FindGuidelineType(GuideLineSectionsViewModel guidelineSectionModel)
{
//GuideLineSectionsViewModel result = new GuideLineSectionsViewModel();
string title = guidelineSectionModel.Title;
int count = Regex.Matches(title, "Low Intensity").Count;
if (count > 0)
{
foreach(SectionViewModel svm in guidelineSectionModel.SectionsSet)
{
foreach(ProblemViewModel pvm in svm.ProblemsSet)
{
pvm.Identified = true;
}
}
}
return guidelineSectionModel;
}
If you prefer LINQ:
if(guideLine.Title.Contains("Low Intensity"))
{
guideLine.SectionsSet.ForEach(s => s.ProblemsSet.ForEach(ps => ps.Identified = true));
}
Note: please read this answer https://stackoverflow.com/a/2962689/1525637 due to possible performance problems with the Regex.Matches, you should use String.Contains instead.

Categories