Best method to pass value in MVC - model-view-controller

I am still new to MVC, so sorry if this is an obvious question:
I have a page where the user can choose one of several items. When they select one, they are taken to another form to fill in their details.
What is the best way to transfer that value to the form page?
I don't want the ID of the item in the second (form) pages URL.
so it's /choose-your-item/ to /redemption/ where the user sees what was selected, and fills the form in. The item selected is displayed, and shown in a hidden form.
I guess one option is to store in a session before the redirect, but was wondering if there was another option.
I am using MVC3

Darin Dimitrov's answer would be best if you don't need to do any additional processing before displaying the /redemption/ page. If you do need to some additional processing, you're going to have to use the TempDataDictionary to pass data between actions. Values stored in the TempDataDictionary lasts for one request which allows for data to be passed between actions, as opposed to the values stored in the ViewDataDictionary which only can be passed from an action to a view. Here's an example below:
public ActionResult ChooseYourItem()
{
return View();
}
[HttpPost]
public ActionResult ChooseYourItem(string chosenItem)
{
TempData["ChosenItem"] = chosenItem;
// Do some other stuff if you need to
return RedirectToAction("Redemption");
}
public ActionResult Redemption()
{
var chosenItem = TempData["ChosenItem"];
return View(chosenItem);
}

If you don't want the selected value in the url you could use form POST. So instead of redirecting to the new page, you could POST to it:
#using (Html.BeginForm("SomeAction", "SomeController"))
{
#Html.DropDownListFor(...)
<input type="submit" value="Go to details form" />
}

To help others, this is how I resolved my issue of needing multiple buttons posting back, and wanting to pass a different Id each time.
I have a single form on the page, that posts back to my controller:
The Form:
#using (Html.BeginForm("ChooseYourItem", "Item", FormMethod.Post))
{
And the code
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ChooseYourItem(string itemId)
{
TempData["ITEMID"] = itemId
return RedirectToAction("Create", "Redemption");
}
Then, inside the form, I create buttons whose name == "itemId", but has a different value each time.
For example
<strong>Item 1</strong>
<button value="123" name="itemid" id="btn1">Select</button>
<strong>Item 2</strong>
<button value="456" name="itemid" id="btn2">Select</button>

Related

ASP.NET Core MVC submit form in partial view / ViewComponent

I have a menu and a content area which displays content based on the selected item in the menu. Since the user is allowed to change the structure of the main menu, I decided that everything will be on page /home/index and will have a guid assigned to the content which needs to be shown. I started with the idea to introduce partial views, but realized that ASP.NET Core doesn't have RenderAction anymore and was replaced by ViewComponents.
So I used ViewComponents and everything works fine, except that I've stumbled on a situation where I need to have a component as a submit form.
In an example: One menu item is the menu is a component that shows a list of users. Another menu item is a component that creates a new user. On create user component I'll have a form that needs to be filled and on successful submit, I want to redirect the user to the component that shows a list of users. In the case of unsuccessful submit, error, wrong input I would of course not want to redirect the user to the list of users.
Since ViewComponents' job is to display view, how should I approach this issue? I'm looking for pointers in the right direction.
I have little experience in this field so any help would be appreciated.
UPDATE
In Index.cshtml:
<tbody>
#foreach (string item in Model.Components)
{
<tr>
<td>
<div class="col-md-3">
#await Component.InvokeAsync(#item)
</div>
</td>
</tr>
}
</tbody>
This is inside the content area. Components are string names of components I'd like to show in the content area (currently listed one after the other).
My ViewComponent which will get called when I click on the menu item to display the form:
public class TestFormViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync()
{
return View("_TestForm", new TestModelPost());
}
}
My _TestForm.cshtml component:
#model TestModelPost
<form asp-controller="Home" asp-action="TestPost" method="post">
<label asp-for="Name"></label>
<input asp-for="Name" /><br />
<label asp-for="Surname"></label>
<input asp-for="Surname" /><br />
<button type="submit">Go</button>
</form>
And the action TestPost called:
[HttpPost]
public IActionResult TestPost(TestModelPost model)
{
// Save model data, etc
// !ModelState.IsValid -> go back to form
// Success -> go to specific id
return RedirectToAction("Index", new { id = 1 });
}
How should I approach this? Or rather, am I even on the right track? I'm not sure how I would go "back" to that view I created in my _TestForm component in case the input was incorrect.
View components, just like child actions before them in ASP.NET MVC do not support POSTs. You need a full fledged action to handle the form post, which will need to return a full view. Essentially, you just need to think about it more abstractly.
A view component is just ultimately a means of dumping some HTML into the eventual response. When the full response is returned to the client, there's no concept of what was a view component, a partial view, etc. It's just an HTML document. Part of that HTML document is your form. That form will have an action, which ultimately should be a route that's handled by one of your controller actions. A traditional form post cause the entire browser view to change, so the response from this action should either be a full view or a redirect to an action that returns a full view. The redirect is the more appropriate path, following the PRG pattern. It is then the responsibility of that view that is eventually returned to determine how the content is constructed, using view components, partials, etc. as appropriate.
Long and short, this really has nothing to do with your view component at all. All it is doing is just dropping the form code on the page.
For forms like this that are part of the layout, it's usually best to include a hidden input that contains a "return URL" in the form. The action that handles the form, then can redirect back to this URL after doing what it needs to do, in order to give the semblance that the user has stayed in the same place.
Kindly refer to the following link:
https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/
it might be of help
public class LoginStatusViewComponent : ViewComponent
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public LoginStatusViewComponent(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}
public async Task<IViewComponentResult> InvokeAsync()
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
var user = await _userManager.GetUserAsync(HttpContext.User);
return View("LoggedIn", user);
}
else
{
return View();
}
}
}
Our InvokeAsync method is pretty self explanatory. We are checking if the current user is signed in using the SignInManager<>, and if they are we fetch the associated ApplicationUser from the UserManager<>. Finally we call the helper View method, passing in a template to render and the model user. If the user is not signed in, we call the helper View without a template argument.
As per your code segment you can try to modify it as follows:
public class TestFormViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync()
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
//user list display
return View("_TestForm", new TestModelPost());
}
else
{
return View();
}
}
}

