MVC6: Custom model binder for an enum with the Flags attribute - asp.net-core-mvc

I have a Model that has a enum property with the Flags attribute.
[Flags]
public enum MyEnumType
{
None,
Standard = 1,
Extra = 2,
Special = 4
}
public class MyModel
{
public MyEnumType Value { get; set; }
}
In the html form I have this edited with checkboxes:
<input name="Value" type="checkbox" value="Standard"/>
<input name="Value" type="checkbox" value="Extra"/>
<input name="Value" type="checkbox" value="Special"/>
In the post body there can be multiple values for Value now. But how can I make a ModelBinder that automatically binds these to my flags-enum?

Related

What is the best way to get a configured value to a class library for validation?

I currently have a Blazor app that references a class library. One of the pages in the web app is used for updating an instance of a class model in the class library. To validate I'm using Validation attributes on the class model. One of the fields for input is email which, for our software, is validated via a configurable regular expression (because each of our sites can be different).
I think the best way to do this is using a custom ValidationAttribute but I don't know the best way to get a value from the web app's app settings to the custom Validation class.
The following code is an example of what I'm trying to accomplish:
Blazor page:
<EditForm Model="#name" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label for="FirstName">First:</label>
</div>
<div>
<input id="FirstName" class="form-control profile-form-control" #bind-value="name.FirstName" #bind-value:event="oninput" type="text" maxlength="30" autocomplete="off" />
</div>
<div>
<label for="LastName">Last:</label>
</div>
<div>
<input id="LastName" class="form-control profile-form-control" #bind-value="name.LastName" #bind-value:event="oninput" type="text" maxlength="100" autocomplete="off" />
</div>
<div>
<label for="Email">Email:</label>
</div>
<div>
<input id="Email" class="form-control profile-form-control" #bind-value="name.Email" #bind-value:event="oninput" type="text" maxlength="100" autocomplete="off" />
</div>
<div>
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
</EditForm>
Model (in separate class library):
public class Person
{
[Required(ErrorMessage = "First name required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name required")]
public string LastName { get; set; }
[Required(ErrorMessage = "Email required")]
[EmailFromRegexValidator(ErrorMessage = "Email not valid")]
public string Email { get; set; }
}
Custom Validation:
public class EmailFromRegexValidator : ValidationAttribute
{
private const string defaultEmailValidationRegex = "^[\\w-]+(\\.[\\w-]+)*#([a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*?\\.[a-zA-Z]{2,6}|(\\d{1,3}\\.){3}\\d{1,3})(:\\d{4})?$";
protected override ValidationResult IsValid(object value, ValidationContext context)
{
string emailRegexString = null;
var configurationBuilder = new ConfigurationBuilder();
var path = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
if (File.Exists(path))
{
configurationBuilder.AddJsonFile(path, false);
var root = configurationBuilder.Build();
emailRegexString = root.GetSection("AppConfiguration").GetSection("EmailRegex").Value;
}
emailRegexString = emailRegexString ?? defaultEmailValidationRegex;
Regex emailRegex = new Regex(emailRegexString);
if (value is string && emailRegex.IsMatch(value as string))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(FormatErrorMessage(context.DisplayName));
}
}
}
The above code works but building configuration from a file path within a class library does not feel optimal. So I was curious if anyone had any better ideas for how to get a configurable value to the EmailFromRegexValidator?
Thanks!
Welcome! You can use ValidationContext to access a service that provides your configuration value.
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var appConfig = (AppConfiguration) validationContext
.GetService(typeof(AppConfiguration));
string emailRegexString = appConfig.EmailRegex;
...
}
public class AppConfiguration
{
public string EmailRegex { get; set; }
}
You can bind AppConfiguration from the config file using to make it available in your DI container:
services.Configure<AppConfiguration>(configuration.GetSection("AppConfiguration"))

How to set radio button to be checked by default

