ASP.NET routing enables us to use URLs that are not physical files, In DD4T we have the default page route definition. In which all page request redirct to Page controller and process the page.
So, how to do this. to render out the form we have BeginDD4TForm html helper as below that generate the form with encrypted route values.
Once this is done, then we need to add the custom handler to read the route information and route to the proper controller and action instead of the tridion page, this is the responsibilty of the class DD4TFormRouteHandler. To use this add this handler in your default page router as below.
In the action of form, once form is posted redirect to the current URL, so that it will load the Tridion page once the form is posted.
Both the helper and RouteHandler source code is below.
DD4TFormRouteHandler.cs
DD4TFormHelper.cs
DD4TFormRouteHandler is a custom route handler responsible for mapping incoming browser requests to particular MVC controller actions, this works along with DD4TFormHelper (that generate the route information for the form)Posting a form in DD4T is not complicated, you can create the mvc form as a normal controller and action, then post it via AJAX. But, when we need to do post the form as normal page, It would need a tweak as the controller/action is not a page existed in tridion. This can be achieved by implementing a custom Mvc RoutHandler and reroute the posted form to the encrypted action and controller. It works as below daigram.
@using (Html.BeginDD4TForm("Post", "Contact", FormMethod.Post)) { @Html.TextBox("username") <input type="submit" name="submit" value="Submit" /> }BeginDD4TForm is a helper in the custom class DD4TFormHelper, which is responsible for generating encrypted route information.
Once this is done, then we need to add the custom handler to read the route information and route to the proper controller and action instead of the tridion page, this is the responsibilty of the class DD4TFormRouteHandler. To use this add this handler in your default page router as below.
//Tridion page route routes.MapRoute( "TridionPage", "{*PageId}", new { controller = "Page", action = "Page" }, // Parameter defaults new { pageId = @"^(.*)?$" } // Parameter constraints ).RouteHandler = new DD4TFormRouteHandler();This is the important step (Register the custom route handler)
In the action of form, once form is posted redirect to the current URL, so that it will load the Tridion page once the form is posted.
Both the helper and RouteHandler source code is below.
DD4TFormRouteHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DD4TFormRouteHandler : MvcRouteHandler | |
{ | |
protected override IHttpHandler GetHttpHandler(RequestContext requestContext) | |
{ | |
string encodedVal; | |
switch (requestContext.HttpContext.Request.RequestType) | |
{ | |
case "POST": | |
encodedVal = requestContext.HttpContext.Request.Form["tprt"]; | |
break; | |
case "GET": | |
encodedVal = requestContext.HttpContext.Request.QueryString["tprt"]; | |
break; | |
default: | |
return null; | |
} | |
if (!string.IsNullOrWhiteSpace(encodedVal)) | |
{ | |
string decryptedString; | |
decryptedString = encodedVal.DecryptWithMachineKey(); | |
var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); | |
var decodedParts = new Dictionary<string, string>(); | |
foreach (var key in parsedQueryString.AllKeys) | |
{ | |
decodedParts[key] = parsedQueryString[key]; | |
} | |
if (decodedParts.Count == 3) | |
{ | |
string controllerName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == "c").Value); | |
string actionName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == "a").Value); | |
string areaName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == "ar").Value); | |
requestContext.RouteData.Values["action"] = actionName; | |
requestContext.RouteData.Values["controller"] = controllerName; | |
requestContext.RouteData.Values["area"] = areaName; | |
} | |
} | |
return base.GetHttpHandler(requestContext); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class DD4TFormHelper | |
{ | |
/// <summary> | |
/// | |
/// </summary> | |
/// <param name="html"></param> | |
/// <param name="action"></param> | |
/// <param name="controllerName"></param> | |
/// <param name="area"></param> | |
/// <param name="additionalRouteVals"></param> | |
/// <param name="htmlAttributes"></param> | |
/// <param name="method"></param> | |
/// <returns></returns> | |
public static MvcForm BeginDD4TForm(this HtmlHelper html, string action, string controllerName, string area, | |
object additionalRouteVals, | |
IDictionary<string, object> htmlAttributes, | |
FormMethod method) | |
{ | |
var formAction = HttpContext.Current.Request.Url.PathAndQuery; | |
return html.RenderForm(formAction, method, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), controllerName, action, area, additionalRouteVals); | |
} | |
/// <summary> | |
/// Helper method to create a new form to execute in the DD4T request pipeline against a locally declared controller | |
/// </summary> | |
/// <param name="html"></param> | |
/// <param name="action"></param> | |
/// <param name="controllerName"></param> | |
/// <param name="method"></param> | |
/// <returns></returns> | |
public static MvcForm BeginDD4TForm(this HtmlHelper html, string action, string controllerName, FormMethod method) | |
{ | |
return html.BeginDD4TForm(action, controllerName, null, new Dictionary<string, object>(), method); | |
} | |
/// <summary> | |
/// Helper method to create a new form to execute in the DD4T request pipeline against a locally declared controller | |
/// </summary> | |
/// <param name="html"></param> | |
/// <param name="action"></param> | |
/// <param name="controllerName"></param> | |
/// <param name="additionalRouteVals"></param> | |
/// <param name="htmlAttributes"></param> | |
/// <param name="method"></param> | |
/// <returns></returns> | |
public static MvcForm BeginDD4TForm(this HtmlHelper html, string action, string controllerName, | |
object additionalRouteVals, | |
IDictionary<string, object> htmlAttributes, | |
FormMethod method) | |
{ | |
return html.BeginDD4TForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); | |
} | |
public static string DecryptWithMachineKey(this string value) | |
{ | |
if (value == null) | |
return null; | |
string[] parts = value.Split('\n'); | |
StringBuilder decryptedValue = new StringBuilder(); | |
foreach (var part in parts) | |
{ | |
decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData); | |
} | |
return decryptedValue.ToString(); | |
} | |
private static MvcForm RenderForm(this HtmlHelper htmlHelper, | |
string formAction, | |
FormMethod method, | |
IDictionary<string, object> htmlAttributes, | |
string controller, | |
string action, | |
string area, | |
object additionalRouteVals = null) | |
{ | |
//ensure that the multipart/form-data is added to the html attributes | |
if (htmlAttributes.ContainsKey("enctype") == false) | |
{ | |
htmlAttributes.Add("enctype", "multipart/form-data"); | |
} | |
var tagBuilder = new TagBuilder("form"); | |
tagBuilder.MergeAttributes(htmlAttributes); | |
// action is implicitly generated, so htmlAttributes take precedence. | |
tagBuilder.MergeAttribute("action", formAction); | |
// method is an explicit parameter, so it takes precedence over the htmlAttributes. | |
tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); | |
var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; | |
if (traditionalJavascriptEnabled) | |
{ | |
// forms must have an ID for client validation | |
tagBuilder.GenerateId("form" + Guid.NewGuid().ToString("N")); | |
} | |
htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); | |
//new DD4TForm: | |
var theForm = new DD4TForm(htmlHelper.ViewContext, controller, action, area, method, additionalRouteVals); | |
if (traditionalJavascriptEnabled) | |
{ | |
htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; | |
} | |
return theForm; | |
} | |
internal class DD4TForm : MvcForm | |
{ | |
/// <summary> | |
/// Creates an DD4TForm | |
/// </summary> | |
/// <param name="viewContext"></param> | |
/// <param name="controllerName"></param> | |
/// <param name="controllerAction"></param> | |
/// <param name="area"></param> | |
/// <param name="method"></param> | |
/// <param name="additionalRouteVals"></param> | |
public DD4TForm( | |
ViewContext viewContext, | |
string controllerName, | |
string controllerAction, | |
string area, | |
FormMethod method, | |
object additionalRouteVals = null) | |
: base(viewContext) | |
{ | |
_viewContext = viewContext; | |
_method = method; | |
_encryptedString = CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); | |
} | |
private readonly ViewContext _viewContext; | |
private readonly FormMethod _method; | |
private bool _disposed; | |
private readonly string _encryptedString; | |
protected override void Dispose(bool disposing) | |
{ | |
if (this._disposed) | |
return; | |
this._disposed = true; | |
_viewContext.Writer.Write("<input name='tprt' type='hidden' value='" + _encryptedString + "' />"); | |
base.Dispose(disposing); | |
} | |
} | |
internal static string CreateEncryptedRouteString(string controllerName, string controllerAction, string area, object additionalRouteVals = null) | |
{ | |
//need to create a params string as Base64 to put into our hidden field to use during the routes | |
var routeParams = string.Format("c={0}&a={1}&ar={2}", | |
HttpUtility.UrlEncode(controllerName), | |
HttpUtility.UrlEncode(controllerAction), | |
area); | |
var additionalRouteValsAsQuery = additionalRouteVals != null ? additionalRouteVals.ToDictionary<object>().ToQueryString() : null; | |
if (additionalRouteValsAsQuery.IsNullOrWhiteSpace() == false) | |
routeParams += "&" + additionalRouteValsAsQuery; | |
return routeParams.EncryptWithMachineKey(); | |
} | |
internal static IDictionary<string, TVal> ToDictionary<TVal>(this object o, params string[] ignoreProperties) | |
{ | |
if (o != null) | |
{ | |
var props = TypeDescriptor.GetProperties(o); | |
var d = new Dictionary<string, TVal>(); | |
foreach (var prop in props.Cast<PropertyDescriptor>().Where(x => !ignoreProperties.Contains(x.Name))) | |
{ | |
var val = prop.GetValue(o); | |
if (val != null) | |
{ | |
d.Add(prop.Name, (TVal)val); | |
} | |
} | |
return d; | |
} | |
return new Dictionary<string, TVal>(); | |
} | |
/// <summary> | |
/// Encrypt the string using the MachineKey in medium trust | |
/// </summary> | |
/// <param name="value">The string value to be encrypted.</param> | |
/// <returns>The encrypted string.</returns> | |
public static string EncryptWithMachineKey(this string value) | |
{ | |
if (value == null) | |
return null; | |
string valueToEncrypt = value; | |
List<string> parts = new List<string>(); | |
const int EncrpytBlockSize = 500; | |
while (valueToEncrypt.Length > EncrpytBlockSize) | |
{ | |
parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize)); | |
valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize); | |
} | |
if (valueToEncrypt.Length > 0) | |
{ | |
parts.Add(valueToEncrypt); | |
} | |
StringBuilder encrpytedValue = new StringBuilder(); | |
foreach (var part in parts) | |
{ | |
var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part)); | |
encrpytedValue.AppendLine(encrpytedBlock); | |
} | |
return encrpytedValue.ToString().TrimEnd(); | |
} | |
public static string ToQueryString(this IDictionary<string, object> d) | |
{ | |
if (!d.Any()) return ""; | |
var builder = new StringBuilder(); | |
foreach (var i in d) | |
{ | |
builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString()))); | |
} | |
return builder.ToString().TrimEnd('&'); | |
} | |
public static bool IsNullOrWhiteSpace(this string str) | |
{ | |
return (str == null) || (str.Trim().Length == 0); | |
} | |
} |
0 comments:
Post a Comment