Retrieve form data in action method : ASP.NET MVC 3

I am trying to use a simple form with only a text field to get some information that will be used in an action method to redirect to a different action method. Here's the context:
I have a route mapped in my global.asax.cs file which prints "moo" the given amount of times. For example, if you typed "www.cows.com/Moo8", "Moo" would be printed 8 times. The number is arbitrary and will print however many "Moo"s as the number in the URL. I also have a form on the homepage set up as follows:
#using (Html.BeginForm("Moo", "Web"))
{
<text>How many times do you want to moo?</text>
<input type="text" name="mooNumber" />
<input type="submit" value="Moo!" />
}
The number submitted in the form should be sent to the action method "Moo" in the "Web" controller (WebController.cs):
[HttpPost]
public ActionResult Moo(int mooNumber)
{
Console.WriteLine(mooNumber);
return RedirectToAction("ExtendedMoo", new { mooMultiplier = mooNumber });
}
Finally, the "Moo" action method should send me back to the original "www.cows.com/Moo8" page; as you can see above I simply used an already existing action method "ExtendedMoo":
public ViewResult ExtendedMoo(int mooMultiplier)
{
ViewBag.MooMultiplier = RouteData.Values["mooMultiplier"];
return View();
}
How can I access the value submitted in my form and use it in the last call to "ExtendedMoo"?
Refer to this post or this, you might get some idea how routing works. Something is wrong with "www.cows.com/Moo8", try to find it out. Hint "{controller}/{action}/{parameter_or_id}"
Instead of RedirectToAction, use Redirect and create the Url.
This should do the trick:
return Redirect(Url.RouteUrl(new { controller = "Web", action = "ExtendedMoo", mooMultiplier = mooNumber }));
I hope i helps.
Oh wow. Turns out that form was on my Homepage, so instead of using "Moo" as the action method, I needed to override the "Homepage" action method with a [HttpPost] annotation over THAT one. Didn't realize that forms submitted to the page they were rendered from - that was a really useful piece of information in solving this problem!
Thanks all for your attempts at helping out!
If I understood right
You can you use form Collection to get the value from textbox.
Make Sure the input tag has both id and name properties mentioned otherwise it wont be available in form collection.
[HttpPost]
public ActionResult Moo(int mooNumber, **formcollection fc**)
{
**string textBoxVal= fc.getvalue("mooNumber").AttemptedValue;**
Console.WriteLine(mooNumber);
return RedirectToAction("ExtendedMoo", new { mooMultiplier = mooNumber });
}

