Two-Way Component Parameter Binding on a class in Blazor? - c#

I have a class MealsQueryInputs that I would like to use as a component parameter with two-way binding capabilities.
All of the demos and sample code I can find are using built-in primitive types and never a class. I can get the MS demos to work but I cannot get binding to a class to work. Is it even possible to do this?
My component FilterSortOptions.razor:
using WhatIsForDinner.Shared.Models
<MudCheckBox Checked="#QueryInputs.Favorite"
Color="Color.Inherit"
CheckedIcon="#Icons.Material.Filled.Favorite"
UncheckedIcon="#Icons.Material.Filled.FavoriteBorder"
T="bool"/>
<MudRating SelectedValue="#QueryInputs.Rating"/>
<MudButton OnClick="#(async () => await OnPropertyChanged())">Apply</MudButton>
#code {
[Parameter]
public MealsQueryInputs QueryInputs { get; set; }
[Parameter]
public EventCallback<MealsQueryInputs> QueryInputsChanged { get; set; }
private async Task OnPropertyChanged()
{
await QueryInputsChanged.InvokeAsync(QueryInputs);
}
}

As MrC said, you should avoid directly binding to the data being supplied as a parameter.
Here is a simple working sample (not MudBlazor) to show the concept
https://blazorrepl.telerik.com/QQEnQjaO54LY3MYK35
You bind to a local variable/property and try not to modify the incoming data directly.
MyComponent
<h1>MyComponent</h1>
<label for="choice">Choose</label>
<input id="choice" type="checkbox" #bind-value=localValue />
#code
{
bool localValue
{
get => Data.SomeChoice;
set {
if (value != localValue)
{
localData = Data with { SomeChoice = value };
InvokeAsync(ValueChanged);
}
}
}
ComplexObject localData;
[Parameter] public ComplexObject Data { get; set; }
[Parameter] public EventCallback<ComplexObject> DataChanged { get; set; }
Task ValueChanged() => DataChanged.InvokeAsync(localData);
}
ComplexObject
public record ComplexObject(bool SomeChoice, string SomeText);
Main
#code
{
ComplexObject data = new(false,"");
}
<MyComponent #bind-Data=data />
You have chosen #data.SomeChoice

Here is how you can bind class objects to a custom razor component
This is FilterSortOptions component
<div>
<label>Rating:</label>
<input type="text" value=#QueryInputs.Rating #oninput=#(val=> {
QueryInputs.Rating=val.Value.ToString();
QueryInputsChanged.InvokeAsync(QueryInputs);
}) />
</div>
<div>
<label>Favourite:</label>
<input type="checkbox" value=#QueryInputs.Rating #onchange=#(val=> {
QueryInputs.Favourite=(bool)val.Value;
QueryInputsChanged.InvokeAsync(QueryInputs);
}) />
</div>
#code {
[Parameter]
public MealsQueryInputs QueryInputs { get; set; }
[Parameter]
public EventCallback<MealsQueryInputs> QueryInputsChanged { get; set; }
}
This is the model to bind, for simplicity Rating is is string type
public class MealsQueryInputs
{
public bool Favourite { get; set; } = false;
public string Rating { get; set; } = "0";
}
Here is the razor page
<h3>Rating: #QueryInputs.Rating</h3>
<h3>Favourite: #QueryInputs.Favourite</h3>
<FilterSortOptions #bind-QueryInputs=#QueryInputs></FilterSortOptions>
#code {
public MealsQueryInputs QueryInputs = new();
}

