How to pass parent model from inside partial view - asp.net

I am rendering a parital view which has a submit button with HttpPost action in controller. This action method needs parent model as its parameter. Is there any way to send parent model as first parameter from inside a partial view?
Main view -> aspx file which has model as ParentModel
Partial view -> ascx file which has model as ParentModel.ChildModel
Controller action -> MyActionName(ParentModel model, int direction, int user)
If I keep the method as HttpPost then by default Parent model gets passed but then I cannot send 2nd and 3rd parameter as their values are decided at run time and these are not childmodel properties. e.g. direction parameter which indicates if user has clicked next/prev button. In this case, next and prev buttons call same action method (MultipleAction submit)
Additional info: My parent model has a collection of child model. I am looping through this collection and calling RenderPartial for each item. So, I cannot pass this parent model directly to my partial view (which is default behavior).
Any suggestions please? Thanks..

You could wrap all those partials into a HTML <form> so that all values get submitted too the server when this form is posted:
<% using (Html.BeginForm()) { %>
<%= Html.TextBoxFor(x => x.SomePropertyOfParent) %>
<%= Html.TextBoxFor(x => x.SomeOtherPropertyOfParent) %>
<%= Html.EditorFor(x => x.Children) %>
<input type="submit" value="OK" />
<% } %>
I use an editor template instead of partials for the Children collection. The custom editor template will automatically be rendered for each element of this children collection and provide any input fields allowing to modify it.
Then when the form is finally submitted all the properties required by the model binder to reconstruct the ParentModel will be sent to the server. As far as the direction and user parameters are concerned I would make them part of the parent view model so that my POST controller action looks like this:
[HttpPost]
public ActionResult MyActionName(ParentViewModel model)
{
...
}

Related

How to create shared form in ASP.NET CORE Razor Pages?