Wizard in ASP.NET MVC3 and Multiple HttpPost

I am using the Wizard control described in http://afana.me/post/create-wizard-in-aspnet-mvc-3.aspx
It works great, but I need to have Multiple HttpPost within the same Controller. In my scenario, I need to add to a collection before moving to next step. In the partial view for that step. I have following set up:
#using (Html.BeginForm("AddJobExperience", "Candidate"))
{
<input type="submit" value="Add Experience" />
}
When I press the Add Experience input, it is routed to the
[HttpPost, ActionName("Index")]
public ActionResult Index(CandidateViewModel viewModel)
{
}
instead of
[HttpPost, ActionName("AddJobExperience")]
public ActionResult AddJobExperience(CandidateViewModel col)
{
}
what am I doing wrong?
It sounds like you need to break up your CandidateViewModel into separate ViewModels and your big Wizard View into separate Views so that there is one per action for each step of the wizard.
Step one they add the job experience, so have a view, viewmodel and an action for that, Step two they do whatever else and you have a separate view, viewmodel and action for that as well. etc, etc
Breaking up your CandidateViewModel into separate ViewModels will mean that you can just focus on the data required for that step, and can add the validation, then when they click submit, it posts the data to the next step.
Then, when you want to improve the UI behaviour, add some AJAX, and maybe use something like JQuery UI Tabs to make it behave more like a wizard in a desktop app.
It sounds like you still have nested forms. Don't do this, it is not valid HTML.
You have 2 options here, depending on what you are trying to achieve. If you want to post your job experiences separately one at a time, then put them in their own #using(Html.BeginForms, but don't nest them in an outer form. When a user clicks the Add Experience button, do your work on that one experience and then return a new view to the user.
If you want to post all of the job experiences at the same time, then wrap all of them in a single #using(Html.BeginForm and do not put #using(Html.BeginForm in your partial views. See another question I answered here for more info on how to post a collection of items in a single HTTP POST.
The 2nd method is what it sounds like you are trying to achieve, and for this, you should probably use AJAX to add multiple job experiences to your collection without doing a full postback. You can then do 1 HTTP POST to submit all job experiences in the collection to your wizard controller. It's not very difficult to implement a feature like this:
Given I can see the Add Experience button
When I click the Add Experience button
Then I should see a new experience partial view with input fields
And I should enter data in these fields
When I click the Add Experience button a second time
Then I should see another new experience partial view with input fields
And I should enter data in these fields
When I click the Add Experience button a third time
Then I should see a third new experience partial view with input fields
And I should enter data in these fields
When I click the Next button in the wizard
Then my controller will receive data for all 3 experiences I submitted in a single form
you need to use ActionMethodSelectorAttribute or ActionNameSelectorAttribute which allow to add new attribute on action to call different action on respective of button click
In View:
#using (Html.BeginForm())
{
<input type="submit" value="Add Experience" name="AddExperience" />
<input type="submit" value="Add Experience" name="AddJobExperience" />
}
add new class FormValueRequiredAttribute in application which extend ActionMethodSelectorAttribute class to check on which button is clicked
//controller/FormValueRequiredAttribute.cs
public class FormValueRequiredAttribute : ActionMethodSelectorAttribute
{
public string ButtonName { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var req = controllerContext.RequestContext.HttpContext.Request;
return !string.IsNullOrEmpty(req.Form[this.ButtonName]);
}
}
then you should add this attribute on action to call corresponding action
In Controller
[HttpPost]
[FormValueRequired(ButtonName = "AddExperience")]
public ActionResult Index(CandidateViewModel viewModel)
{
return View();
}
[HttpPost]
[ActionName("Index")]
[FormValueRequired(ButtonName = "AddJobExperience")]
public ActionResult AddJobExperience_Index(CandidateViewModel viewModel)
{
return View();
}
Note if your Html.BeginForm method in Index.cshtml then you don't need specify ActionName attribute on Index Action, now AddJobExperience_Index act same as Index Action.

