ASP.Net MVC 6 PartialView model binding confusion - asp.net-core-mvc

Background
We need to submit model with items. For that purpose, we make this complex model:
public class PaymentViewModel
{
public decimal? Payment { get; set; }
public string Description { get; set; }
public List<SplittedPaymentViewModel> SplittedPayment { get; set; } = new List<SplittedPaymentViewModel>();
}
public class SplittedPaymentViewModel
{
public short SplittedPaymentId { get; set; }
public decimal? Payment { get; set; }
public string Description { get; set; }
}
For rendering html, we this two views, one regular: CreatePayment
#model ZevApp.ViewModels.Record.PaymentViewModel
...
<input class="form-control" asp-for="Payment" type="number" />
<input class="form-control" asp-for="Description" />
...
<div class="panel-body" id="SplittedPayments">
#foreach (var spItem in Model.SplittedPayment)
{
#Html.Partial("SplittedPaymentPartial", spItem);
}
...
</div>
And the other Partial: SplittedPaymentPartial
#model ZevApp.ViewModels.Record.SplittedPaymentViewModel
...
<input class="form-control" asp-for="Payment" type="number" />
<input class="form-control" asp-for="Description" />
...
As you can see, from the first view, we call partial view and pass them item by item form the SplittedPayment list.
Unexpectedly behavior
We run the code but unexpectedly behavior is occur:
Each partial view bind Payment and Description form parent PaymentViewModel, not from a passed item?
Does anybody know what is wrong?
Each view define model at the beginning of the file.
From the controller we return PaymentViewModel, and for a test, there are tree SplittedPaymentViewModels. But we can't see values from the items.

I found that problem was CreatePayment.cshtml (thanks to the people from MVC community). It doesn't provide any information about the spItem expression when invoking the partial view.
var savePrefix = ViewData.TemplateInfo.HtmlFieldPrefix;
for (var i = 0; i < Model.SplittedPayment.Count; i++)
{
ViewData.TemplateInfo.HtmlFieldPrefix = Html.NameFor(m => m.SplittedPayment[i]);
var spItem = Model.SplittedPayment[i];
#Html.Partial("SplittedPaymentPartial", spItem);
ViewData.TemplateInfo.HtmlFieldPrefix = savePrefix;
}
You can find full description on https://github.com/aspnet/Mvc/issues/4106

Related

.net core razor page #Html.Displayfor showing a random number

I'm using .net core (.net 5) to create a calculator.
Here's my model:
public class VogelsRulePlusModel
{
[Required]
[Range(0,30)]
public double SpherePower { get; set; }
[Required]
[Range(-30,0)]
public double CylinderPower { get; set; }
[DisplayFormat(DataFormatString = "{0:0.00}")]
public double Result { get; set; }
}
To display the output of the calculation, I'm using the the following tag "helper"
<div class="form-group">
<label asp-for="Result" class="control-label">#Html.DisplayFor(m => Model.Result)</label>
</div>
For some reason, when the page renders, I get a random number.
Here's the html this tag "helper" creates:
<div class="form-group">
<label class="control-label" for="Result">6.00</label> <-- ??
</div>
Does anyone know why this is happening and how to make it stop?
UPDATE: I'm such a doofus...again, I had the wrong controller in here.
This is what I'm using for the view.
public IActionResult VogelsRulePlus(VogelsRulePlusModel baseCurve)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
baseCurve.Result = BaseCurve.VogelsRulePlus(baseCurve.SpherePower, baseCurve.CylinderPower);
return View(baseCurve);
}

Connecting Telerik Blazor Control to Entity and Ardalis.SmartEnum