I'm implementing asp.net core 3.1. I have three radio buttons in my razor view and with the following code, I want to send the selected radio button value to Index action in controller in order to show its related data. My problem is, I can't set one of those radio buttons to be checked by default.
#model CSD.ChartObjects
<form method="post">
#foreach (var year in Model.Years)
{
<input type="radio" asp-for="Year" value="#year" />#year<br />
}
<input type="submit" asp-action="Index" />
</form>
Here is my model object that is read in razor
public class ChartObjects
{
public List<ChartModel> Percent { get; set; }
public List<ChartModel> Time { get; set; }
public List<ChartModel> Avg { get; set; }
public List<ChartModel> Total { get; set; }
public string Year { get; set; }
public string[] Years = new[] { "1398", "1399", "1400" };
}
And here is the body of my HomeController:
[HttpGet]
public IActionResult Index()
{
return (BuildIndexModel("1399"));
}
[HttpPost]
public IActionResult Index([FromForm] string currentYear)
{
return (BuildIndexModel(currentYear));
}
public IActionResult BuildIndexModel(string currentYear)
{
...
}
I think this will work:
#foreach (var year in Model.Years)
{
var fi = (year == Model.Years[0]) ? true : false ;
<input type="radio" asp-for="Year" value="#year" checked="#fi" />#year<br />
}
My problem is, I can't set one of those radio buttons to be checked by default.
To set a default checked radio button, you can try following code snippet.
<form method="post">
#foreach (var year in Model.Years)
{
<input type="radio" asp-for="Year" value="#year" checked="#(year == Model.Years.FirstOrDefault() ? "checked" : null)"/>#year<br />
}
<input type="submit" asp-action="Index" />
</form>
Update:
my data by default is for 1399
You can pass default year through ViewData, like below.
In controller action
ViewData["defaultyear"] = "1399";
In view page
<input type="radio" asp-for="Year" value="#year" checked="#(year == ViewData["defaultyear"].ToString() ? "checked" : null)"/>#year<br />
I don't know how to use asp.net, but In JS, I just simply have to access the attributes of the HTML Input tag so you can then assign the attribute "checked" to true.
I guess is something like this:
HtmlElement Input1 = webBrowser1.Document.GetElementById("ID"); // consider adding an ID
Input1.Attributes.Add("checked", "true");
Check this two links:
How to: Set HTML Attributes for Controls in ASP.NET Web Pages
HtmlDocument.GetElementById(String) Method

mvc3 checkbox value after submit

I have a form with 2 fields a dropdownlist and a checkbox. I have everything working correctly but i can not for some reason obtain the value of a checkbox if it is checked this is my code..
[HttpPost]
public ActionResult view(string pick)
{
switch (pick)
{
case "Deny":
// capture checkbox value here
break;
case "Accept":
// capture checkbox value here
break;
}
return View();
}
This is my view
#using (Html.BeginForm("view", "grouprequest", FormMethod.Post, new {}))
{
#Html.DropDownList("pick", new SelectList(new List<Object>{
new{ Text ="Accept", Value= "Accept"},new{ Text ="Deny", Value= "Deny"}}, "Value", "Text"), new {})
<input type="submit" name="work" id="work" value="Update" style="font-size:16px" />
foreach (var item in Model)
{
<input type="checkbox" id="#item.grouprequestID" name="#item.grouprequestID" value="#item.grouprequestID" />
}
}
Basically the dropdownlist has 2 options which are Accept and Deny I can capture which one the user chooses via the SWITCH-case in the controller now how can I capture the value of the checkboxes? If you notice the Checkboxes have a variable to them named #groupRequestID so every checkbox has a different unique value like 1,2,3 etc.. any help would be greatly appreciated !!
The Model
public class grouprequest
{
[Key]
public int grouprequestID { get; set; }
public int? profileID { get; set; }
public int? registrationID { get; set; }
public DateTime expires { get; set; }
public int? Grouplink { get; set; }
}
Check boxes when posted to the server act a little strange.
If a box is checked the browser will send name=value as in
<input type="checkbox" name="name" value="value" />
But if the checkbox is not checked the server doesn't send anything.
<input type="checkbox" name="Check1" id="Checks1" value="Hello" checked="checked"/>
<input type="checkbox" name="Check1" id="Checks1" value="Hello1" />
<input type="checkbox" name="Check1" id="Checks1" value="Hello2" />
Will result in Check1 = Hello
What this means is if all your check boxes are related, naming them the same will populate the same attribute of your ActionMethod. If that attribute is an enumeration it will contain only the ones that are checked.
If you have this in your view:
<input type="checkbox" name="MyValues" value="1" checked="checked"/>
<input type="checkbox" name="MyValues" value="2" />
<input type="checkbox" name="MyValues" value="3" />
and this as your controller action method:
public ActionMethod MyAction(IEumerable<int> myValues)
The myValues variable will look like this:
myValues[0] == 1
You should also note that if you are using the Html helper extension:
#Html.CheckBoxFor(m => m.MyValue)
Where MyValue is a bool the extension will create a checkbox input tag and also a hidden input tag with the same name, meaning a value will always be passed into the controller method.
Hope this helps.

KeyPairValue in ASP.NET MVC3 Razor

I have a weird problem.
I'm making dynamic form in Razor. I'm using dictionary to store dynamically added inputs.
I generate code like that:
<input type="hidden" value="96" name="Inputs[0].Key">
<input type="text" name="Inputs[0].Value">
I receive in my controller this dictionary. It always has as many elements that I added, but all of them are empty.
This is part of my model:
public class MetriceModelTaskSchedule
{
public IEnumerable<KeyValuePair<long, string>> Inputs { get; set; }
}
What can be wrong here?
What can be wrong here?
The fact that the KeyValuePair<TKey, TValue> class has the Key and Value properties which are readonly. They do not have a setter meaning that the model binder simply cannot set their value.
So as always start by defining a view model:
public class InputViewModel
{
public long Key { get; set; }
public string Value { get; set; }
}
and then:
public class MetriceModelTaskSchedule
{
public IEnumerable<InputViewModel> Inputs { get; set; }
}
Alternatively you could use a Dictionary:
public class MetriceModelTaskSchedule
{
public IDictionary<long, string> Inputs { get; set; }
}
Also make sure that you have respected the standard naming convention for your input fields in the view so that the model binder can successfully bind them to your model:
<div>
<input type="text" name="Inputs[0].Key" value="1" />
<input type="text" name="Inputs[0].Value" value="value 1" />
</div>
<div>
    <input type="text" name="Inputs[1].Key" value="2" />
    <input type="text" name="Inputs[1].Value" value="value 2" />
