How does #Html.BeginForm() works? - asp.net-mvc

I'm very new to ASP.NET, just started the MVC tutorial today on asp.net. I got here http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/examining-the-edit-methods-and-edit-view
So far so good, the problem:
In my View I have the following code
(Model is set to the view with #model MyFirstMVC4.Models.Movie)
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
//... bla bla html input
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
My MovieController
// Shows the view
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
//
// POST: /Movie/Edit/5
[HttpPost] // Handles the view above
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
And here is the question - How the heck does it pass the Movie object to the POST method above?! When I observe the client side there is
<form action = "/Movie/Edit/1" ... />
Here I don't understand why action = url of the very same view page?!1
Also on the server side there is just Html.BeginForm() :(
How does it realize to what action method to post and what route parameters to pass?
It works, I just don't know why

The version of BeginForm in the code,
with no parameters, sends an HTTP POST to the current URL, so if the view is a response to
/Movie/Edit/5, the opening form tag will look like the following:
< form action="/Movie/Edit/5" method="post">
The BeginForm HTML helper asks the routing engine how to reach the Edit action of the
MovieController. Behind the scenes it uses the method named GetVirtualPath on the Routes
property exposed by RouteTable — that’s where your web application registered all its routes in
global.asax. If you did all this without an HTML helper, you’d have to write all the following
code:
#{
var context = this.ViewContext.RequestContext;
var values = new RouteValueDictionary{
{ "controller", "movie" }, { "action", "edit" }
};
var path = RouteTable.Routes.GetVirtualPath(context, values);
}
<form action="#path.VirtualPath" method="get">
...
</form>
You asked how is movie object is passed. That is called model binding.
When you have an action with a parameter, the MVC runtime uses a model binder to build the
parameter. You can have multiple model binders registered in the MVC runtime for different types
of models, but the workhorse by default will be the DefaultModelBinder.
In the case of an Movie
object, the default model binder inspects the Movie and finds all the movie properties available
for binding. Following the naming convention you examined earlier, the default model binder can automatically convert and move values from the request into an movie object (the model binder can
also create an instance of the object to populate).
In other words, when the model binder sees an Movie has a Title property, it looks for a value
named “Title” in the request. Notice the model binder looks “in the request” and not “in the form
collection.” The model binder uses components known as value providers to search for values in
different areas of a request.
The model binder can look at route data, the query string, and the form
collection, and you can add custom value providers if you so desire.

When you call BeginForm() without any parameters it default to using the same controller/action used to render the current page. It assumes you'll have an action with the correct name on your controller that will accept postbacks (which you do). It uses the RouteValues to do this.
It automatically binds each input control (by name) to the parameters of the action accepting the postback - or in your case, the properties of the object parameter for the action accepting the postback.

[HttpPost] attribute is given to the action that you want to be called on the POST submit of the form.
to understand how #using (Html.BeginForm()) works , you need to know what page it is already on . using #using (Html.BeginForm()) in 2 different views will come back to two different controllers

We can create forms by typing simple html or by html helpers.
One of them Html.BeginForm(); it is a little bit odd because you actually can wrap it in a using statement because this particular helper returns an object that implements IDisposable in C#. First it writes out with opening tag. And at the bottom when the generated code calls dispose on that object, that’s when it will write out closing form tag . So BeginForm gives me an object that will write out my opening form tag and my closing from tag. After that you don't worry about anything you can just focus on labels and inputs

Related

Model usage within View

I think I'm not clear with the #model that can be part of the view
For example
#model MyModel
Is it the input argument that I can populate and call the view with?
return View("MyView", MyModel);
Is it the output variable I can populate during the post of the view (for next control action)
[HttpPost]
public ActionResult SomePostAction(MyModel myModel) //(and in post action)
Is it both ??
3, it's both!
It is called model binding. A feature of ASP.NET which makes it trivial to bind a mode to a view. Hence the name 'view model', which those models are usually called.
Assigning a model to your view gives you a so-called strongly typed view, which fully exposes the power over the Razor syntax.
The model binder is capable of binding the values of every input field back to the model when posting the form, as long as the name attribute of the form matches the name of the property on the view model. Html helpers such as Html.EditorFor(m => m.SomeProperty) makes this a trivial task.
As Mystere Man mentions, it's also possible to do this without an actual model in your view. For instance, this works:
Html (I omitted the form tag and submit button):
<input type="text" name="SomeString" />
with this method in your controller:
[HttpPost]
public ActionResult SomeAction(string someString)
{
// ...
}
The #model declaration at the top of your view is related to the model object you passed to the View() method in your controller (option 1 in your question). The #model declaration is your way of telling the Razor view engine that the view is strongly typed. That means the C# compiler can double check any properties of your view accesses.
Suppose you had the following class
public class MyModel
{
public string Name { get; set; }
}
Without a strongly typed view you might have something like this in your view
<div>
Hello, #Model.Nmae
</div>
Notice that there's a typo in Name. ASP.Net has no idea what your model is so it has to use a dynamic object. You won't find that error until runtime. If you had delcared #model MyModel you would have an error at build time because MyModel doesn't have a Nmae property.
However, it's not uncommon to use the same model type as a parameter of the action. Imagine your page is an HTML form. In that case the model that your view is strongly typed to and the model that's passed to an MVC action could be the same.

ASP MVC 4 - binding and passing data from view to controller in repeating controls

Being new to ASP MVC, I met the following problem.
I have a list of "repeating" controls on my page, which are presented by the following Razor code:
#model BankBLL.Interfaces.ISecureFolder
...(some irrelevant code here)
<header><h3 >Commitee list</h3></header>
#foreach (var commitee in Model.Commitees)
{
<a href="#Url.Action("CommiteePage", "SecureFolder", commitee)">
<div class="commiteeButtonImageContainer">#commitee.Name</div>
<img src="~/Images/CommiteeButtonImage.png"/>
</a>
}
Model.Commitees here is a List of ICommitee objects, that means that I am trying to "bind" each Url.Action to a corresponding ICommitee commitee object.
However, when it comes to my controller action:
public ActionResult CommiteePage(ICommitee commitee)
{
return View("CommiteePage", commitee);
}
looks like I am making it a wrong way, because application returns "Cannot create an instance of an interface." error, that means that application is unable to retreive required commitee object when the action link is clicked.
Is there a way to bind each row "item datacontext" (ICommitee object in this case) to correspoding Url.Action?
Unfortunately could not post it earlier due to reputation regulations.
Finally resolved this issue due to good explanation at:
HTML.ActionLink method
When you try to pass an argument from Url.Action or Html.ActionLink - you have to specify explicitly the final "null" argument responsible for html arguments.
In my case the following code works correctly:
slightly changed controller action (now receives just name instead of commitee object itself)
public ActionResult CommiteePage(string commiteeName)
{
return View("CommiteePage", SecureFolder.Commitees.First(o=>o.Name == commiteeName));
}
and changed syntax for html calling this action:
#foreach (var commitee in Model.Commitees)
{
<a href="#Url.Action("CommiteePage", "SecureFolder", new { commiteeName=commitee.Name }, null)">
<div class="commiteeButtonImageContainer">#commitee.Name</div>
<img src="~/Images/CommiteeButtonImage.png"/>
</a>
}
Now view correctly passes the name of selected commitee to controller so that I can redirect to corresponding commitee view.
Thank you all for helping to resolve this issue!
The main problem is that the default model binder cannot create an instance of an interface. Try to be more specific, i.e. public ActionResult CommiteePage(ImplementedCommiteeType commitee). You can also create a CommiteeViewModel: ICommitee class in which you can transport your structures (in Controllers and Views only).
Or you can create your own model binder which knows what to implement. This is slightly more complicated.

umbraco mvc surface controller, can't return view from HttpPost Action

Overview of the problem:
I've created a Surface controller with an action that is called using #Html.Action(...).
The #Html.Action call is done within a Macro partial view and the macro is included within the content of a page using the rich text editor.
(I'm new to this so if i'm going about things the wrong way then please let me know.)
The Surface controller has a GET and a POST action but it's the get action called within the macro partial.
Get action renders fine, entering no data into the form will invalidate the model state (which is what i'm currently testing).
submitting the form (with no entered data) means i can step into my POST action, ModelState.IsValid is set to false and CurrentUmbracoPage() is returned.
All fine... No Exceptions encountered when debugging...
It's at this point that the error text "Error loading Partial View script" appears on the page.
All I'm trying to do is return the same page with the validation messages showing.
Details:
Umbraco v6.0.5
The Controller I'm currently working on is used to reset a user's password. I also have a login conroller that is getting around this issue by using RedirectToCurrentUmbracoPage().
to access the page that contains the macro i use the address http://{testhost}/Reset-Password
the error text returned reads: Error loading Partial View script (file: ~/Views/MacroPartials/ResetPassword.cshtml)
code is within a seperate solution and views and bin directories are copied accross.
nuget package UmbracoCMS.Scaffolding is used.
Controller code:
public class ResetPasswordSurfaceController : SurfaceController {
[ChildActionOnly]
[HttpGet]
public ActionResult Reset(string token, string email) {
// Validation Code Omited
var user = Membership.GetUser(username);
return PartialView("Reset", new ResetPasswordSurfaceModel { UserID = user.ProviderUserKey.AsInt() });
}
[HttpPost]
public ActionResult PostReset(ResetPasswordSurfaceModel model) {
if (ModelState.IsValid) {
//Password reset code omited
return RedirectToCurrentUmbracoPage();
}
//works but only partial view content is rendered
// return PartialView("Reset",model);
return CurrentUmbracoPage();
}
}
View - ~\Views\ResetPasswordSurface\Reset.cshtml:
#model UmbracoExt.Models.ResetPasswordSurfaceModel
#using (Html.BeginUmbracoForm("PostReset", "ResetPasswordSurface")) {
#Html.EditorForModel()
<input type="submit" value="Submit" />
}
Macro Partial View - ~\Views\MacroPartials\ResetPassword.cshtml:
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#Html.Action("Reset", "ResetPasswordSurface")
Any help is appreciated.
Edit:
Removing the [HttpGet] attribute from the Reset Action has revealed that after the PostReset action is called the Reset action is also called.
Renaming PostReset to Reset and re-adding the httpget attribute to the original Reset Action results in the post action being called twice.
the second time it is called causes the exception:
Can only use UmbracoPageResult in the context of an Http POST when using a SurfaceController form
I have reverted the changes so i'm back at Reset ([HttpGet]) being called after the PostReset action.
So the problem still stands. How can i get around this issue?
I need to return the result from the PostReset Action.
This is how I solved this problem:
I created extension method for model:
public static class ExtensionMethods
{
public static void MapModel<T>(this WebViewPage<T> page) where T : class
{
var models = page.ViewContext.TempData.Where(item => item.Value is T);
if (models.Any())
{
page.ViewData.Model = (T)models.First().Value;
page.ViewContext.TempData.Remove(models.First().Key);
}
}
}
Controller code:
[HttpPost]
public ActionResult Index(MyModel model)
{
TempData.Add("MyModel", model);
return RedirectToCurrentUmbracoPage();
}
Partial view code:
#using UmbracoTest.Extension
#using UmbracoTest.Models
#model MyModel
#{
this.MapModel<MyModel>();
}
#using (Html.BeginUmbracoForm("Index", "Home", FormMethod.Post))
{
<div>
#Html.TextBox("Text", Model.Text )
</div>
<input type="submit" name="submit" value="Submit" />
}
The Answers were given to me here
All credit goes to Shannon Deminick
The post action does not return anything for the response (that bit was new to me).
After the post when the Reset action is run the second time, since the modelstate is maintained, by passing a newly instantiated model, this model will inherit the model state of the model processed in the POST action (PostReset).
During the second time the Reset action was called, the validation logic meant it never gets to the point where it returns the partial view.
i temporarily bypassed the validation logic and sure enough the model validation messages were displayed.
I fixed this error by resolving a naming conflict:
Make sure that the GET and POST methods are named differently
Make sure the controller name doesn't conflict with any document types

