Postback a single item on a Razor ListBoxFor - asp.net-mvc

I have a ListBoxFor helper that is populated with users that have to be verified from a db. I click a single user and then click a submit button. This works fine and sets the admin verified bit in the db to true.
However what I am trying to do is on an item in the list being clicked, a value auto posted back and then I will fill a textarea with the users description. I gather I will use AJAX but have found it hard to get good documentation on using AJAX with HTMLHelpers in the this way.
EDIT: Updated the Model, View and Controller as per suggestions.
Model:
public class UserAdminVerifyModel
{
public SelectList ToBeVerifiedAdmin { get; set; }
public string[] SelectedUsers { get; set; }
public List<string> userdesc { get; set; }
}
Controller:
public ActionResult AdminVerifyListBox()
{
UserAdminVerifyModel verifusermodel = new UserAdminVerifyModel();
verifusermodel.ToBeVerifiedAdmin = GetUsersToBeVerified();
return View(verifusermodel);
}
View:
#using (Html.BeginForm("AdminVerifyListbox", "UserRegLog"))
{
#Html.ListBoxFor(x => x.SelectedUsers, Model.ToBeVerifiedAdmin)
<br />
<input type="submit" value="Submit" title="submit" />
}
}

ListBoxFor is used to generate a multiple selection list. This means that you should not be binding it to a simple string property. You should use an array of strings:
public class UserAdminVerifyModel
{
public SelectList ToBeVerifiedAdmin { get; set; }
public string[] SelectedUsers { get; set; }
public List<string> userdesc { get; set; }
}
and in the view bind the ListBoxFor to the SelectedUsers collection property:
#using (Html.BeginForm("AdminVerifyListbox", "UserRegLog"))
{
#Html.ListBoxFor(x => x.SelectedUsers, Model.ToBeVerifiedAdmin)
<br />
<input type="submit" value="Submit" title="submit" />
}
Also your ToBeVerifiedAdmin is already a SelectList. You should not be calling the constructor once again in your view. This should be done in your controller action which is responsible for populating this ToBeVerifiedAdmin property from wherever your information is stored.

Related

MVC Return Fields to Controller

I've got a controller to retrieve and return values for my drop down, and a second, that when an option from the dropdown is selected, uses the values (Title and ID) in an API Request.
Controllers
public ActionResult GetEpics()
{
//Code to retrieve list
Epics = new GetEpicsViewModel();
Epics.Epics = epicsList;
return View(Epics);
}
[HttpPost]
public ActionResult Build(GetEpicsViewModel epic)
{
GetEpicsViewModel epicTest = epic;
//API Request
return View();
}
This is displayed in my drop down list as below:
View
#using (Html.BeginForm("Build", "GetEpics", FormMethod.Post))
{
<label for="input_OutputType"> Process: #Html.DropDownListFor(model => model.Id, new SelectList(Model.Epics, "Id", "Title")) </label>
<input type="submit" value="Submit" />
}
This works fine, but how would I then go about passing both the Title and ID to my controller?
I can pass the ID through fine, but cant figure out how to pass the Title as well.
Screenshot
Models
public class DevOpsEpic
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
and
public class GetEpicsViewModel
{
public string Id { get; set; }
public string Title { get; set; }
public List<DevOpsEpic> Epics { get; set; }
}
Realise this is probably a really simple answer, but just cant figure it out!
You can use jQuery for that, so when your dropdown is changed, set title value in hidden file.
#using (Html.BeginForm("Build", "GetEpics", FormMethod.Post))
{
<label for="input_OutputType"> Process: #Html.DropDownListFor(model => model.Id, new SelectList(Model.Epics, "Id", "Title"),new { name = "Id" }) </label>
<input type="hidden" id="Title" name="Title" />
<input type="submit" value="Submit" />
}
$('#dropdownId').change(function(){
$('#Title').val($('#dropdownId option:selected').text());
});

MVC Core - strange view rendering issue

