Asp.Net MVC 3 not binding collections to models on posting - asp.net-mvc-3

i have a object with many properties of which one is an array of some other complex object
something like this
class obj1
{
public string prop1 {get; set;}
public string prop2 {get; set;}
public obj2[] array {get; set;}
}
class obj2
{
SomeEnum Type{get; set;}
string Content{get; set;}
}
I have created an editor template for the array of obj2 lets name it obj2ArrayTemplate
which is like this
#for (int i = 0; i < Model.Length; i++)
{
#Html.EditorFor(model=>model[i],"obj2Template")
}
and an editor template for obj2 lets name It obj2Template
which is like this
<div class="editor-label">
#Html.DisplayFor(model=>model.Type,"SomeEnum",ViewData)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Content)
#Html.ValidationMessageFor(model => model.Content)
</div>
now because property Type if of type SomeEnum which is an enum and hence cannot be rendered directly by the Asp.Net MVC
I have created a Template for this too
which is something like this
<input type="text" value="#Model.ToString()" id="#ViewData.TemplateInfo.HtmlFieldPrefix" name="#ViewData.TemplateInfo.HtmlFieldPrefix"/>
the view is being rendered correctly and the HTML of the combined rendered view is
<div class="editor-field">
<input class="text-box single-line" id="array__0__Content" name="array.[0].Content" type="text" value="FBID" />
</div>
<div class="editor-label">
<input type="text" value="Blog" id="array.[1].Type" name="array.[1].Type"/>
</div>
<div class="editor-field">
<input class="text-box single-line" id="array__1__Content" name="array.[1].Content" type="text" value="SOme random blog" />
</div>
<div class="editor-label">
<input type="text" value="Twitter" id="array.[2].Type" name="array.[2].Type"/>
</div>
<div class="editor-field">
<input class="text-box single-line" id="array__2__Content" name="array.[2].Content" type="text" value="Twitter Profile" />
</div>
when iam posting back the form containing this html
chrome is showing me this posted data
prop1:val1
prop2:val2
array.[0].Type:Facebook
array.[0].Content:FBID
array.[1].Type:Blog
array.[1].Content:SOme random blog
array.[2].Type:Twitter
array.[2].Content:Twitter Profile
but still array field which is an array of obj2 in the model of type obj1 is null
what am I doing wrong?

Could crack that one :)
inspecting the request that was being posted from the server I found out that there is one .(dot) extra in the request so the array is not being populated
so instead of
array.[0].Type:Facebook
array.[0].Content:FBID
array.[1].Type:Blog
array.[1].Content:SOme random blog
array.[2].Type:Twitter
array.[2].Content:Twitter Profile
this should be posted back
array[0].Type:Facebook
array[0].Content:FBID
array[1].Type:Blog
array[1].Content:SOme random blog
array[2].Type:Twitter
array[2].Content:Twitter
and I found no apparent reason of why Asp.Net MVC framework is putting in that extra dot. when im kind of doing everything the MVC way
so I changed my code to include a little hack.
I added this like to the Editor template of obj 2
#{
ViewData.TemplateInfo.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix.Replace(".", "");
}
so now everything is running fine and smooth.

Related

Using remote attribute in .net core razor pages. The Parameter does not get value in Action Method of Controller