I have to create a reusable form in my page header. It should be displayed on each page. Simple input and submit button that will send a POST request.
Options that I'm aware of are partial views or view components.
I checked a documentation about view components but there is no mention that it works with forms. Only InvokeAsync method is available to initialize a view.
With partial view, it may be hard to define its page model and I don't understand where to put its POST handler.
One other option I see, somehow to place a form directly on _Layout.cshtml, but, again, it doesn't have its page model (afaik),
So what is a way to create a shared form, and where POST request should be handled?
You should use a view component, as this allows you to have a somewhat self-contained model and view interaction.
public class SharedFormViewComponent : ViewComponent
{
public Task<IViewComponentResult> InvokeAsync() =>
Task.FromResult(View(new SharedFormViewModel()));
}
Then, put your form HTML code in Views\Shared\Components\SharedForm\Default.cshtml. For your form's action, you'll need to specify a route. More on that in a sec. Then, to display your form:
#await Component.InvokeAsync("SharedForm")
Now, as you've somewhat discerned, view components can't be posted to. It's not an issue of "not supporting forms"; they're literally not part of the request pipeline, and therefore can't respond to requests such as a POST. You'll need a distinct action that will handle the POST on some controller. On your component's form tag, then:
<form asp-action="SharedFormHandlerAction" asp-controller="Foo" asp-area="" method="post">
The asp-area attribute should be provided just in case this is used in the context of different areas.
You'll also need a "return URL". This will be the URL of the current page, so that after the user successfully posts the form, they'll go back to the page they submitted it from. You can achieve that by adding a hidden input to your form:
<input type="hidden" name="returnUrl" value="#(Context.Request.Query["returnUrl"].FirstOrDefault() ?? (Context.Request.Path + Context.Request.QueryString))" />
Your handler action should then take a param like string returnUrl = null, and on success you should do:
return !string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)
? Redirect(returnUrl)
: RedirectToAction("SomeDefaultAction");
Where things get a little tricky is in handling validation errors. Since you're posting to a different action, you can't return the previous view the user was on to show the validation errors within the from in the layout or whatever. Instead, you'll want a view specific to this handler action which will simply be your shared form. You can use the view for your view component here as a partial:
<partial name="~\Views\Shared\Components\SharedForm\Default.cshtml" />
If you want to use a PageModel, you could do so in a BasePageModel class that inherits from PageModel and that all your pages inherit from. You can use a named handler (https://www.learnrazorpages.com/razor-pages/handler-methods#named-handler-methods) in the BasePageModel class to process the form submission. Add the form directly to the Layout, which will need an #model directive for your BasePageModel type.
public class BasePageModel : PageModel
{
[BindProperty]
public string SearchString { get; set; }
public void OnPostBaseSearch()
{
// process the search
}
}

MVC 4 - pass data via ViewBag to _Layout partial view

I have a _Layout.cshtml files as a partial view as header on each main view.
I would like to define a Select element on the _Layout and pass some data to the partial view using ViewBag so that the data is populated on the view and can later be submitted.
My questions are:
Where is the ActionResult function defined that contains and defines the data in ViewBag?
What do I do if I want to submit a form on the partial view? Where and which action should be defined/used to accept the HttpPost command?
Thanks!
What I suggest is to make a base controller class.
Inherit all your controllers from it.
The code to render the data for the layout, can lie in it's constructor, or some other common function that all your controllers can use as children of this base class.
public class BaseController : System.Web.Mvc.Controller
{
public BaseController()
{
// This code will run for all your controllers
ViewBag.MyData = "SomeData";
}
}
About your question:
What do I do if I want to submit a form on the partial view? Where and which action should be defined/used to accept the HttpPost command?
You can just put the controller name on your form:
#using (Html.BeginForm("ActionName", "Controller"))
There are possibly a few misunderstandings about how _layout.cshmtl and partial views work:
_layout.cshtml is not a partial view. It is the layout template used by all your pages. It is kind of the "outer" of the page. It is automatically applied (except if you set Layout = null). A partial view in turn is the "inner" of the page. You call it explicitly from your page using #Html.Partial.
Even though your page is rendered by multiple views - the actual view, the layout, maybe some partial views - it is still the result of a single controller action. (Except if you use #Html.Action for rendering partial "actions"). Also, the page rendered is a single HTML page, that is, any forms on the page are simply HTML forms.
Therefore, the answer to "where is the ActionResult function defined" is: In the action that you want your page to be rendered for.*
The answer to "Which action should be used to accept the HttpPost command" is the same as if the form was on your page: You can define an arbitrary action on an arbitrary controller for receiving the form. You just need to refer to that action when you render the form:
#using (Html.BeginForm("action", "controller")) { ... }
*) If you want to prevent having to build the select list in each and every controller action that relies on _layout, you could conceivably use #Html.Action. That is, you define a "partial action" which is nothing else than a controller action that returns a PartialView() and a partial view to render the model from that action. Then you can use that partial action to build the select list.
However having read some news about ASP.NET vNext, partial actions seem not to be liked to much by the community and in vNext there will be another way to achieve the same.
Still if you want to go this way this enables you to separate the logic for your dropdown (language? user menu?) from your other actions and views:
class UserController
{
PartialViewResult UserMenuDropdown()
{
return PartialView(BuildUserMenuFrom(.....));
}
[Post]
ActionResult PostUserMenu()
{
// do whatever you want once the form is posted
}
}
In your _layout you call the partial action:
#Html.Action("UserMenuDropdown", "User")
And in the view for UserMenuDropdown you render the form:
#using (Html.BeginForm("PostUserMenu"))
{
#Html.DropDownListFor(m => m.UserMenuSelectList)
}
This way your dropdown list becomes a "first class member", with its own controller action, main view, and model. You don't need a ViewBag for this, and you don't have to build the select list in each and every controller action.

passing data by POST method between views in asp.net

I have data in a particular view page(collected from the user) which I need to send to another view page which has a form needing this data. Now I can use:-
1. post method of javascript(jquery)
$().redirect('/Events/Create', {'arg1': 'value1', 'arg2': 'value2'});
or
A form:-
$('#inset_form').html(' < form action="/Events/Create"
method="post"style="display:none;">< input type="text" name="lat"
value="' + latitude + '" />< /form>');
document.forms['vote'].submit();
Now my question is, which method should be chosen?
Also, the '/Events/Create' page has form in the following way:-
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.lat)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.lat)---------> THIS field accepts the POSTed data
#Html.ValidationMessageFor(model => model.lat)
</div>
<div>
....OTHER INPUT FIELDS.....
</div>
<p>
<input type="submit" value="Create" />
</p>
}
So, my next question is how do I make the text box 'lat' created by #Html.EditorFor in the form show the POSTed data as its default value. Then, the user can fill other fields and then submit the form.
As per HTTP standards, if a call is not changing the state of the website (i.e. data), you should use a GET request. This means you should provide the user with a link including the proper query string to populate the form on the /Events/Create page.
Also, keep in mind you never send data from view to view in MVC. Everything goes through a controller action so make sure your "Create" action on the "Events" controller accepts the proper arguments to populate the form for submission. If the Model.lat variable has a value in it when rendering the view, the text box will be prepopulated with that value.
Why do you want to POST the data to the second form? If you want to show the form and load it with default values, create a GET action method for it and pass the default values in the url. Although there's nothing stopping you having two POST methods for the same view, the normal approach would be to load the form using a GET, then POST back the data after editing.
[HttpGet]
public ActionResult Create(string lat)
{
MyView view = new MyView();
view.lat = lat;
View(view);
}
[HttpPost]
public ActionResult Create(MyView view)
{
//process posted data after editing
}
The view object here is a ViewModel class containing properties for the data that you will edit on the form. Make your View strongly typed to this ViewModel type.
To invoke the GET method from your jQuery (above):
window.location = "Events/Create?lat=myValue";

