Copied to clipboard

Flag this post as spam?

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


  • Andreas Pfanner 196 posts 314 karma points
    Jun 19, 2015 @ 08:53
    Andreas Pfanner
    0

    MVC SurfaceController -Post Action

    Hi,

    I've created a SurfaceController and Partial View to display a list of umbraco contents and allow them to be deleted in the frontend.

    The displaying works well, but I don't know how to create the delete buttons? I guess it should be posted to the current page, so Html.ActionLink isn't the right for that I guess.

    Does anybody can provide me an example how to do that? Lets say I have a Delete(int contentId) action in my surface controller, and in my Partial View (@inherits Umbraco.Web.Mvc.UmbracoViewPage<>

    Any guess?

    Best Regards Andreas

  • Stefano Beretta 101 posts 246 karma points
    Jun 19, 2015 @ 09:19
    Stefano Beretta
    0

    Hi Andreas!

    You can easily fire a javascript function using the onclick attribute (or bind the buttons using jquery).

    The function should execute an ajax post action and, if succeeded, remove the correct box:

        <div id="box-width-id-9999">
        ...all my htyml box
    
        <button class="delete-button" onclick="DeleteBox(9999)" />
        </div>
    
        <script>
        function DeleteBox(myId){
        var jsonParams={'contentId' : myId}
        $.ajax({
                type: 'POST',
                url: '/myPostAction',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(jsonParams),
                cache: false,
                dataType: 'json',
                success: function (data) {
                  //maybe show a loader even if the ajax call should be fast
                  $('#box-width-id-' + myId).hide()
                },
                error: function (err) {alert(err);}
            })
        }
        </script>
    

    Hope this help

    S

  • Andreas Pfanner 196 posts 314 karma points
    Jun 19, 2015 @ 15:41
    Andreas Pfanner
    0

    Hi Stefano

    isn't it possible to do with a server side post (Umbraco.BeginForm)? I should also mention that I embed my partial inside of a macro (to be able to place it inside RTE). So I can only post to the current page url (without an action part in the URL). Should that be done with javascript anyway?

    Best Regards Andreas

  • Stefano Beretta 101 posts 246 karma points
    Jun 19, 2015 @ 15:59
    Stefano Beretta
    0

    Hi Andreas!

    Unfortunately I can't help you in that way.

    Anyway I think you can use async submit for your forms, but I can't help you for the implementation (I miss a lot of know-how about async submit).

    I'll ask my colleague asap :-D

    Best regards

    S

  • Nicholas Westby 2054 posts 7100 karma points c-trib
    Jun 19, 2015 @ 16:36
    Nicholas Westby
    0

    EDIT: I think I misunderstood the question. Rusty's answer below seems more appropriate. Leaving my answer as it is tangentially related and may also be of use.

    I handle that with a custom ActionMethodSelectorAttribute:

    // Namespaces.
    using System;
    using System.Web.Mvc;
    
    
    /// <summary>
    /// When this attribute decorates an action method, that action method will
    /// only be routed to if the specified request parameter matches the specified value.
    /// </summary>
    public class AcceptParameterAttribute : ActionMethodSelectorAttribute
    {
    
        #region Properties
    
        /// <summary>
        /// The name of the request parameter.
        /// </summary>
        public string Name { get; set; }
    
    
        /// <summary>
        /// The value of the request parameter. Optional.
        /// </summary>
        /// <remarks>
        /// If unspecified, the check will just be to see if the value exists rather than
        /// if the value matches something in particular.
        /// </remarks>
        public string Value { get; set; }
    
        #endregion
    
    
        #region Methods
    
        /// <summary>
        /// Indicates whether or not this request contains the specified name/value.
        /// </summary>
        public override bool IsValidForRequest(
            ControllerContext controllerContext,
            System.Reflection.MethodInfo methodInfo)
        {
            var req = controllerContext.RequestContext.HttpContext.Request;
            bool valid;
            if (string.IsNullOrEmpty(this.Value))
            {
                valid = !string.IsNullOrEmpty(req.Form[this.Name]);
            }
            else
            {
                valid = string.Equals(req.Form[this.Name], this.Value,
                    StringComparison.InvariantCultureIgnoreCase);
            }
            return valid;
        }
    
        #endregion
    
    }
    

    Then in my surface controller, I decorate the action methods with the custom attribute:

        [HttpPost]
        [AcceptParameter(Name = "btnDelete")]
        [ActionName("UpdateMember")]
        public ActionResult DeleteMember(EditMember model) {}
    
        [HttpPost]
        [AcceptParameter(Name = "btnUpdate")]
        public ActionResult UpdateMember(EditMember model) {}
    

    Here's what the markup looks like (the name is what I use to differentiate the buttons):

    <button type="submit" name="btnUpdate" value="Update">
        Update
    </button>
    <button type="submit" name="btnDelete" value="Delete">
        Delete
    </button>
    

    That allows me to respond to different buttons within the same form using different action methods in my surface controller. I also had to specify them both to use the same action name (even though the functions are different) using the ActionName attribute.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jun 19, 2015 @ 16:38
    Rusty Swayne
    2

    You should be able to use an ActionLink - but use HttpGet

     [HttpGet]
     public ActionResult DeleteContent(int contentId, int redirectId) 
     {
           // Validate here -> if fails -> return or throw
            var contentService = ApplicationContext.Services.ContentService;
             contentService.Delete(contentId);
            return RedirectToUmbracoPage(redirectId);
     }
    

    In your view you would iterate through your collection of content that could be deleted:

     @foreach(var content in DeletableContent) 
     {
          <li>
    
               @Html.ActionLink("Delete", "DeleteContent", "[MySurfaceControllerName]", new { area = "[PluginControllerAlias]", contentId = content.Id, redirectId = CurrentPage.Id}, new { @class = "btn btn-danger" })
          </li>
     }
    

    You might also consider encrypting your contentId so it is not so tempting - Umbraco has a pretty nifty extension that makes this easy:

       var encrypted = content.Id.ToString().EncryptWithMachineKey();
    

    and then

       var decrypted = int.Parse(value.DecryptWithMachineKey());
    
  • Andreas Pfanner 196 posts 314 karma points
    Jun 22, 2015 @ 11:05
    Andreas Pfanner
    0

    Thanks for your postings!

    @Rusty: When I try your approach, I never get a link (href) set. Does this approach work, when I include my partial view (which is connected to my surface controller) through a macro?

    Lets say, I have the following page in Umbraco

    /en/subscriptions/overview

    It's a normal text page, where I placed a macro containing the following in RTE:

    @Html.Action("SubscriptionListRenderForm", "SubscriptionListSurface", new { startNode = startNodeId })

    The SubscriptionListRenderForm action (child action) of the controller retrieves all contents below startnode and renders a partial view, which displays a content grid.

    In the grid, I have a delete link/button on each content in order to delete the content.

    In my environment, what would be the URL to delete a content? Should it be (for example) /en/subscriptions/overview/Delete/3424 ?

    This doesn't seem to work with the HttpGet action I defined in my surface controller..

    Best Regards Andreas

  • Andreas Pfanner 196 posts 314 karma points
    Jun 22, 2015 @ 11:23
    Andreas Pfanner
    0

    Sorry I had a typo in the Html.ActionLink call. Now it points to:

    /umbraco/Surface/SubscriptionListSurface/DeleteContent?contentId=3016&redirectId=2958

    (Not yet encrypted) I guess this looks right, and it works well.

    Special thanks to Rusty, and the others for all the input !

    One last question: How about an edit option in that scenario? After klick to "Edit" the fields of the content grid should be changed to input controls. Is there a best practice for doing so?

    Best Regards Andreas

  • Andreas Pfanner 196 posts 314 karma points
    Jun 22, 2015 @ 11:57
    Andreas Pfanner
    101

    Hey Guys,

    I leave my latest question (edit - best practice) open (please don't ignore) while adding another note: While creating a Icon Link instead of text link I figured out that there is another Helper Method. Instead of using Html.ActionLink, the following is possible too:

     <a href="@Url.SurfaceAction("Delete", "SubscriptionListSurface", new { contentId = item.Id, redirectId = UmbracoContext.Current.PageId })"><span class="glyphicon glyphicon-trash"></span></a>
    

    It creates such a link:

    http://.../en/subscription/overview?ufprt=BBC5BEA677E4FC613C0B5BC3218C307C89C94A8F37B5F7B7CECCE2F045C36B2A28BE05736E97402422B6BAD417BBADE5697F3CAA914A2B6E6879890A7587D19CE20290879111EF1206C43E2EF4F23A7502401B1C60F8F973D92751CC4370933FDB3718916FD38C7CE90C73896BCF5662B8ECA1F83BC53982E08C7CC98B223FD109A2E24D08CA2A72C79ECAC6F9E42C7F23F0EBF6CEB3C6CA626892C47478C0A2B75A6D99776EB0536DCE165160FE8B83CFE7D1AB4EDF8D42AFF5CA73A96D5EB4F1D5337FFE6CE2B93C9E708C02D71CD2857481F919A49567997D1E7C0F62D3D238C49B8D1AAAA5131CD1881A16923CBAEA8295A298898F8B5B692AF47C35F36690D566DD4656492733FE14A8A32890083FF7A106DF8688A18D19F656A62466DF1A03B14CAEE4E5E4981AB56D8BE5F48F2AEF55806C908B449510F82AA3BB25CA9447312D815CB621E518193961E572813FBDA520F81929C1322F2F261C127F27F3749C24A44C7EFE099972A6CB1F9EE804A59B7ADADC8BFC88AC421B03EA796FF3BA41E4E0C97CF0AA39E24B678A48D86A8F9713494C19606B6A93D6AE52B20B2DE68469F8F87FF1C5246B9BF7CEDD9E31B3ABD54B4BC9E3767A7CFA592887DECC5FCA6F4572CBB57088A92C477EEF0B8D4C51048F2865457B8190B65C9565AABA638D01B1ED23CBFEF06D906E7D26F0D1F0114D4016EC9CCC6076B635457AEA8705BE3DBA538434B5BF351006A0759299AB37346CD8FD73EB7D6BF1859D73E29021C17D3F0DAE33DE522FFD8ADEB2D93D0AB17135D98D48C954C1B50B3D62FB081F75DA4B068792F1D304D2B0FAF71912F0DEDF654B640742D6E4CAA7FE88F2

    Which I personally even like more than the link from Html.ActionLink. It also makes it obsolete to encrypt the url parameters by myself, right?

    Best Regards Andreas

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jun 22, 2015 @ 15:55
    Rusty Swayne
    0

    Cool, I did not know there was a SurfaceAction extension.

    It looks like there is an overload to include an area as well - I've been doing it the hard way. I'd say you've found the answer yourself and should mark your response as the best solution. #H5YR

  • Andreas Pfanner 196 posts 314 karma points
    Jun 23, 2015 @ 08:24
    Andreas Pfanner
    0

    Hi Rusty,

    I don't understand the purpose of areas yet. Can you give me an explanation, what you do with that? Can it help building edit functionality?

    Best Regards Andreas

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jun 23, 2015 @ 18:25
    Rusty Swayne
    1

    Hi Andreas,

    Areas are an MVC thing. You can think of them sort of like namespaces for controller routes.

    Umbraco makes it pretty easy to group your controllers into areas by decorating them with the "PluginController" attribute.

      [PluginController("MyArea")]
      public class MyAreaExampleController : SurfaceController, IRenderMvcController {
      {
             [HttpPost]
             public ActionResult MyPostMethod() { .... 
      }
    

    By doing so you can increase the degree of certainty that any controllers you add to an application will not conflict with controllers added by some package you may have installed.

    Umbraco will handle the routing stuff for you in the case of Route Hijacking (another Umbracoism) but there are times when you need to be a bit more specific - generally when using forms, ChildActionsOnly Action results and ActionLink.

    I've been doing it by adding the area to the route values ... something like

             using (Html.BeginUmbracoForm<MyAreaExample>("MyPostMethod", new { area = "MyArea" }))
    

    or like in the ActionLink we discussed earlier in this thread.

    Obviously they are not required, but I tend to designate an area on every controller I write.

Please Sign in or register to post replies

Write your reply to:

Draft