MVC Razor check radio input to enable - c#

I am trying to have this input radio button checked base on the model but I am getting string error. Is this the correct way to set the checked enable?
Error
'string' does not contain a definition for 'Regimens'
debugger
View
<label for="RegimenReferencesC_#Model">
<input type="radio" id="RegimenReferencesC_#Model" checked="#(Model.Regimens == (int)RegimenReferences.D ? "true" : "false")" name="RegimenReferences" value="#((int)RegimenReferences.C)" class="regimen-reference">
(c) #RegimenReferences.C.ToDescriptionString()
</label>
Tried it this way too
<label for="RegimenReferencesD_#Model">
<input type="radio" id="RegimenReferencesD_#Model" name="RegimenReferences" checked="#(Model.Regimens == (int)RegimenReferences.D ? true : false)" value="#((int)RegimenReferences.D)" class="regimen-reference">
(d) #RegimenReferences.D.ToDescriptionString()
</label>
Model
public class ReferencesModel
{
public long id { get; set; }
public string Link { get; set; }
public string Text { get; set; }
public string Type { get; set; }
public int Regimens { get; set; }
public Guid? GuidelineId { get; set; }
public int SortOrder { get; set; }
}

It seems clear from <label for="RegimenReferencesC_#Model"> that #Model in the Razor view is a string, or that line would not work.
Therefore, when you try #(Model.Regimens ..., you get the error, since a string does not have a property or method named Regimens.
In other words, check your Model in the view. It is probably a string and not the object you are looking for.

You are missing some relevant code in your post so I am going to fill in the blanks with assumptions:
Given the following Class and Enum:
public class ReferencesModel
{
public long id { get; set; }
public string Link { get; set; }
public string Text { get; set; }
public string Type { get; set; }
public int Regimens { get; set; }
public Guid? GuidelineId { get; set; }
public int SortOrder { get; set; }
}
public enum RegimenReferences
{
[Description("This is A")]
A = 0,
[Description("This is B")]
B = 1,
[Description("This is C")]
C = 2,
[Description("This is D")]
D = 3
}
and the following Helper method for displaying enum annotation text:
public static string ToDescriptionString<T>(this T e) where T : IConvertible
{
if (e is Enum)
{
Type type = e.GetType();
Array values = System.Enum.GetValues(type);
foreach (int val in values)
{
if (val == e.ToInt32(CultureInfo.InvariantCulture))
{
var memInfo = type.GetMember(type.GetEnumName(val));
var descriptionAttribute = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() as DescriptionAttribute;
if (descriptionAttribute != null)
{
return descriptionAttribute.Description;
}
}
}
}
return null;
}
Your Razor code would be as follows to use a radio button and base checked on the value:
<label for="RegimenReferencesD_#Model">
<input type="radio" id="RegimenReferencesC_#Model" #if (Model.Regimens == (int)RegimenReferences.D) { Html.Raw("checked"); } name="RegimenReferences" value="#((int)RegimenReferences.D)" class="regimen-reference">
(D) #RegimenReferences.D.ToDescriptionString()
</label>

Related

Looping over QuickGrid-columns instead of hardcoding with Blazor

I have been using the QuickGrid build by the Blazor team (https://aspnet.github.io/quickgridsamples/). It is working, but I have many columns, so I am interested in figuring out whether I can loop over the <PropertyColumn>-components instead of hardcoding them.
My Component.razor component:
<div class="scrollable" >
<QuickGrid Items="MethodThatGetsIQueryableElements()">
#foreach (var ColumnDTO in ColumnDTOs) {
if (ColumnDTO.Show)
{
if (ColumnDTO.Name == ComponentEnums.Department)
{
<PropertyColumn Property="#(v => v.Department)" Sortable="true" />
}
if (ColumnDTO.Name == ComponentEnums.Name)
{
<PropertyColumn Property="#(v => v.Name)" Sortable="true" />
}
[... Numerous other columns in same format...]
}
}
</QuickGrid>
</div>
The initial conditional statement is connected with a checkbox, so basically checking/unchecking will show/hide the given column. My main difficulty is with the lambda expression for the Property-parameter. I can't seem to get it working with a foreach-loop.
The Items I am trying to loop through is a IQueryable<RowDTO> with the RowDTO just holding information on each row of data. The ColumnDTO holds the name of the column based on an enum-class (ComponentEnums) and the show/hide-boolean. Samples of the classes:
RowDTO.cs:
public class RowDTO
{
public RowDTO(string department, string name, [....] )
{
Department = department;
Name = name;
[....]
}
public string Department { get; set; }
public string Name { get; set; }
[....]
}
ColumnDTO.cs:
public class ColumnDTO
{
public ColumnDTO(ComponentEnums name, bool show)
{
Name = name;
Show = show;
}
public ComponentEnums Name { get; set; }
public bool Show { get; set; } = true;
}