I am using Remote Attribute validation
The method is invoked successfully on textchange event. However, the parameters in the action method of the controller does not get the value of the field.
Here is the Action Method in the HomeController.cs. When invoked the Name parameter remains null. I will be pleased if someone solve this problem
[AcceptVerbs("Get", "Post")]
public async Task<ActionResult> IsExist(string Name)
{
List<Keywords> keywords = new List<Keywords>();
HttpClient client = _api.Initial();
HttpResponseMessage res = await client.GetAsync("api/Keywords");
if (res.IsSuccessStatusCode)
{
var result = res.Content.ReadAsStringAsync().Result;
keywords = JsonConvert.DeserializeObject<List<Keywords>>(result);
}
if (keywords.FirstOrDefault(x => x.Name == Name) == null)
{
return Json(false);
}
else
{
return Json(true);
}}
Here is the Model
public partial class Keywords
{
public int Id { get; set; }
[Display(Name = "Name of Keyword")]
[Required]
[Remote(action: "IsExist",controller: "Home", ErrorMessage = "Keyword already present!")]
public string Name { get; set; }}
Here is the razor page in which I want to implement validation
#page
#model Consumer.Pages.Keyword.CreateModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Keywords</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Keywords.Name" class="control-label"></label>
<input asp-for="Keywords.Name" class="form-control" />
<span asp-validation-for="Keywords.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Keywords.Department" class="control-label"></label>
<select asp-for="Keywords.DepartmentId" class="form-control" asp-items="ViewBag.Department"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
I found the solution. It is to
Remove partial in the model class definition.
Replace in the Razor Page
<input asp-for="Keywords.Name" class="form-control" />
with
<input asp-for="Keywords.Name" name="Name" class="form-control" />
The [Remote] attribute is all but useless. There's a long-standing problem from the ASP.NET MVC days that migrated its way into ASP.NET Core unabated, as well. Namely, the action that handles the remote must take a param that matches the input name of what what's being validated. In other words, your action takes a param, name, but what's being sent to it is Keywords.Name, which cannot bind to name. You'd have to do something like:
public async Task<ActionResult> IsExist(Keywords keywords)
And then, then the value will be available via keywords.Name. This obviously makes the usage highly dependent on the particular implementation, so if there's a situation with a different input name like Foo.Keywords.Name, then you'd need an entirely different action, to match that binding, and basically duplicate the logic (though you could factor out the majority the logic into some common method all the actions could utilize).
Long and short, you're probably better off just handling this yourself manually. Just bind to the input event on the name input, and then call your method via AJAX. Then you can explicitly control the key that's sent in the request body, such that it will always match your action. Just note that you'll also want to debounce sending that AJAX request so that it only happens every few keystrokes or so. Otherwise, you're going to hammer your action.

Using hidden fields during HTTP post for the reload of same page postback

I have a View where the User can leave a feedback comment in regards to the content video that is playing on the page. Using the following code I am able to manually enter the UserID and ContentID and it saves to the database without problem.
#using (Html.BeginForm("Screencast", "Media", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div class="row">
<div class="six columns">
<div class="row">
<div class="six columns">
#Html.LabelFor(c => c.FeedbackComment.UserID)
#Html.TextBoxFor(c => c.FeedbackComment.UserID)
</div>
<div class="six columns">
#Html.LabelFor(c => c.FeedbackComment.ContentID)
#Html.TextBoxFor(c => c.FeedbackComment.ContentID)
</div>
<div class="row">
<div class="twelve columns">
#Html.LabelFor(c => c.FeedbackComment.FeedbackString)
#Html.TextAreaFor(c => c.FeedbackComment.FeedbackString)
</div>
</div>
</div>
</div>
</div>
<input type="submit" value="Submit button" class="medium button bottom20"/>
}
However, when the user is on the page before the HTTP post I actually have the related variables in my Model called:
Model.User.UserID
Model.SelectedItem.ContentID
I want to pass these in as hidden fields but when I try to do either:
#HtmlHiddenFor(c => c.FeedbackComment.UserID, #Model.User.UserID)
#HtmlHiddenFor(c => c.FeedbackComment.ContentID, #Model.SelectedItem.ContentID)
or
#HtmlHidden("UserID",#Model.User.UserID)
#HtmlHidden("ContentID",#Model.User.UserID)
Then these values are returned null despite the values being populated before the post. I read about a workaround of putting the input tags in manually but when I did this the #Using.HtmlBeginForm was returning an error of not being set to an instance of an object
Can someone please help me to understand how I can pass these values to the same page using the values I have in the model prior to the post.
Given the following view model (partial):
public class YourViewModel
{
public User User { get; set; }
public SelectedItem SelectedItem { get; set; }
}
You can bind the these properties to a hidden form element. When you post back these properties will still contain their values.
#Html.HiddenFor(x => x.User.UserID)
#Html.HiddenFor(x => x.SelectedItem.ContentID)
Your action method:
public ActionResult YourActionMethod(YourViewModel viewModel)
{
// When you debug here you will see that the values are retained
}
I hope this helps.

