id is taken from the URL parameter, and not from the Model - c#

I have this same problem with my code, and I want to avoid the side effects without changing my variable name.
That is:
My URL is (...)/MarriageByPersonId?id=231
This goes search the Person and creates a new Marriage object (with Marriage.id = null).
It finally reaches the view with Model=Marriage:
<div class="form-group">
#Html.LabelFor(m => m.id)
#Html.TextBoxFor(m => m.id, new { #class = "form-control", #readonly = "readonly" })
#Html.ValidationMessageFor(m => m.id)
</div>
This form-group, although id is null in the model, shows the id from the GET. This behavior, in my case is unexpected and undesired.
Is there a way to block this undesired behavior and use the model only?
UPDATE
The relevant part of the controller:
[HttpGet]
public ActionResult MarriageByPersonId(int id)
{
var person = _personRepository.GetById(id);
var marriage = _marriageRepository.GetById(person.marriage_id);
return Marriage(marriage);
}
private ActionResult Marriage(Marriage marriage)
{
var people = _personRepository.GetAll();
ViewBag.men = Utils.GetPersonsSelectListByGender(people, isMale: true);
ViewBag.women = Utils.GetPersonsSelectListByGender(people, isMale: false);
return View(nameof(MarriageController.Marriage), marriage);
}

Try using an attribute on your id parameter to specify its source. If you don't want it bound, just choose a source that won't be populated. Your options are
[FromHeader]
[FromQuery]
[FromRoute]
[FromForm]
You don't want [FromQuery] obviously, since that's the behavior you're looking to avoid. Try [FromForm] and if it's not present there, it should remain unbound.
Your code would then look like this:
[HttpGet]
public ActionResult MarriageByPersonId([FromForm]int id)
{
var person = _personRepository.GetById(id);
var marriage = _marriageRepository.GetById(person.marriage_id);
return Marriage(marriage);
}

HhtpGet will always get parameters from URL. Try make another method with HttpPost and post your model to that method then it should work.
Or another idea is try to remove httpGet and try use it without it. I had similar problem. Seems that default mvc binder is confused when you have same property name in query and in model.

Related

Getting the selected value from a DropDownListFor using a ViewBag list