angular kendo grid custom filter not working on objects that has collection property

I want to have a filter on a column that is a collection of objects.
I use kendo-grid to show Product objects:
public class Product
{
public string Id{ get; set; }
public string Title{ get; set; }
public ObservableCollection<Tag> Tags { get; set; }
}
public class Tag
{
public string Id{ get; set; }
public string Title{ get; set; }
}
and I want to filter grid by tags, so I wrote custom filter for kendo grid in typescript
private tagFilter: any[] = [];
public tagChange(values: any[], filterService: FilterService): void {
filterService.filter({
filters: values.map(value => ({
field: "tags.id",
operator: "eq",
value
})),
logic: "or"
});}
public tagFilters(filter: CompositeFilterDescriptor): FilterDescriptor[] {
this.tagFilter.splice(
0, this.tagFilter.length,
...flatten(filter).map(({ value }) => value)
);
return this.tagFilter; }
const flatten = filter => { const filters = (filter || {}).filters; if (filters) {
return filters.reduce((acc, curr) => acc.concat(curr.filters ? flatten(curr) : [curr]), []); } return [];};
and HTML Code is
<kendo-grid-column field="tags.id" title="Tag" width="150">
<ng-template kendoGridFilterMenuTemplate let-column="column" let-filter="filter" let-filterService="filterService">
<kendo-multiselect style="width:220px"
[data]="allTags"
textField="title"
valueField="id"
[valuePrimitive]="true"
[value]="tagFilters(filter)"
(valueChange)="tagChange($event, filterService)">
</kendo-multiselect>
</ng-template>
and backend code is C# and and search service is
public virtual DataSourceResult SearchKendo(DataSourceRequest criteria)
{
using (var repository = _repositoryFactory())
{
return repository.Products.Include("Tags").ToDataSourceResult(criteria.Take, criteria.Skip, criteria.Sort, criteria.Filter);
}
}
but in run time I get this error
System.Linq.Dynamic.Core.Exceptions.ParseException: 'No property or field 'id' exists in type 'ObservableCollection`1''
It seems that "tags" is not known as collection of objects, indeed it is considered as a object

Retrieving data from a ViewModel in POST method

I have a View model that looks like this
public class ItemViewModel
{
[Required]
public int Id { get; set; }
public string ItemId { get; set; }
public string ItemName { get; set; }
public string MFGNumber { get; set; }
public IList<ItemPartViewModel> Parts { get; set; }
public IList<ItemComponentViewModel> Components{ get; set; }
public IList<ComponentPartViewModel> ComponentParts { get; set; }
public IList<ComponentSubCompViewModel> ComponentSubComps { get; set; }
public IList<SubCompPartViewModel> SubCompParts { get; set; }
public IList<SubCompSubCompViewModel> SubCompSubComps { get; set; }
public IList<SubCompSubCompPartViewModel> SubCompSubCompParts { get; set; }
}
As you can see the Viewmodel also has corresponding view models that look like this
public class ItemPartViewModel
{
[Required]
public int ID { get; set; }
public string PartID { get; set; }
public HtmlString PartLink { get; set; }
public string MFGNumber { get; set; }
public string PartName { get; set; }
public float QtyInItem { get; set; }
public float OnHand { get; set; }
public float OnWorkOrder { get; set; }
public float Committed { get; set; }
public float FSTK { get; set; }
// This is the additional property to contain what user picks
public PartActionType SelectedActionType { get; set; }
}
The ItemViewModel is populated through my OrderSelection GET method that looks like this
public ActionResult SpecialOrderSelection(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
JobOrder jobOrder = db.JobOrders.Find(id);
if (jobOrder == null)
{
return HttpNotFound();
}
ViewBag.JobOrderID = jobOrder.ID;
ItemInstance ii = db.ItemInstances.Where(x => x.serialNumber == jobOrder.serialNumber).FirstOrDefault();
Item item = db.Items.Find(ii.ItemID);
var vm = new ItemViewModel
{
Id = item.ID,
ItemId = item.ItemID,
ItemName = item.Name,
Parts = new List<ItemPartViewModel>(),
Components = new List<ItemComponentViewModel>(),
ComponentParts = new List<ComponentPartViewModel>(),
ComponentSubComps = new List<ComponentSubCompViewModel>(),
SubCompParts = new List<SubCompPartViewModel>(),
SubCompSubComps = new List<SubCompSubCompViewModel>(),
SubCompSubCompParts = new List<SubCompSubCompPartViewModel>()
};
foreach (ItemHasParts ihp in item.IHP)
{
Part part = db.Parts.Find(ihp.PartID);
vm.Parts.Add(new ItemPartViewModel
{
ID = part.ID,
PartID = part.PartID,
PartLink = part.PartIDLink,
MFGNumber = part.MFG_number,
QtyInItem = ihp.qty,
OnHand = part.On_Hand,
OnWorkOrder = part.On_Order_Count(true, true),
Committed = part.CommittedCount(true, true),
FSTK = part.FSTK,
PartName = part.Name,
SelectedActionType = PartActionType.Transfer
});
}
return View(vm);
}
The data then is correctly shown on the selection page. But on this page users must select whether they want to harvest/transfer/or dispose of a part. So once the user has finished selecting their options they hit a 'submit' button. This then POSTS to this method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SpecialOrderSelection(ItemViewModel model)
{
//list of transfers
//list of harvests
//list of disposals
if (ModelState.IsValid)
{
JobOrder jobOrder = db.JobOrders.Find(model.Id);
if (jobOrder == null)
{
return HttpNotFound();
}
ViewBag.JobOrderID = jobOrder.ID;
// do whatever with 'model' and return or redirect to a View
}
//ViewBag.submitted = true;
return RedirectToAction("SpecialOrderSummary", new { ID = jobOrder.ID });
}
The problem here is that for each list, (Parts/Components/ComponentParts/etc.) The ID is null. Why is it null on the POST but not the GET? And how can I fix this so it is not null
Here is the beginning of my View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.Id)
<div class="form-horizontal">
<h2 class="noprint">Special Order Selection</h2>
<p style="color:red" class="noprint">Please select what is to be done with each component/part</p>
<td align="left">
<hr class="noprint" />
<h4 class="noprint"><b>Work Order ID:</b> #Html.DisplayFor(model => j.ID)</h4>
<br class="noprint" />
And here is the payload of it
<form action="/JODetails/SpecialOrderSelection/3092" method="post"><input name="__RequestVerificationToken" type="hidden" value="LETTERSANDNUMBERS" /><input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Id" name="Id" type="hidden" value="3092" /> <div class="form-horizontal">
<h2 class="noprint">Special Order Selection</h2>
<p style="color:red" class="noprint">Please select what is to be done with each component/part</p>
Here is an example of what a 'Part' in my 'parts' list is returning
Your hidden field in the form is Id, which is the Model.Id (ItemViewModel) and not the part! and yet the property you're inspecting on POST is ID (which is the Part ID). You're looking for the item part ID, but you only have the ItemViewModel Id in the form. Since you have other values for item part properties, you must be iterating over that list of parts somewhere in your form. Add a hidden input for the part ID there.
#{
foreach(var part in Model.Parts) {
#Html.HiddenFor(model => part.ID)
}
}

How to use #bind-Value in a dynamic form?

I am using Blazor to create a dynamic form from the properties of my model.
I am using a for each loop to loop through the properties of the model.
public class SensorType
{
public int Id { get; set; }
[Required]
[MaxLength(30)]
[Display(Name = "Sensor Type Name")]
public string Name { get; set; }
[Required]
[MaxLength(500)]
public string Description { get; set; }
[Required]
[MaxLength(2048)]
[Display(Name = "Datasheet URL")]
public string DatasheetUrl { get; set; }
}
I implemented this razor view, where I try to bind to public SensorType sensortype { get; set; }. But I need to bind to sensortype.property where property is whatever property the model has that is in the for each loop. But I cannot simply just call say #bind-Value="sensortype.property". Any ideas on how to do this? I don't want to have to manually type every field. Thanks!
<EditForm Model="#sensortype" OnValidSubmit="#SaveSensorType">
#foreach(var property in typeof(SensorType).GetProperties())
{
if(property.Name == "Id")
{
continue;
}
<div class="form-group row">
<label class="col-sm-2 col-form-label">#(GetAttribute(property, false)) </label> //This function get the name of the property in a human-readable way.
<div class="col-sm-10">//I would like to bind here to sensortype's property in the for each loop but sensortype.property gives me an error.
<InputTextArea class="form-control" #bind-Value="sensortype.property" value="sensortype.property.Name" placeholder="Description of the Type of Sensor" />
</div>
</div>
}
I am playing this days with blazor and I made a kind of DynamicForm, is not perfect but is working. I just want to show you my dynamic form as proof of concept, not at something which I wold use in production.
Basicly we want to write something like this:
<DynamicForm #bind-Model="MySensorType" />
//this will generate a form with fields for all properties of the model
So, on Index view let's create a property for MySensorType and some markup to see if the model is changing when the form fields are edited.
#page "/"
<div style="display:flex">
<div>
<DynamicForm #bind-Model="MySensorType" />
</div>
<div style="background:yellow;flex:1;margin:20px;">
<p>Id: #MySensorType.Id</p>
<p>Name: #MySensorType.Name</p>
<p>Description: #MySensorType.Description</p>
<p>Url: #MySensorType.DatasheetUrl</p>
</div>
</div>
#code {
public SensorType MySensorType { get; set; } = new SensorType();
public class SensorType
{
public int Id { get; set; } = 1;
public string Name { get; set; } = "Some Name";
public string Description { get; set; } = "Some Description";
public string DatasheetUrl { get; set; } = "This is a URL";
}
}
In order to generate automatically the fields, we need some kind of DynamicField.
Below component is for named "DynamicField"
<div>
<label>#Caption</label>
#if (Value is String sValue)
{
<input type="text" value="#sValue" #onchange="OnChange"/>
}
#if (Value is int iValue)
{
<input type="number" value="#iValue" #onchange="OnChange" />
}
</div>
#code {
[Parameter] public string Caption { get; set; }
[Parameter] public object Value { get; set; }
[Parameter] public EventCallback<object> ValueChanged { get; set; }
async void OnChange(ChangeEventArgs e)
{
await ValueChanged.InvokeAsync(e.Value);
}
}
Now, we can create the wrapper so called DynamicForm:
#typeparam T
#foreach (var p in Properties)
{
<DynamicField Value="#p.Value" Caption="#p.Key" ValueChanged="#((e)=>OnValueChanged(e,p.Key))"/>
}
#code{
[Parameter] public T Model { get; set; }
[Parameter] public EventCallback<T> ModelChanged { get; set; }
public Dictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();
protected override void OnInitialized()
{
var props = Model.GetType().GetProperties();
foreach (var p in props)
{
Properties.Add(p.Name, p.GetValue(Model));
}
}
void OnValueChanged(object e, string prop)
{
var p = Model.GetType().GetProperty(prop);
if (p.PropertyType == typeof(int))
{
var intValue = Convert.ToInt32(e);
p.SetValue(Model, intValue);
}
if (p.PropertyType == typeof(string))
{
p.SetValue(Model, e.ToString());
}
ModelChanged.InvokeAsync(Model);
}
}
What actually is happening here, we are using Reflection to get all properties of the model, send them to DynamicFields, and when those values are changed we set the new value to the model and call ModelChanged to send new values.
On my computer this works, and every time when I change a value, MySensorType is showing the new value on Index component.
You can see that I created dynamic fields only for Number and String, if you have DateTime or Select, you need to extend this DynamicField, for select will be more difficult.
By the way, on Index view you can put a button and call SaveChanges with your logic and use MySensorType.

MVC Binding List of Objects in Object

I'm trying to dynamically build a table that needs to be bound to a ViewModel on form submission.
First item is the Action the form is submitting to. Then the Parent ViewModel and then the child ViewModel. Below those are two text fields representing the data I need bound.
When I submit the form all the other fields from the page are bound to their respective property but the complex object of ProgramViewModels is not.
What am I missing?
public ActionResult Add(AddEntityViewModel viewModel){
Code that does something
}
public class AddEntityViewModel
{
public IList<int> Counties { get; set; }
public IList<ProgramViewModel> Programs { get; set; }
public IList<int> Regions { get; set; }
public string EntityName { get; set; }
public bool IsValid()
{
if (string.IsNullOrEmpty(EntityName))
return false;
if (Counties == null || Counties.Count == 0)
return false;
if (Programs == null || Programs.Count == 0)
return false;
if (Regions == null || Regions.Count == 0)
return false;
return true;
}
}
public class ProgramViewModel
{
public int Id;
public string SystemId;
}
<input type="hidden" id="Programs[0]__Id" name="Programs[0].Id" data-programid="3" value="3">
<input type="text" id="Programs[0]__SystemId" name="Programs[0].SystemId" style="width:100%" maxlength="50">
Update:
Changed the fields to properties after adiga's answer. But that too did not fix the issue.
public class ProgramViewModel
{
public int Id { get; set; }
public string SystemId { get; set; }
}
Your ProgramViewModel contains fields. Change them to properties.
public class ProgramViewModel
{
public int Id { get; set; }
public string SystemId { get; set; }
}
The DefaultModelBinder uses reflection and binds only the properties and not fields.
If you have a List of a object, you should be performing a foreach instruction to get all of them:
<% foreach(var x in values) { %>
<div>hello <%= x.name %></div>
<% } %>

Categories