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<>
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>
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?
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):
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.
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());
@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..
(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?
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:
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
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.
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
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:
Hope this help
S
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
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
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:
Then in my surface controller, I decorate the action methods with the custom attribute:
Here's what the markup looks like (the name is what I use to differentiate the buttons):
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.
You should be able to use an ActionLink - but use HttpGet
In your view you would iterate through your collection of content that could be deleted:
You might also consider encrypting your contentId so it is not so tempting - Umbraco has a pretty nifty extension that makes this easy:
and then
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
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
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:
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
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
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
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.
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
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.
is working on a reply...