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.
Related
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
}
}
I have my _Layout.cshtml file that uses the ViewBag model to render some dynamic content.
I understand ViewBag can be populated in the controller and accessed in the view and/or layout page.
My question is, if my Layout page is using #ViewBag.SiteName, I want to avoid having to set this variable in each controller before I return the view. Is there a way to set this variable on a global level? Or how else should I pass this data to the layout page?
If you set anything in ViewBag - this happens after the Layout has been rendered -
You've missed the boat.
As others have mentioned, you can create a "helper" controller:
public class LayoutController : BaseController
{
[ChildActionOnly]
public ActionResult SiteName()
{
return new ContentResult {Content = "Site name goes here"};
}
}
Then, in your layout:
#{Html.Action("SiteName", "Layout")}
Sory about my question, I am brand new to MVC 4 Razor, it's different from Asp.NET Web form.
Look like joomla, and other web languague, how can i create a "module", eg: "news, ads, counter" and stick it to asp.NET page.
I have a layout.cshtml in share folder, i think it's "Master Page" (like Master Page in Asp.NET webform)
How can i create some positions in that layout ?
You can create partial views or controller/actions that return a partial.
Partial Views
First create a partial view (which is a razor view without boilerplate markup like doctype, html and body elements.
To use a partial view in Razor:
#Html.Partial("name-of-partial-view", model-for-the-partial-view)
Actions returning a partial
To have a controller create a partial for you, create an action like this:
public DemoController : Controller
{
[ChildActionOnly] // Optional attribute, making this action invisible to the routing system
public ActionResult Demonstration(string someparam)
{
// Do something with someparam to get information to display
return PartialView();
}
}
You'll need to create a partial view to be returned from this action. (As before, a partial doesn't have the boilrplate markup like doctype, html and body.)
And to call it from Razor:
#Html.Action("Demonstration", "Demo", new { someparam = "something" });
If you want this partial on every page, put it somewhere in your layout page.
I have a MVC3 view that enables the user to create a couple different things. Within the parent view the forms to do so are broken up via jquery ui tabs like the following:
<div id="tabs">
<ul>
<li>New Thing 1</li>
<li>Different New Thing</li>
</ul>
<div id="tabs-1">#Html.Action("CreateNewThing", "NewThingController")</div>
<div id="tabs-2">#Html.Action("CreateDifferentThing", "DifferentThing")</div>
<div></div>
</div>
<script type="text/javascript">
$(function () {
$("#tabs").tabs();
});
</script>
Within the partial view I have:
#model NewThingViewModel
#using (Html.BeginForm("CreateNewThing", "NewThingController", FormMethod.Post, new { id = "frmCreateNewThing" }))
{
...
with input fields, a submit button, etc. This seems to work well: it renders everything and posts just fine to the right controller action method.
However I'm now wiring in the validation and I've got an issue.
In the controller it is rendering the view like so:
public ActionResult CreateNewThing(NewThingViewModel model)
{
... initializing model fields, drop downs etc.
return PartialView("CreateNewThing", model);
}
I have a seperate post method like so:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateNewThing(NewThingViewModel newThingViewModel, FormCollection collection)
{
.....
}
Sample Model:
public class NewThingViewModel
{
[Required]
[StringLength(50)]
[Display(Name = "Display Name:")]
public string DisplayName { get; set; }
}
The trouble is, when the page first comes up the fields marked as [Required] through DataAnnotations in the model are showing up red as well as the validation summary showing them invalid when the page initially shows. I.E. it's acting like it's already been posted before the user gets to enter anything on the initial load or even put anything in the text boxes.
I know the first non-post CreateNewThing is firing because I can catch it in the debugger and I know the 2nd one does not on the initial load.
What would cause my validations to fire on the Get?
Is it due to the way Html.Action works and the fact that I'm rendering partial views onto another view?
I'm using UnobtrusiveJavaScriptEnabled and ClientValidationEnabled = true in web.config.
I can't find anyone else that has run into this particular problem. Every other example just seems to work, then again I don't find an example where the view is broken into three partials contained within jquery ui tabs.
How do I fix this?
Options:
Do I need to manually manipulate the Model.IsValid as a workaround?
Use a different mechanism to render the partial views on the parent view instead of Html.Action?
Use some javascript/jquery to catch the validation and stop it?
Don't have method parameters on your GET controller action. You can initialize an empty model and pass it to the view but you dont need a model to be passed into the method
You're passing in an "empty" model (which I assume has default values set for your required properties), when you should be passing in null.
How can I create an individual controller and model for a partial view? I want to be able to place this partial view any where on the site so it needs it's own controller. I am current rendering the partial as so
#Html.Partial("_Testimonials")
Why not use Html.RenderAction()?
Then you could put the following into any controller (even creating a new controller for it):
[ChildActionOnly]
public ActionResult MyActionThatGeneratesAPartial(string parameter1)
{
var model = repository.GetThingByParameter(parameter1);
var partialViewModel = new PartialViewModel(model);
return PartialView(partialViewModel);
}
Then you could create a new partial view and have your PartialViewModel be what it inherits from.
For Razor, the code block in the view would look like this:
#{ Html.RenderAction("Index", "Home"); }
For the WebFormsViewEngine, it would look like this:
<% Html.RenderAction("Index", "Home"); %>
It does not need its own controller. You can use
#Html.Partial("../ControllerName/_Testimonials.cshtml")
This allows you to render the partial from any page. Just make sure the relative path is correct.
If it were me, I would simply create a new Controller with a Single Action and then use RenderAction in place of Partial:
// Assuming the controller is named NewController
#{Html.RenderAction("ActionName",
"New",
new { routeValueOne = "SomeValue" });
}
The most important thing is, the action created must return partial view, see below.
public ActionResult _YourPartialViewSection()
{
return PartialView();
}
You don't need a controller and when using .Net 5 (MVC 6) you can render the partial view async
#await Html.PartialAsync("_LoginPartial")
or
#{await Html.RenderPartialAsync("PartialName");}
or if you are using .net core 2.1 > you can just use:
<partial name="Shared/_ProductPartial.cshtml"
for="Product" />
Html.Action is a poorly designed technology.
Because in your page Controller you can't receive the results of computation in your Partial Controller. Data flow is only Page Controller => Partial Controller.
To be closer to WebForm UserControl (*.ascx) you need to:
Create a page Model and a Partial Model
Place your Partial Model as a property in your page Model
In page's View use Html.EditorFor(m => m.MyPartialModel)
Create an appropriate Partial View
Create a class very similar to that Child Action Controller described here in answers many times. But it will be just a class (inherited from Object rather than from Controller). Let's name it as MyControllerPartial. MyControllerPartial will know only about Partial Model.
Use your MyControllerPartial in your page controller. Pass model.MyPartialModel to MyControllerPartial
Take care about proper prefix in your MyControllerPartial. Fox example: ModelState.AddError("MyPartialModel." + "SomeFieldName", "Error")
In MyControllerPartial you can make validation and implement other logics related to this Partial Model
In this situation you can use it like:
public class MyController : Controller
{
....
public MyController()
{
MyChildController = new MyControllerPartial(this.ViewData);
}
[HttpPost]
public ActionResult Index(MyPageViewModel model)
{
...
int childResult = MyChildController.ProcessSomething(model.MyPartialModel);
...
}
}
P.S.
In step 3 you can use Html.Partial("PartialViewName", Model.MyPartialModel, <clone_ViewData_with_prefix_MyPartialModel>). For more details see ASP.NET MVC partial views: input name prefixes