Best approach to switch actions from different controllers depending on user selection - asp.net-mvc

I am working with a screen that needs to perform a payment based on the selected "Payment Methods" exposed with a checkbox for each one as you can see in the following mockup.
The most important thing, is that every payment method has its own logic based on the API that each provide to us where some of them need to redirect a URL for login on their site (i.e. PayPal), and some others only to specify some credentials and execute the payment itself. So, according with this, we have been forced to create a controller for each one in order to delegate the interaction with the user once he chose an option. Being more clearer, it will be something like this:
public class PayPalController : BaseController, IPaymentController
{
public ActionResult Pay()
{
//logic for a pay with PayPal
return View("PaymentSuccess");
}
}
public class BankOfNigeriaController : BaseController, IPaymentController
{
public ActionResult Pay()
{
//logic for a pay with Bank Of Nigeria
return View("PaymentSuccess");
}
}
public class BankOfAngolaController : BaseController, IPaymentController
{
public ActionResult Pay()
{
//logic for a pay with Bank Of Angola
return View("PaymentSuccess");
}
}
public interface IPaymentController
{
ActionResult Pay();
}
Focused on the view that I exposed on the top, I am wondering which is the best practice to call the proper action method.
Option 1: consists on create a middle controller that redirects to the proper action.
The ViewModel has an string that it will be used for the redirect URL as selected option. In example: "PayPal" or "BankOfNigeria".
public class PayPalPaymentViewModel
{
[Required]
public string PaymentSelected { get; set; }
}
The View shows a begin form that will POST to a middle controller that, according with the chosen option will perform another redirect.
#model Bollore.IES.Web.Models.PaymentMethodViewModel
#{
ViewBag.Title = "Payment Methods";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("RedirectPay", "PaymentMethod", FormMethod.Post))
{
//HTML WITH LIST OF CHECKBOXES
<input type="submit" />
}
The MIDDLE Controller receives in the ViewModel the chosen method, and redirects to their own controller. I didn't test it, at the moment is only pseudo-code.
public class PaymentMethodController : BaseController
{
[HttpGet]
public ActionResult RedirectPay()
{
//returns the view
return View();
}
[HttpPost]
public ActionResult RedirectPay(PaymentMethodViewModel model)
{
//it could be "PayPal/Pay" or "BankOfNigeria/Pay", etc.
return RedirectToAction("Pay", model.PaymentSelected);
}
}
Option 2: consists on redirect the action directly from the client-side of the view without passing over the middle controller, which in this case, it will only exist for the GET of the view.
The View will be gotten by the PaymentMethodController, but the POST the the "payment method" action, will be done by javascript.
#model Bollore.IES.Web.Models.PaymentMethodViewModel
#{
ViewBag.Title = "Payment Methods";
Layout = "~/Views/Shared/_Layout.cshtml";
}
//LIST OF CHECKBOXES AND ALL THE STUFF
<input id="clickMe" type="button" value="Save" onclick="callPaymentMethodAction();" />
<script type="text/javascript">
function callPaymentMethodAction(e) {
var selectedMethodUrl = GetMethodSelectedUrl(); //it will return 'PayPal', 'BankOfAngola', etc
$.ajax({
url: 'selectedMethodUrl',
data: { id: id },
success: function(){
alert('Payed');
}
});
};
</script>
I repeat that is a pseudo-code that could not compile, but I hope you get the idea.
So, which of this two is the best approach to deal with a problem like
this? How can you manage the fact to be able to call multiple
controllers from the view?

Related

ASP.NET Core MVC. How to handle and process a request, display a UI with a form POST, and then send response