I'm having trouble getting the data from a DropDownListFor using a ViewBag list with my model. Here is my Controller code:
[HttpGet]
public ActionResult JoinTeam()
{
var TeamList = _db.TeamModels.ToList();
SelectList list = new SelectList(TeamList, "Id", "TeamName");
ViewBag.TeamList = list;
return View();
}
And the Razor view form looks like this:
#using (Html.BeginForm("JoinTeam", "Home", FormMethod.Post))
{
#Html.TextBoxFor(m => m.DisplayName, new { #class = "form-control form-control-lg", placeholder = "Enter your Battle Net ID" })
<br/>
#Html.DropDownListFor(m => m.TeamModel, (SelectList)ViewBag.TeamList, "- Select a Team to Join -", new { #class= "form-control form-control-lg" })
<br />
<button type="submit" class="btn btn-primary" style="width:100%;text-align:center;">Submit</button>
}
The TextBoxFor helper is returning the data correctly, but whatever option I have selected in the drop down does not get passed into my post method. Does anyone have any ideas?
The post action does work as it's getting the data from the model for the TextBoxFor help, but here's what it looks like:
[HttpPost]
public async Task<ActionResult> JoinTeam(GuardianModel model)
{
try
{
string BNETId = model.DisplayName.Replace("#", "%23");
long memberId = 0;
if (ModelState.IsValid)
{
Bungie.Responses.SearchPlayersResponse member = await service.SearchPlayers(MembershipType.Blizzard, BNETId);
memberId = member[0].MembershipId;
}
using (var context = new CoCodbEntities1())
{
var g = new GuardianModel
{
MembershipId = memberId.ToString(),
DisplayName = BNETId,
MembershipType = 4,
TeamID = model.TeamModel.Id
};
TempData["UserMessage"] = ViewBag.TeamList.Id;
return RedirectToAction("Success");
}
}
catch
{
}
return View();
}
These are the values getting passed into the Post action
From the screenshot you shared, it looks like TeamModel property is the virtual navigational property of type TeamModel. You should not bother about loading that. All you need to worry about loading the forign key property value (usually a simple type like an int or so.
Your SELECT element name should be TeamID. When the form is submitted, it will map the selected option value to the TeamID property value of your model which is the foreign key property.
#Html.DropDownListFor(m => m.TeamID, (SelectList)ViewBag.TeamList,
"- Select a Team to Join -", new { #class= "form-control form-control-lg" })
While this might fix the issue, It is a good idea to use a view model instead of using your entity class.
I found the issues I was having. All I needed to get passed into the post action was the Id of the TeamModel. So I changed this line:
#Html.DropDownListFor(m => m.TeamModel.Id, (SelectList)ViewBag.TeamList, "- Select a Team to Join -", new { #class= "form-control form-control-lg" })
I just added the Id and it seemed to work.

Why does .NET MVC 4 take the value from the GET query string instead of the model binder

I have the following .cshtml code:
#Html.TextBoxFor(x => x.BeginDate, "{0:dd.MM.yyyy}", new { #class = "datefield" })
#Html.TextBoxFor(x => x.EndDate, "{0:dd.MM.yyyy}", new { #class = "datefield" })
By using POST, the values displayed are correct(also because I am using a model binder)
However, if I use GET (I am using PagedList.Mvc), and I create the following query: Index?page=3&beginDate=2015-07-01&endDate=2015-07-06, the following values are displayed in the textboxes: 2015-07-01 and 2015-07-06, instead of 01.07.2015 and 06.07.2015.
But If I change the parameters names to Index?page=3&iBeginDate=2015-07-01&iEndDate=2015-07-06, the values are displayed correctly in the textboxes (01.07.2015 and 06.07.2015).
My headers for my GET and POST methods are the following:
[HttpGet]
public ActionResult Index(int page = 1, int? workCenterId = null, DateTime? beginDate = null, DateTime? endDate = null, int? shiftId = null)
{
var model = GetModel(page: page, workCenterId: workCenterId, beginDate: beginDate, endDate: endDate, shiftId: shiftId);
return View(model);
}
[HttpPost]
public ActionResult Index([ModelBinder(typeof(ReportModelBinder))]ReportModel model)
{
var newModel = GetModel(model);
return View(newModel);
}
Why does MVC prefer the GET parameters over the ones that are sent via the model ?
There isnt a preference, the action that is called is based on the request method (GET/POST etc) and action name, the default model binder will use value providers to get values for parameters and model properties, these values will come from the query string, form values, cookies, route data etc.

How do I resolve ModelState.IsValid = false while binding a List type?

I'm getting an ModelState.IsValid = false from a List that contains a class object that has its own id's.
I've seen some examples of how to exclude class properties from the [HttpPost] method while binding that look like this:
[Bind(Exclude="Id,SomeOtherProperty")]
My Question:
How do you exclude the Id that belongs to a property as it does with List? Or, if there's a better way of handling this, please shed some light on the subject.
Here's my PostController.cs:
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Create([Bind(Include = "Title,URL,IntroText,Body,Created,Modified,Author,Tags")] Post post)
{
if (ModelState.IsValid) /*ModelState.IsValid except for its not... */
{
// this is failing so I unwrapped the code below temporarily
}
using (UnitOfWork uwork = new UnitOfWork())
{
var newPost = new Post
{
Title = post.Title,
URL = post.URL,
IntroText = post.IntroText,
Body = replace,
Author = post.Author,
Tags = post.Tags
};
uwork.PostRepository.Insert(newPost);
uwork.Commit();
return RedirectToAction("Index", "Dashboard");
}
return RedirectToAction("Index", "Dashboard");
}
Update: Relevant excerpt from my Create.cshtml (This turned out to be the problem.)
<div class="form-group">
#Html.LabelFor(model => model.Tags, htmlAttributes: new { #class = "control-label col-md-2 col-md-offet-3" })
<div class="col-md-7">
#for (var i = 0; i < 4; i++)
{
#Html.HiddenFor(m => m.Tags[i].Id)
#Html.EditorFor(model => model.Tags[i].Name)
}
</div>
</div>
Gists: Post.cs | Tag.cs
I wanted to include this picture so you could see, visually, what was failing. Each Tag[i].Id tag is causing the invalid state.
To restate my question, how do I omit the List<Tag> Id from my POST method and achieve valid state?
As #StephenMuecke pointed out in the comments of the OP. I just needed to remove the field from the View:
#Html.HiddenFor(m => m.Tags[i].Id)
Now the ModelState.IsValid returns true.

How to properly use SelectListItem for HTML.DropDownList instead of SelectList?

I've been digging through other posts trying to figure out how to go from using a SelectList in my controller to fill an #Html.DropDownList in my View to instead use what seems to be the commonly suggested SelectListItem, but I'm completely lost?
I have a main INV_Assets model that when I go to the Edit View, I include Drop Down Lists for other Model Properties (Location, Manufacturer, Model, Status, Type, Vendor, etc.) My current code below adequately fills the lists and allows me on Edit() to change the chosen entity value to any other value stored in that relevant table.
CURRENT CODE:
Controller:
// GET: INV_Assets/Edit/5
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
INV_Assets iNV_Assets = await db.INV_Assets.FindAsync(id);
if (iNV_Assets == null)
{
return HttpNotFound();
}
ViewBag.Location_Id = new SelectList(db.INV_Locations, "Id", "location_dept", iNV_Assets.Location_Id);
ViewBag.Manufacturer_Id = new SelectList(db.INV_Manufacturers, "Id", "manufacturer_description", iNV_Assets.Manufacturer_Id);
ViewBag.Model_Id = new SelectList(db.INV_Models, "Id", "model_description", iNV_Assets.Model_Id);
ViewBag.Status_Id = new SelectList(db.INV_Statuses, "Id", "status_description", iNV_Assets.Status_Id);
ViewBag.Type_Id = new SelectList(db.INV_Types, "Id", "type_description", iNV_Assets.Type_Id);
ViewBag.Vendor_Id = new SelectList(db.INV_Vendors, "Id", "vendor_name", iNV_Assets.Vendor_Id);
return View(iNV_Assets);
}
// POST: INV_Assets/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,Model_Id,Manufacturer_Id,Type_Id,Location_Id,Vendor_Id,Status_Id,ip_address,mac_address,note,owner,cost,po_number,description,invoice_number,serial_number,asset_tag_number,acquired_date,disposed_date,created_date,created_by,modified_date,modified_by")] INV_Assets iNV_Assets)
{
if (ModelState.IsValid)
{
db.Entry(iNV_Assets).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewBag.Location_Id = new SelectList(db.INV_Locations, "Id", "location_dept", iNV_Assets.Location_Id);
ViewBag.Manufacturer_Id = new SelectList(db.INV_Manufacturers, "Id", "manufacturer_description", iNV_Assets.Manufacturer_Id);
ViewBag.Model_Id = new SelectList(db.INV_Models, "Id", "model_description", iNV_Assets.Model_Id);
ViewBag.Status_Id = new SelectList(db.INV_Statuses, "Id", "status_description", iNV_Assets.Status_Id);
ViewBag.Type_Id = new SelectList(db.INV_Types, "Id", "type_description", iNV_Assets.Type_Id);
ViewBag.Vendor_Id = new SelectList(db.INV_Vendors, "Id", "vendor_name", iNV_Assets.Vendor_Id);
return View(iNV_Assets);
}
View - Just [Locations] for example:
<div class="form-group">
#*#Html.LabelFor(model => model.Location_Id, "Location_Id", htmlAttributes: new { #class = "control-label col-md-2" })*#
<span class="control-label col-md-2">Location:</span>
<div class="col-md-10">
#Html.DropDownList("Location_Id", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Location_Id, "", new { #class = "text-danger" })
</div>
</div>
What I'm trying to do now is add a value to each list stating "Add New", which I want to allow users to click on and have a (partial view?) popup for them to immediately add a new relevant record (Ex. New [Location] of "Warehouse 2") and then be able to choose that from the [Locations] list for the particular Asset being Edited.
Can anyone walk me through this?
A lot of the suggestions are to add a SelectList or IEnumerable<SelectListItem> to my relevant Model properties, but from there I am lost on what to tweak in my controller/view? Currently I am using Code-First Migrations with an InventoryTrackerContext.cs inside my DAL folder for the project.
You're confusing two very different aspects of this. First, Html.DropDownList only requires an IEnumerable<SelectListItem>. Passing a full SelectList object satisfies this parameter merely because a SelectList is an IEnumerable<SelectListItem>. The advice to not use SelectList is simply to save yourself the work of having to construct a full SelectList object (and remembering to do things like set the selectedValue to the right item), when Razor will handle this for you. Whether you use SelectList or IEnumerable<SelectListItem> has no bearing on the remainder of your question.
As far as adding items to an existing drop down list goes, you have to use JavaScript for that. At a basic level, it's as simple as just just selecting the select element in the DOM and appending a new option node.

ASP.NET MVC GetFullHtmlFieldId not returing valid id

I have taken a look at but it did not help me out
GetFullHtmlFieldId returning incorrect id attribute value
ASP.NET GetFullHtmlFieldId not returning valid id
Problem
Basically I have the following problem:
I have a custom validation attribute which requires to get the fieldId of the control
public class MyValidationAttribute : ValidationAttribute, IClientValidatable
{
//...... Collapsed code
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ViewContext vwContext = context as ViewContext;
var fieldId = vwContext.ViewData.TemplateInfo.GetFullHtmlFieldId(metadata.PropertyName);
//...... Collapsed code
yield return clientValidationRule;
}
//...... Collapsed code
}
The result of GetFullHtmlFieldId depends on how I build my asp.net mvc page:
// Edit.cshtml or Create.cshtml
#Html.EditorFor(model => model.MyBoolProperty)
// Shared/EditorTemplates/Boolean.cshtml
#Html.CheckBoxFor(model => model)
result of GetFullHtmlFieldId incorrect: MyBoolProperty_MyBoolProperty
// Edit.cshtml or Create.cshtml
#Html.CheckBoxFor(model => model.MyBoolProperty)
result of GetFullHtmlFieldId correct: MyBoolProperty
Even with more complex editors I see this incorrect behavior
// Edit.cshtml or Create.cshtml
#Html.EditorFor(model => model.JustAnArray[1].ComplexProperty.MyBooleanProperty)
// Shared/EditorTemplates/Boolean.cshtml
#Html.CheckBoxFor(model => model)
result of GetFullHtmlFieldId incorrect: JustAnArray_1__ComplexProperty_MyBoolProperty_MyBoolProperty
// Edit.cshtml or Create.cshtml
#Html.CheckBoxFor(model => model.JustAnArray[1].ComplexProperty.MyBooleanProperty)
result of GetFullHtmlFieldId correct: JustAnArray_1__ComplexProperty_MyBoolProperty
Also this is returning correct value
// Edit.cshtml or Create.cshtml
#Html.EditorFor(model => model.JustAnArray[1])
// Shared/EditorTemplates/ComplexProperty.cshtml
#Html.CheckBoxFor(model => model.MyBooleanProperty)
result of GetFullHtmlFieldId correct: JustAnArray_1__ComplexProperty_MyBoolProperty
It looks like that using #Html.CheckBoxFor(model => model), it gives incorrect results but when using #Html.CheckBoxFor(model => model.MyBoolProperty) it is working as expected
I have the same issue with other controls (like TextBoxFor)
Question
How can I get the proper fieldId of my control in my validation attribute, independent of how you build the page.
I'd rather use already existing methods (maybe the same methods as which are used by TextBoxFor and CheckBoxFor and other controls) than mimic this already existing functionality. If I mimic the generation of the fieldId, I have a change I don't take care of all situations where the ASP.NET controls take care of.
It seems that the valid prefix depends on the expressions used to build the page, and that isn't available to the GetClientValidationRules method. It would be great if someone has a solution for it, but as the method GetClientValidationRules is trying to initialize the data needed for the Javascript validation, you could try to resolve this on the client side.
On some of the default validation attributes like the [Compare], which depends on a second field, they set a parameter *.propertyName and in the unobtrusive adapter they replace the *. part with the valid prefix retrieved from the input name attribute. You could try a similar approach for the id.
However this would be needed if you were interested in another field. In this case it seems you are interested in the id of the very same input field that you are validating. You could then retrieve it from the input element itself. It will be available in both the unobtrusive adapter or the method itself:
//You could get it in the adapter and pass it to the validator in the "param"
adapters.add("dummy", function (options) {
var fullFieldId = options.element.id;
if (console) console.log("Full field id: " + fullFieldId);
setValidationValues(options, "dummy", fullFieldId);
});
$jQval.addMethod("dummy", function (value, element, param) {
var fullFieldId = param;
//do whatever validation logic
return true;
});
//You could also retrieve it directly in the validator method
$jQval.addMethod("dummy", function (value, element, param) {
var fullFieldId = element.id;
//do whatever validation logic
return true;
});
adapters.add("dummy", function (options) {
setValidationValues(options, "dummy", {});
});
Hope it helps!

Categories

Resources