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!
Related
I'm trying to send multiple objects via FormMethod.Post, but the problem is that only the value of the first one is saved and the value of second one is the same as first. The problem is probably in the Razor syntax which I'm pretty new at, here is the code I'm using:
#using (Html.BeginForm("chAdress", "Adress", FormMethod.Post))
{
#Html.Label("Number")
#Html.TextBoxFor(model => model.Number)
#Html.ValidationMessageFor(model => model.Number)
#Html.Label("Distance")
#Html.TextBoxFor(model => model.Distance)
#Html.ValidationMessageFor(model => model.Distance)
#Html.Label("New Number")
#Html.TextBoxFor(model1 => model1.Number)
#Html.ValidationMessageFor(model1 => model1.Number)
#Html.Label("New Distance")
#Html.TextBoxFor(model1 => model1.Distance)
#Html.ValidationMessageFor(model1 => model1.Distance)
<button type="submit">Change Adress</button>
}
And here is the controller that should make the change:
public void chAdress(Adress model, Adress model1)
{
prepareConnection();
string distance = model.Distance.ToString();
string newDistance = model1.Distance.ToString();
Dictionary<string, object> queryDict = new Dictionary<string, object>();
queryDict.Add("distance", distance);
queryDict.Add("newDistance", newDistance);
var query = new Neo4jClient.Cypher.CypherQuery("start n=node(*) where (n:Adress) and exists(n.distance) and n.distance =~ {distance} set n.distance = {newDistance} return n",
queryDict, CypherResultMode.Set);
List<Adress> adrese = ((IRawGraphClient)client).ExecuteGetCypherResults<Adress>(query).ToList();
}
After running in debug mode, I see that the value of distance is always the same as the newDistance, what is the best way to fix this issue?
Views can only be typed to one model. You appear to be trying referencing a Model and Model1 in your view. You should create a new ViewModel to contain all properties that you want to return from your form and then, if needed, process that from your controller into the distinct objects you need.
Since you actually have just one model (but are trying to use it like 2) you are overwriting the properties of the previously set values.
POST method will always use the property name to submit the data. Event though you have 2 different models but still it has the same property name which will always get overridden by the latest property value which is new newDistance in this case. Either add a new property name newDistance to model like model.newDistance or create a different model with property name as newDistance like model1.newDistance.
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.
This has been a thorn in my side for a while. If I use EditorFor on an array of objects and the editor Template has a form in it ex.
public class FooController:Controller {
public ActionResult Action(Foo foo) {
// ...
}
}
Index.cshtml
#model IEnumerable<Foo>
#Html.EditorFor(m=> m)
EditorTemplate
#model Foo
#using (Html.BeginForm("action", "controller"))
{
#Html.TextBoxFor(f=> f.A)
#Html.CheckBoxFor(f=> f.B)
#Html.LabelFor(f=> f.B)
}
So I'll hit a few problems.
The checkbox label's for doesn't bind correctly to the checkbox (This has to do with the label not receiving the proper name of the property ([0].A as opposed to A).
I'm aware I can get rid of the pre- text by doing a foreach on the model in Index but that screws up ids and naming as the framework doesnt realize there are multiples of the same item and give them the same names.
For the checkboxes I've just been doing it manually as such.
#Html.CheckBoxFor(m => m.A, new {id= Html.NameFor(m => m.A)})
<label for="#Html.NameFor(m => m.A)">A</label>
However I cant solve the inability of the controller to accept the item as a single model. I've even tried allowing an array of Foo's in the Action parameters but that only work when its the first item being edited ([0]...) if its any other item in the array (ex. [1].A) the controller doesn't know how to parse it. Any help would be appreciated.
Make your model a class with the properties you need.
create a class in your Models subfolder
public class MyModel {
public IEnumerable<Foo> Foolist { get ; set;}
public string Something { get;set;}
}
your EditorFor will have to have a foreach loop for Foolist...
MVC will attempt to put your model together from the form and return it to your POST action in the controller.
Edit:
You could create an EditorTemplate for foo. In Views/Shared/EditorTemplates folder, create FooTemplate.cs
#model Foo
<div class="span6 float-left" style="margin-bottom: 6px">
#Html.TextBoxFor(m => m.A, new { style = "width:190px" })
#Html.CheckBoxFor(m => m.B, new { style = "width:40px" })
#Html.ValidationMessage("foo", null, new { #class = "help-inline" })
</div>
then in your view
#foreach (var myFoo in Model)
{
#EditorFor(myFoo)
}
This still suffers from the "model gets passed back as a whole" requiredment of yours. Not sure about why there is a need to process these individually.
Hah finally solved this - Here's how I did it. As a bit of background HTML forms use the name attribute when submitting forms, but the label for element uses Id . so I only adapt the id tag to have the prefix and not the name tag.
--In the cshtml file
#{
var prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
ViewData.TemplateInfo.HtmlFieldPrefix = "";
}
then I can specify the id for the properties by their prefix while letting the name remain the same like so
#Html.CheckBoxFor(m => m.A,
new {id = prefix+"."+ Html.NameFor(m => m.A)})
<label for="#prefix.#Html.NameFor(m => m.A)">A!</label></div>
I have a problem with connecting the script with the autocomplete function to my Json controller. The view is a formula, where the user can insert data, like dates with the datepicker function and general text to describe the issues. The whole formula is in this:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
All Textboxes, DropDownLists and Editors are connected to the model like so:
<div class="editor-label">
#Html.LabelFor(model => model.Overview)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Overview)
#Html.ValidationMessageFor(model => model.Overview)
</div>
At the moment I try to insert the Textbox, where the autocomplete should happen like this:
<b>Name: </b>
#Html.TextBox("searchTerm", null, new { id = "txtSearch" })
The txtSearch is connected to my skript SearchUser.js:
$(function () {
$("#txtSearch").autocomplete({
source: '#url.Action("New1", "Dialog")',
minLength: 1
});
});
when I use a source an array of strings, the autocomplete appears.
the JavaScript is registered on top of the view and jQueryUI is registered in _Layout.cshtml. I am using jquery 1.11.3 and jqueryui 1.11.4 .
In The Controller New1 in the JsonResult you find this:
public JsonResult Dialog(string search)
{
List<string> users = db
.Users
.Where(p => p.FirstName.ToLower().Contains(search.ToLower()))
.Select(p => p.LastName)
.ToList();
return Json(users, JsonRequestBehavior.AllowGet);
}
when i test the website and look for http://localhost:51299/New1/Dialog?search=m
i get the json file. The json file contains this: ["Mueller"]
But when I go to my formula http://localhost:51299/New1/Create and insert "m" into the TextBox nothing happens.
So now my question: What can i do to make it work?
Update (It's working!!!)
Aaaaah its working!!!. Thanks a lot! He couldnt use the source, so now I changed it to "/New1/Dialog".
I know it is not a good way to use the direct url instead of '#url.Action("Dialog", "New1")', but i think he couldnt differ between normal ' and ".
If you have an Idea why i couldnt use #url.Action, i would be interested in it.
View (Create.cshtml)
#Html.TextBox("searchTerm", null, new { id = "searchTerm" })
Skript (SearchUser.js)
$(function () {
$("#searchTerm").autocomplete({
source: "/New1/Dialog",
minLength: 1
});
});
controller (New1Controller.cs)
public JsonResult Dialog(string term)
{
List<string> users = db
.Users
.Where(p => p.LastName.ToLower().Contains(term.ToLower()))
.Select(x => x.LastName)
.ToList();
return Json(users, JsonRequestBehavior.AllowGet);
}
jQueryUI autocomplete is using the name term (not search) to craft a request. In other words, when you type "m", it's sending the following request:
http://localhost:51299/New1/Dialog?term=m
You should be able to fix this by simply renaming the parameter:
public JsonResult Dialog(string term)
I I need to validate some thing in mvc in my controller I have to
#Html.ValidationMessageFor(model => model.somestring)// in the view
if (model.string = some condition)
ModelState.AddModelError("somestring", "String cannot be empty");// in the controller
but if in my view I have a custom object like
#Html.ValidationMessageFor(model => model.someobject.somestring)// in the view
how do I validate it? Is the following syntax correct?
if (model.someobject.somestring = some condition)
ModelState.AddModelError("somestring", "String cannot be empty");// in the controller
You need to make sure the full path to your property is used when specifying your key:
ModelState.AddModelError("someobject.somestring", "String cannot be empty);