</div>
...

MVC3: Portions of Model Not Reconstituted on Postback

Portions of my models are not being correctly reconstructed on postback.
Models
public class DemographicsModel
{
public List<QuestionModel> Questions { get; set; }
}
public abstract class QuestionModel
{
[HiddenInput(DisplayValue = false)]
public int ID { get; set; }
[HiddenInput(DisplayValue = false)]
public string Title { get; set; }
}
public abstract class ChooseQuestionModel : QuestionModel
{
public abstract List<SelectListItem> Items { get; set; }
}
public class ChooseManyQuestionModel : ChooseQuestionModel
{
[Required]
[DataType("CheckBoxList")]
public override List<SelectListItem> Items { get; set; }
}
Views
ChooseManyQuestionModel.cshtml
#model X.Y.Z.ChooseManyQuestionModel
<div class="Form Wide NoLabel">
<div class="Title">#this.Model.Title</div>
#Html.TypeStamp()
#Html.EditorFor(m => m.ID)
#Html.EditorFor(m => m.Title)
#Html.EditorFor(m => m.Items)
</div>
CheckBoxList.cshtml
#model IEnumerable<SelectListItem>
#if (!this.Model.IsNullOrEmpty())
{
foreach (var item in this.Model)
{
<div>
#Html.HiddenFor(m => item.Value)
#Html.HiddenFor(m => item.Text)
#Html.CheckBoxFor(m => item.Selected)
#Html.LabelFor(m => item.Selected, item.Text)
</div>
}
}
I believe the issue lies within CheckBoxList.cshtml since these items are not being re-constituted on postback.
HTML Output
<div class="Form Wide NoLabel">
<div class="Title">Question title displays here?</div>
<input id="Questions_1___xTypeStampx_" name="Questions[1]._xTypeStampx_" type="hidden" value="Hrxh2HjDRorBAZWo18hsC0OvbJwyswpDkfTBfNF2NC8=" />
<input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="Questions_1__ID" name="Questions[1].ID" type="hidden" value="76" />
<input id="Questions_1__Title" name="Questions[1].Title" type="hidden" value="Question title displays here?" />
<div>
<input id="Questions_1__Items_item_Value" name="Questions[1].Items.item.Value" type="hidden" value="148" />
<input id="Questions_1__Items_item_Text" name="Questions[1].Items.item.Text" type="hidden" value="Organization Type 1" />
<input data-val="true" data-val-required="The Selected field is required." id="Questions_1__Items_item_Selected" name="Questions[1].Items.item.Selected" type="checkbox" value="true" /><input name="Questions[1].Items.item.Selected" type="hidden" value="false" />
<label for="Questions_1__Items_item_Selected">Organization Type 1</label>
</div>
</div>
</div>
Controller
public class AccountController : BaseController
{
public ActionResult Demographics()
{
return this.View(new DemographicsModel());
}
[HttpPost]
public ActionResult Demographics(DemographicsModel model)
{
return this.View(model);
}
}
On postback, the DemographicsModel is populated with the correct types (I'm using MvcContrib to handle abstract type binding). The List<Question> is populated with all of the correct data including the ID and Title of each question from the hidden fields. However, List<SelectListItem> within each question is set to null.
Update 1
The issue is definitely occurring because the fields are not named correctly. For instance, the "item" field names are being generated like this:
Questions_1__Items_item_Value
When they should really look like this (addition of item index and removal of erroneous "item"):
Questions_1__Items_1__Value
Similarly, the field IDs are being generated like this (addition of item index and removal of erroneous "item"):
Questions[1].Items.item.Value
Instead of:
Questions[1].Items[0].Value
Using Fiddler with the correct IDs being posted back, the model is constructed correctly with all radio buttons and checkboxes in place.
Try the following.
In ChooseManyQuestionModel.cshtml, change #Html.EditorFor(m => m.Items) to:
#Html.EditorForModel(m => m.Items)
Then, in CheckBoxList.cshtml, change #model IEnumerable<SelectListItem> to:
#model SelectListItem
Finally, in each item, modify each lambda expression, and change item to m, then remove the foreeach loop. This will allow the Editor to iterate through the collection, and should give you correct id generation for each element.
When foreach loop is used the ids generated in HTML are all same.
When for look is used the ids generated with the for loops index so binding is happening correctly and all the data is available after post back.
In this scenario, it seems the Helper class is not doing what you want it to do. I would suggest writing your own helper class to name your inputs exactly as you require them to be.

Resources