How to send the data to one page to other page using asp.net mvc

I have two pages with two grids,
in my first page I have 10 editable rows. and I have one next button.
I need to get all these 10 rows edited values to the next page to store the edited values to the other variable ID's in the next page. what is the best way to use this type of situations
Thanks
Have your editable rows inside of a <form> in the first view
// BeginForm will render a <form> around the rowmodel HTML
using (Html.BeginForm('PostToMe', 'MyController')) {
foreach (RowModel row in Model) {
// HTML to render out a row
}
}
then send each row as an instance of a view model type with properties that match the data for each row
public class RowModel
{
// properties here
}
Finally post a collection of those types to the controller action. e.g.
public class MyController : Controller
{
[HttpPost]
public ActionResult PostToMe(IList<RowModel> rows)
{
// do something with rows
}
}
In ASP.NET MVC there is no notion of Page. There are controllers, actions and views. So probably what you are talking about is how to have a view send information to a controller action. Well there are couple of ways. One consist in generating an HTML <form> in this view, filling it with input elements and pointing this form action attribute to the target action:
<% using (Html.BeginForm("TargetAction", "SomeController")) { %>
... some input fields, maybe in a grid, whatever
<input type="submit" value="GO" />
<% } %>
If you want to select a particular row in the grid then you could generate a form on each row in this grid and use the item id as hidden field.

Html.ValidationMessage renders on all fields, regardless if they're valid or not

My model is correctly validated. If I take a peak in the validation results during debug, I will see that everything is correct. However, all my validation results will show, even if only one is invalid. Again, during debug, only one field is correctly showing up in the validation results, but when my view is rendered all our displayed:
[HttpPost]
public ActionResult Create(Widget widget)
{
if (widge.Valid)
{
// Save to db
}
retun View(widget);
}
My view:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" Inherits="System.Web.Mvc.ViewPage<Widget>" %>
// took out a lot of html here
<form action="Create" method="post">
<input name="Widget.City" value="<%= Model.City == null ? "" : Model.City%>" />
<%= Html.ValidationMessage("Widget.City")%>
<input name="Widget.Department" value="<%= Model.Department == null ? "" : Model.Department %>" />
<%= Html.ValidationMessage("Widget.Department")%>
<button type="submit">Save</button>
</form>
Let us say City and Department are set to NotNull in my model and I correctly put in a City, but leave Department blank. Again, it will show it is invalid on the controller, with the property Department having a problem, yet in my view I'll get "may not be null" messages for both properties. If I have 4 properties and 4 ValidationMessage tags in my view, even if one property is not valid ... all 4 will show. What's the deal?
If I'm not mistaken, I think you want to be using
Html.ValidationMessageFor(model => model.City)
Not what you're using currently in your view.
Also... Since you're using a strongly typed view, you should be checking ModelState.IsValid to determine whether or not you should save your Widget. That is if you're using data annotations on your view model.
What are you using as your input to the [HttpGet] action of the view?
Since you're using the format Widget.Property the view expects the model to have a Widget property containing the data for the widget.. i.e it is looking for Model.Widget.Property but your model only contains Model.Property. Based on the code posted here, you're only passing the widget back to the view and it will interpret that as having a NULL Widget property, thus triggering all NotNull validation.
A solution here is to assign the Widget post model (what you're accepting in the [HttpPost] action) to a Widget property of the of the model you're passing back to the view.
public class CreateViewModel
{
public Widget Widget { get; set; }
}
[HttpPost]
public ActionResult Create(Widget widget)
{
if (widge.Valid)
{
// Save to db
}
var viewModel = new CreateModel() { Widget = widget };
retun View( viewModel );
}
Hopefully I articulated that correctly.. Its been a long day = )

Resources