I am using an MVC wizard that uses multiple .cshtml files for the steps. Because I am using Orchard CMS this was the only type of wizard that worked. That's not my question. I am using a CheckBoxList helper that puts some code in the controller. This is
just for one checkbox, meaning that my model/view model/repository works for one checkboxlist, if I want 2 checkboxlists I have to create additional code for the model/view model/repository AND I have to create additional controller code. This checkboxlist
code posts fine to itself (to the same view). However, I have 2 problems:
1) I want to use one checkbox in different wizards (reuse - for example, two separate wizards asking different questions of the user, but the common question is "Male or Female"). To do that I have to copy/paste the controller code. If I use it for 4 different
wizards, I have to copy/paste into the ActionResult of a wizard's particular step 4 times. Ouch. This is not DRY.
2) My wizard serializes data to pass it to the next step (i.e., View). This works in the reverse as well as the wizard has "Back" and (obviously) "Next" buttons. The problem is the code I have for the CheckBoxList in the controller is located on, say, Step
1. If I want to post it to a "Confirm" page (which to keep it simple is Step 2 of the wizard), I have to copy all of the code to the Confirm ActionResult. Again, not DRY.
I know that you can either create an Action Filter (probably the perverbial "grenade to kill a fly"), create a method inside the Controller and call it in each ActionResult, or create some type of Service Layer (way out of my league). I am leaning toward
creating a method and calling it, but I cannot get the code right.
Any help would be greatly appreciated. Below is some of the code I use:
Controller (with wizard code - keeping it real simple, many of my wizards have 6-7steps); the CheckBoxList is BankruptcyConsideration:
[ValidateOnlyIncomingValues, Themed]
public class BankruptcyController : Controller
{
private IBankruptcyMailer _bankruptcyMailer = new BankruptcyMailer();
public IBankruptcyMailer BankruptcyMailer
{
get { return _bankruptcyMailer; }
set { _bankruptcyMailer = value; }
}
private BankruptcyData bankruptcyData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["bankruptcyData"];
if (serialized != null) //Form was posted containing serialized data
{
bankruptcyData = (BankruptcyData)new MvcSerializer().Deserialize(serialized, SerializationMode.Signed);
TryUpdateModel(bankruptcyData);
}
else
bankruptcyData = (BankruptcyData)TempData["bankruptcyData"] ?? new BankruptcyData();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["bankruptcyData"] = bankruptcyData;
}
//
// STEP 1:
// Solutions/Quote/EMail/BasicDetails
publicActionResult EMailQuoteBasicDetails(string nextButton, string[] bankruptcyconsiderations, PostedBankruptcyConsiderations postedBankruptcyConsiderations)
{
if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("EMailQuoteConfirm");
//var model = new BankruptcyData();var selectedBankruptcyConsiderations = newList<BankruptcyConsideration>();
var postedBankruptcyConsiderationIDs = newstring[0];
if (postedBankruptcyConsiderations == null) postedBankruptcyConsiderations = new PostedBankruptcyConsiderations();
// if an array of posted city ids exists and is not empty,// save selected idsif (bankruptcyconsiderations != null && bankruptcyconsiderations.Any())
{
postedBankruptcyConsiderationIDs = bankruptcyconsiderations;
postedBankruptcyConsiderations.BankruptcyConsiderationIDs = bankruptcyconsiderations;
}
// if a view model array of posted city ids exists and is not empty,// save selected idsif (postedBankruptcyConsiderations.BankruptcyConsiderationIDs != null && postedBankruptcyConsiderations.BankruptcyConsiderationIDs.Any())
{
postedBankruptcyConsiderationIDs = postedBankruptcyConsiderations.BankruptcyConsiderationIDs;
bankruptcyData.WasPostedBankruptcyConsideration = true;
}
// if there are any selected ids saved, create a list of citiesif (postedBankruptcyConsiderationIDs.Any())
selectedBankruptcyConsiderations = BankruptcyConsiderationRepository.GetAll()
.Where(x => postedBankruptcyConsiderationIDs.Any(s => x.BankruptcyConsiderationId.ToString().Equals(s))).ToList();
// setup a view model
bankruptcyData.AvailableBankruptcyConsiderations = BankruptcyConsiderationRepository.GetAll();
bankruptcyData.SelectedBankruptcyConsiderations = selectedBankruptcyConsiderations;
bankruptcyData.PostedBankruptcyConsiderations = postedBankruptcyConsiderations;
//return View(bankruptcyData);return View("EMailQuote/BasicDetails", bankruptcyData);
}
//
// STEP 2:
// Solutions/Quote/EMail/Confirm
public ActionResult EMailQuoteConfirm(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("EMailQuoteBasicDetails");
else if (nextButton != null)
return RedirectToAction("EMailQuoteSubmitted");
else
return View("EMailQuote/Confirm", bankruptcyData);
}
//
// STEP 3:
// Solutions/Quote/EMail/Submitted
public ActionResult EMailQuoteSubmitted()
{
// Todo: Save bankruptcyData to database; render a "Submitted" view
BankruptcyMailer.EMailQuote(bankruptcyData).Send();
return View("EMailQuote/Submitted", bankruptcyData);
}
}
So, you can see that if I want to pass the selected check boxes to the Confirm view it's likely I have to repeat the code inside the "BasicDetails" action. And if I wanted to re-use that checkbox in other wizard steps I'd have to copy/paste.
I tried putting it in a method and calling it within the ActionResult but to no sucess.
In case you need it, here is the view code (stripped down):
@Html.CheckBoxListFor(x => x.PostedBankruptcyConsiderations.BankruptcyConsiderationIDs,
x => x.AvailableBankruptcyConsiderations,
x => x.BankruptcyConsiderationId,
x => x.BankruptcyConsiderationName,
x => x.SelectedBankruptcyConsiderations)
Now, the code I have in the controller would ordinarily post to itself, and this is how:
I am using an MVC wizard that uses multiple .cshtml files for the steps. Because I am using Orchard CMS this was the only type of wizard that worked. That's not my question. I am using a CheckBoxList helper that puts some code in the controller. This is
just for one checkbox, meaning that my model/view model/repository works for one checkboxlist, if I want 2 checkboxlists I have to create additional code for the model/view model/repository AND I have to create additional controller code. This checkboxlist
code posts fine to itself (to the same view). However, I have 2 problems:
1) I want to use one checkbox in different wizards (reuse - for example, two separate wizards asking different questions of the user, but the common question is "Male or Female"). To do that I have to copy/paste the controller code. If I use it for 4 different
wizards, I have to copy/paste into the ActionResult of a wizard's particular step 4 times. Ouch. This is not DRY.
2) My wizard serializes data to pass it to the next step (i.e., View). This works in the reverse as well as the wizard has "Back" and (obviously) "Next" buttons. The problem is the code I have for the CheckBoxList in the controller is located on, say, Step
1. If I want to post it to a "Confirm" page (which to keep it simple is Step 2 of the wizard), I have to copy all of the code to the Confirm ActionResult. Again, not DRY.
I know that you can either create an Action Filter (probably the perverbial "grenade to kill a fly"), create a method inside the Controller and call it in each ActionResult, or create some type of Service Layer (way out of my league). I am leaning toward
creating a method and calling it, but I cannot get the code right.
Any help would be greatly appreciated.
What you are asking for is a Partial View. I'll provide an example of both the controller method and the View code below:
I think you misunderstand my problem - either that or you have mis-posted a reply for some other poor soul looking for an answer :).
I do not need a partial view at the moment. Yes, I can put checkbox code into, say, an EditorTemplate for reuse. THAT IS NOT THE PROBLEM. I am trying to REUSE code from inside a controller's ActionResult. I don't want to copy/paste all the code within the
ActionResult 5, 10 or 50 times (or however many times I want to use it). I know there are several ways of doing this, but I have not been able to make it work.
If I am explaining myself poorly I apologize. But I don't think I am.
Controllers may have also methods that are non action methods...that you can use to reuse code. It is enough to decorate a method with NonAction attribute. ...If the right place for your code is the controller...otherwise consider also putting the shared
code in your business layer....and then calling it from various controllers. I mean if the code deal purely with user interaction job...you may create a nonaction controller method, otherwise put everuthing in your business layer...You may also define a custom
base controller to inherit from(something like a WizardControllerBase..)...and you may also try putting some code in action filters or custom model binders...You may also define al helper to deal with some aspects of your wizard...you might also try defining
a "Wizard Helper"... that takes care of handling your wizard steps..(something based on a "WizardHandler" class)...A lot of possibilities. You need just to understand which one is more adequate to your problem.
Thanks for the reply. I have been researching all of your suggestions over the last day. I'm no pro, so for my purposes the private action method would seem best, especially just to get something done quick. Problem is I am basically just trying different
variations until one works, and that's not been going well. ;)
Might I impose upon you to provide an example private method within the controller using the code I listed above? Also, my problem is in how I actually call it within an ActionResult. For example, if I were to cut-out the non-wizard code in the BasicDetails
ActionResult above and put it into a private method, I would think to do the following:
)
{
// setup properties
//var model = new BankruptcyData();
var selectedBankruptcyConsiderations = newList<BankruptcyConsideration>();
var postedBankruptcyConsiderationIDs = newstring[0];
if (postedBankruptcyConsiderations == null) postedBankruptcyConsiderations = new PostedBankruptcyConsiderations();
// if an array of posted city ids exists and is not empty,// save selected idsif (bankruptcyconsiderations != null && bankruptcyconsiderations.Any())
{
postedBankruptcyConsiderationIDs = bankruptcyconsiderations;
postedBankruptcyConsiderations.BankruptcyConsiderationIDs = bankruptcyconsiderations;
}
// if a view model array of posted city ids exists and is not empty,// save selected idsif (postedBankruptcyConsiderations.BankruptcyConsiderationIDs != null && postedBankruptcyConsiderations.BankruptcyConsiderationIDs.Any())
{
postedBankruptcyConsiderationIDs = postedBankruptcyConsiderations.BankruptcyConsiderationIDs;
model.WasPosted = true;
}
// if there are any selected ids saved, create a list of citiesif (postedBankruptcyConsiderationIDs.Any())
selectedBankruptcyConsiderations = BankruptcyConsiderationRepository.GetAll()
.Where(x => postedBankruptcyConsiderationIDs.Any(s => x.Id.ToString().Equals(s))).ToList();
// setup a view model
model.AvailableBankruptcyConsiderations = BankruptcyConsiderationRepository.GetAll();
model.SelectedBankruptcyConsiderations = selectedBankruptcyConsiderations;
model.PostedBankruptcyConsiderations = postedBankruptcyConsiderations;
//return View(model);
}
And then the stripped out ActionResult would look like this:
public ActionResult EMailQuoteBasicDetails(string nextButton)
{
if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("EMailQuoteConfirm");
return View("EMailQuote/BasicDetails", bankruptcyData);
}
But if I try:
public ActionResult EMailQuoteBasicDetails(string nextButton)
{
if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("EMailQuoteConfirm");
One problem I see is that you appartently wanted your private MethodBankruptcyConsideration(...) to return a View while only public ActionResult methods are allowed to return a View.
Another problem I is see is that your final public ActionResult EMailQuoteBasicDetails(string nextButton) is using a bankruptcyData that has not been set to anything.
Typically what is done is this:
In an [HttpGet] method you would instanciate a new bankruptcyData member and pass that to the View as the Model class.
The View than sets properties in its Model class and passes the Model class back to the [HttpPost] method.
Now I've used a third component of the URL along with a Model class before in that order. But if that doesn't fit with your use case, consider moving the string nextButton into your Model class.
remesqny
Member
9 Points
23 Posts
Trying to reuse code within a controller
Feb 18, 2012 10:14 PM|LINK
I am using an MVC wizard that uses multiple .cshtml files for the steps. Because I am using Orchard CMS this was the only type of wizard that worked. That's not my question. I am using a CheckBoxList helper that puts some code in the controller. This is just for one checkbox, meaning that my model/view model/repository works for one checkboxlist, if I want 2 checkboxlists I have to create additional code for the model/view model/repository AND I have to create additional controller code. This checkboxlist code posts fine to itself (to the same view). However, I have 2 problems:
1) I want to use one checkbox in different wizards (reuse - for example, two separate wizards asking different questions of the user, but the common question is "Male or Female"). To do that I have to copy/paste the controller code. If I use it for 4 different wizards, I have to copy/paste into the ActionResult of a wizard's particular step 4 times. Ouch. This is not DRY.
2) My wizard serializes data to pass it to the next step (i.e., View). This works in the reverse as well as the wizard has "Back" and (obviously) "Next" buttons. The problem is the code I have for the CheckBoxList in the controller is located on, say, Step 1. If I want to post it to a "Confirm" page (which to keep it simple is Step 2 of the wizard), I have to copy all of the code to the Confirm ActionResult. Again, not DRY.
I know that you can either create an Action Filter (probably the perverbial "grenade to kill a fly"), create a method inside the Controller and call it in each ActionResult, or create some type of Service Layer (way out of my league). I am leaning toward creating a method and calling it, but I cannot get the code right.
Any help would be greatly appreciated. Below is some of the code I use:
Controller (with wizard code - keeping it real simple, many of my wizards have 6-7steps); the CheckBoxList is BankruptcyConsideration:
[ValidateOnlyIncomingValues, Themed] public class BankruptcyController : Controller { private IBankruptcyMailer _bankruptcyMailer = new BankruptcyMailer(); public IBankruptcyMailer BankruptcyMailer { get { return _bankruptcyMailer; } set { _bankruptcyMailer = value; } } private BankruptcyData bankruptcyData; protected override void OnActionExecuting(ActionExecutingContext filterContext) { var serialized = Request.Form["bankruptcyData"]; if (serialized != null) //Form was posted containing serialized data { bankruptcyData = (BankruptcyData)new MvcSerializer().Deserialize(serialized, SerializationMode.Signed); TryUpdateModel(bankruptcyData); } else bankruptcyData = (BankruptcyData)TempData["bankruptcyData"] ?? new BankruptcyData(); } protected override void OnResultExecuted(ResultExecutedContext filterContext) { if (filterContext.Result is RedirectToRouteResult) TempData["bankruptcyData"] = bankruptcyData; } // // STEP 1: // Solutions/Quote/EMail/BasicDetails public ActionResult EMailQuoteBasicDetails(string nextButton, string[] bankruptcyconsiderations, PostedBankruptcyConsiderations postedBankruptcyConsiderations) { if ((nextButton != null) && ModelState.IsValid) return RedirectToAction("EMailQuoteConfirm"); //var model = new BankruptcyData(); var selectedBankruptcyConsiderations = new List<BankruptcyConsideration>(); var postedBankruptcyConsiderationIDs = new string[0]; if (postedBankruptcyConsiderations == null) postedBankruptcyConsiderations = new PostedBankruptcyConsiderations(); // if an array of posted city ids exists and is not empty, // save selected ids if (bankruptcyconsiderations != null && bankruptcyconsiderations.Any()) { postedBankruptcyConsiderationIDs = bankruptcyconsiderations; postedBankruptcyConsiderations.BankruptcyConsiderationIDs = bankruptcyconsiderations; } // if a view model array of posted city ids exists and is not empty, // save selected ids if (postedBankruptcyConsiderations.BankruptcyConsiderationIDs != null && postedBankruptcyConsiderations.BankruptcyConsiderationIDs.Any()) { postedBankruptcyConsiderationIDs = postedBankruptcyConsiderations.BankruptcyConsiderationIDs; bankruptcyData.WasPostedBankruptcyConsideration = true; } // if there are any selected ids saved, create a list of cities if (postedBankruptcyConsiderationIDs.Any()) selectedBankruptcyConsiderations = BankruptcyConsiderationRepository.GetAll() .Where(x => postedBankruptcyConsiderationIDs.Any(s => x.BankruptcyConsiderationId.ToString().Equals(s))).ToList(); // setup a view model bankruptcyData.AvailableBankruptcyConsiderations = BankruptcyConsiderationRepository.GetAll(); bankruptcyData.SelectedBankruptcyConsiderations = selectedBankruptcyConsiderations; bankruptcyData.PostedBankruptcyConsiderations = postedBankruptcyConsiderations; //return View(bankruptcyData); return View("EMailQuote/BasicDetails", bankruptcyData); } // // STEP 2: // Solutions/Quote/EMail/Confirm public ActionResult EMailQuoteConfirm(string backButton, string nextButton) { if (backButton != null) return RedirectToAction("EMailQuoteBasicDetails"); else if (nextButton != null) return RedirectToAction("EMailQuoteSubmitted"); else return View("EMailQuote/Confirm", bankruptcyData); } // // STEP 3: // Solutions/Quote/EMail/Submitted public ActionResult EMailQuoteSubmitted() { // Todo: Save bankruptcyData to database; render a "Submitted" view BankruptcyMailer.EMailQuote(bankruptcyData).Send(); return View("EMailQuote/Submitted", bankruptcyData); } }So, you can see that if I want to pass the selected check boxes to the Confirm view it's likely I have to repeat the code inside the "BasicDetails" action. And if I wanted to re-use that checkbox in other wizard steps I'd have to copy/paste.
I tried putting it in a method and calling it within the ActionResult but to no sucess.
In case you need it, here is the view code (stripped down):
@Html.CheckBoxListFor(x => x.PostedBankruptcyConsiderations.BankruptcyConsiderationIDs, x => x.AvailableBankruptcyConsiderations, x => x.BankruptcyConsiderationId, x => x.BankruptcyConsiderationName, x => x.SelectedBankruptcyConsiderations)Now, the code I have in the controller would ordinarily post to itself, and this is how:
But if I place this in the Confirm ActionResult/View, nothing obviously shows up.
Thanks!
eric2820
Contributor
2777 Points
1161 Posts
Re: Trying to reuse code within a controller
Feb 19, 2012 12:22 AM|LINK
What you are asking for is a Partial View. I'll provide an example of both the controller method and the View code below:
public PartialViewResult Nav( string MemberName )
{
List<Menu> menu = new List<Menu>();
menu.Add( new Menu( "Home", "Index", "Home", MemberName ) );
menu.Add( new Menu( "Products", "Products", "Home", MemberName ) );
menu.Add( new Menu( "Testimonials", "Testimonials", "Home", MemberName ) );
menu.Add( new Menu( "About Us", "AboutUs", "Home", MemberName ) );
menu.Add( new Menu( "Privacy Policy", "Privacypolicy", "Home", MemberName ) );
menu.Add( new Menu( "Cypher", "Index", "PassGenerator", MemberName ) );
menu.Add( new Menu( "Matrix", "Matrix", "Home", MemberName ) );
menu.Add( new Menu( "Join", "Join", "Join", MemberName ) );
return PartialView( menu );
}
In your view you would use something like this:
@{ Html.RenderAction( "Nav", "Nav" ); }
http://www.my-msi.net/Admin
blog
If a post helps you, please mark it as Ansered, thank-you.
remesqny
Member
9 Points
23 Posts
Re: Trying to reuse code within a controller
Feb 19, 2012 01:53 PM|LINK
I think you misunderstand my problem - either that or you have mis-posted a reply for some other poor soul looking for an answer :).
I do not need a partial view at the moment. Yes, I can put checkbox code into, say, an EditorTemplate for reuse. THAT IS NOT THE PROBLEM. I am trying to REUSE code from inside a controller's ActionResult. I don't want to copy/paste all the code within the ActionResult 5, 10 or 50 times (or however many times I want to use it). I know there are several ways of doing this, but I have not been able to make it work.
If I am explaining myself poorly I apologize. But I don't think I am.
francesco ab...
All-Star
20888 Points
3277 Posts
Re: Trying to reuse code within a controller
Feb 19, 2012 02:53 PM|LINK
Controllers may have also methods that are non action methods...that you can use to reuse code. It is enough to decorate a method with NonAction attribute. ...If the right place for your code is the controller...otherwise consider also putting the shared code in your business layer....and then calling it from various controllers. I mean if the code deal purely with user interaction job...you may create a nonaction controller method, otherwise put everuthing in your business layer...You may also define a custom base controller to inherit from(something like a WizardControllerBase..)...and you may also try putting some code in action filters or custom model binders...You may also define al helper to deal with some aspects of your wizard...you might also try defining a "Wizard Helper"... that takes care of handling your wizard steps..(something based on a "WizardHandler" class)...A lot of possibilities. You need just to understand which one is more adequate to your problem.
Mvc Controls Toolkit | Data Moving Plug-in Videos
remesqny
Member
9 Points
23 Posts
Re: Trying to reuse code within a controller
Feb 19, 2012 03:12 PM|LINK
Thanks for the reply. I have been researching all of your suggestions over the last day. I'm no pro, so for my purposes the private action method would seem best, especially just to get something done quick. Problem is I am basically just trying different variations until one works, and that's not been going well. ;)
Might I impose upon you to provide an example private method within the controller using the code I listed above? Also, my problem is in how I actually call it within an ActionResult. For example, if I were to cut-out the non-wizard code in the BasicDetails ActionResult above and put it into a private method, I would think to do the following:
private MethodBankruptcyConsideration(string[] bankruptcyconsiderations, PostedBankruptcyConsiderations postedBankruptcyConsiderations) { // setup properties //var model = new BankruptcyData(); var selectedBankruptcyConsiderations = new List<BankruptcyConsideration>(); var postedBankruptcyConsiderationIDs = new string[0]; if (postedBankruptcyConsiderations == null) postedBankruptcyConsiderations = new PostedBankruptcyConsiderations(); // if an array of posted city ids exists and is not empty, // save selected ids if (bankruptcyconsiderations != null && bankruptcyconsiderations.Any()) { postedBankruptcyConsiderationIDs = bankruptcyconsiderations; postedBankruptcyConsiderations.BankruptcyConsiderationIDs = bankruptcyconsiderations; } // if a view model array of posted city ids exists and is not empty, // save selected ids if (postedBankruptcyConsiderations.BankruptcyConsiderationIDs != null && postedBankruptcyConsiderations.BankruptcyConsiderationIDs.Any()) { postedBankruptcyConsiderationIDs = postedBankruptcyConsiderations.BankruptcyConsiderationIDs; model.WasPosted = true; } // if there are any selected ids saved, create a list of cities if (postedBankruptcyConsiderationIDs.Any()) selectedBankruptcyConsiderations = BankruptcyConsiderationRepository.GetAll() .Where(x => postedBankruptcyConsiderationIDs.Any(s => x.Id.ToString().Equals(s))).ToList(); // setup a view model model.AvailableBankruptcyConsiderations = BankruptcyConsiderationRepository.GetAll(); model.SelectedBankruptcyConsiderations = selectedBankruptcyConsiderations; model.PostedBankruptcyConsiderations = postedBankruptcyConsiderations; //return View(model); }And then the stripped out ActionResult would look like this:
public ActionResult EMailQuoteBasicDetails(string nextButton) { if ((nextButton != null) && ModelState.IsValid) return RedirectToAction("EMailQuoteConfirm"); return View("EMailQuote/BasicDetails", bankruptcyData); }But if I try:
public ActionResult EMailQuoteBasicDetails(string nextButton) { if ((nextButton != null) && ModelState.IsValid) return RedirectToAction("EMailQuoteConfirm");MethodBankruptcyConsideration(bankruptcyconsiderations, postedBankruptcyConsiderations); return View("EMailQuote/BasicDetails", bankruptcyData); }or any variation thereon it's not working.
eric2820
Contributor
2777 Points
1161 Posts
Re: Trying to reuse code within a controller
Feb 19, 2012 08:15 PM|LINK
One problem I see is that you appartently wanted your private MethodBankruptcyConsideration(...) to return a View while only public ActionResult methods are allowed to return a View.
Another problem I is see is that your final public ActionResult EMailQuoteBasicDetails(string nextButton) is using a bankruptcyData that has not been set to anything.
Typically what is done is this:
In an [HttpGet] method you would instanciate a new bankruptcyData member and pass that to the View as the Model class.
The View than sets properties in its Model class and passes the Model class back to the [HttpPost] method.
Now I've used a third component of the URL along with a Model class before in that order. But if that doesn't fit with your use case, consider moving the string nextButton into your Model class.
http://www.my-msi.net/Admin
blog
If a post helps you, please mark it as Ansered, thank-you.