I'd like to know how I can achieve the flow I need using ASP.NET Core MVC. The situation is that I have a POST request coming in from a third party. I need to handle that request (process headers etc), then display a UI which allows the user to submit a form POST back to my app. My app then needs to use information from the initial request AND the user's form POST in order to return a result to the third party.
This is the rough flow I'm going with, but can't make everything work, is there a good way to do this?
Step 1: Handle the initial POST in a controller and display a view for the user:
[HttpPost("initialRequest")]
public async Task<IActionResult> HandleInitialRequest()
{
var state = SomeStateFromRequestToUseLater();
var model = LoadedModelToUseInView();
return View("UserView", model);
}
Step 2: Display the view to the user, which has a way for the user to submit:
#model LoadedModel
...
<form method="post" asp-controller="SameController" asp-action="handleUserAction">
<input type="text" asp-for="#Model.SomeProperty" />
...
<button type="submit">Submit</button>
</form>
Step 3: Handle the submitted form. This is the part that isn't working, because I need to know both the form data and the information from the initial request (from step 1)
[HttpPost("handleUserAction")]
public async Task<IActionResult> HandleUserAction()
{
var state = null; // How to get this state from step 1
var combinedModel = ModelFromStateAndFormSubmission();
return View("SuccessView", model);
}
Hopefully the problem is clear, I don't know if my issue is trying to do something in the wrong way, or just not finding out the right way of passing this data around.
I feel it can't be right to pass the State to the view, but maybe this is the best solution?
If I have not misunderstood your question, I recommend you to use the following two methods.
The first method, you can use ViewModel to contains request information and form information:
public class requestModel
{
//put the request information in this model
}
public class userForm
{
//put the properties form needed in this model
}
public class userRequest
{
public requestModel requestmodel {get;set;}
public userForm userform {get;set;}
}
HandleInitialRequest
[HttpPost("initialRequest")]
public async Task<IActionResult> HandleInitialRequest()
{
userRequest userrequest = new userRequest()
{
requestmodel = SomeStateFromRequestToUseLater();
userform = LoadedModelToUseInView();
}
return View("UserView", userrequest);
}
View
#model userRequest
...
<form method="post" asp-controller="SameController" asp-action="handleUserAction">
<input type="text" asp-for="#Model.userform.SomeProperty" />
...
<button type="submit">Submit</button>
</form>
HandleUserAction
[HttpPost("handleUserAction")]
public async Task<IActionResult> HandleUserAction(userRequest model)
{
//now, userRquest contains the information about request and form,
//SO you just need to return this model
}
The second method, You can use TempData["xxx"] to pass data between controllers action:
[HttpPost("initialRequest")]
public async Task<IActionResult> HandleInitialRequest()
{
TempData["state"] = SomeStateFromRequestToUseLater();
var model = LoadedModelToUseInView();
return View("UserView", model);
}
[HttpPost("handleUserAction")]
public async Task<IActionResult> HandleUserAction()
{
string state = (string)TempData["state"];
var combinedModel = ModelFromStateAndFormSubmission();
return View("SuccessView", model);
}
I noticed that you will combine the information about request and user form in last, I perfer to recomment you to use the first method, Because it doesn't nees to combine the information again.

.net MVC 4 step wizard correct way to populate viewmodels?

I followed Darin's post at
multi-step registration process issues in asp.net mvc (splitted viewmodels, single model)
Its a very elegant solution, however im having trouble seeing how you would populate the individual step viewmodels with data. Im trying to emulate amazons checkout step-system which starts with selecting an address, then shipping options, then payment information.
For my first viewmodel i require a list of addresses for my current logged in user which i poll the database for to display on screen
In my head, this is the viewmodel that makes sense to me.
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
public List<AddressViewModel> Addresses { get; set; }
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
However there seems to be no good way to populate the addresses from the controller.
public class WizardController : Controller
{
public ActionResult Index()
{
var wizard = new WizardViewModel();
wizard.Initialize();
return View(wizard);
}
[HttpPost]
public ActionResult Index(
[Deserialize] WizardViewModel wizard,
IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
// TODO: we have finished: all the step partial
// view models have passed validation => map them
// back to the domain model and do some processing with
// the results
return Content("thanks for filling this form", "text/plain");
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
// Even if validation failed we allow the user to
// navigate to previous steps
wizard.CurrentStepIndex--;
}
return View(wizard);
}
}
So i removed the list of address view models
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
This is what i came up with a custom editor template for the view model. It calls a Html.RenderAction which returns a partial view from my user controller of all the addresses and uses Jquery to populate a hidden input field for the view model's required SelectedAddressId property.
#model ViewModels.Checkout.ShippingAddressViewModel
<script src="../../Scripts/jquery-1.7.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
//Check to see if the shipping id is already set
var shippingID = $("#SelectedAddressId").val();
if (shippingID != null) {
$("#address-id-" + shippingID.toString()).addClass("selected");
}
$(".address-id-link").click(function () {
var shipAddrId = $(this).attr("data-addressid").valueOf();
$("#SelectedAddressId").val(shipAddrId);
$(this).parent("", $("li")).addClass("selected").siblings().removeClass("selected");
});
});
</script>
<div>
#Html.ValidationMessageFor(m => m.SelectedAddressId)
#Html.HiddenFor(s => s.SelectedAddressId)
<div id="ship-to-container">
#{Html.RenderAction("UserAddresses", "User", null);}
</div>
</div>
And the users controller action
[ChildActionOnly]
public ActionResult UserAddresses()
{
var user = db.Users.Include("Addresses").FirstOrDefault(
u => u.UserID == WebSecurity.CurrentUserId);
if (user != null)
{
return PartialView("UserAddressesPartial",
Mapper.Map<List<AddressViewModel>>(user.Addresses));
}
return Content("An error occured");
}
The partial view
#model IEnumerable<AddressViewModel>
<ul>
#foreach (var item in Model)
{
<li id="address-id-#item.AddressID">
#Html.DisplayFor(c => item)
<a class="address-id-link" href="#" data-addressid="#item.AddressID">Ship To this Address
</a></li>
}
</ul>
My solution just seems super out of the way/sloppy to me, is there a better more concise way to populate the viewmodel than using partial views from a different controller for this?
There's nothing wrong with using a child action like this to populate the user's addresses. In fact, I think this is actually the optimal approach. You've got full separation of concerns and single responsibility in play. Just because something requires more "pieces" (extra action, views, etc.) doesn't make it sloppy or otherwise wrong.
The only other way to handle this would be with dependency injection. Namely, your ShippingAddressViewModel would need a dependency of the currently logged in user, so that it could populate the list of addresses from that in its constructor. However, since ShippingAddressViewModel is not exposed in your view, you would have to pass the dependency through Wizard which is a bit of code smell. Wizard is not dependent on a user, but it would have dependence forced upon it by virtue of having your view model abstracted away inside it.
Long and short, while there's a way you could do this without the child actions and partial views, it would actually be nastier and sloppier than with them.

