I have an MVC 4 app and am using HTML.BeginForm to create a form on a View. I have several field sets where I have positioned them on an absolute basis. For example, here is my BeginForm and one of my fieldsets:
<div style="width:1100px; height:490px;">
#using (Html.BeginForm("Create", "BicycleSellerListing", FormMethod.Post, new { enctype = "multipart/form-data" })) {
#Html.ValidationSummary(true)
<fieldset style="position:absolute; top:180px; left:150px; width:550px">
<legend>Bicycle Information</legend>
...
...
The problem is that if there are errors in the ModelState when a submit is done, the errors generated are covered by some of the fields in the fieldsets. Is there a way I can indicate where I want model state errors to display?
There are couple ways to display errors. #Html.ValidationSummary() will display all of the errors in one place. So depending where you put #Html.ValidationSummary(true) - that's where errors will be displayed. For example, you can put it after the form, not in the beginning like you have it.
Another way is to use #Html.ValidationMessageFor(...) helper. It takes your mode's property name so the error will only be displayed for that property. Again, you can put it anywhere you want. Usually people put it next to the input so that error message is displayed next to the input as well.
Related
I have a partial view (_FormCustomer) that displays a form for creating a customer. I also have a View Component (Countrylist) that generates a options list of countries. Now I want to show the country list in my form. This is what I do:
Index.cshtml
<partial name="_FormCustomer" for="#Model._Customer" />
_FormCustomer.cshtml
<select asp-for="#Model.Land" class="form-control">
#await Component.InvokeAsync("Countrylist");
</select>
CountrylistViewComponent.cs
public async Task<IViewComponentResult> InvokeAsync()
{
return View(await _countryRepository.GetCountriesAsync());
}
(The function GetCountriesAsync() returns a list of countries; this works fine.)
Pages/Componenst/Countrylist/default.cshtml
#model List<Country>
#foreach (Country country in Model)
{
<option value="#country.code">#country.name</option>
}
Unfortunately, select-box stays empty when I call the partial. When I call #await Component.InvokeAsync("Countrylist"); directly from Index.cshtml, however, it works fine.
So it looks like you cannot use a View Component inside a Partial View. Is this conclusion right? Or am I doing something wrong?
Thanks Phantom2018, found the problem after your post.
#0: I'm using Razor pages
#1: this had no effect
#2: this was a typo in my question, not in my code
#3: the debugger shows me that the vie component gets called, so
My actual code is a little different, I want to pre select a country if it's available:
<select asp-for="#Model.Country" class="form-control">
#if (Model == null)
{
await Component.InvokeAsync("Countrylist");
}
else
{
await Component.InvokeAsync("Countrylist", Model.Country);
}
</select>
And after some testing, I found the solution:
<select asp-for="#Model.Country" class="form-control">
#if (Model == null)
{
#await Component.InvokeAsync("Countrylist");
}
else
{
#await Component.InvokeAsync("Countrylist", Model.Country);
}
</select>
Don't know why, but I had to use #'s before the awaits.
I have now tested this scenario and can confirm that the data loads fine - both, when the view component is directly included on the page or when it is included in a partial View. (I have tested this on Razor pages - but it is likely to work the same when using MVC. You have not mentioned if you are using MVC or Razor pages.)
A couple of things you can try to see if the loading works fine:
1) From all "Select"s and "Partials" remove the "for*" attributes. That way you can first check if the data loads & then you can worry about binding to the selected item. (Also, in your provided code, you have omitted the model variables - so it is not possible to comment on them.)
2) Remove the last ";" in your _FormCustomer.cshtml
<select asp-for="#Model.Land" class="form-control">
#await Component.InvokeAsync("Countrylist")
</select>
Note that I have removed the trailing ";" in the await statement. I noticed that the ";" was added as another "option" in the select !
3) I also noticed that even minor syntax errors (not picked up by Intellisense) can cause the select to not load. Debug to see if your InvokeAsync is actually being called - in a scenario where there was a minor syntax error, the InvokeAsync was not even being called.
Also keep in mind that:
"When a partial view is instantiated, it receives a copy of the
parent's ViewData dictionary. Updates made to the data within the
partial view aren't persisted to the parent view. ViewData changes in
a partial view are lost when the partial view returns."
I have read multiple posts on similar issue but did not work.
I have fixed footer buttons and facing issue in calling "Post" version Edit action in Project controller. Here is what I'm trying to do
Let me know if question needs further explaination.
(I tried using Ajax.ActionLink which is suggested in multiple posts too but did not work out.
Similar Question
Finally I managed to fix it by some workarounds. Posting solution here to help someone.
Like I said earlier, I tried using Ajax.ActionLink but I was not able to achieve the same. Instead I looked for Calling Form Submit action from outside of form that's what I actually need here.
Form: Name your Form something, say "editProjDetailsForm"
#using (Html.BeginForm(null, null, FormMethod.Post,
new { #class = "form-horizontal",
name = "editProjDetailsForm" }))
Footer: Call this method from the footer button.
<input type="button" onclick="document.editProjDetailsForm.submit();"
class="btn btn-primary"
value="Save Changes" />
I tried this one too in footer but it did not workout:
#Ajax.ActionLink("Save Changes", "Edit", new { id = Model.ProjectId },
new AjaxOptions { HttpMethod = "POST" })
Helpful Posts
Naming A form using Begin Form
Submit Button outside HTML.BeginForm
Submit Button outside HTML.BeginForm (another link)
ASP.net ActionLink and POST Method
Error in case you have parameterized constructor in ViewModel and
did not declare parameterless constructor
Display Issue if you are using bootstrap In footer I had one input type = button and one action link with class= button. Both nested in one btn-group but there height were appearing different as visible in following snapshot:
Fix: Found that it is a known issue and there is one suggested solution but did not work out much for me (i.e. for Internet Explorer).
input type=submit and anchor button in btn-group appear as different sizes
Solution: add .btn { line-height:normal!important; } or if you want to do only for a specific button lets say the above input button then do this:
<input type="button" onclick="document.editProjDetailsForm.submit();"
class="btn btn-primary"
value="Save Changes"
style="line-height:normal!important;" />
From reading the posts in this thread - and being unable to post the question there for some bizarre reason :( I will ask it here in hope of getting a solution
Am I write in saying that I have to do validation like below..
I add the html5 attribute (data-required-msg/validationMessage) to the textbox and the required attribute as well..
I make a span for the invalid msg and tie it to the field with the "data-for" attribute. The message "Please enter name" should appear in this span then.
Questions
Is this the only way to work with this?
Is there no way for me to display the proper error message ("Error Message I want to show"), as in any way to tie to the mvc attributes on the ViewModel. As another poster said this is a lot more scalable/re-usable and better design.
Using a data-for="Name" is very brittle as a change in the Model field name will not reflect there and that could be forgotten about hence delivering buggy software. You are losing the type safety of something like
#Html.ValidationMessageFor(m=> m.Name)
Code
public class AViewModel
{
[Required(ErrorMessage="Error Message I want to show")]
public string Name { get; set; }
}
<div class="validation-wrapper">
<div class="input-wrapper">
#Html.TextBoxFor(m => m.Name, new { placeholder = "eg. John Smith", data_required_msg="PleaseEnter name", required="required" } )
</div>
<span class="k-invalid-msg" data-for="Name"></span>
</div>
Cheers,
J
In order to be able to do what you are saying, you need to be using Kendo UI for ASP.NET MVC. With that you can continue using your DataAnnotations attributes and Html.ValidationMessageFor() helpers as before. All you will need to do is call $('[your_form_selector]').kendoValidator() after your form (or on document.ready()).
I'm learning MVC and am stumped by this. I'm trying to factor some common code that gets data and displays it in a WebGrid into a partial view that i can use on multiple pages.
My home controller Index method just does a return View(). The Home view looks like this:
#using (Ajax.BeginForm("SearchAction", "Search",
new AjaxOptions { UpdateTargetId = "data-grid", HttpMethod = "Post" }))
{
#Html.TextBoxFor(model => model.name)
<input type="submit" value="Search" />
}
#{
<div id="data-grid">
#Html.Partial("SearchResults", Model)
</div>
}
I'm trying to use Ajax to avoid losing my search form data when clicking a WebGrid pager link, which are rendered as normal links.
My SearchController looks like this:
public ActionResult SearchAction(string name)
{
return RedirectToAction("SearchResults", new { name = name });
}
public ActionResult SearchResults(string name)
{
//does database query and sticks results in the viewbag
//filter on optional name parameter
VieweBag.Members = MyQueryResults;
return PartialView();
}
My SearchResults shared view, data is passed in via ViewBag.Members:
#{
var grid = new WebGrid(null, rowsPerPage: ViewBag.Pagesize);
grid.Bind(ViewBag.Members);
#grid.GetHtml(// etc. etc.)
}
The results I'm getting is that the ViewBag.Pagesize and ViewBag.Members binding fails since there is no data in the viewbag. Obviously, my partial controller is not being called to do the initial query and put stuff in the ViewBag when the home page is first loaded. How do I make that happen?
The other weird thing is that if I just copy the database query code into my home controller (where it originally was) to force the original query, then if I put some text into the search field and do a search, the partial view renders by itself on a new page. Why is that happening, I thought it would only render as part of my home page.
I've cobbled this partial view together from various answers/places and have no doubt gotten something horribly wrong :\
The partial page won't pass through a controller, but simply render the view directly. If you want to pass view data to the partial view, there is an overloaded function that takes a viewdata dictionary. I'm sorry I can't be more detailed, but I'm on my mobile (waiting for my son to fall asleep in the other room) :)
Update:
If you want to trigger a GET action for your partial view, you can use Html.Action. Here are some useful links:
MSDN RenderAction
Difference between RenderPartial and RenderAction
Further, it would probably make sense for you to move your form tags into your partial view, but those are details for when you clean up the code.
Jonass is right, the ViewBag only propagates between the Controller and the View.
One thing you can do is make the model of the partial view be the same as the type of data you're putting into the ViewBag.
So if for example your MyQueryResults is of type:
IEnumerable<Result>
In your partial view you'd add
#Model IEnumerable<Result>
And then in the main view, you'd pass it through the Render method:
#Html.Partial("SearchResults", ViewBag.Members);
You'll need to tweak this a bit to make sure it's the right type, but this should do the trick.
Good luck!
Sorry but I am new to C# and ASP.NET and I saw alot of posts about this problem but I quite didn't get it. I am trying to understand how to pass a GET parameter to an action thru HTML.ActionLink:
here is the the URL:
http://localhost:36896/Movies/SearchIndex?searchString=the
and my CSHTML page should look like this:
<input type="Text" id="searchString" name="searchString" />
#Html.ActionLink("Search Existing", "SearchIndex", new { searchString = "the"})
this hard coded parameter "the" is actually working, but how can I select the input element with id=searchString, with something like document.getElementById("searchString").value
Thanks,
If the value you want to send as GET parameter is not known on the server you cannot use the Html.ActionLink helper to add it. You need to use javascript to manipulate the existing link and append the parameter.
It looks like you have an input field that contains a search string and you want to send the value entered in this field to the server. A better way to handle this scenario is to use an HTML form with method="GET" instead of an ActionLink. This way you don't need to use any javascript - it's part of the HTML specification:
#using (Html.BeginForm("SearchIndex", "Movies", FormMethod.Get))
{
#Html.EditorFor(x => x.SearchString)
<button type="submit">Search</button>
}
Now when you click on the Search button the value entered in the SearchString field will automatically be sent to the SearchIndex action:
http://localhost:36896/Movies/SearchIndex?searchString=the
But if you absolutely insist on using an ActionLink you will have to write javascript to manipulate the href of the existing link when this link is clicked in order to append the value to the url. It's an approach I wouldn't recommend though because the HTML specification already provides you this functionality throughout HTML forms.
This makes the #Html.EditorFor refer to the Title field of the object, kinda in a random way but it works!
#using (Html.BeginForm ("SearchIndex", "Movies", FormMethod.Get))
{
#Html.EditorFor( x => x.ElementAt(0).Title)
<button type="submit">Search</button>
}
Still couldn't pass input parameter to the URL in the GET.
EDIT:
FINAL SOLUTION:
#Html.TextBox("SearchString")
<button type="submit">Filter</button>
and on the controller side, switch the input parameter. Basically it will automatically recognize the passed parameter.
public ActionResult SearchIndex(string searchString)
{
...
}