I am using MVC to display a simple form in a view:
ViewModel:
public class CreateSaleViewModel
{
public string OrderId { get; set; }
public decimal TotalAmount { get; set; }
public bool ShowInstoreConfirmDetails { get; set; }
}
Controller action:
[HttpGet]
public IActionResult CreateSale()
{
return View(new CreateSaleViewModel());
}
View:
#model CreateSaleViewModel
<form asp-controller="Sales" asp-action="CreateSale" method="post">
<input asp-for="OrderId" />
<input asp-for="TotalAmount" />
<button type="submit" name="CreateSale" id="CreateSale">
button
</button>
</form>
I then post to a new view, where the same details need to be entered. To do this I store the old values in hidden inputs and provide another form to re-enter the details.
ViewModel:
public class ConfirmDetailsViewModel
{
public string OrderId { get; set; }
public decimal TotalAmount { get; set; }
public string ConfirmOrderId { get; set; }
public decimal ConfirmTotalAmount { get; set; }
}
Controller:
[HttpPost("Confirmdetails")]
public IActionResult ConfirmDetails(CreateSaleViewModel model)
{
var viewModel = new ConfirmDetailsViewModel
{
ConfirmOrderId = model.OrderId,
ConfirmTotalAmount = model.TotalAmount,
OrderId = string.Empty,
TotalAmount = 0.0m
};
return View("ConfirmDetails", viewModel);
}
View:
#model ConfirmDetailsViewModel
<form asp-controller="Sales" asp-action="Summary" method="post">
<input type="hidden" value="#Model.ConfirmOrderId" id="OrderIdConfirm" />
<input type="hidden" value="#Model.ConfirmTotalAmount" id="TotalAmountConfirm" />
<input type="hidden" value="#Model.OrderId" id="banana" />
<input asp-for="OrderId" />
<input asp-for="TotalAmount" />
<button type="submit" name="CreateSale" id="CreateSale">
button
</button>
</form>
My problem is on the confirmdetails view orderId and TotalAmount retain the values that were posted from the previous page.
I have debugged the controller and can see the ConfirmOrderId and ConfirmTotalAmount properties have the correct values, and also OrderId and TotalAmount are empty strign and 0 respectively.
Even stranger is that
<input type="hidden" value="#Model.OrderId" id="banana" />
Has the correct value of "".
Does anyone know what is causing this issue?
MVC stores the posted back values in ModelState.
These values are used by default in #Html helpers - as a convenience. This allows the values of hidden form fields to be preserved through postbacks, even if they don't have properties in the view-model.
Unfortunately what is usually a convenience turns into a headache, if you try to modify the model's properties within the action. Helpers take their values from ModelState, ignoring the updated view-model.
To solve this, call ModelState.Clear()
removes all the posted back values from ModelState
the helpers will now use the values from the view-model.
Controller:
[HttpPost]
public IActionResult ConfirmDetails(CreateSaleViewModel model)
{
var viewModel = new ConfirmDetailsViewModel
{
ConfirmOrderId = model.OrderId,
...
};
ModelState.Clear(); // force asp-helpers to use the updated model's values
return View("ConfirmDetails", viewModel);
}

ASP.NET MVC view model binding: how to populate a collection of objects?

