Enum based validation - c#

I have a model which should be validated.
public class VersandModalInput
{
public Courier Courier { get; set; } = Courier.UPS;
public List<PaketInput> Pakete { get; set; } = new List<PaketInput>();
public int AnzahlPakete { get; private set; } = 0;
public bool MailSenden { get; set; } = true;
public void AddPaket(decimal gewicht = 0)
{
Pakete.Add(new PaketInput { Paketnummer = ++AnzahlPakete, Gewicht = gewicht });
}
public void RemovePaket()
{
Pakete.RemoveAt(--AnzahlPakete);
}
public void RemovePaket()
{
Pakete.RemoveAt(--AnzahlPakete);
}
}
My Model as a List. Here is the class:
public class PaketInput
{
public int Paketnummer { get; set; }
[Required]
[Range(0.1, 31)]
public decimal Gewicht { get; set; }
public string Trackingnummer { get; set; } = ""; // This should be required is Courier of VersandModalInput is Manual
public string MbeTrackingnummer { get; set; } = "";
}
I want to have the VersandModalInput become invalid if the Courier of it is Manual and any of the trackingnumbers of from List pakete is empty. The packages are displayed in the frontend and the error should be visible right under the specific field. This is my UI for pakete:
<table class="table table-responsive-xs">
<thead>
<tr>
<th>Paket</th>
<th>Gewicht (Kg)</th>
#if (BelegVersandInput.Versandtyp == Versandtyp.Manuell)
{
<th>Trackingnummer</th>
}
</tr>
</thead>
<tbody>
#foreach (var paket in BelegVersandInput.Pakete)
{
<tr>
<td>#paket.Paketnummer</td>
<td>
<InputNumber #bind-Value="paket.Gewicht" class="form-control" />
<ValidationMessage For="() => paket.Gewicht" />
</td>
#if (BelegVersandInput.Versandtyp == Versandtyp.Manuell)
{
<td>
<InputText #bind-Value="paket.Trackingnummer" class="form-control" />
<ValidationMessage For="() => paket.Trackingnummer" />
</td>
}
</tr>
}
</tbody>
Is there any way to recrete this kind of validation with the classes specified above?

You can use FluentValidation:
install-package FluentValidation
Then try this:
public sealed class BelegVersandInput : Lagerregal.VersandModalInput
{
public BelegTyp BelegTyp { get; set; }
public int Belegnummer { get; set; }
public Versandtyp Versandtyp { get; set; }
public void Validate()
{
var validationResult = new BelegVersandInputValidator().Validate(this);
if (!validationResult.IsValid)
throw new Exception(string.Join(',', validationResult.Errors.Select(e => e.ErrorMessage)));
}
}
public class BelegVersandInputValidator : AbstractValidator<BelegVersandInput>
{
public BelegVersandInputValidator()
{
RuleFor(p=> p.Pakete).NotEmpty().When(p=> p.Versandtyp == Versandtyp.Manual)
}
}

Related

blazor dynamic forms add validation without model class

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.

Is there a way to rerun OnInitializedAsync when a variable is changed in Blazor WASM?