Model with List - approaches to add new item to the list from a Razor view

I have a model with various properties but the one of interest is a List of another type of Model.
For example:
public class User
{
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<UserInterest> Interests { get; set; }
}
I then use an Editor Template within my view to render out a view for each item of the model items.
#Html.EditorFor(x => x.Interests)
The EditorFor template looks something like:
#model Interest
<div>
#Html.HiddenFor(x => x.Id)
#Html.TextBoxFor(x => x.InterestText)
#Html.CheckBoxFor(x => x.Delete)
....
</div>
Something very similar to the accepted answer here: Model Containing List of Models (MVC-3, Razor)
My question is - how would you from the client-side (jQuery) create a new item within the property without going back to the server. I currently have a rough way of doing it whereby I post the data back to my controller which returns the model back with a new blank item within the Interests property.
This seems to be overkill making a HTTP request and not very elegent. I was thinking of using jQuery .Clone() but not entirely sure on what I'd need to do in terms of naming the elements and clearing existing values.
So does anybody have any suggestions. I'm hoping to get more opinions and different approaches.
You can simply create the Textbox and checkbox on the fly and add that to the DOM. When saving it, Use jQuery ajax to post that data ( new record data) to an action method and save it there. Return a status back (Succcess /Falied) from your action method to your client side code ( your callback function of jQuery ajax/post) and check it there. If it is success, Show a success message to the user and append the new item to the existing list.
Sample jSFiddle : http://jsfiddle.net/carwB/2/
If you want to return some complex data ( ex : All new records with its id etc..) along with the status, you may return JSON from your action method.
EDIT : To keep your Model binding works with the newly added dynamic elements, you need to follow the naming convention of the elements.
The trick is to keep the id property value of the html element in this format.
CollectionName_ItemIndex__PropertyName
and name property value in this format
CollectionName[ItemIndex].PropertyName
I created a sample working program and explained it how it works Here based on your requirements.
In such situations I prefer to use client templating. You send data to server with ajax and then receive JsonResult. Look at JsRender this is javascript lib without jQuery dependency.
1.Create two partial view one is for list item and second one is creation
2.First partail view should be inside the div which has id 'divMdeolList'
3.and Creation view will have the code like that
#using (Ajax.BeginForm("SubmitData", new AjaxOptions { UpdateTargetId = "divMdeolList" }))
{
#Html.TextBoxFor(x => x.InterestText)
<p>
<input type="submit" value="Create" />
</p>
}
4. And then create a ActionResult type action on controller that will render the partialview
public ActionResult SubmitData(YourModel model)
{
//Do : save the record
return PartialView("FirstPartailView", model);
}
This will update the View without postback