Updated Answer
Firstly, if your using an object then you are passing around references to the same object. So when you update the object in the sub-component, you're updating the same object the parent is using. You don't need to pass the object back in the callback unless you create a noew copy of it.
Secondly, your not binding the mud controls to the object.
Let's look at your code:
<MudCheckBox Checked="#QueryInputs.Favorite"
Color="Color.Inherit"
CheckedIcon="#Icons.Material.Filled.Favorite"
UncheckedIcon="#Icons.Material.Filled.FavoriteBorder"
T="bool"/>
Checked="#QueryInputs.Favorite" doesn't bind the control to the field. It just sets the initial value.
I think (I don't use Mudblazor and it's a little different from standard Blazor Form Controls) you need to do this:
<MudCheckBox #bind-Checked="#QueryInputs.Favorite"></MudCheckBox>
The same is true for MudRating.
<MudRating #bind-SelectedValue="#QueryInputs.Rating" />
Then the button:
<MudButton OnClick="#(async () => await OnPropertyChanged())">Apply</MudButton>
can be simplified to this. You're wrapping an async method within an async method.
<MudButton OnClick="OnPropertyChanged">Apply</MudButton>
// or
<MudButton OnClick="() => OnPropertyChanged()">Apply</MudButton>
Original Answer
There are a couple of issues here:
QueryInputs is a Parameter and therefore should never be modified by the code within the component. You end up with a mismatch between what the Renderer thinks the value is and what it actually is.
When the parent component renders it will always cause a re-render of any component that is passed a class as a parameter. The Renderer has no way of telling if a class has been modified, so it applies the heavy handed solution - call SetParametersAsync on the component.
A solution is to use a view service to hold the data and events to notify changes. One version of the truth! Search "Blazor Notification Pattern" for examples of how to implement this. I'll post some code if you can't find what you want.

Related

How can I add different controls dynamically to a web page using asp.net core?