I understand OnInitializedAsync is running once before the component is loaded. However, I am passing in a variable to the API and I would like to have it rerender the UI once the variable is changed. I am just not sure what the best way to go about it is.
#page "/"
#inject HttpClient Http
<h1>Pokemon</h1>
<p></p>
#if (pokemons == null)
{
<p><em>Loading...</em></p>
}
else
{
#foreach (var n in pokemons.results)
{
<div class="card" style="width: 18rem;">
<img src=#n.url class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">#n.name</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
Go somewhere
</div>
</div>
}
#pokemons.count
<br />
#offset
<nav aria-label="Page navigation example">
<ul class="pagination">
#for (int b = 0; b < pokemons.count; b += 20)
{
int local_b = b;
<li class="page-item"><button class="page-link" #onclick="() => Paginate(local_b)">#b</button></li>
}
</ul>
</nav>
}
#code {
private PokemonList pokemons;
private PokemonDetail pokemonDetails;
private int limit = 20;
private int offset = 0;
public void Paginate(int value)
{
Console.WriteLine(value);
this.offset = value;
InvokeAsync(StateHasChanged);
}
protected override async Task OnInitializedAsync()
{
pokemons = await Http.GetFromJsonAsync<PokemonList>($"https://pokeapi.co/api/v2/pokemon/?offset={offset}&limit={limit}");
foreach (var p in pokemons.results)
{
pokemonDetails = await Http.GetFromJsonAsync<PokemonDetail>(p.url);
p.url = pokemonDetails.sprites.front_default;
}
}
public class PokemonList
{
public int count { get; set; }
public string next { get; set; }
public List<Pokemon> results { get; set; }
}
public class Pokemon
{
public string name { get; set; }
public string url { get; set; }
}
public class PokemonDetail
{
public PokemonSprites sprites { get; set; }
}
public class PokemonSprites
{
public string front_default { get; set; }
}
}
You can have a function for your API call and can call this function whenever you want
private async Task FetchPokemonList()
{
pokemons = await Http.GetFromJsonAsync<PokemonList>($"https://pokeapi.co/api/v2/pokemon/?offset={offset}&limit={limit}");
foreach (var p in pokemons.results)
{
pokemonDetails = await Http.GetFromJsonAsync<PokemonDetail>(p.url);
p.url = pokemonDetails.sprites.front_default;
}
}
And can call FetchPokemonList in OnInitializedAsync
protected override async Task OnInitializedAsync()
{
await FetchPokemonList();
}

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

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>

ASP.net MVC LINQ null reference exception [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 7 years ago.
I got null reference exception when i trying to view data from table for actually logged user - Klient_has_sklep and if that user isnt signed with Sklep table.
When i viewing data for user who is signed with Sklep table everything is ok.
I think problem is in Controller with viewModel
This is my controller
public ActionResult Index(int? id)
{
var viewModel = new Klient_has_SklepIndexData();
viewModel.Klients = db.Klients
.OrderBy(i => i.Nazwisko);
UserManager UM = new UserManager();
int idZalogowanego = UM.GetUserID(User.Identity.Name);
ViewBag.idzal = idZalogowanego;
viewModel.Klient_has_Skleps = viewModel.Klients.Where(i => i.SYSUserID == idZalogowanego).Single().Klient_has_Sklep;
return View(viewModel);
}
This is my view
#model Sklepy.Models.ViewModel.Klient_has_SklepIndexData
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>#ViewBag.idzal.</h2>
<h2>Twoje zniżki w sklepach</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>asd</th>
</tr>
#if (Model != null)
{
#foreach (var item in Model.Klient_has_Skleps)
{
<tr>
<td>#item.Znizka1</td>
</tr>
}
}
</table>
This is my Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sklepy.Models.DB;
namespace Sklepy.Models.ViewModel
{
public class Klient_has_SklepIndexData
{
public IEnumerable<Klient> Klients { get; set; }
public IEnumerable<Klient_has_Sklep> Klient_has_Skleps {get; set;}
}
}
Klient class code
public partial class Klient
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Klient()
{
this.Klient_has_Sklep = new HashSet<Klient_has_Sklep>();
}
public int KlientID { get; set; }
public int SYSUserID { get; set; }
public string Imię { get; set; }
public string Nazwisko { get; set; }
public string Adres { get; set; }
public string Telefon { get; set; }
public string Email { get; set; }
public virtual SYSUser SYSUser { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Klient_has_Sklep> Klient_has_Sklep { get; set; }
}
Create your ViewModel as shown below. I made modification to make default initialization with List.
public class Klient_has_SklepIndexData
{
public IList<Klient> Klients { get; set; }
public IList<Klient_has_Sklep> Klient_has_Skleps { get; set; }
public Klient_has_SklepIndexData()
{
Klients = new List<Klient>();
Klient_has_Skleps = new List<Klient_has_Sklep>();
}
}
And your Action Code.
public ActionResult Index(int? id)
{
var viewModel = new Klient_has_SklepIndexData();
viewModel.Klients = db.Klients
.OrderBy(i => i.Nazwisko).ToList();
UserManager UM = new UserManager();
int idZalogowanego = UM.GetUserID(User.Identity.Name);
ViewBag.idzal = idZalogowanego;
var skelp = viewModel.Klients.FirstOrDefault(i => i.SYSUserID == idZalogowanego);
if(skelp != null){
if(skelp.Klient_has_Sklep != null){
viewModel.Klient_has_Skleps = skelp.Klient_has_Sklep.ToList();
}
}
return View(viewModel);
}

Categories