passing value from textbox to controller

How can I get value from textbox "EmailList" and send it to controler? I'm always using webforms, and this is my first contact with mvc.
View:
#Html.TextBox("EmailList")
#Html.Action("SendEmails")
Controller:
public ActionResult SendEmails()
{
// some operations on EmailList
}
EDIT
And what if I need just to open simple method 'onclick'? Not actionresult. for example -
public void SendEmails()
{
// some operations on EmailList
}
So to get the value back into the controller you're going to need to issue a POST first of all so you'll want to setup your controller action for a POST:
[HttpPost]
public ActionResult SendEmails(string EmailList)
{
}
also notice I added a parameter EmailList that's named exactly the same as the control on the form. Next we need to make sure your HTML is setup right, so when you build the form control build it like this:
#Html.BeginForm("SendEmails", "{ControllerNameHere}", FormMethod.Post)
and then your text box, well leave it alone, it should work just fine.
If you want to pass the EmailList to a method then you should have a form surrounding with the email textfield
#using (Html.BeginForm("",""))
{
#Html.TextBox("EmailList")
<input type="submit" id="emailSubmit" value="Submit Email" />
}
Then write a script to override the form default behaviour
<script type="text/javascript">
$(function () {
$("form").submit(function (e) {
e.preventDefault();
var emailValue = $("#EmailList").val();
$.ajax({
url: '/Home/SendEmails',
data: { text: emailValue }
});
});
});
</script>
Now you can add a parameter to your method like this:
public void SendEmails(string text)
{
string email=text;
//or you can look into the Request.Form or Request.Querystring
}
Hope it helps
Have a model
public class EmailSubmitModel
{
public string EmailList {get; set;}
}
In your controller
public ActionResult SendEmails(EmailSubmitModel emailSubmitModel)
{
}

MVC3 Form Posted Value