Until some days ago it was quite easy to manage model binding in my application. I had a view model, called PersonOfferDTO, containing a collection of PersonProductOfferDTO. (yes, I'm using the DTO as a view model because a view model in this case would be equal to the DTO). Here below a simplified version of PersonOfferDTO
public class PersonOfferDTO
{
[DataMember]
public Guid PersonOfferId { get; private set; }
[DataMember]
public ICollection<PersonProductOfferDTO> Offers { get; set; }
}
And here below a simplified version of PersonProductOfferDTO
public class PersonProductOfferDTO
{
[DataMember]
public Guid PersonProductOfferId { get; private set; }
[DataMember]
public Guid PersonOfferId { get; set; }
[DataMember]
public int Quantity { get; set; }
[DataMember]
public decimal UnitPrice { get; set; }
}
I was able to populate the ICollection thanks to the method shown below (HTML code).
<form method="POST" action="/Offers/AddNewPersonOffer">
<input name="PersonProductOffers.Index" value="myKey1" hidden>
<input name="PersonProductOffers[myKey1].Quantity">
<input name="PersonProductOffers[myKey1].UnitPrice">
<input name="PersonProductOffers.Index" value="myKey2" hidden>
<input name="PersonProductOffers[myKey2].Quantity">
<input name="PersonProductOffers[myKey2].UnitPrice">
</form>
But during the last days I have increased the depth of my objects tree, so now I have the following code.
public class PersonOfferDTO
{
[DataMember]
public Guid PersonOfferId { get; private set; }
[DataMember]
public ICollection<PersonOfferParagraphDTO> Paragraphs { get; set; }
}
[DataContract]
public class PersonOfferParagraphDTO
{
[DataMember]
public Guid PersonOfferParagraphId { get; set; }
[DataMember]
public ICollection<PersonProductOfferDTO> PersonProductOffers { get; set; }
}
As you can see there is now one further level between PersonOfferDTO and PersonProductOfferDTO, and I can't figure out how to perform a "multilevel binding": create a PersonOfferDTO with more PersonOfferParagraphDTO each one containing more PersonProductOfferDTO.
NOTE: I don't want to use an incremental index ([0] , [1], ....)... but a string (["myKey"])
EDIT
By request, I add the controller here below
public ActionResult AddNewPersonOffer(PersonOfferDTO offer)
{
if (!UserHasPermissions())
{
return PartialView("_forbidden");
}
var errors = OffersCRUD.AddNewPersonOffer(offer);
if(errors.Count() == 0)
{
return RedirectToAction("Index");
}
return PartialView("_errors", new ErrorsViewModel(errors));
}
If you want to populate them with your own keys, you can define your collections within your view model as a Dictionary<string, YOURCLASS>it accepts a non-integer index value.
Example view model with Dictionary:
public class ViewModelTest
{
public Dictionary<string, Class1> Values { get; set; }
}
Example class to be used in the dictionary collection:
public class Class1
{
public int MyProperty { get; set; }
public Dictionary <string, Class2> MoreValues { get; set; }
}
public class Class2
{
public int AnotherProperty { get; set; }
}
Here's a form that populates the values:
#using (Html.BeginForm())
{
<input type="text" name="Values[yourkey1].MyProperty" />
<input type="text" name="Values[yourkey1].MoreValues[anotherKey1].AnotherProperty" />
<input type="text" name="Values[yourkey2].MyProperty" />
<input type="text" name="Values[yourkey2].MoreValues[anotherKey2].AnotherProperty" />
<input type="submit" />
}
Instead of writing your input tags yourself, you can use the helper methods and enjoy intellisense, assuming that you have your view model defined within the view with the same structure defined in your action method:
#model ViewModelTest
#using (Html.BeginForm())
{
#Html.TextBoxFor(x => x.Values[yourkey1].MyProperty)
#Html.TextBoxFor(x => x.Values[yourkey1].MoreValues[anotherKey1].AnotherProperty)
#Html.TextBoxFor(x => x.Values[yourkey2].MyProperty)
#Html.TextBoxFor(x => x.Values[yourkey2].MoreValues[anotherKey2].AnotherProperty)
<input type="submit" />
}
You'll have to introduce a view model for this of course and not just get away with using your DTO ;).
PS: A DTO shouldn't be used as a domain model either, it's for transporting information around your layers.

ASP.NET MVC 5.0 Complex Model binding

I have a view with the name "Create". This view gets the "SchoolViewModel" which contains two classes:
public class SchoolViewModel
{
public List<Teacher> ListTeacher { get; set; }
public List<SchoolClass> ListSchoolClass { get; set; }
public ClassComplete ClassComplete { get; set; }
}
Each list in "SchoolViewModel" provides data from a database.
At the "Create" page you should be able now to select a teacher and class (DropDownList). The "ClassComplete" object contains the two classes (Teacher and SchoolClass) and the roomname
public class ClassComplete
{
public string RoomName { get; set; }
public SchoolClass SchoolClass { get; set; }
public Teacher Teacher { get; set; }
}
I want only to post the "ClassComplete" object.
My ActionResult
[HttpPost]
public ActionResult Create(ClassComplete cp)
{
// Do something
return View();
}
Edit:
Razor View
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.ListTeacher[0].TeacherName)
#Html.EditorFor(m => m.ListSchoolClass[0].ClassName)
#Html.TextBoxFor(m => m.cl.RoomName)<br />
<input type="submit" value="Click" />
}
Is this the right way ?
best regards
If you want to POST only ClassComplete model you will need to indicate the binding prefix:
[HttpPost]
public ActionResult Create([Bind(Prefix="ClassComplete")] ClassComplete cp)
{
// Do something
return View();
}
and in your view:
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.ClassComplete.RoomName)
<br />
<input type="submit" value="Click" />
}
The TextBoxFor will generate the following input field in the resulting markup:
<input type="text" name="ClassComplete.RoomName" />
Notice the name of the input field. That's the reason why you need to indicate this prefix in your controller action.
This will also work for the other properties if you want to send them you just need to include the corresponding input fields:
#Html.TextBoxFor(m => m.ClassComplete.SchoolClass.SomeProperty)
#Html.TextBoxFor(m => m.ClassComplete.Teacher.SomeOtherProperty)
...