how to get list of all countries in world through google map API?

I am working on a project and in the registration page I want to get a list of all countries in a dropdown list through google API. i used cultureInfo but it get list of just unik 127 countries. i want to use google Map API. Can anyone please show me what I need to do in the Controller, the View and model or whatever is required ? I have a view of the registraion page
#model Retail_MVC.Models.registration
#{
Layout = null;
}
<div class="my_wrapper">
<div class="my_left_box">
#Html.LabelFor(model => model.username)
</div>
<div class="my_right_box">
<input type="text" id="txtusername" name="UserName"/>
</div>
</div>
-- here i want to get dropdownlist for all countries--

Unobtrusive validation editorfor

I've got a partial View, loaded from an Action, so the parent view contains:
#Html.Action("TourSearch")
The TourSearch View uses a editor as such:
#Html.EditorFor(model => model.ImpersonatedAgentModel, "ImpersonatedAgentView")
where ImpersonatedAgentModel is as such:
[Serializable]
public class ImpersonatedAgentModel
{
[Required(ErrorMessage = "Please provide a User ref")]
public string AgentImpersonatedUserName { get; set; }
[Required(ErrorMessage="Please provide a ABTA/AgencyCode")]
public string AgentImpersonatedBranchCode { get; set; }
[Required(ErrorMessage = "Please provide a User ref")]
public int? AgentImpersonatedBranchID { get; set; }
}
My editor is pretty straight forward:
#model Travel2.WebUI.Models.ImpersonatedAgentModel
<ul id="agencyDetails">
<li>
<label for="AgentImpersonatedBranchCode">ABTA/Agency Code: *</label>
#Html.TextBoxFor(model => model.AgentImpersonatedBranchCode, new {ID="txtBranchCode" })
#Html.ValidationMessageFor(model => model.AgentImpersonatedBranchCode, "*")
<input id="txtBranchId", type="hidden" value="#Model.AgentImpersonatedBranchID" />
<input id="txtUserName", type="hidden" value="#Model.AgentImpersonatedUserName" />
<input id="hidCurrentController" type="hidden" value='#ViewContext.Controller.ValueProvider.GetValue("controller").RawValue' />
</li>
<li>
<label for="AgentImpersonatedUserName">User ref: *</label>
<select id="ddlUser" disabled="disabled" ></select>
<input type="hidden" id="txtUserID" />
#Html.HiddenFor(model => model.AgentImpersonatedUserName, new {ID="AgentImpersonatedUserName" })
#Html.HiddenFor(model => model.AgentImpersonatedBranchID, new {ID="AgentImpersonatedBranchID"})
#Html.ValidationMessageFor(model => model.AgentImpersonatedUserName, "*")
</li>
</ul>
Now in Chrome, all works fine. But when I fill in the form in IE but not the data in the Editor, it passes validation incorrectly!
If I examine the markup, using IEs poor excuse for Firebug, I can see the validation attributes,
<input name="ImpersonatedAgentModel.AgentImpersonatedBranchCode" id="txtBranchCode" type="text" data-val="true" data-val-required="Please provide a ABTA/AgencyCode" jQuery172048066185567747205="94"/>
so why is IE ignoring them!! Stupid IE
Found the answer here:
http://www.tigraine.at/2011/08/26/jquery-validate-and-microsofts-unobtrusive-validation-dont-play-well-together/
We were referencing Jquery validation too and this seems to be causing this error

ASP.NET MVC 3 Partial View Template

