Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Jason Espin 368 posts 1335 karma points
    Oct 28, 2015 @ 11:43
    Jason Espin
    0

    Redirect to another Controller, Model and View during Controller processing

    Hi all,

    I am currently in the process of developing a very complex, multi-stage booking engine for one of our client sites which we built using Umbraco v7. The booking engine itself is a variation of the tutorial posted here http://umbraco.com/follow-us/blog-archive/2015/2/13/creating-multi-step-forms-using-a-surfacecontroller/ by Sebastiaan Janssen. It's a great tutorial if you haven't seen it already.

    In my template, I call the relevant action on my controller as follows:

    <div class="bookingform">
                @Html.Action("ShowBookingForm", "Booking")
    </div>
    

    This action simply defines which stage of the booking process the user is at an performs some of the more complex validation that cannot be handled by the model.

    The very first time it is called, I need to check a JSON cache stored as a property for my Umbraco page to see if the page (a tour) has any tour dates available in the future.

    if (model.Stage == 0)
                {
                    List<DateTime> instanceDates = new List<DateTime>();
                    string instances = model.Content.GetProperty("tripInstances").Value.ToString();
                    string jsonInstances = instances.Trim('+');
                    List<JSONTourInstance> tourInstances = JsonConvert.DeserializeObject<List<JSONTourInstance>>(jsonInstances);
                    foreach (JSONTourInstance item in tourInstances)
                    {
                        DateTime date = new DateTime();
                        if (DateTime.TryParse(item.Date, out date))
                        {
                            if (date >= DateTime.Now)
                            {
                                instanceDates.Add(date);
                            }
                        }
                    }
    
                    if (instanceDates.Count() == 0)
                    {
                        BookingEnquiryModel bookingmodel = new BookingEnquiryModel();
                        return View(bookingmodel);
                    }
                }
    

    If there are not dates available in the future then we don't really want to show the booking process and instead want to show an enquiry form so that the user can ask about any tour dates in the distant future. This is where my main issue occurs. How do I successfully redirect to another controller that will be handling the Enquiry form and have this display on my page instead of the booking view that is generated by the orginal call to action?

    I have tried multiple methods. As you can see above, I have tried returning a view with the Enquiry model passed in instead. This simply produces an error as the original call to action is expecting a BookingModel.

    I have also tired a RedirectToAction("ShowEnquiryForm", "BookingEnquiry") but this does nothing at all.

    There must be some way of calling one controller, realizing not all of the prerequisites to go down that route are met and instead going down another route with another controller but how is this done?

    I am still relatively new to .Net and MVC in general and haven't done much MVC outside of Umbraco however, general MVC doesn't seem to be of any help here as Umbraco seems to break the rules a bit to acheive what it needs to do.

    Any help or pointers here would be greatly appreciated.

    J

  • Lars-Erik Aabech 350 posts 1102 karma points MVP 8x c-trib
    Oct 29, 2015 @ 14:00
    Lars-Erik Aabech
    100

    Since you're rendering the result of the action using @Html.Action(), a redirect will do nothing. The result is just embedded in the template currently shown by Umbraco.

    But View() has an overload where you can specify the view to render. For instance return View("~/Views/Partials/NewForm.cshtml", emptyModel).

    There are other options too, but I think this will solve your immediate needs, no?

  • Jason Espin 368 posts 1335 karma points
    Nov 04, 2015 @ 10:49
    Jason Espin
    0

    Hi Lars,

    Thanks for your response. So would I be correct in thinking I need to replace my @Html.Action() in my initial template view and instead directly reference the view and model I need? Or are you saying that I should be returning View() from my controller as I have done in my example above but pass in the view reference and model as you have defined in your answer?

    Cheers,

    Jason

  • Lars-Erik Aabech 350 posts 1102 karma points MVP 8x c-trib
    Nov 04, 2015 @ 10:52
    Lars-Erik Aabech
    0

    Give returning a specific view from the controller a go.

    Carl's examples are also good alternatives.

  • Jason Espin 368 posts 1335 karma points
    Nov 04, 2015 @ 14:34
    Jason Espin
    1

    Great. That works a treat. Thanks #h5yr

  • Jason Espin 368 posts 1335 karma points
    Nov 04, 2015 @ 15:08
    Jason Espin
    0

    I've just found one big error with this solution. If you use something like this:

    return View("~/Views/Partials/AvailabilityForm.cshtml", enquiry);
    

    and redirect to a partial view that contains this:

    @using (Html.BeginUmbracoForm<EnquiryController>("Submit"){}
    

    When it comes to your controller, if you use

    if (!ModelState.IsValid)
                {
                    return CurrentUmbracoPage();
                }
    

    Then it will return the original page and model that was called using:

    @Html.Action("ShowBookingForm", "Booking")
    

    Rather than returning to the enquiry form to allow the user to correct their mistakes. Anyone know how to get around this?

  • Lars-Erik Aabech 350 posts 1102 karma points MVP 8x c-trib
    Nov 05, 2015 @ 09:02
    Lars-Erik Aabech
    0

    I would use client-side validation like jQuery validate or just plain HTML 5 validation to prevent the user from getting as far.

    Otherwise, you can put a variable, or even the invalid model in ViewData and use use to check whether you should show the form again. Google found this blog entry from Markus Johansson that might shed some light. :)

  • Jason Espin 368 posts 1335 karma points
    Nov 06, 2015 @ 10:02
    Jason Espin
    1

    Hi Lars,

    Using Javascript only validation is a very bad idea especially when it comes to validating models so I am afraid this isn't an option.

    Cheers,

    Jason

  • Carl Jackson 139 posts 478 karma points
    Oct 29, 2015 @ 17:24
    Carl Jackson
    1

    As Lars says a redirect won't do anything from a child action.

    You can simply

    return AlternativeActionName();
    

    From a controller action and pass it any relevant parameters.

    The only thing to be careful with is the view name which will get rendered. From what I remember it is done from Controllers ViewContext which will have been set on your initial action.

    You may need to specify a specific view name explicitly from your actions. eg

    return View("ViewNameHere", bookingmodel);
    

    I may be wrong on the view name part - I just have some memory having to do this before!

    Thanks

    Carl

  • Carl Jackson 139 posts 478 karma points
    Oct 29, 2015 @ 17:32
    Carl Jackson
    0

    Just realised you need to call another controller.

    I'd recommend adding adding a "checker" controller and action which returns a view with :

    @model bool
    if(Model){
        @Html.ActionResult("FormController", "Booking") 
    }
    else{
        @Html.ActionResult("EnquiryController", "Enquire")
    }
    

    That way you can seperate the logic of checking and doing

    Thanks

    Carl

  • Jason Espin 368 posts 1335 karma points
    Nov 04, 2015 @ 10:51
    Jason Espin
    0

    Hi Carl,

    Are you saying to call this from a Controller? If so, this does not work. As I mentioned above in my code example I have already tried this method and it errors out.

    Cheers,

    Jason

  • Carl Jackson 139 posts 478 karma points
    Nov 04, 2015 @ 12:15
    Carl Jackson
    1

    Hi Jason.

    no - You need to have one controller which will return something like :

    return View(model.Stage == 0);
    

    With the view I gave above, Which will then uses the true/false to determine which controller / action to call next.

    Thanks

    Carl

  • Jason Espin 368 posts 1335 karma points
    Nov 04, 2015 @ 15:44
    Jason Espin
    0

    Hi Carl,

    Thanks for your comments. The problem is if I call another form in any of my views, I cannot validate it because when I use:

    return CurrentUmbracoPage()
    

    when a model is invalid to allow the user to go back and change their response, they are redirected back to the original action rather than seeing the form they have just filled in.

    Any ideas?

  • Carl Jackson 139 posts 478 karma points
    Nov 04, 2015 @ 23:12
    Carl Jackson
    0

    Not sure I follow.

    On submission of the form all other variables should be the same as on page load and so the controllershould return the view with the correct form for which validation errors can be shown.

    Are you still looking for a solution? If not then I may have just not got your problem right in my head :)

    Thanks

    Carl

  • Jason Espin 368 posts 1335 karma points
    Nov 06, 2015 @ 10:01
    Jason Espin
    0

    Hi Carl,

    Still looking for a solution. Basically, I show my booking form to start with:

    @Html.Action("ShowBookingForm", "Booking")
    

    This is a multi-stage booking process so it basically involves multiple forms being generated from the same action. To start with the user is shown a calendar input which when clicked populates a drop-down with the times a trip is available on the day. Selecting a time and clicking submit sends a web service request to our software on the server which returns whether or not there is any availability. If there is no availability for the particular tour, we need to display the enquiry form. Otherwise we go to stage two of the booking process.

    This is where I encounter my problem. Using the methods you describe above, I can display the enquiry form. However, if the user submits an invalid enquiry form, they are routed back to the first stage of the booking process rather than seeing the enquiry form they have just completed with the options they selected.

    I hope this sheds some light on my issue.

    Cheers,

    Jason

  • Carl Jackson 139 posts 478 karma points
    Nov 06, 2015 @ 11:17
    Carl Jackson
    0

    Hi Jason.

    I see....

    With my solution you would need to role that initial action into my "checker" controller and view.

    That way you can determine if you need to show:

    • the initial date seletor (nothing is submitted),
    • the booking form (date has been selected and places are available or there is an error in booking)
    • the contact form (date selected and no places available, or there is an error on the contact form)

    you'd need to store the current state somewhere as the user works through things.

    I'd recommend storing it on the user session personally.

    the view would need to take an int (or enum to be neat) so for example...

    @model int
    if(Model == 1){
        @Html.ActionResult("FormController", "Booking") 
    }
    else if(Model == 2){
        @Html.ActionResult("EnquiryController", "Enquire")
    }
    else{
        @Html.ActionResult("FormController", "SelectDate")
    }
    

    Thanks

  • Lars-Erik Aabech 350 posts 1102 karma points MVP 8x c-trib
    Nov 06, 2015 @ 10:05
    Lars-Erik Aabech
    0

    Sorry you're still struggling, Jason.

    Maybe this tutorial on the Umbraco blog will help?

  • Jason Espin 368 posts 1335 karma points
    Nov 06, 2015 @ 10:26
    Jason Espin
    0

    Hi Lars,

    As I mentioned above in my original post, that is what I used although that does not go into enough depth with regards to routing to alternative controllers. Unfortunately Umbraco doesn't seem to comply with a lot of the standard MVC methods and this is why workarounds such as those detailed in Sebaastians post exist.

    Cheers,

    Jason

  • Lars-Erik Aabech 350 posts 1102 karma points MVP 8x c-trib
    Nov 06, 2015 @ 10:32
    Lars-Erik Aabech
    0

    As long as you pass the model, you can add whatever information you need to it. For instance which partial view to include, which values should be shown to the user etc.

    Controller pseudo-code assuming controller from tutorial:

    // set model.StepIndex, then...
    var views = new[]{"Step1", "Step2", "Summary"};
    model.CurrentView = views[model.StepIndex];
    model.CurrentValues = new { FirstName=submittedModel.FirstName };
    

    View pseudo-code replacing tutorial switch:

    @Html.Partial(Model.CurrentView, Model)
    
  • Jason Espin 368 posts 1335 karma points
    Nov 06, 2015 @ 14:11
    Jason Espin
    0

    Hi Lars,

    I don't know where you are getting this information from though as CurrentView and CurrentValues are not valid model attributes.

    Cheers,

    Jason

  • Lars-Erik Aabech 350 posts 1102 karma points MVP 8x c-trib
    Nov 06, 2015 @ 14:13
    Lars-Erik Aabech
    0

    Don't you have a class representing the model?
    The first step in the tutorial you are following is to create a model class.
    You are in charge of that class, and can do whatever you want with it.

    By CurrentValues, I mean the values you are sending back and forth between view and controller. You should add any relevant property, such as "FirstName".

    If this is all new to you, I suggest going through a few MVC tutorials as such, and then come back to Umbraco.

  • Lars-Erik Aabech 350 posts 1102 karma points MVP 8x c-trib
    Nov 06, 2015 @ 14:27
    Lars-Erik Aabech
    0

    Unless you could be persuaded to spend a few bucks on the Umbraco Forms package. It really really simplifies stuff like this. :)

    http://umbraco.com/products-and-support/forms/

  • Jason Espin 368 posts 1335 karma points
    Nov 06, 2015 @ 15:42
    Jason Espin
    0

    I have ended up solving this issue as follows as none of the other suggestions seem to work:

    In my template I have the following:

        @if (TempData["ShowEnquiryForm"] != null || availableDates.Count() == 0)
                    {
                        <div class="enquiryForm">
                            @Html.Action("ShowEnquiryForm", "Enquiry")
                        </div>
    
                    }else if (availableDates.Count() > 0)
                    {                
                        <div class="bookingform">
                            @Html.Action("ShowBookingForm", "Booking")
                        </div>
                    }
    

    This displays the booking form initially if there are tours available otherwise it displays the enquiry form.

    Following the availability check in the booking form, in my booking controller I have the following (currently it will always fire due to the true == true statement):

    if (model.Stage == 1)
                {
                    EnquiryModel enquiry = new EnquiryModel();
                    if (true == true)
                    {
                        TempData["ShowEnquiryForm"] = true;
                        return View("~/Views/Partials/AvailabilityForm.cshtml", enquiry);
                    }
                    GroupTourAvailabilityData[] availabilityData = null;
                    Dictionary<int, GroupTourAvailabilityData[]> availability = CheckSessionAvailability(model);
                    availability.TryGetValue(model.Tour_Instance.Instance_Id.Value, out availabilityData);
                    if (availabilityData.Where(x => x.Availability > 0).Count() > 0)
                    {
                        // Do nothing
                    }
                    else
                    {
                        TempData["ShowEnquiryForm"] = true;
                        return View("~/Views/Partials/AvailabilityForm.cshtml", enquiry);
                    }
                }
    

    This then proceeds with the booking process if there is availability otherwise it sets the tempdata value of showenquiryform so that on page load, the alternative controller for the enquiry form is displayed instead.

    This is the managed by the following controller actions:

    public class EnquiryController : SurfaceController
        {
            [ChildActionOnly]
            public ActionResult ShowEnquiryForm(EnquiryModel model)
            {
                model = model ?? new EnquiryModel();
                return View(model);
            }
    
            [HttpPost]
            public ActionResult Submit(EnquiryModel model)
            {
                if (!ModelState.IsValid)
                {
                    TempData["ShowEnquiryForm"] = true;
                    return CurrentUmbracoPage();
                }
    
                TempData["message"] = "Your enquiry has been received successfully";
    
                return RedirectToCurrentUmbracoPage();
    
            }
        }
    

    Because again set the tempdata value of ShowEnquiryForm on form failure, when the page reloads, the enquiry form with their previous entries is displayed. Upon successful submission, a partial in my initial view looks for a tempdata value of message and displays a model to notify them their enquiry has been received whilst displaying the booking selection wizard.

    This may be a roundabout way of doing it but i from over a year now of working with Umbraco and .Net MVC this seems like the only viable solution for this issue.

    Cheers,

    Jason

Please Sign in or register to post replies

Write your reply to:

Draft