I am new to .net core - have been using aspx web pages and .net framework 4.x for a number of years. I have a project where we want to display different controls (textbox, dropdown, checkbox) on the page based on values returned from a query. For example, user chooses "A" from a dropdown list and it shows 10 controls, if they choose object B it shows 8 controls, etc. Previously in .net framework, I would use a content placeholder with an ID and then find that ID and start adding controls (controls.Add(newControl)) in the placeholder. It doesn't seem that is an option with .net core. It seems like this would be a common need for various web applications, but I'm not finding many hits.
Another question is whether this can be done in the code behind or if it has to be done on the client-side. If one of the controls in the list is a dropdown, there will be a query that a subroutine will run to get the Key/Value pairs for the dropdown. To me this means it would be more effective on the server side.
I haven't really found any good examples when I do some searching. Can anyone point me to a good resource or provide me with a basic example - either client-side or server-side? Thanks!
There are many options, but I'll describe a simple one, using server side processing. As you explained in your comment, there will be 2 pages:
One that will display the select element that will be used to choose a set of controls.
The page that will be returned according to the previous choise, displaying the selected set of controls.
I assume that you know how to build the first page.
For the second page, you can leverage the ASP.NET Core MVC pattern to achieve the desired result.
You will need the three usual MVC elements:
An Action in a Controler.
A ViewModel for your Razor View.
A Razor View.
The Action does the following:
Receives the id of the selected set of control (via the Action's parameter).
Uses this id to retrieve the information about the corresponding set of controls from your repository.
Builds a ViewModel out of the received information.
Builds a View using the obtained ViewModel.
Return the builded View.
Here is some simplified example code:
In your controller, add the following method:
#!lang-cs
Public IActionResult GetProgramControlSet(int ProgramId)
{
// Here, use the id to get the data from your repository
// that will be used to build set of controls.
// Supposing you have defined a GetControls method,
// it could look like:
var SelectedControls = MyRepository.GetControls(ProgramId);
// If needed, you can build a ViewModel out of the received SelectedControls.
var SelectedControlsViewModel = new ControlSetViewModel(SelectedControls);
return View(SelectedControlsViewModel)
}
Of course, many things are missing here: error handling, etc...
Here is what the ViewModel could be:
#!lang-cs
public class ControlSetViewModel
{
public string Name { get; private set; }
public List<IControl> Controls { get; private set; }
public ControlSetViewModel(...)
{
// Whatever needs to be done to construct the ViewModel
}
}
public enum ControlKind
{
Button,
Select,
Textarea
//...
}
public interface IControl
{
ControlKind Kind { get; }
}
public class ControlButton : IControl
{
public ControlKind Kind => ControlKind.Button;
public string Label { get; set; }
public string Text { get; set; }
public string Color { get; set; }
// ... All other needed properties for the button
}
public class ControlTextarea : IControl
{
public ControlKind Kind => ControlKind.Textarea;
public string Label { get; set; }
public string PlaceholderText { get; set; }
public string RowCount { get; set; }
// ... All other needed properties for the textarea
}
public class ControlSelect : IControl
{
public ControlKind Kind => ControlKind.Select;
public string Label { get; set; }
public string PlaceholderText { get; set; }
public List<SelectOption> Options { get; set; }
// ... All other needed properties for the select
}
public class SelectOption
{
public string Text { get; set; }
public string Value { get; set; }
}
You could also use inheritance instead of interface for the control classes.
Now the view.
It is a Razor page containing something akin to
#model ControlSetViewModel
#*... some HTML ...*#
<div>
<h1>#Model.Name</h1>
#foreach(var control in Model.Controls)
{
<div>
switch(control.GetControlKind())
{
case ControlKind.TextArea:
var Textarea = (ControlTextarea)control;
<label>#Textarea.Label</label>
<textarea rows="#Textarea.RowCount"/>
break;
case ControlKind.Select:
var Select = (ControlSelect)control;
<label>#Select.Label</label>
<select>
#foreach(var option in Select.Options)
{
<option value="#option.Value">#option.Text</option>
}
</select>
break;
#*... etc ...*#
default:
#*... etc ...*#
}
</div>
}
</div>
#*... More HTML ...*#
Of course this is far to be finished. All the infrastructure and code that will actually react to the displayed controls is missing.
Is it a form you that will be posted?
Is it Javascript code that will react to the control manipulation?
Or another mecanism?
This questions will need to be addressed.

How to set ValidationMessage<TValue>.For Property dynamically in Blazor?

This Section of the Docs describes, how to display Validation Messages.
<ValidationMessage For="() => Parameters.PropertyA"></ValidationMessage>
How can the ValidationMessage.For Property be set dynamically?
Since For is of type Expression<Func<TValue>>, I want to pass a Func instead, but this doesn't compile:
[Parameter]
public Func<string> PropertyLocator { get; set; }
<ValidationMessage For="PropertyLocator"></ValidationMessage>
this compiles, but Validation Messages won't be resolved correctly
<ValidationMessage For="() => PropertyLocator"></ValidationMessage>
I also tried to make the Component generic, such that it knows about the Parameters Type:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Components;
public partial class MyComponent<TParam>
{
[Parameter]
public TParam Parameters { get; set; }
[Parameter]
public Func<TReportParam, string> PropertyLocator { get; set; }
}
#using System.Linq.Expressions
#typeparam TParam
<ValidationMessage For="#((Expression<Func<string>>)(() => PropertyLocator(this.Parameters)))"></ValidationMessage>
<MyComponent TParam="MyParameters" Parameters="BindToSomeValue" PropertyLocator="(parameters) => parameters.PropertyA" />
But this leads to the following run-time exception:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: The provided expression contains a InvocationExpression1 which is not supported.
FieldIdentifier only supports simple member accessors (fields,
properties) of an object. System.ArgumentException: The provided
expression contains a InvocationExpression1 which is not supported.
FieldIdentifier only supports simple member accessors (fields,
properties) of an object. at
Microsoft.AspNetCore.Components.Forms.FieldIdentifier.ParseAccessor[String](Expression`1
accessor, Object& model, String& fieldName) at
Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create[String](Expression`1
accessor) at
Microsoft.AspNetCore.Components.Forms.ValidationMessage`1[[System.String,
System.Private.CoreLib, Version=5.0.0.0, Culture=neutral,
PublicKeyToken=7cec85d7bea7798e]].OnParametersSet() at
Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()
at
Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
I've created a small sample page.
The model uses DataAnnotations as the validation mechanism.
public class DemoInputModel
{
[Required]
public String PropertyOne { get; set; }
[MinLength(2)]
public String PropertyTwo { get; set; }
[MaxLength(5)]
public String PropertyThree { get; set; }
}
On the page, the model is initialized and set as the edit context. We have three text inputs and a select box. The select box can be used to toggle the validation message. If the value is of the select box is changed, a new expression is assigned to the ValidationMessage.
#using System.ComponentModel.DataAnnotations;
#using System.Linq.Expressions;
#page "/test"
<h1>ValidationMessageTest</h1>
<EditForm Model="_model">
<DataAnnotationsValidator />
<ValidationMessage For="ValidationResolver"></ValidationMessage>
<InputText #bind-Value="_model.PropertyOne" />
<InputText #bind-Value="_model.PropertyTwo" />
<InputText #bind-Value="_model.PropertyThree" />
<InputSelect #bind-Value="SelectedValidationProperty">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</InputSelect>
#*<ValidationSummary />*#
</EditForm>
#code {
private DemoInputModel _model = new DemoInputModel
{
PropertyOne = "Test",
PropertyTwo = "42",
PropertyThree = "Math.PI",
};
private String _selectedValidationProperty;
public String SelectedValidationProperty
{
get => _selectedValidationProperty;
set
{
_selectedValidationProperty = value;
ChangeValidator(value);
}
}
public Expression<Func<String>> ValidationResolver { get; set; }
protected override void OnInitialized()
{
SelectedValidationProperty = "1";
base.OnInitialized();
}
public void ChangeValidator(String value)
{
switch (value)
{
case "1":
ValidationResolver = () => _model.PropertyOne;
break;
case "2":
ValidationResolver = () => _model.PropertyTwo;
break;
case "3":
ValidationResolver = () => _model.PropertyThree;
break;
default:
break;
}
}
}
Did you mean something like this? It gets slightly more complicated if your model doesn't have only strings, like in the example. A "quick" workaround could be to have an Expression for each possible type.
Under the hood, the expression is used to create a FieldIdentifier. The FieldIdentifier is then used to get the corresponding property/field from the EditContext to check the validation status. Hence, you are constrained in what to choose for the expression. The error message FieldIdentifier only supports simple member accessors (fields, properties) of an object gives a good indication of this limitation.
I want to pass a Func instead
Why? If there isn't a specific reason why you should pass Func<TValue> instead of Expression<Func<TValue>>, just have the parameter
[Parameter]
public Expression<Func<string>> PropertyLocator { get; set; }
If you want only a Func<> because you are going to reuse it for something else other than the For parameter of ValidationMessage, you can take a look at Extracting Func<> from Expression<> to get a Func<> from the Expression<Func<string>> PropertyLocator.
If you really want to pass a Func<>, maybe you will get some problems to transform when trying to convert a .net Func to a .net Expression<Func>.
After some research I stumbled about the following blazor feature:
The holy trinity of blazor bindings
Read more about it here.
In short, if a [Parameter] is bound with the follwoing syntax...
<MyComponent #bind-Value="My.Binding.Path" />
... it not only supports two-way bindings, but it also sets a locator expression.
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public Expression<Func<string>> ValueExpression { get; set; }
you may use any type, instead of string
since the value of the ValueExpression is set automatically, you can use this behavior to display the validation message for the bound property. Simply add the ValidationMessage Component to your component with the expression.
<ValidationMessage For="ValueExpression" />
A little extra
If you're building a Component that supports Validation (which at this point, I assume you are). The following might also be interesting for you.
Not only can you use the holy trinity to display validationmessages, but also to create Components supporting validation. There are many articles covering this topic.
In short:
Build your component
Notify field changes on the EditContext whenever needed
To make the above created MyComponents Value Property support validation, just follow these steps.
Define a CascadingParameter EditContext, this gets the current EditContext, usually from the EditForm Component. Also note that the EditContext may not be set, if there's no CascadingValue. For example if the Component isn't placed inside an EditForm:
[CascadingParameter]
public EditContext? EditContext
Define a property to store a FieldIdentifier and set it when parameters are set.
public FieldIdentifier? FieldIdentifier { get; private set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
await base.SetParametersAsync(parameters);
if (this.EditContext != null && this.DateExpression != null && this.FieldIdentifier?.Model != this.EditContext.Model)
{
this.FieldIdentifier = Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create(this.DateExpression);
}
}
Trigger the validation for the Field whenever you need (usually after the invocation of ValueChanged):
this.Value = value;
this.ValueChanged.InvokeAsync(this.Value);
if (this.FieldIdentifier?.FieldName != null)
{
this.EditContext?.NotifyFieldChanged(this.FieldIdentifier!.Value);
}

How to bind to value of component rendered with the component tag helper

I have a need to use some custom inputs built as razor components on pre-existing views and pages, but can't seem to get it to work using the component tag helper. For example, the component code that I've been testing with (from https://chrissainty.com/creating-bespoke-input-components-for-blazor-from-scratch/) at first results in an exception because ValueExpression ends up being null (no options for binding using the tag helper, from what I can tell). If I then set ValueExpression myself, I end up with a json exception (object cycle detected). I think maybe because the mechanism for moving parameters from the tag helper to the underlying component doesn't support Func<> objects? Not sure.
Am I trying to use the tag helper incorrectly perhaps? I'm using it in other places to render self-contained components (like an entire EditForm), and that seems to be working fine, but how to get it working in this particular use case eludes me :(
Inside .cshtml file I want the control to render in:
<component type="typeof(MyComponent)" render-mode="ServerPrerendered" param-ValueExpression="(Func<string>)(() => LocalProperty)" />
MyComponent.razor
<input class="_fieldCssClasses" value="#Value" #oninput="HandleInput" />
#if (_showValidation) {
<div class="validation-message">You must provide a name</div>
}
#code {
private FieldIdentifier _fieldIdentifier;
private string _fieldCssClasses => EditContext?.FieldCssClass(_fieldIdentifier) ?? "";
private bool _showValidation = false;
[CascadingParameter] private EditContext EditContext { get; set; }
[Parameter] public string Value { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
[Parameter] public bool Required { get; set; }
protected override void OnInitialized() {
_fieldIdentifier = FieldIdentifier.Create(ValueExpression);
}
private async Task HandleInput(ChangeEventArgs args) {
await ValueChanged.InvokeAsync(args.Value.ToString());
if (EditContext != null) {
EditContext.NotifyFieldChanged(_fieldIdentifier);
} else if (Required) {
_showValidation = string.IsNullOrWhiteSpace(args.Value.ToString());
}
}
}
So to resolve this, I just made a use-case-specific wrapper component around the inner component, making sure to also pass in an Id that the inner component will assign to the name and id attributes of the input it's rendering (allows it to "bind" when you submit the form), and then included the wrapper component on the page/view. The wrapper simply satisfies the dependencies of the inner component, which the component tag helper does not provide a mechanism for if the dependency is more complex (like a Func<> object).
MyComponentWrapper.razor:
<div class='purdy'>
<MyComponent Id="#Id" #bind-Value="Value"></MyComponent>
</div>
...
#code {
[Parameter]
public string Id { get; set; }
[Parameter]
public string Value { get; set; }
...
}
MyContainingPage.cshtml:
<component type="typeof(MyComponentWrapper)" render-mode="ServerPrerendered" param-Id="myFormInput" param-Value="LocalProperty" />
...

.NET MVC Updating the View with a model property

I used the approach described in this article to create a drop down.
The Model
public class IceCreamFlavor
{
public int Id { get; set; }
public string Name { get; set; }
}
The View Model
public class ViewModel
{
private readonly List<IceCreamFlavor> _flavors;
[Display(Name = "Favorite Flavor")]
public int SelectedFlavorId { get; set; }
public IEnumerable<SelectListItem> FlavorItems
{
get { return new SelectList(_flavors, "Id", "Name");}
}
}
The View
#Html.LabelFor(m=>m.SelectedFlavorId)
#Html.DropDownListFor(m => m.SelectedFlavorId, Model.FlavorItems)
#Html.ValidationMessageFor(m=>m.SelectedFlavorId)
<input type="submit" value="Submit" />
This approach works fine.
Now I want to display a property of the Model on the same view. As an example assume we had the following properties.
public class IceCreamFlavor
{
public int Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
}
Now underneath the Dropdown I need to display the price as
Price : 15.99
How can I achieve this?
I would rather choose a another solution, since firing ajax for every selected input is useless and consuming.
Using the normal current DropDownListFor in addition with outputting the complete price list to hiding input value. value e.g: '10.0;12.0;...' which every value is than can be taken by simple JavaScript procedure with the option index as the mark for the value you should take.
Constructing a new MyDropDownListFor which will follow as the current but instead of just constructing normal <option>.. it will also add to that html tags the price or whatever additional parameter you want it to display as well. Examples: Here Here Here
No matter what solution you take, it will have to be combined with supporting simple JavaScript method which then renders the Selection and Displaying the Price which already been downloaded.
To render a property off of the model after submitting, you can just break into HTML to display it:
#if (Model.Price != 0.0F)
{
<b>Price #Model.Price.ToString("0.00") </b>
}
To achieve this, add a collection onto the ViewModel:
public class ViewModel
{
private readonly System.Collections.Generic.List<IceCreamFlavor> _flavors;
public ViewModel()
{
// Construct Flavors
}
public List<IceCreamFlavor> AllFlavors
{
get
{
return _flavors;
}
}
[Display(Name = "Favorite Flavor")]
public int SelectedFlavorId { get; set; }
public System.Web.Mvc.SelectList FlavorItems
{
get { return new System.Web.Mvc.SelectList(_flavors, "Id", "Name");}
}
}
Then on the View:
#if (Model.AllFlavors.Any(f => f.Id == Model.SelectedFlavorId))
{
<b>Price #Model.AllFlavors.First(f => f.Id == Model.SelectedFlavorId).Price.ToString("0.00") </b>
}
You could, of course, just expose the selected Flavor as a property on the ViewModel (similar display principle applies). But, the advantage of exposing all the Flavors as a property, is you can easily move to storing this in JavaScript on page and query that, rather than relying on the submit button.
Then you can roll your own drop down onchange events using JavaScript / JQuery to read from this object stored on page. (Or use AJAX to make a call to another action to return the value as needed..)
The solution not exposing all flavors is:
Property on ViewModel:
public IceCreamFlavor SelectedFlavor
{
get
{
return _flavors.FirstOrDefault(f => f.Id == this.SelectedFlavorId);
}
}
Display on View:
#if (Model.SelectedFlavor != null)
{
<b>Price #Model.SelectedFlavor.Price.ToString("0.00") </b>
}

An object used as a model in a partial view creates another in the controller?

There is something weird going on in my app. It's not dammageable, but it's a curious behavior and I'm reaching out to you to understand what's happening.
I was working on some partial view based on a model, and it worked. I figured out I had to replace a lot of stuff with the correct input.
So here's a snippet of my old model:
public class SearchObjInfo
{
public string m_ObjName { get; set; }
public string m_ObjType { get; set; }
public decimal? m_ObjNumber { get; set; }
public string m_ObjSymbol { get; set; }
public string m_ObjPower { get; set; }
}
And here's the same snippet with the new class I made to construct this partial view:
public class SearchObjInfoPartial
{
public string m_ObjName { get; set; }
public IEnumerable<SelectListItem> m_ObjType { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:0}")]
public int m_ObjNumber { get; set; }
public IEnumerable<SelectListItem> m_ObjPower { get; set; }
public IEnumerable<SelectListItem> m_ObjSymbol { get; set; }
}
Now the way the render is made is actually quite identical, and not. I used lots of stuff like these before:
<label>
Text: Write a name, part of a name, or a word.
</label>
Object Name: #Html.TextBox("_objectName") <br/>
Object Number: <input type="number" min="0" max="9999" name="_objNumber" value="decimal" style="width: 70px"/><br/>
Type: #Html.DropDownList("_objType", "All") <br/>
Power: #Html.DropDownList("_objSymbol", "=") #Html.DropDownList("_objValue", String.Empty)<br/>
But now I render my partial view this way:
#model MyApp.Utilities.SearchObjInfoPartial
Object Name: #Html.TextBoxFor(item => item.m_ObjName, Model.m_ObjName, String.Empty) <br/>
Object Number: #Html.EditorFor(item => item.m_ObjNumber)<br />
Power: #Html.DropDownListFor(item => item.m_ObjPower, Model.m_ObjPower, String.Empty) #Html.DropDownListFor(item => item.m_ObjSymbol, Model.m_ObjSymbol, String.Empty)
Type: #Html.DropDownListFor(item => item.m_ObjType, Model.m_ObjType, String.Empty) <br/>
Before rendering I deal with the SelectLists, no problems here.
Now here's where it gets interesting:
In my controllers I used to have methods receiving huge amounts of data (see here: How to deal with many possible values to make a query?)
But now I made something else. Without thinking, I tried to add the old search model in the controller method like this:
public ActionResult BrowseObjectList(SearchObjInfo searchObj, string _objName, (...))
And I just found out that it works even if the receiving object is not the same as the one used in my partial view model. How is that even possible? I mean, the proper fields will fill up and I can "safely" deal with my searchObj item, though I do not find this secure after all...
Thats what MVC framework does for you man.
Browser simply sends the form collection to server as Name Value Collection. As the request hits server, MVC framework will match the values with parameter in the Action method.
Form collection values are mapped to Model object properties. This is done by doing a match with Property Name and Name of the value in Form collection. Just check the client side code by view source, you can see that the input tags will have an attribute 'name' which matches with the property name of model.
QueryString values will also be mapped to parameters in Action method based on name.
Even you add a hidden field and specify a parameter with same name in action method...tada you will get the value of hidden field in that variable on post back
In your case though the model is different, its property name are same m_ObjName, m_ObjType, m_ObjNumber, m_ObjSymbol, m_ObjPower. So MVC do a match for you.
Try with different property name and see the results ;-)

Categories

Resources