Hi all,
I’ve been investigating what it would take to make Umbraco play nice with Razor scripts that want to “abort” the normal page content and return custom content – JSON strings in reply to an AJAX call, for example.
Been stewing on this topic for the past week or so, trying to come up with an elegant way to do something like this. Having found a post by joeriks (http://joeriks.com/2011/11/06/separating-html-and-logic-in-razor-webpages-or-umbraco-macroscript/) which also touches on returning a JSON string in response to an AJAX post, I’ve attempted to delve into the best way to do such a thing with Umbraco without having to create a special Document Template as demonstrated in said post.
Tl;dr…
I succeeded.
I’ve now got some working code against the 4.8 branch, and I’m presently trying to check it into my fork to create a pull request against (authentication issues for some reason are preventing me). So now I’m going to create a pull request.
Arrgggh… looks like I stuffed up a previous pull request. Bugger. So now the pull request “Master ContentType support enh” has the changes from this commit in it as well. Sorry ‘bout that – not really sure how to clean it up now. Will gladly take any advice.
I’ll have to write up some documentation on how it works and how to take advantage of it of course, but having seen questions/posts/etc. about this kind of thing kicking around for some time now, I’m guessing it may be useful for others?
My next question is, how fast can I get it into the core? J Wouldn’t mind seeing it in the next release ;P
Here’s my User Story:
As a developer, I want to render a form with a Razor Macro, and instead of refreshing the page on submit, I want to retrieve a JSON string and use Knockout.js or something similar to update the UI based on the response. I would also like to be able to have the Razor Script handle the form post and reply without relying on external libraries/modules/etc.
Robert Foster
R & E Foster & Associates
Refactored i.T
m: 0418 131 065 | e: rfo...@refoster.com.au
www: http://refactored.com.au/blog
The idea is to create a system that relies on nothing more than the Razor Macro Sxript itself – I wanted it to be set up so that developers wouldn’t have to build a dll or a add an alternate template in order to just return data in response to an AJAX post/get… I have used both of those techniques at times too, but sometimes I just want to use a script without having to build more infrastructure.
This change allows you to build a Razor script that can return JSON data while cancelling the usual page content that the Macro might be rendered within (patch that makes this possible can be viewed in the existing pull request).
An example might be the following script (I haven’t included the Javascript (jQuery/knockout) that handles the form post/response – that’s trivial) – stripped down as much as I could… :
@inherits umbraco.MacroEngines.DynamicNodeContext
@* Render something *@
<form method="post" action="#" id="addProductToCart" data-bind="submit: addToCart">
<input type="hidden" id="formId" name="formId" value="@FormId" /><input type="hidden" id="productId" name="productId" value="@product.Id" />
<span class="price">@product.FormatCurrency(@product.UnitPriceIncTax)</span>
<label for="productQty">Quantity:</label><input type="number" id="productQty" name="productQty" value="1" />
<button type="submit">Add to Cart</button>
</form>
@functions{
// FormId is used to ensure that this script can differentiate between
// the form rendered by this script and any other form.
public string FormId {get; private set;}
public string Message { get; private set; }
protected override void InitializePage()
{
base.InitializePage();
FormId = "addProductToCart";
if (IsPost)
{
var formId = Request["formId"];
var sQty = Request["productQty"];
var sProductId = Request["productId"];
int productId = 0; int qty = 0;
// If some of the elements don't match up or are invalid, then do nothing.
var IsValid = (formId == FormId &&
int.TryParse(sProductId, out productId) &&
int.TryParse(sQty, out qty));
if (IsValid)
{
// Update the Cart.
Library.ShoppingCart.Add(productId, qty);
}
else
{
Message = "Please check the quantity and try again.";
}
var json = Json.Encode(new { isValid = IsValid, message = Message, itemCount =
ShoppingCart.TotalItemCount, totalValue = ShoppingCart.TotalPrice });
//Write the data to the output and close the connection.
Response.Clear();
Response.CacheControl = "no-cache";
Response.AddHeader("Content-Type", "application/json");
Response.AddHeader("Pragma", "no-cache");
Response.Write(json);
// Complete Request is a method on the BaseContext, and is a wrapper for
// Context.ApplicationInstance.CompleteRequest();
// It also sets a flag on the Macro that indicates that we want to terminate page
// rendering.
CompleteRequest();
It actually shouldn’t change anything under the hood in the normal running of things – it simply allows a script to tell the ASP.Net Event Handler pipeline to skip to the end, and set flag so that Umbraco can ignore normally rendered content.
It should be possible to build in some convenience methods to do things like form generation/handling in the Macro’s BaseContext that could be hooked into to do the custom work by the script while also implementing some security measures to prevent things like XSS and the like – currently it’s up to the script to implement these kind of things. Possibly even take advantage of some of the MVC technology.
I see this as the little brother of a full blown MVC integration into Umbraco… you can have a pseudo-controller implementation in the script and allow it to handle the response appropriately without interference.
--
You received this message because you are subscribed to the Google Groups "Umbraco development" group.
To post to this group, send email to umbra...@googlegroups.com.
To unsubscribe from this group, send email to umbraco-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msg/umbraco-dev/-/P3Vk057uR-YJ.
For more options, visit https://groups.google.com/groups/opt_out.
I should probably attempt to separate out the example I gave from and the actual functionality of the patch
Given that the controls have all been rendered and are in memory by the time the controls are being iterated, I don’t think the performance impact will be significant. I have been thinking however that using an event somehow might be a better way to go… perhaps just haven’t had time to explore that path lately.
Perhaps the way forward on that one is move the CompleteRequest() method to UmbracoContext or one of the related classes – then we could just check the context to see whether or not we are meant to bypass content rendering… would certainly remove the need to iterate through the controls…
Rob.