Using SmartEnum's https://github.com/ardalis/smartenum to handle Entity Framework 5.0 fields for things such as State, Country, Etc.
public sealed class StatesEnum : SmartEnum<StatesEnum>
{
public static readonly StatesEnum NotSpecified = new StatesEnum("(Select One)", 0);
public static readonly StatesEnum Alabama = new StatesEnum("Alabama", 1);
public static readonly StatesEnum Alaska = new StatesEnum("Alaska", 2);
public static readonly StatesEnum Arizona = new StatesEnum("Arizona", 3);
public static readonly StatesEnum Arkansas = new StatesEnum("Arkansas", 4);
public static readonly StatesEnum California = new StatesEnum("California", 5);
...
}
I am creating a Blazor Component for creating a job posting in our company's job board that uses these SmartEnum field types as one of the values in the entity. In the component I am using Telerik's Blazor DropDownList https://docs.telerik.com/blazor-ui/components/dropdownlist/overview to handle the fields that use these SmartEnum values. I have it correctly displaying the enum text and values using this code:
<TelerikDropDownList Id="dropStates" Data="#StatesEnum.List" TextField="#nameof(StatesEnum.Name)" ValueField="#nameof(StatesEnum.Value)" #bind-Value="#JobPostingVM.StateId"></TelerikDropDownList>
I am attempting to directly connect my View Model class (JobPostingVM) to the drop down list instead of creating a separate property for the selected value but I run into issues with the code complaining that you cant cast a SmartEnum to a type of int. Looking for a elegant solution outside of setting a property to store each selected value in this long form.
I am a Support Engineer with Telerik on the UI for Blazor team. I did some testing with the described scenario and found that it will require using a value external to the model.
For example, use a selectedValue variable as shown in the Bind to a Model documentation. The selectedValue variable can be set in the OnInitializedAsync method. Let me provide an example of this below.
Example
In the example, I am using a person model that is bound to a form with a SmartEnum for the SelectedState property. I am also setting default values in the model so that it displays the binding on page load. Let's go over each below.
Person Model
The person class is a basic CLR object with default values set for the collection properties.
public class Person
{
// Added for test
[Required(ErrorMessage = "Select a state")]
public StatesEnum SelectedState { get; set; } = StatesEnum.Alabama; // Default Value
// Original properties
[Required(ErrorMessage = "Enter your first name")]
[StringLength(10, ErrorMessage = "That name is too long")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Enter your last name")]
[StringLength(15, ErrorMessage = "That name is too long")]
public string LastName { get; set; }
[Required(ErrorMessage = "An email is required")]
[EmailAddress(ErrorMessage = "Please provide a valid email address.")]
public string Email { get; set; }
public int? Gender { get; set; } = 4; // Default Value
[Required]
[Range(typeof(DateTime), "1/1/2020", "1/12/2030", ErrorMessage = "Value for {0} must be between {1:dd MMM yyyy} and {2:dd MMM yyyy}")]
public DateTime StartDate { get; set; }
[Required(ErrorMessage = "Choose the team and technology you want to work on")]
public string PreferredTeam { get; set; } = "Blazor"; // Default Value
}
The SelectedState SmartEnum
The selected state is a property dervid from the SmartEnum implementation.
public sealed class StatesEnum : SmartEnum<StatesEnum>
{
private StatesEnum(string name, int value) : base(name, value)
{
}
public static readonly StatesEnum NotSpecified = new StatesEnum("(Select One)", 0);
public static readonly StatesEnum Alabama = new StatesEnum("Alabama", 1);
public static readonly StatesEnum Alaska = new StatesEnum("Alaska", 2);
public static readonly StatesEnum Arizona = new StatesEnum("Arizona", 3);
public static readonly StatesEnum Arkansas = new StatesEnum("Arkansas", 4);
public static readonly StatesEnum California = new StatesEnum("California", 5);
}
Blazor Component
The custom form comonent then uses the OnInitializedAsync method to populate the model and set the selected value.
#page "/"
#using TBACS.DropDownListSmartEnumBinding.Models
<div class="container-fluid">
<div class='row my-4'>
<div class='col-12 col-lg-9 border-right'>
<div class="row example-wrapper">
<div class="col-xs-12 col-sm-6 offset-sm-3 example-col card p-4">
<EditForm Model="#person" OnValidSubmit="#HandleValidSubmit" class="k-form">
<DataAnnotationsValidator />
<fieldset>
<legend>User Details</legend>
<div class="form-group">
<label for="StatesDDL">
<span>Select State <span class="k-required">*</span></span>
</label>
<TelerikDropDownList Data="#StatesEnum.List"
#bind-Value="#selectedValue"
TextField="#nameof(StatesEnum.Name)"
ValueField="#nameof(StatesEnum.Value)"
Id="StatesDDL"
Width="100%">
</TelerikDropDownList>
</div>
<div class="form-group">
<label for="FNameTb">
<span>First Name <span class="k-required">*</span></span>
</label>
<TelerikTextBox #bind-Value="#person.FirstName" Width="100%" Id="FNameTb" />
</div>
<div class="form-group">
<label for="LNameTb">
<span>Last Name <span class="k-required">*</span></span>
</label>
<TelerikTextBox #bind-Value="#person.LastName" Width="100%" Id="LNameTb" />
</div>
<div class="form-group">
<label for="GenderDDL">
<span>Gender <span class="k-field-info">optional</span></span>
</label>
<TelerikDropDownList #bind-Value="#person.Gender" DefaultText="Select gender"
Data="#genders" TextField="Text" ValueField="Id"
Width="100%" PopupHeight="auto" Id="GenderDDL">
</TelerikDropDownList>
</div>
<div class="form-group">
<label for="EmailTb">
<span>Email <span class="k-required">*</span></span>
</label>
<TelerikTextBox #bind-Value="#person.Email" Width="100%" Id="EmailTb" />
</div>
</fieldset>
<fieldset>
<legend>Team Preferences</legend>
<div class="form-group">
<label for="StartDateDP">
<span>Start date <span class="k-required">*</span></span>
</label>
<TelerikDatePicker #bind-Value="#person.StartDate" Width="100%" Id="StartDateDP" />
</div>
<div class="form-group">
<label for="TeamDDL">
<span>Preferred Team <span class="k-required">*</span></span>
</label>
<TelerikDropDownList #bind-Value="#person.PreferredTeam"
DefaultText="Preferred team" Id="TeamDDL"
Data="#Teams" Width="100%">
</TelerikDropDownList>
</div>
</fieldset>
<ValidationSummary />
#if (ShowSuccessMessage)
{
<div class="alert alert-info">
Application form submitted Successfully, we will get back to you
</div>
}
<div class="text-right">
<TelerikButton ButtonType="#ButtonType.Button" OnClick="#CancelForm">Cancel</TelerikButton>
<TelerikButton ButtonType="#ButtonType.Submit" Primary="true">Submit</TelerikButton>
</div>
</EditForm>
</div>
</div>
</div>
</div>
</div>
#code{
Person person { get; set; }
bool ShowSuccessMessage { get; set; }
protected override Task OnInitializedAsync()
{
// Bind model on load
GetDefaultPerson();
return base.OnInitializedAsync();
}
async void HandleValidSubmit()
{
// implement actual data storage here
ShowSuccessMessage = true;
await Task.Delay(2000);
ShowSuccessMessage = false;
GetDefaultPerson();
StateHasChanged();
}
void CancelForm()
{
GetDefaultPerson();
}
// External selected value primitive
int selectedValue { get; set; } = 0;
void GetDefaultPerson()
{
// in reality you may be pulling data from a service or authentication logic
// not only for the form model, but also for the data sources below
person = new Person()
{
StartDate = DateTime.Now.AddDays(7)
};
// Get the selected value from the model.
selectedValue = person.SelectedState.Value;
}
IEnumerable<DropDownModel> genders = new List<DropDownModel>
{
new DropDownModel {Text = "female", Id = 1},
new DropDownModel {Text = "male", Id = 2},
new DropDownModel {Text = "other", Id = 3},
new DropDownModel {Text = "I'd rather not say", Id = 4}
};
List<string> Teams = new List<string>
{
"Blazor", "Python", "Ruby", "Java", "JavaScript", "Assembler"
};
}
Misc. Models
The other models for the example are built around the DropDown.
Menu Item
public class MenuItem
{
public string Text { get; set; }
public string Url { get; set; }
public List<MenuItem> Items { get; set; }
}
DropDownModel
public class DropDownModel
{
public int? Id { get; set; }
public string Text { get; set; }
}
Wrapping Up
I believe the example answers the question. From what I understand, it seems like the result is what you were hoping to avoid, unfortunately. Let me know if you have any additional questions.
I hope this helps! Thank you for developing with UI for Blazor.
Eric D. Rohler