Below Scenario, I think I must see the START text in my form when first loaded.
When I click send data button and submit, I was waiting to see FINISH text in my form.
Buy the START text never changes when I click the button and post the form...
Anybody can tell the problem?
MY CONTROLLER:
namespace MvcApplication1.Controllers
{
public class BuyController : Controller
{
public ActionResult Index(BuyModel model)
{
if (Request.HttpMethod == "GET")
{
model.Message= "START";
return View(model);
}
else
{
BuyModel newModel = new BuyModel();
newModel.Message= "FINISH";
return View(newModel);
}
}
}
}
MY VIEW :
#model MvcApplication1.Models.BuyModel
#using (Html.BeginForm("Index", "Buy", FormMethod.Post))
{
#Html.TextBoxFor(s => s.Message)
<button type="submit" >Send</button>
}
MY MODEL:
public class BuyModel
{
public string Message { get; set; }
}
public class BuyController : Controller
{
public ActionResult Index()
{
BuyModel model = new BuyModel();
model.Message= "START";
return View(model);
}
[HttpPost]
public ActionResult Index(BuyModel model)
{
model = new BuyModel();
model.Message= "FINISH";
ModelState.Clear(); // the fix
return View(model);
}
}
View:
#model MvcApplication1.Models.BuyModel
#using (Html.BeginForm("Index", "Buy"))
{
#Html.TextBoxFor(s => s.Message)
<button type="submit" >Send</button>
}
Your issue is because your original code, that Action Method will only be executed as an HTTP GET request. ASP.NET MVC allows you to specify a post with the [HttpPost] attribute (see above code).
I'm not sure what you are getting at with your POST desired-behavior. It seems like you are just wiping out whatever form values are pushed on the POST. So modify my above code accordingly, but it should give you the general idea.
Edit: it seems to be that the text box is retaining its value after the POST. It's not just with "START", but if you type anything into that text box and hit submit, you'll have a POST with the exact same text in the text box that was there when you submitted the form.
Edit Edit: see the changed code. Call ModelState.Clear() in your POST action method and you'll have the right value reflected.
If you are posting, and not returning a RedirectResult, by default the helpers will use the value from ModelState. You either need to clear ModelState or have a different approach.
The PRG (post redirect get) pattern in MVC is very important. So if its a post, and you aren't redirecting, the helpers assume there is an error that needs to be corrected and the value is pulled from ModelState.

HTTP Post request from different controller actions and ModelState

I have a weird need in an ASP.NET MVC 3 application which blocks my current progress. Here is the case:
I have a little search engine for the products and I render this search engine on multiple pages. This SE makes a HTTP POST request to product controller's search action. It fine till here.
Let's assume that I am on home controller's index action (/home/index). I make a search and check if ModelState.IsValid. As a result, it is not valid. So, I should return this back with the entered model (so that user won't lose the values) and model state errors. But when I do that I ended up with different URL (/product/search) as expected.
If I do a redirect, I lose the ModelState and cannot display error messages.
I have different solutions so far and they all look dirty. Any idea?
Edit
Here is a little project which demonstrates this:
This is the ProductController:
public class ProductController : Controller {
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return RedirectToAction("Index", "SearchResult");
}
return View(searchModel);
}
}
This is the SearchModel:
public class SearchModel {
[Required]
public string ProductCategory { get; set; }
[Required]
public string ProductName { get; set; }
}
This is the *_SearchPartial*:
#model MvcApplication20.SearchModel
#using (Html.BeginForm("search", "product"))
{
#Html.EditorForModel()
<input type="submit" value="Search" />
}
And finally this is the Home controller Index action view which renders the *_SearchPartial*:
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
#Html.Partial("_SearchPartialView")
Here, when I submit the form and if the model state fails, how should I proceed at the Product controller Search action?
Here, when I submit the form and if the model state fails, how should
I proceed at the Product controller Search action?
Normally in this case you should render the _SearchPartialView but not as a partial but as a full view with layout so that the user can fix his errors. No need to stay at Home/Index in this case:
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return RedirectToAction("Index", "SearchResult");
}
// since we are returning a view instead of a partial view,
// the _SearchPartialView template should be displayed with the layout
return View("_SearchPartialView", searchModel);
}
And if you wanted to stay on the same page upon error you could use an AJAX call to perform the search. So you would AJAXify this search form and then in the success callback test the result of the Search action and based on it decide whether to refresh the partial in order to show the error or redirect to the results action using window.location.href:
something along the lines of:
$(document).on('submit', '#searchForm', function() {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function(result) {
if (result.redirectTo) {
// no validation errors we can redirect now:
window.location.href = result.redirectTo;
} else {
// there were validation errors, refresh the partial to show them
$('#searchContainer').html(result);
// if you want to enable client side validation
// with jquery unobtrusive validate for this search form
// don't forget to call the .parse method here
// since we are updating the DOM dynamically and we
// need to reattach client side validators to the new elements:
// $.validator.unobtrusive.parse(result);
}
}
});
return false;
});
This obviously assumes that you have now wrapped the partial call in a div with id="searchContainer" and that you provided an id="searchForm" when generating the search form:
<div id="searchContainer">
#Html.Partial("_SearchPartialView")
</div>
and now the search action:
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return Json(new { redirectTo = Url.Action("Index", "SearchResult") });
}
return PartialView("_SearchPartialView", searchModel);
}
As far as I know the ModelState is lost when doing a RedirectToAction, the solution would be to save the modelstate in the TempData one example of this, that I'm using is this:
http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
This is also discussed in various posts for instance MVC Transfer Data Between Views

Resources