How to pass an entire ViewModel back to the controller

I have a ViewModel that contains two objects:
public class LookUpViewModel
{
public Searchable Searchable { get; set; }
public AddToSearchable AddToSearchable { get; set; }
}
The two contained models look something like this:
public class Searchable
{
[Key]
public int SearchableId { get; set; }
public virtual ICollection<AddToSearchable> AddedData { get; set; }
}
public class AddToSearchable
{
[Key]
public int AddToSearchableId { get; set;}
[Required]
public int SearchableId { get; set; }
[Required]
public String Data { get; set; }
[Required]
public virtual Searchable Searchable { get; set; }
}
I have a view that uses my LookUpViewModel and receives input to search for a SearchableId. If the Searchable object is found, a LookUpViewModel object is created and passed to the View. The view then displays editor fields for AddToSearchable.Data. Once submitted, I want the LookUpViewModel to be passed to an action method to handle all the back-end code. The only problem is, the LookUpViewModel passed to my action method contains a null reference to Searchable and a valid reference to AddToSearchable.. i.e. I'm missing half of my data.
Here's an example of what my view looks like:
#model HearingAidTrackingSystem.ViewModels.LookUpViewModel
#using (Html.BeginForm("LookUp", "Controller", "idStr", FormMethod.Post))
{
<input type="text" name="idStr" id="idStr"/>
<input type="submit" value="Search" />
}
#if (Model.Searchable != null && Model.AddToSearchable != null)
{
using (Html.BeginForm("AddMyStuff", "Controller"))
{
Html.HiddenFor(model => model.Searchable.SearchableId);
Html.HiddenFor(model => model.Searchable.AddedData);
Html.HiddenFor(model => model.AddToSearchable.AddToSearchableId);
Html.HiddenFor(model => model.AddToSearchable.SearchableId);
Html.HiddenFor(model => model.AddToSearchable.Searchable);
<div class="editor-field">
#Html.EditorFor(model => model.AddToSearchable.Data)
#Html.ValidationMessageFor(model => model.AddToSearchable.Data);
</div>
<input type="submit" value="Submit" />
}
}
and here are my action methods:
public ActionResult LookUp(LookUpViewModel vm)
{
return View(vm);
}
[HttpPost]
public ActionResult LookUp(string idStr)
{
int id = /*code to parse string to int goes here*/;
Searchable searchable = dal.GetById(id);
LookUpViewModel vm = new LookUpViewModel { Searchable = searchable,
AddToSearchable = new AddToSearchable() };
//When breakpoint is set, vm contains valid references
return View(vm);
}
[HttpPost]
public ActionResult AddMyStuff(LookUpViewModel vm)
{
//**Problem lies here**
//Do backend stuff
}
Sorry for the lengthy post. I tried my best to keep it simple. Any suggestions you may have.. fire away.
Two methods to fix it:
You can add to do HiddenFor() for all properties of Model.Searchable.
You can use serialization to transfer your Model.Searchable into text presentation and repair it from serialized form in controller.
Update: The problem is: You need to use #Html.HiddenFor(), not Html.HiddenFor();.

Resources