How do I presist data from the view during EDIT GET to EDIT POST?

I have a dilemma that I hope someone can help me with. During the Edit Get stage of loading and updating a page before it is loaded, I store the checkboxes that are checked like this:
foreach (var course in courses.Select((x, i) => new { Data = x, Index = i }))
{
#: <td>
<br /><br /><br />
<input type="checkbox" id="checkbox" name="selectedCourses" value="#course.Data.CourseID"
#(Html.Raw(course.Data.Assigned ? "checked=\"checked\"" : "")) />
</td>
bool theNewString = course.Data.Assigned;
String a = theNewString.ToString();
assignedCourses.Add(a);
}
At the top of the view I defined the list to store the data like this so that I can be sent in the BeginForm:
#{ List<String> assignedCourses = new List<String>(); }
Then I try to send the list to the Edit POST like this:
#using (Html.BeginForm(new { assigned = assignedCourses }))
In my controller signature looks like this:
[HttpPost]
public ActionResult Edit(int id, List<String> assigned)
The list is loaded at the end of the get stage but the data in the list does not go through to Edit POST. My question is how do I get the list that I created at the end of the edit get stage to persist so that I can use it in my post?
Thanks for any ideas.
I'll try make this as simple as possible. So your Course model is something like this...
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public bool Assigned { get; set; }
}
...and you want to display some saved values on screen to edit. So starting at the controller, I've dummied some data and set one of the Assigned booleans to true so it is checked when the page loads.
public Controller Courses
{
public ActionResult Edit(int id)
{
var courses = new Course[] {
new Course() { Id = 1, Name = "maths", Assigned = true },
new Course() { Id = 2, Name = "english", Assigned = false },
new Course() { Id = 3, Name = "science", Assigned = false }
};
return View(courses);
}
Now, your page should expect a collection of these courses, so at the top of your page define the type that the View is expecting...
cshtml
#model IEnumerable<ExampleProject.Domain.Course>
...which you can then enumerate through in your View and create your checkboxes.
#using (Html.BeginForm("Edit", "Courses", FormMethod.Post))
{
#foreach(var item in Model)
{
<label for="#item.Id">#item.Name</label>
<input type="checkbox" name="courses" id="#item.Id" value="#item.Id" checked="#item.Assigned" />
}
<button type="submit">Save changes</button>
}
This will render the following html:
<label for="1">maths</label>
<input type="checkbox" name="courses" id="1" value="1" checked="checked">
<label for="2">english</label>
<input type="checkbox" name="courses" id="2" value="2">
<label for="3">science</label>
<input type="checkbox" name="courses" id="3" value="3">
Now you can check what you like and submit your form. In true http fashion your controller action will receive the values were checked on the View:
[HttpPost]
public ActionResult Courses(int id, int[] courses)
{
// courses will contain the values of the checkboxes that were checked
return RedirectToAction("Index"); // etc
}
Hopefully this helps. You can be a bit smarter and use the Html helpers for some more complicated binding (eg. Html.CheckBox, Html.CheckBoxFor) but this should get you going and it's clear to see what's going on.

Razor view dropdown list for a model class in MVC3

I have two model class in MVC3 one for Services which have those properties
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Image { get; set; }
public int ChildOf { get; set; }
It also have a DB table by Entityframework
Another model is Quata which have those properties
public int ID { get; set; }
public string Sender_Name { get; set; }
public string Description { get; set; }
.....
......
public Services Service_ID { get; set; }
It also have a DB table by Entityframework
I want to create a Razor(C#) view (for Quata) where user can send a quata by fill a html form but where i wanna show a dropdown list with Services ID as dropdown value and Services Name as dropdown text which is also come dynamically from the Services DB table .
My question is how i should create that dynamic dropdown list by #Html.DropDownListFor ? and send the selected data from that dropdown list to a Controller ?
Try this
Controller:
public ActionResult Create()
{
var Services = new Services();
Services.Load(); //load services..
ViewBag.ID = new SelectList(Services.ToList(), "Id", "Name");
return View();
}
[HttpPost]
public ActionResult Create(Quata Quata)
{
//save the data
}
A strong Typed View: (Using Razor)
#model Quata
#using (Html.BeginForm()) {
<fieldset>
<legend>Quata</legend>
<div>
#Html.LabelFor(model => model.Service_ID.ID, "Service")
</div>
<div>
#Html.DropDownList("ID", String.Empty)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
take a look at #Html.DropDownListFor
So say your viewmodel has a list of said Services.
Something that may work for you is the following (you may not need a for loop here, editor is supposed to eliminate that, but I had some weird binding issues).
In your top level view which points at your viewmodel (#model Quata, assuming Quata is your viewmodel) have this code :
#For i = 0 To Model.DropdownListInput.Count - 1
Dim iterator = i
#Html.EditorFor(Function(x) x.DropdownListInput(iterator), "EnumInput")
Next
In your Editor Template (create a subfolder under the view folder this dropdownlist will be in called editor templates and name the template whatever you desire, mine was EnumInput).
In your editor template, which should point at your model for Services (#model Services) have something like the following code (with substitutions for your appropriate variable names):
#<div class="editor-label">
#Html.LabelFor(Function(v) v.value, Model.DisplayName)
</div>
#<div class="editor-field">
#Html.DropDownListFor(Function(v) v.value, New SelectList(Model.ParamEnums, "ValueForScript", "EnumValue"), "--Please Select A Value--")
#Html.ValidationMessageFor(Function(v) v.value)
</div>
Replace the list with your list and the lambda values with yours (#Html.DropDownListFor(x => x.id, New SelectList(x.ServiceList, "ID", "Name"), "--Please Select A Value--") or something like that.
Note that this code is in VB, but it should provide a rough guide.

MVC3 :: Passing an object and an HttpPostedFile in an action

I'm having problems getting an uploaded file (HTTPPostedFile) and an object posted to an action. I have a class called widget:
public class Widget
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FilePath { get; set; }
}
and in the Widget controller I have an 'Add' method
public ActionResult Add()
{
return View();
}
and an overloaded method to accept what the user posts back
[HttpPost]
public ActionResult Add(Widget widget, HttpPostedFile file)
{
// Save posted file using a unique
// Store the path/unique name in Widget.FilePath
// Save new Widget object
return View();
}
and in the View I have the following:
#model Project.Models.Widget
#{
using(Html.BeginForm())
{
Html.LabelFor(model => model.FirstName)<br />
Html.TextBoxFor(model => model.FirstName)<br />
Html.LabelFor(model => model.LastName)<br />
Html.TextBoxFor(model => model.LastName)<br />
<input type="file" id="file" /><br />
<input type="submit" value="Save" />
}
}
What I want to do is have the user fill out the form and select a file to upload. Once the file is uploaded, I want to save the file off using a unique name and then store the path of the file as widget.FilePath.
Each time I try, the widget object is populated, but the uploadedFile is null.
Any Help would be greatly appreciated.
There are a couple of issues with your code.
Make sure you have set the proper enctype="multipart/form-data" to your form, otherwise you won't be able to upload any files.
Make sure that your file input has a name attribute and that the value of this attribute matches the name of your action argument. Assigning an id has no effect for the server side binding.
For example:
#model Project.Models.Widget
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.LabelFor(model => model.FirstName)<br />
#Html.TextBoxFor(model => model.FirstName)<br />
#Html.LabelFor(model => model.LastName)<br />
#Html.TextBoxFor(model => model.LastName)<br />
<input type="file" id="file" name="file" /><br />
<input type="submit" value="Save" />
}
Also make sure that your controller action works with a HttpPostedFileBase instead of HttpPostedFile:
[HttpPost]
public ActionResult Add(Widget widget, HttpPostedFileBase file)
{
// Save posted file using a unique
// Store the path/unique name in Widget.FilePath
// Save new Widget object
return View();
}
Also you could merge the 2 parameters into a single view model:
public class Widget
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FilePath { get; set; }
public HttpPostedFileBase File { get; set; }
}
and then:
[HttpPost]
public ActionResult Add(Widget widget)
{
// Save posted file using a unique
// Store the path/unique name in Widget.FilePath
// Save new Widget object
return View();
}
Finally read the following blog post: http://haacked.com/archive/2010/07/16/uploading-files-with-aspnetmvc.aspx

Resources