To be honest, I have no idea what to call this or how to start to search it.
I have a display page with a standard layout.
<div>
<label for="field">Field Name:</label>
#Model.Field
</div>
While trying to make this more change friendly, I want to make a template instead of typing out each field with the above code.
I created a partial view with the following:
#model System.Object
<div>
#Html.LabelFor(m => m)
#Html.DisplayFor(m => m)
</div>
On my view, I added the following:
#Html.Partial("_BillField", Model.Field)
The model then has a description with like:
public ModelName {
[Display(Name="Field Description")]
public decimal Field { get; set; }
}
This works when on the main view, but the label is missing when using the template. What am I missing?
Update: Per #K. Bob I make the change to the partial view:
<div>
#Html.LabelFor(m => m)
#Html.DisplayFor(m => m)
</div>
Update 2: For clarity of what I want.
In the end, I want to be able to do:
#Html.Partial("_BillField", Model.Field1)
#Html.Partial("_BillField", Model.Field2)
#Html.Partial("_BillField", Model.Field3)
And have the equivalent of:
<div>
<label for="field1">Field 1 Name:</label>
#Model.Field1
</div>
<div>
<label for="field2">Field 2 Name:</label>
#Model.Field2
</div>
<div>
<label for="field3">Field 3 Name:</label>
#Model.Field3
</div>
Sorry for not making that clearer.
The partial doesn't need told what the #model is, it'll use the parent #model, if you take out the #model in the partial does that help?
If I have this as the view....
#model MyApp.Models.ModelName
#{
ViewBag.Title = "Test";
}
<h2>Test</h2>
<div>
<div class="editor-label">
#Html.LabelFor(m => m.Field)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.Field)
#Html.ValidationMessageFor(m => m.Field)
</div>
</div>
#Html.Partial("_partial", Model) #*note I pass the whole model*#
And this as the partial....
#model MyApp.Models.ModelName
<div>
<div class="editor-label">
#Html.LabelFor(m => m.Field)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.Field)
#Html.ValidationMessageFor(m => m.Field)
</div>
</div>
Then it does what I think you want to do (obv. it does it twice but you could remove the code from the main view).
I'm not sure it gives you a huge benefit though. Maybe I've misunderstood something.
I think that potentially you should use Display/Editor templates for types, your model includes 3 fields of that type.
#Html.Partial("_BillField", Model.Field1)
#Html.Partial("_BillField", Model.Field2)
#Html.Partial("_BillField", Model.Field3)
Defining a Template for the type rather than a Partial view is possibly more effective. See ASP.NET MVC 3 - Partial vs Display Template vs Editor Template for a detailed comparison.
In this case your View would look more like:
#Html.DisplayFor(model => model.Field1)
#Html.DisplayFor(model => model.Field2)
#Html.DisplayFor(model => model.Field3)
Your model would be:
public class model
{
[DisplayName("Field1")]
public ComplexType Field1 {get;set;}
[DisplayName("Field2")]
public ComplexType Field2 {get;set;}
[DisplayName("Field3")]
public ComplexType Field3 {get;set;}
}
Or whatever the data annotation is for the display name.
Sounds like you want a DisplayFor(m => m):
http://buildstarted.com/2010/09/10/overriding-displayfor-and-editorfor-to-create-custom-outputs-for-mvc/
Function reference: http://buildstarted.com/2010/09/29/htmlhelper-guide-for-mvc-3-part-2/
You don't need to ToString() your property.
#Html.LabelFor(m => m)
UPDATED
Based on what you want to do, re-use views, take a look at this longer than normal post about reuse of validation and partial views I wrote up to the answer for ASP.NET MVC 3 - Model Validation. It is extremely detailed.
Added a new answer as the other one didn't answer the question once clarified.
This post LabelFor extension should be able to be adjusted to suit your needs I think, but rather than override better to create your own extension.
You'll need to call it slightly differently from how you've proposed because you need to use the m=>m.Field syntax. But I'm sure this should do what you need without having to use a partial view.
If you use Model.Field syntax you will only ever send in the actual value of the Field property, like 1.23 for your decimal, you need to use the m=>m.Field to get more than just the evaluation of the property so that you can change the text in the label.

Resources