Copied to clipboard

Flag this post as spam?

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


  • Roy Berris 89 posts 577 karma points c-trib
    Sep 03, 2021 @ 14:41
    Roy Berris
    0

    How to make a controller that is RenderMvc and Surface?

    I have a contact page called "ContactPage".

    Now I have a custom view model. I understand that I need a ContactPageController : SurfaceController to handle post data. But how to I render the initial page with with my view model. Because if it was a RenderMvc controller I could just return CurrentTemplate(new ContactPageModel(...));. But I can't do that with the surface controller.

    So what is the solution to this? Can't I have the POST version of my contact page with the same name as the GET page?

  • Marc Goodson 2157 posts 14431 karma points MVP 9x c-trib
    Sep 04, 2021 @ 09:33
    Marc Goodson
    2

    Hi Roy

    The SurfaceController has been designed in a specific way to be auto-routed and to handle a POST request from the front end of Umbraco, it kind of sits on the Surface...

    Some people would say that a POST request in MVC shouldn't return a View, but instead should only ever 'process the POST' and then redirect to a success GET action...

    ... and SurfaceControllers are kinda setup to do this kind of thing eg

    You handle the POST and then return 'CurrentUmbracoPage()' or 'RedirectToCurrentUmbracoPage()' to place control back into the underlying RenderMvcController, and you can pass values by the querystring from the SurfaceController (or viewbag) if needs be.

    Or you can RedirectToUmbracoPage() or RedirectToUmbracoUrl() to send the request, once the POST is handled to a GET endpoint that will return a view.

    https://our.umbraco.com/documentation/reference/routing/surface-controllers-actions#querystring-parameter-using-a-string-value

    So with this proposed pattern of working, say if you had an Advanced Search Form, you might have a SurfaceController handle the POST of the form, validate the values supplied are valid (return CurrentUmbracoPage if not to show validation errors) - then if all valid, Redirect to another Umbraco page, that has a hijacked RenderMvcController to take in the values of the form, passed via Querystring, that then 'does the search' and returns the View/Template that displays the search results...

    ... but alot of people prefer to have that all in one Controller ...

    which is possible (people call this hybrid controllers) if you create a new controller, and make it inherit from SurfaceController AND implement IRenderMvcController

    eg people tend to end up with something like this as a base controller to inherit from:

    namespace MyLovely.Controllers.Base
    {       
        public abstract class HybridSurfaceRenderMvcController : SurfaceController, IRenderMvcController
        {
            /// <summary>
            /// Checks to make sure the physical view file exists on disk.
            /// </summary>
            /// <param name="template"></param>
            /// <returns></returns>
            protected bool EnsurePhysicalViewExists(string template)
            {
                try
                {
                    var result = ViewEngines.Engines.FindView(ControllerContext, template, null);
                    if (result.View == null)
                    {
                        LogHelper.Warn<HybridSurfaceRenderMvcController>("No physical template file was found for template " + template);
                        return false;
                    }
                }
                catch (Exception ex)
                {
                    // Log the exception.
                    LogHelper.Error<HybridSurfaceRenderMvcController>("Surface Render Mvc Controller Error: EnsurePhysicalViewExists", ex);
                }
    
                return true;
            }
    
            /// <summary>
            /// Returns an ActionResult based on the template name found in the route values and the given model.
            /// If the template found in the route values doesn't physically exist, then an empty ContentResult will be returned.
            /// </remarks>
            protected ActionResult CurrentTemplate<T>(T model)
            {
                var template = ControllerContext.RouteData.Values["action"].ToString();
                if (!this.EnsurePhysicalViewExists(template))
                {
                    return this.HttpNotFound();
                }
    
                return this.View(template, model);
            }
    
            /// <summary>
            /// The default action to render the front-end view.
            /// </summary>
            public virtual ActionResult Index(ContentModel model)
            {
                return this.CurrentTemplate(model);
            }   
    
    }
    

    Then this would be able to 'hijack' a RenderMvcController route by convention, and handle a SurfaceController postback 'in the same file'.

    Personally I tend to keep them separate, over the years, I just seem to prefer it, when debugging something.

    But hopefully that gives you a bit of an insight into what's going on and allows you to form your own approach!

    regards

    marc

  • Dhanesh Kumar MJ 165 posts 521 karma points MVP c-trib
    Sep 04, 2021 @ 13:26
    Dhanesh Kumar MJ
    101

    Hi Roy,

    @Marc is right, and also you can achieve this by using the following approach

    namespace TestProject.Core.Controllers
        {
    public class BillingController : RenderMvcController
    {
      //inject services
       public BillingController (){
                    }
    
        [HttpGet]
        public ActionResult Index(ContentModel model)
        {
            return RenderView(false);
        }
    
        private ActionResult RenderView(bool addedToBasket)
        {
            return View("/Views/BillingAddress.cshtml", addressDetails);
        }
    
        [HttpPost]
            public ActionResult Index(AddressViewModel billingAddress)
            {
                return Redirect('');
            }
        }
    }
    

    And in view /Views/BillingAddress.cshtml form for POSTING

    POST :

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<TestProject.Core.Models.Checkout.Billing_Address>
    
    @{
        Layout = "_Layout.cshtml";
    
      @using (Html.BeginForm("Index", "Billing", FormMethod.Post, new { @id = "billingAddress" }))
            {
            }
    }
    

    Regards Dhanesh

  • Roy Berris 89 posts 577 karma points c-trib
    Sep 06, 2021 @ 06:33
    Roy Berris
    0

    @Marc and @Dhanesh. Thanks, still learning Umbraco so both options are really helpful!

Please Sign in or register to post replies

Write your reply to:

Draft