How to pass both model value & text value to controller in mvc?

I want to pass two values from view to controller . i.e., #Model.idText and value from textbox. here is my code:
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Model.idText
<input type="text" name="textValue"/>
<input type="submit" name="btnSubmit"/>
}
But problem is if i use "Url.ActionLink() i can get #Model.idText . By post action i can get textbox value using FormCollection . But i need to get both of this value either post or ActionLink
using ajax you can achieve this :
don't use form & declare your attributes like this in tags:
#Model.idText
<input type="text" id="textValue"/>
<input type="submit" id="btnSubmit"/>
jquery:
$(function (e) {
// Insert
$("#btnSubmit").click(function () {
$.ajax({
url: "some url path",
type: 'POST',
data: { textField: $('#textValue').val(), idField: '#Model.idText' },
success: function (result) {
//some code if success
},
error: function () {
//some code if failed
}
});
return false;
});
});
Hope this will be helpful.
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Html.Hidden("idText", Model.idText)
#Html.TextBox("textValue")
<input type="submit" value="Submit"/>
}
In your controller
public ActionResult SaveData(String idText, String textValue)
{
return null;
}
I'm not sure which part you are struggling with - submitting multiple values to your controller, or getting model binding to work so that values that you have submitted appear as parameters to your action. If you give more details on what you want to achieve I'll amend my answer accordingly.
You could use a hidden field in your form - e.g.
#Html.Hidden("idText", Model.idText)
Create a rule in global.asax and than compile your your with params using
#Html.ActionLink("My text", Action, Controller, new { id = Model.IdText, text =Model.TextValue})
Be sure to encode the textvalue, because it may contains invalid chars
Essentially, you want to engage the ModelBinder to do this for you. To do that, you need to write your action in your controller with parameters that match the data you want to pass to it. So, to start with, Iridio's suggestion is correct, although not the full story. Your view should look like:
#using HTML.BeginForm("SaveData","Profile",FormMethod.Post)
{
#Html.ActionLink("My text", MyOtherAction, MaybeMyOtherController, new { id = Model.IdText}) // along the lines of dommer's suggestion...
<input type="text" name="textValue"/>
<input type="submit" name="btnSubmit"/>
#Html.Hidden("idText", Model.idText)
}
Note that I have added the #Html.Hidden helper to add a hidden input field for that value into your field. That way, the model binder will be able to find this datum. Note that the Html.Hidden helper is placed WITHIN your form, so that this data will posted to the server when the submit button is clicked.
Also note that I have added dommer's suggestion for the action link and replaced your code. From your question it is hard to see if this is how you are thinking of passing the data to the controller, or if this is simply another bit of functionality in your code. You could do this either way: have a form, or just have the actionlink. What doesn't make sense is to do it both ways, unless the action link is intended to go somewhere else...??! Always good to help us help you by being explicit in your question and samples. Where I think dommer's answer is wrong is that you haven't stated that TextValue is passed to the view as part of the Model. It would seem that what you want is that TextValue is entered by the user into the view, as opposed to being passed in with the model. Unlike idText that IS passed in with the Model.
Anyway, now, you need to set up the other end, ie, give your action the necessary
[HttpPost]
public ActionResult SaveData(int idText, string textValue) // assuming idText is an int
{
// whatever you have to do, whatever you have to return...
}
#dommer doesn't seem to have read your code. However, his suggestion for using the Html.ActionLink helper to create the link in your code is a good one. You should use that, not the code you have.
Recapping:
As you are using a form, you are going to use that form to POST the user's input to the server. To get the idText value that is passed into the View with the Model, you need to use the Html.Hidden htmlhelper. This must go within the form, so that it is also POSTed to the server.
To wire the form post to your action method, you need to give your action parameters that the ModelBinder can match to the values POSTed by the form. You do this by using the datatype of each parameter and a matching name.
You could also have a complex type, eg, public class MyTextClass, that has two public properties:
public class MyTextClass
{
public int idText{get;set}
public string TextValue{get;set;}
}
And then in your controller action you could have:
public ActionResult SaveData(MyTextClass myText)
{
// do whatever
}
The model binder will now be able to match up the posted values to the public properties of myText and all will be well in Denmark.
HTH.
PS: You also need to read a decent book on MVC. It seems you are flying a bit blind.
Another nit pick would be to question the name of your action, SaveData. That sounds more like a repository method. When naming your actions, think like a user: she has simply filled in a form, she has no concept of saving data. So the action should be Create, or Edit, or InformationRequest, or something more illustrative. Save Data says NOTHING about what data is being saved. it could be credit card details, or the users name and telephone...

