I'm tired to search on web a solution for this. Basicly i am using InputBase to extend the normal inputbox to a custom component. For single selection in its ok, but turns complicate when i have mutiple selection "select multiple="multiple""
So here is the code:
File: XDropDownMultiSelect.razor
#using System.Linq.Expressions
#typeparam T
#inherits InputBase<T>
#if (!string.IsNullOrEmpty(Label))
{
<label class="form-label">#Label</label>
}
<select #bind="CurrentValue" class="form-control select2 #CssClass" id="#Id" #attributes="AdditionalAttributes" multiple>
#if (DropdownValues != null)
{
foreach (var cursor in DropdownValues)
{
<option value="#cursor.Key">#cursor.Value</option>
}
}
</select>
#code {
[Inject] public IJSRuntime _js { get; set; }
[Parameter, EditorRequired] public string Id { get; set; }
[Parameter] public string Label { get; set; }
[Parameter] public Expression<Func<T>> ValidationFor { get; set; }
[Parameter] public bool ShowDefaultOption { get; set; } = true;
[Parameter] public Dictionary<string, string> DropdownValues { get; set; }
[Parameter] public string Selected { get; set; }
protected override bool TryParseValueFromString(string value, out T result, out string validationErrorMessage)
{
if (typeof(T) == typeof(string))
{
result = (T)(object)value;
validationErrorMessage = null;
return true;
}
else if (typeof(T) == typeof(string[]))
{
result = (T)(object)(new string[] { value });
validationErrorMessage = null;
return true;
}
else if (typeof(T) == typeof(Guid))
{
Guid.TryParse(value, out var parsedValue);
result = (T)(object)parsedValue;
validationErrorMessage = null;
return true;
}
else if (typeof(T).IsEnum)
{
try
{
result = (T)Enum.Parse(typeof(T), value);
validationErrorMessage = null;
return true;
}
catch (ArgumentException)
{
result = default;
validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid.";
return false;
}
}
throw new InvalidOperationException($"{GetType()} does not support the type '{typeof(T)}'.");
}
}
I use "CurrentValue" instead of "CurrentValueAsString" because is an array no string, and if i set CurrentValueAsString will have a render json problem...
Now i call by simple:
<XDropDownMultiSelect #bind-Value="usersSelected" Id="test" DropdownValues="usersAll" />
#code{
public string[] usersSelected { get; set; } = new [] { "user1" };
public string[] usersAll{ get; set; } = new [] { "user1", "user2", "user3" };
Its working, but dosenĀ“t bind the new selection values to my selectValues object.
i found how to use CurrentValueAsString
protected override string FormatValueAsString(T? value)
{
if( value != null)
{
return value.ToJson(); //this is extension my to convert any object to json format.
}
return base.FormatValueAsString(value);
}
But this not update the source model with new selections.
I have an ExtendedInputText component which inherits from InputText
#inherits InputText
<div class="flex">
<label class="w-1/2">
#Label
#if(Required){
<span class="text-red-500 ml-1">*</span>
}
</label>
<InputText
class="flex-1 border border-gray-200 bg-white p-2 rounded"
placeholder="#Label"
Value="#Value"
ValueChanged="#ValueChanged"
ValueExpression="#ValueExpression"
Required="#Required"
/>
</div>
#code
{
[Parameter]
public bool Required { get; set; }
[Parameter]
public string Label { get; set; }
}
I intend on using it to replace this
<EditForm Model="Command" OnValidSubmit="OnValidSubmit">
<FluentValidationValidator />
<ValidationSummary />
<div class="">
<label>Title <span class="text-red-500">*</span></label>
<InputText id="Title" #bind-Value="Command.Title" />
<ValidationMessage For="#(() => Command.Title)" />
</div>
<button type="submit" class="p-2 bg-positive-500 text-white rounded">Create</button>
</EditForm>
with this
<EditForm Model="Command" OnValidSubmit="OnValidSubmit">
<FluentValidationValidator />
<ValidationSummary />
<ExtendedInputText Label="Title" Required="true" #bind-Value="Command.Title"/>
<button type="submit" class="p-2 bg-positive-500 text-white rounded">Create</button>
</EditForm>
How would I go about also passing <ValidationMessage For="#(() => Command.Title)" /> to the ExtendedInputText component and rendering it from within?
With the help of Nicola and Shaun, this is the solution that worked for me.
#inherits InputText
<div class="flex">
<label class="w-1/2 text-right font-semibold mr-1 py-2">
#Label
#if (Required)
{
<span class="text-red-500 ml-1">*</span>
}
</label>
<div class="flex-1">
<InputText class="w-full border border-gray-200 bg-white p-2 rounded"
placeholder="#Label"
Value="#Value"
ValueChanged="#ValueChanged"
ValueExpression="#ValueExpression"
Required="#Required"/>
#ValidationFragment
</div>
</div>
#code
{
[Parameter]
public bool Required { get; set; }
[Parameter]
public string Label { get; set; }
private RenderFragment ValidationFragment => (builder) =>
{
var messages = EditContext.GetValidationMessages(FieldIdentifier).ToList();
if(messages is not null && messages.Count > 0)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", "text-red-500 p-2 w-full");
builder.OpenComponent<ValidationMessage<string>>(330);
builder.AddAttribute(340, "For", ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
};
}
They key part was the private RenderFragment ValidationFragment which is built programatically to display associated errors stored in the cascading EditContext
The full code for a similar component. Note that the component gets the Validation message, you don't need to pass it.
I think I've included the only dependant class, but I may have missed something.
/// ============================================================
/// Author: Shaun Curtis, Cold Elm Coders
/// License: Use And Donate
/// If you use it, donate something to a charity somewhere
/// ============================================================
using Blazr.SPA.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
using System;
using System.Linq;
using System.Linq.Expressions;
#nullable enable
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
#pragma warning disable CS8602 // Dereference of a possibly null reference.
namespace Blazr.UIComponents
{
public class FormEditControl<TValue> : ComponentBase
{
[Parameter]
public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter] public Expression<Func<TValue>>? ValueExpression { get; set; }
[Parameter] public string? Label { get; set; }
[Parameter] public string? HelperText { get; set; }
[Parameter] public string DivCssClass { get; set; } = "mb-2";
[Parameter] public string LabelCssClass { get; set; } = "form-label";
[Parameter] public string ControlCssClass { get; set; } = "form-control";
[Parameter] public Type ControlType { get; set; } = typeof(InputText);
[Parameter] public bool ShowValidation { get; set; }
[Parameter] public bool ShowLabel { get; set; } = true;
[Parameter] public bool IsRequired { get; set; }
[Parameter] public bool IsRow { get; set; }
[CascadingParameter] EditContext CurrentEditContext { get; set; } = default!;
private readonly string formId = Guid.NewGuid().ToString();
private bool IsLabel => this.ShowLabel && (!string.IsNullOrWhiteSpace(this.Label) || !string.IsNullOrWhiteSpace(this.FieldName));
private bool IsValid;
private FieldIdentifier _fieldIdentifier;
private ValidationMessageStore? _messageStore;
private string? DisplayLabel => this.Label ?? this.FieldName;
private string? FieldName
{
get
{
string? fieldName = null;
if (this.ValueExpression != null)
ParseAccessor(this.ValueExpression, out var model, out fieldName);
return fieldName;
}
}
private string MessageCss => CSSBuilder.Class()
.AddClass("invalid-feedback", !this.IsValid)
.AddClass("valid-feedback", this.IsValid)
.Build();
private string ControlCss => CSSBuilder.Class(this.ControlCssClass)
.AddClass("is-valid", this.IsValid)
.AddClass("is-invalid", !this.IsValid)
.Build();
protected override void OnInitialized()
{
if (CurrentEditContext is null)
throw new InvalidOperationException($"No Cascading Edit Context Found!");
if (ValueExpression is null)
throw new InvalidOperationException($"No ValueExpression defined for the Control! Define a Bind-Value.");
if (!ValueChanged.HasDelegate)
throw new InvalidOperationException($"No ValueChanged defined for the Control! Define a Bind-Value.");
CurrentEditContext.OnFieldChanged += FieldChanged;
CurrentEditContext.OnValidationStateChanged += ValidationStateChanged;
_messageStore = new ValidationMessageStore(this.CurrentEditContext);
_fieldIdentifier = FieldIdentifier.Create(ValueExpression);
if (_messageStore is null)
throw new InvalidOperationException($"Cannot set the Validation Message Store!");
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
var showHelpText = (messages.Count == 0) && this.IsRequired && this.Value is null;
if (showHelpText && !string.IsNullOrWhiteSpace(this.HelperText))
_messageStore.Add(_fieldIdentifier, this.HelperText);
}
protected void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
{
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
if (messages != null || messages.Count > 1)
{
_messageStore.Clear();
}
}
protected void FieldChanged(object sender, FieldChangedEventArgs e)
{
if (e.FieldIdentifier.Equals(_fieldIdentifier))
_messageStore.Clear();
}
protected override void OnParametersSet()
{
this.IsValid = true;
{
if (this.IsRequired)
{
this.IsValid = false;
var messages = CurrentEditContext.GetValidationMessages(_fieldIdentifier).ToList();
if (messages is null || messages.Count == 0)
this.IsValid = true;
}
}
}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (IsRow)
builder.AddContent(1, RowFragment);
else
builder.AddContent(2, BaseFragment);
}
private RenderFragment BaseFragment => (builder) =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(10, "class", this.DivCssClass);
builder.AddContent(40, this.LabelFragment);
builder.AddContent(60, this.ControlFragment);
builder.AddContent(70, this.ValidationFragment);
builder.CloseElement();
};
private RenderFragment RowFragment => (builder) =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(10, "class", "row form-group");
builder.OpenElement(20, "div");
builder.AddAttribute(30, "class", "col-12 col-md-3");
builder.AddContent(40, this.LabelFragment);
builder.CloseElement();
builder.OpenElement(40, "div");
builder.AddAttribute(50, "class", "col-12 col-md-9");
builder.AddContent(60, this.ControlFragment);
builder.AddContent(70, this.ValidationFragment);
builder.CloseElement();
builder.CloseElement();
};
private RenderFragment LabelFragment => (builder) =>
{
if (this.IsLabel)
{
builder.OpenElement(110, "label");
builder.AddAttribute(120, "for", this.formId);
builder.AddAttribute(130, "class", this.LabelCssClass);
builder.AddContent(140, this.DisplayLabel);
builder.CloseElement();
}
};
private RenderFragment ControlFragment => (builder) =>
{
builder.OpenComponent(210, this.ControlType);
builder.AddAttribute(220, "class", this.ControlCss);
builder.AddAttribute(230, "Value", this.Value);
builder.AddAttribute(240, "ValueChanged", EventCallback.Factory.Create(this, this.ValueChanged));
builder.AddAttribute(250, "ValueExpression", this.ValueExpression);
builder.CloseComponent();
};
private RenderFragment ValidationFragment => (builder) =>
{
if (this.ShowValidation && !this.IsValid)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", MessageCss);
builder.OpenComponent<ValidationMessage<TValue>>(330);
builder.AddAttribute(340, "For", this.ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
else if (!string.IsNullOrWhiteSpace(this.HelperText))
{
builder.OpenElement(350, "div");
builder.AddAttribute(360, "class", MessageCss);
builder.AddContent(370, this.HelperText);
builder.CloseElement();
}
};
// Code lifted from FieldIdentifier.cs
private static void ParseAccessor<T>(Expression<Func<T>> accessor, out object model, out string fieldName)
{
var accessorBody = accessor.Body;
if (accessorBody is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert && unaryExpression.Type == typeof(object))
accessorBody = unaryExpression.Operand;
if (!(accessorBody is MemberExpression memberExpression))
throw new ArgumentException($"The provided expression contains a {accessorBody.GetType().Name} which is not supported. {nameof(FieldIdentifier)} only supports simple member accessors (fields, properties) of an object.");
fieldName = memberExpression.Member.Name;
if (memberExpression.Expression is ConstantExpression constantExpression)
{
if (constantExpression.Value is null)
throw new ArgumentException("The provided expression must evaluate to a non-null value.");
model = constantExpression.Value;
}
else if (memberExpression.Expression != null)
{
var modelLambda = Expression.Lambda(memberExpression.Expression);
var modelLambdaCompiled = (Func<object?>)modelLambda.Compile();
var result = modelLambdaCompiled();
if (result is null)
throw new ArgumentException("The provided expression must evaluate to a non-null value.");
model = result;
}
else
throw new ArgumentException($"The provided expression contains a {accessorBody.GetType().Name} which is not supported. {nameof(FieldIdentifier)} only supports simple member accessors (fields, properties) of an object.");
}
}
}
#pragma warning restore CS8622
#pragma warning restore CS8602
#nullable disable
and CSSBuilder
/// ============================================================
/// Author: Shaun Curtis, Cold Elm Coders
/// License: Use And Donate
/// If you use it, donate something to a charity somewhere
/// ============================================================
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace Blazr.SPA.Components
{
public class CSSBuilder
{
private Queue<string> _cssQueue = new Queue<string>();
public static CSSBuilder Class(string cssFragment = null)
{
var builder = new CSSBuilder(cssFragment);
return builder.AddClass(cssFragment);
}
public CSSBuilder()
{
}
public CSSBuilder (string cssFragment)
{
AddClass(cssFragment);
}
public CSSBuilder AddClass(string cssFragment)
{
if (!string.IsNullOrWhiteSpace(cssFragment)) _cssQueue.Enqueue(cssFragment);
return this;
}
public CSSBuilder AddClass(IEnumerable<string> cssFragments)
{
if (cssFragments != null)
cssFragments.ToList().ForEach(item => _cssQueue.Enqueue(item));
return this;
}
public CSSBuilder AddClass(string cssFragment, bool WhenTrue)
{
if (WhenTrue) return this.AddClass(cssFragment);
return this;
}
public CSSBuilder AddClassFromAttributes(IReadOnlyDictionary<string, object> additionalAttributes)
{
if (additionalAttributes != null && additionalAttributes.TryGetValue("class", out var val))
_cssQueue.Enqueue(val.ToString());
return this;
}
public CSSBuilder AddClassFromAttributes(IDictionary<string, object> additionalAttributes)
{
if (additionalAttributes != null && additionalAttributes.TryGetValue("class", out var val))
_cssQueue.Enqueue(val.ToString());
return this;
}
public string Build(string CssFragment = null)
{
if (!string.IsNullOrWhiteSpace(CssFragment)) _cssQueue.Enqueue(CssFragment);
if (_cssQueue.Count == 0)
return string.Empty;
var sb = new StringBuilder();
foreach(var str in _cssQueue)
{
if (!string.IsNullOrWhiteSpace(str)) sb.Append($" {str}");
}
return sb.ToString().Trim();
}
}
}
It looks like this in action:
I use the following code for a component I've created LabelText but should be used for your case:
public partial class LabelText<T>: ComponentBase
{
[Parameter] public Expression<Func<T>> For { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
private FieldIdentifier _fieldIdentifier;
...
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "label");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "for", _fieldIdentifier.FieldName);
builder.AddContent(3, label + GetRequired());
builder.CloseElement();
}
protected override void OnParametersSet()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException($"{GetType()} requires a cascading parameter " +
$"of type {nameof(EditContext)}. For example, you can use {GetType()} inside " +
$"an {nameof(EditForm)}.");
}
if (For == null) // Not possible except if you manually specify T
{
throw new InvalidOperationException($"{GetType()} requires a value for the " +
$"{nameof(For)} parameter.");
}
_fieldIdentifier = FieldIdentifier.Create(For);
}
UPDATE
I can't explain better then the excellent piece of code from #MrC
private RenderFragment ValidationFragment => (builder) =>
{
if (this.ShowValidation && !this.IsValid)
{
builder.OpenElement(310, "div");
builder.AddAttribute(320, "class", MessageCss);
builder.OpenComponent<ValidationMessage<TValue>>(330);
builder.AddAttribute(340, "For", this.ValueExpression);
builder.CloseComponent();
builder.CloseElement();
}
else if (!string.IsNullOrWhiteSpace(this.HelperText))
{
builder.OpenElement(350, "div");
builder.AddAttribute(360, "class", MessageCss);
builder.AddContent(370, this.HelperText);
builder.CloseElement();
}
};
you only need to add a paremeter like ValidationMessage="(() => Command.Title)" and this RenderFragment does the job for you.
I have the following custom date input:
CustomDateInput.razor
<input type="date"
class="#CssClass"
#bind=CurrentValue
#attributes=AdditionalAttributes />
#if (ValidationFor is not null)
{
<ValidationMessage For="#ValidationFor" />
}
#using System.Linq.Expressions
#inherits InputBase<DateTime?>
#code {
[Parameter] public Expression<Func<DateTime>>? ValidationFor { get; set; }
protected override bool TryParseValueFromString(string? value, out DateTime? result, out string validationErrorMessage)
{
result = CurrentValue;
validationErrorMessage = "";
return true;
}
}
FooPage.razor
<CustomInputDate #bind-Value="FooDate" ValidationFor="() => FooDate"></CustomInputDate>
FooPage.razor.cs
public DateTime? FooDate { get; set; }
However, I get errors:
How can I modify my CustomDateInput to allow for a Validation parameter to show a ValidationMessage?
The component. I've lifted the BuildRenderTree code from InputDate and added in the ValidationMessage component. You need to do it this way as I don't know a way to do the For binding in Razor. I've tied the For directly into the ValueExpression property of InputBase. You'll probably need to add a bit of formatting/css to prettify it.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
using System;
namespace Blazor.Starter.Components.TestComponents
{
public class CustomDate : InputDate<DateTime?>
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "input");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "type", "date");
builder.AddAttribute(3, "class", CssClass);
builder.AddAttribute(4, "value", BindConverter.FormatValue(CurrentValueAsString));
builder.AddAttribute(5, "onchange", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
builder.CloseElement();
builder.OpenComponent<ValidationMessage<DateTime?>>(6);
builder.AddAttribute(7, "For", this.ValueExpression);
builder.CloseComponent();
}
}
}
And a demo page. There's a button to manually set an error message in the validationMessageStore.
#page "/editortest"
<h3>EditorTest</h3>
<EditForm EditContext="editContext">
<div>
<CustomDate #bind-Value="model.Date"></CustomDate>
</div>
</EditForm>
<div class="m-3 p-3"><input #bind-value="_errormessage"><button class="btn btn-dark ms-2" #onclick="SetError">Set Error</button></div>
#code {
private dataModel model { get; set; } = new dataModel();
private EditContext editContext;
private string _errormessage { get; set; } = "Error in date";
protected override Task OnInitializedAsync()
{
this.editContext = new EditContext(model);
return base.OnInitializedAsync();
}
private void SetError( MouseEventArgs e)
{
var validationMessageStore = new ValidationMessageStore(this.editContext);
validationMessageStore.Clear();
var fi = new FieldIdentifier(this.model, "Date");
validationMessageStore.Add(fi, _errormessage);
}
public class dataModel
{
public string Email { get; set; }
public DateTime? Date { get; set; }
}
}
I have different way to reach the same result, you can try it!:
[Parameter] public bool ShowError { get; set; } = false;
<input type="date"
class="#CssClass"
#bind=CurrentValue
#attributes=AdditionalAttributes #onfocus="#(() => ShowError =true)"/>
#if(ShoError)
{
foreach(var msg in EditCotext.GetValidationMessages(FieldIdententifier))
{
<div class="alidation-message">#msg</div>
}
}
you need aslo to keep you code TryParseValueFromString
I have an application written using C# on the top of ASP.NET Core 5.0.
I have the following view-model
public class TestVM
{
public Name { get; set; }
public MenuViewModel<string> State { get; set;}
public TestVM()
{
State = MenuViewModel<string>();
}
}
Here is a stripped down version of my MenuViewModel
public class MenuViewModel
{
[BindNever]
public IEnumerable<SelectListItem> Items { get; set; }
}
public class MenuViewModel<T> : MenuViewModel
{
public T Value { get; set; }
}
The problem, is when the post request comes in, the viewModel.State.Value is null. When I evaluate Request.Form I do see the key State.Value with the correct value of CA
Here is a stripped down of my action method in the controller.
[HttpPost, ValidateAntiForgeryToken]
public IActionResult Store(TestVM viewModel)
{
if(ModelState.IsValid)
{
// do some
}
return View(viewModel);
}
How can I bind the form data from the request to State.Value property correctly?
Updated I created an editor-template to allow me to render the MenuVieModel. The ~/Views/Shared/EditorTemplates/MenuViewModel.cshtml contains the following code
#model dynamic
#{
if (!(Model is MenuViewModel m))
{
return;
}
dynamic obj = new System.Dynamic.ExpandoObject();
obj.Class = "form-control";
if (Html.ViewData.ModelMetadata.IsRequired)
{
obj.Required = true;
}
}
#Html.DropDownList("Value", m.Options, Html.ViewData.ModelMetadata.Placeholder, obj)
Firsly,you need know that for each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.
Here is a working demo you could follow:
Model:
public class TestVM
{
public string Name { get; set; }
public MenuViewModel<string> State { get; set; }
public TestVM()
{
State =new MenuViewModel<string>();
}
}
public class MenuViewModel
{
[BindNever]
public IEnumerable<SelectListItem> Items { get; set; }
}
public class MenuViewModel<T> : MenuViewModel
{
public T Value { get; set; }
}
View:
#model dynamic
#{
if (!(Model is MenuViewModel m))
{
return;
}
dynamic obj = new System.Dynamic.ExpandoObject();
obj.Class = "form-control";
if (Html.ViewData.ModelMetadata.IsRequired)
{
obj.Required = true;
}
}
<form asp-action="Store">
#*change here,And I do not find Options in your MenuViewModel,So I change it to Items*#
#Html.DropDownList("State.Value", m.Items, Html.ViewData.ModelMetadata.Placeholder, obj)
<input type="submit" value="post" />
</form>
Controller:
public IActionResult Index()
{
var model = new MenuViewModel<string>()
{
Items = new List<SelectListItem>() {
new SelectListItem() { Value = "-1", Text = "--- Select ---" },
new SelectListItem() { Value = "org1", Text = "org1" },
new SelectListItem() { Value = "org2", Text = "org2" },
new SelectListItem() { Value = "org3", Text = "org3" }
}
};
return View(model);
}
[HttpPost, ValidateAntiForgeryToken]
public IActionResult Store(TestVM viewModel)
{
if (ModelState.IsValid)
{
// do some
}
return View(viewModel);
}
Result:
I have a viewModel that I want to bind to pjax postback action:
public class MyViewModel
{
public MySetting mySetting{ get; set; }
public IList<MyDetail> myDetails{ get; set; }
}
This is the function I want to hit to handle the postBack
[HttpPost]
public ActionResult SaveMySettings(MyViewModel viewModel)
{
viewModel.mySettings; // This populate fine
viewModel.myDetails; // Is NULL
// handle saving here
return this.PAjax("myPage", null, viewModel);
}
My custom classes are generated using Entity Framework from DB:
public class MySetting
{
private bool _settingA;
[ColumnAttribute(Storage="_settingA", DbType="Bit NOT NULL")]
public bool settingA
{
get
{
return this._settingA;
}
set
{
if ((this._settingA!= value))
{
this.OnsettingAChanging(value);
this.SendPropertyChanging();
this._settingA= value;
this.SendPropertyChanged("settingA");
this.On_settingAChanged();
}
}
}
}
public class MyDetail
{
private bool _detailA;
[ColumnAttribute(Storage="_detailA", DbType="Bit NOT NULL")]
public bool detailA
{
get
{
return this._detailA;
}
set
{
if ((this._detailA!= value))
{
this.OnsettingAChanging(value);
this.SendPropertyChanging();
this._detailA= value;
this.SendPropertyChanged("detailA");
this.On_detailAChanged();
}
}
}
}
My ASPX page look like this:
#model MyProject.MyViewModel
<div id="main">
<form id="myForm" post="/Settings/SaveMySettings">
#Html.TextBoxFor(m => m.mySetting.settingA)
#for (int i = 0; i < Model.myDetails.Count(); i++){
#Html.CheckBoxFor(m => Model.myDetails[i].detailA, #checked = "checked" }) MyCheckBox #Model.myDetails[i].detailA
}
Save Settings
</form>
</div>
And I am trying to postback using this PJax code:
<script lang="javascript">
(function ($) {
$('#saveChanges').click(function () {
var f = $(this).closest('form');
$.pjax({
type: 'POST',
url: "/Settings/SaveMySettings",
container: '#main',
data: $("#myForm").serializeArray()
});
return false;
});
})(jQuery);
</script>
When I try this, MyViewModel only populate mySettings but not myDetails, it seem like that the list would not be binded, what am I doing wrong?