I have an ASP .Net Core 1.1 MVC web app. The app talks to a web API backend to read/write data to the data store.
In the web app, I have an Edit page, where you can edit a record of a particular object. One of the fields in the record is a dropdownlist (i.e. an HTML <SELECT> tag). My problem is I don't know how to get the selected option from the dropdownlist (in the View) back to the controller.
To give you brief background, this web app will be used by real estate agents to manage their properties. To simply things, for now the property has a basic structure - an ID, and address and a "Property Type" (house, flat, office, etc)
So I have these two models:
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace InspectionsData.Models { public partial class PropertyType { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string PropertyTypeName { get; set; } // e.g. house, flat, office, etc. } }
and
using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace InspectionsData.Models { [Table("property")] public class Property { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int? Id { get; set; } public string Street { get; set; } public string City { get; set; } public string Region { get; set; } public string Country { get; set; } public int? PropertyTypeId { get; set; } [ForeignKey("PropertyTypeId")] public PropertyType PropertyType { get; set; } } }
I then created a ViewModel so that I can feed both of the above models to the view:
using InspectionsData.Models; using Microsoft.AspNetCore.Mvc.Rendering; namespace InspectionsTestClient.ViewModels { public class PropertyIndexViewModel { public Property Property { get; set; } public SelectList PropertyTypes { get; set; } } }
Then I have a controller which populates the view:
// GET: Property/Edit/5 public async Task<ActionResult> Edit(int id) { string apiUrl = "http://localhost:50082/api/";
if (!ModelState.IsValid) { return BadRequest(ModelState); } //Read property from Web API
string url = apiUrl + "Properties/" + id.ToString(); HttpResponseMessage responseMessage = await client.GetAsync(url); if (responseMessage.IsSuccessStatusCode) { var responseData = responseMessage.Content.ReadAsStringAsync().Result; var prop = Newtonsoft.Json.JsonConvert.DeserializeObject<Property>(responseData); if (prop == null) { return NotFound(); } List<PropertyType> propertyTypes = null; url = apiUrl + "PropertyTypes"; responseMessage = await client.GetAsync(url); if (responseMessage.IsSuccessStatusCode) { responseData = responseMessage.Content.ReadAsStringAsync().Result; propertyTypes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PropertyType>>(responseData); } PropertyIndexViewModel vm = new PropertyIndexViewModel() { Property = prop, PropertyTypes = new SelectList(propertyTypes, "Id", "PropertyTypeName") }; return View(vm); } return View("Error"); }
I know it's inneficient in making two calls to the API - one to get the property and one to ge the list of Property Types - I will improve this later, but for now that's how it is...
and the view:
@model InspectionsTestClient.ViewModels.PropertyIndexViewModel @{ ViewData["Title"] = "Edit"; } @{ Layout = "_Layout"; }<h2>Edit</h2> @Html.ValidationSummary(); <form asp-action="Edit"><div class="form-horizontal"><h4>Property</h4><hr /><div asp-validation-summary="ModelOnly" class="text-danger"></div><input type="hidden" asp-for="Property.Id" /><div class="form-group"><label asp-for="Property.Street" class="col-md-2 control-label"></label><div class="col-md-10"><input asp-for="Property.Street" class="form-control" /><span asp-validation-for="Property.Street" class="text-danger"></span></div></div><div class="form-group"><label asp-for="Property.City" class="col-md-2 control-label"></label><div class="col-md-10"><input asp-for="Property.City" class="form-control" /><span asp-validation-for="Property.City" class="text-danger"></span></div></div><div class="form-group"><label asp-for="Property.Region" class="col-md-2 control-label"></label><div class="col-md-10"><input asp-for="Property.Region" class="form-control" /><span asp-validation-for="Property.Region" class="text-danger"></span></div></div><div class="form-group"><label asp-for="Property.Country" class="col-md-2 control-label"></label><div class="col-md-10"><input asp-for="Property.Country" class="form-control" /><span asp-validation-for="Property.Country" class="text-danger"></span></div></div><div class="form-group"><label asp-for="Property.PropertyType" class="col-md-2 control-label"></label><div class="col-md-10"><select asp-for="Property.PropertyType" asp-items="@Model.PropertyTypes"></select></div></div><div class="form-group"><div class="col-md-offset-2 col-md-10"><input type="submit" value="Save" class="btn btn-default" /></div></div></div></form><div><a asp-action="Index">Back to List</a></div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
When the user clicks the SAVE button on the form, it posts back to another controller action:
// POST: Property/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Edit(int id, Property prop) { try { if (ModelState.IsValid) { string apiUrl = "http://localhost:50082/api/";
string url = apiUrl + "properties/" + id.ToString(); string jsonInString = JsonConvert.SerializeObject(prop); HttpContent content = new StringContent(jsonInString, Encoding.UTF8, "application/json"); HttpResponseMessage responseMessage = await client.PutAsync(url, content); if (responseMessage.IsSuccessStatusCode) { return RedirectToAction("Index"); } } return View(); } catch { return View(); } }
So this controller action posts the updated property to the web API. The problem is that, everything works 100% until it gets to this final controller action - if I inspect the "prop" parameter, it contains all the modifications made, but it does not contain the selected PopertyType - how can I return the selected PropertyType from the view back to the controller?
Thanks...