How can I emulate model binding behaviour when rendering an ActionLink?

In the following code, the get action returns a betting card for a given race date, and the post I use the post action to transform properties of the bound model to route values for the get action.
Essential aspects of the Details View:
#using (Html.BeginForm("Upload", "BettingCard",
FormMethod.Post, new { id = "uploadForm", enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true, "The upload was unsuccessful. The following error(s) occurred: ")
<div id="date-selector">
<div id="ymd">
#Html.LabelFor(model => model.RaceDate)
#Html.DropDownListFor(model => model.RaceDay, Model.YmdLists.Days)
#Html.DropDownListFor(model => model.RaceMonth, Model.YmdLists.Months)
#Html.DropDownListFor(model => model.RaceYear, Model.YmdLists.Years)
<input type="submit" value="Upload for this date" />
</div>
</div>
#Html.Telerik().Upload().Name("UploadedFiles")
}
Essential aspects of the controller code:
[HttpGet]
public ActionResult Details(int year, int month, int day) {
var model = new BettingCardModel
{
ResultMessage = "No betting card was located for the selected date."
};
DateTime passedDate;
if (!DateTimeHelper.TrySetDmy(year, month, day, out passedDate)) {
ModelState.AddModelError("", "One or more values do not represent a valid date.");
return View(model);
}
model.RaceDate = passedDate;
var bettingCard = _bettingCardService.GetByRaceDate(passedDate);
model.MapFromEntity(bettingCard);
return View(model);
}
[HttpPost]
public ActionResult Details(BettingCardModel model)
{
return RedirectToAction("Details", new { year = model.RaceYear, month = model.RaceMonth, day = model.RaceDay });
}
A good deal of the above code is experimental and diagnostic, so I'd like to avoid getting into a review of code that works, and rather concentrate on what I need to achieve. In the Details view I only need one 'command', being 'Display for Date', so I get off easily by using the submit button and the http post takes care of model binding. However, in the Upload view, I need two commands, being 'Display for Date' and 'Upload for Date', so I would like to make the 'Display for Date' operate strictly with the get actions, and only use a post action to submit an uploaded betting card for the date.
My problem is that when I make the 'Display for Date' command use an ActionLink instead of a submit, using model.RaceDay etc. as routing values, the URL parameters passed to Details all still contain their initial values, not values set by the user in the dropdowns. It seems the model binding code (whatever that may be) is not invoked for action links. What could I do here to avoid need a post just to do that binding?
I realise this probably not a direct model binding issue, but I don't know how else to express my question. When elements 'bound' to model properties are rendered, they have a bit more on their side than a simple input, say, and some basic styling, but something is 'built' around that input with lots of metadata. I would like some way to use that metadata to map to a URL when a get link on the page is clicked.
The problem you're having is that all of the model data and metadata is generated on the server dynamically and given to the client as static content. The binding is only aware of a change to the Model once it is submitted to the Server. All of that model metadata is static on the client side, using pure .NET it will have no way to know when a user changes a value in the drop-down to also change that value in a static anchor tag, which is what the ActionLink renders to. The answer is to use javascript. There are many many way to accomplish what you're trying to do through javascript. You could potentially write a custom HtmlHelper class to generate the javascript for you. However if you don't want to use javascript then you will HAVE to do a post to get the data the user selected to the Server.
If you're trying to avoid having to re-write code then you can create a partial view for the contents of the form and embed that in two separate views. Another thing you could try is to detect which button was pushed by having two submit buttons with the same name like so:
<input type="submit" name="command" value="Update" />
<input type="submit" name="command" value="Display" />
Then in your Controller in the [HttpPost] action you can detect which was pushed via the Request.Forms like this:
[HttpPost]
public ActionResult Details(BettingCardModel model)
{
if (Request.Forms["command"].Equals("Display"))
{
return RedirectToAction("Details", new { year = model.RaceYear, month = model.RaceMonth, day = model.RaceDay });
}
// Do your update code here...
return // Whatever it is you return for update.
}
hopefully this helps you.

Resources