server side validation in the main view cause also validation in partial view, how to stop that?

I have layout with login partial view (username, password and submit button) and models ( some controls and submit button)with validation(server side and client side) displayed in normal views (#RenderBody() in layout).
My problem is when do server side validation in any of my views it also validate the login partial view because it execute the httppost function of the login. how can I stop that??
login view controller
[HttpGet]
public ActionResult LogOn()
{
return PartialView();
}
//
// POST: /Account/LogOn
[HttpPut]
public ActionResult LogOn(LogOnModel model)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
ViewBag.UserName = model.UserName;
}
else
{
ModelState.AddModelError("", Resources.Account.Account.LoginFailureText);
}
}
return PartialView(model);
}
and model controller
public ActionResult MyModel()
{
ViewBag.DisplayThxMsg = false;
return View();
}
[HttpPost]
public ActionResult MyModel(Models.FeedbacksModel feedback)
{
if (ModelState.IsValid)
{
//do something
}
else{
//do another thing
}
return View(feedback);
}
I find your question very difficult to understand. Im guessing your problem is you have a login partial control displayed as part of site layout and is shown on all pages. So while submitting any page, the username password validation kicks in, and you want to prevent that.
Understand that all validation # server - side happens while model binding, As properties are bound to the posted fields, the attributes on the fields are looked at and honored / catered to. So to prevent server side validation simply put the login partial view in it's own form so it is not sent while submitting other forms on the page.
In short have 2 forms - one form for login and one for feedback. Don't put all input fields in the same form.
If you still have validation errors after that, then it is because of other reasons like, type conversion problems. The default model binder will add some errors for basic type conversion issues (for example, passing a non-number for something which is an "int").The sample DataAnnotations model binder will fill model state with validation errors taken from the DataAnnotations attributes on your model.
EDIT
If you look at line number 125
#using (Html.BeginForm()){Html.RenderAction("LogOn", "Account");}
You have the above code which will render the login form.
It will do so inside the other form at line 45
<form id="form1" runat="server" method="post">
This has no end tag therefore it will encompass the whole document till </html>
You should change the structure from
<form id="form1" runat="server" method="post">
#using (Html.BeginForm()){Html.RenderAction("LogOn", "Account");}
</form
to
<form id="form1" runat="server" method="post">
</form>
#using (Html.BeginForm()){Html.RenderAction("LogOn", "Account");}
This line #using (Html.BeginForm()){Html.RenderAction("LogOn", "Account");} will render this form <form id="LoginView1" action="LogOn"> and all child elements of it.
LATEST EDIT
In your layout page use this :
#Html.Partial("~/Views/Shared/LogOnPartial.cshtml", new LogOnModel())
instead of this :
#Html.Action("LogOnPartial", "Account")
The reason why it all works is, the LogOnPartial method marked with [HttpPost] is called because the request was in a POST context. What you want is, You just need the view without the action executing even when POSTing. The above code does that. It renders the view without calling the action method. MVC3 is sort of a stupid servent : It only knows that it should call the Action method marked with [HttpPost] when the request is in a post context. It doesn't know that the request is in a post context for another action (index) and not this one (logonpartial). So now you can remove this method
public ActionResult LogOnPartial()
{
return PartialView();
}
which will no longer be used.
Note that you need to change the account controller's LogOnPartial Method to return
return RedirectToAction("Index","Home"); instead of return PartialView(model); on successful login. And on FAILURE you cannot render a partialview as you have coded. You must return an entirely new View. It must neither be index nor LogonPartails - just return return View("Login_Error_View"); which has its own layout. Otherwise it will be difficult to control the workflow.

Resources