A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Anton Kallenberg
May 24, 2012
  3426
(0 votes)

Page Constraints

I have built a small plug-in that I call PageConstraints. The plugin adds support for adding constraints to PageTypeBuilder page types. Constraints can be added with .NETs built-in DataAnnotations or by implementing custom code.

For example. Following page type is a regular PTB page type with built-in DataAnnotation attributes and a custom Attribute (UrlResolvable).

   1: [PageType("73312396-6092-475f-9f3a-e5be1adfcf95", Filename = "~/Templates/Pages/Author.aspx")]
   2: public class AuthorPage : TypedPageData
   3: {
   4:     [Required(ErrorMessage = "name cant be empty")]
   5:     [PageTypeProperty(Type = typeof(PropertyString))]
   6:     public virtual string Name { get; set; }
   7:  
   8:     [UrlResolvable(@"^http\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$", ErrorMessage = "could not resolve remote url or remote server did not return status code 200")]
   9:     [PageTypeProperty(Type = typeof(PropertyString))]
  10:     public virtual string WebPage { get; set; }
  11:  
  12:     [Range(18, 130, ErrorMessage = "author must be between 18 and 130 years old")]
  13:     public virtual int? Age { get; set; }
  14: }

 

Custom attributes

Its quite simple to implement a custom DataAnnotation attribute that you can add to your page type. Following Attribute is checking if a entered url is a valid uri, matching a regular expression and if the remote server of the entered url is returning a response with http status 200 by extending build-in RegularExpressionAttribute. You can see how it can be used with WebPage property at AuthorPage.

   1: public class UrlResolvableAttribute : RegularExpressionAttribute
   2: {
   3:     public string Url { get; set; }
   4:  
   5:     public UrlResolvableAttribute(string pattern) : base(pattern) { }
   6:  
   7:     public override bool IsValid(object value)
   8:     {
   9:         if (value == null) return true;
  10:  
  11:         var valid = base.IsValid(value);
  12:         if (!valid)
  13:             return false;
  14:  
  15:         string url;
  16:         if (!GetUrl(value, out url)) return false;
  17:  
  18:         Uri uri;
  19:         if (!GetUri(url, out uri)) return false;
  20:  
  21:         var request = WebRequest.Create(uri.ToString());
  22:         try
  23:         {
  24:             using (var reponse = (HttpWebResponse)request.GetResponse())
  25:             {
  26:                 if (reponse == null)
  27:                 {
  28:                     return false;
  29:                 }
  30:  
  31:                 var statusCode = reponse.StatusCode;
  32:                 return statusCode == HttpStatusCode.OK;
  33:             }
  34:         }
  35:         catch (WebException ex)
  36:         {
  37:             if (ex.Status == WebExceptionStatus.ProtocolError)
  38:             {
  39:                 var response = ex.Response as HttpWebResponse;
  40:                 if (response != null && response.StatusCode != HttpStatusCode.OK)
  41:                 {
  42:                     return false;
  43:                 }
  44:             }
  45:         }
  46:  
  47:         return false;
  48:     }
  49:  
  50:     private bool GetUrl(object value, out string url)
  51:     {
  52:         url = value != null ? value.ToString() : string.Empty;
  53:         if (string.IsNullOrEmpty(url))
  54:         {
  55:             return false;
  56:         }
  57:         return true;
  58:     }
  59:  
  60:     private bool GetUri(string url, out Uri uri)
  61:     {
  62:         uri = null;
  63:         try
  64:         {
  65:             uri = new Uri(url);
  66:         }
  67:         catch (Exception)
  68:         {
  69:             return false;
  70:         }
  71:         return true;
  72:     }
  73: }

Custom constraint

If you need to add custom code that is not supported by DataAnnotations for the constraint with a context of the current page, implement the method IsViolated from interface IConstraintPage.

   1: [PageType("cfe42b48-496c-475e-86db-c2030175bc83", Filename = "~/Templates/Pages/Book.aspx")]
   2: public class BookPage : TypedPageData, IConstraintPage
   3: {
   4:     [Required(ErrorMessage = "book must have a name")]
   5:     [StringLength(30, ErrorMessage = "book cant have a name longer that 10 chars")]
   6:     [PageTypeProperty(Type = typeof(PropertyString))]
   7:     public virtual string Name { get; set; }
   8:  
   9:     [Required(ErrorMessage = "isbn cant be empty")]
  10:     [RegularExpression(@"^(?=[-0-9xX ]{13}$)(?:[0-9]+[- ]){3}[0-9]*[xX0-9]$", ErrorMessage = "isbn is not valid")]
  11:     [PageTypeProperty(Type = typeof(PropertyString))]
  12:     public virtual string Isbn { get; set; }
  13:  
  14:     [Required(ErrorMessage = "book must have authors")]
  15:     [PageTypeProperty(Type = typeof(PropertyLinkCollection))]
  16:     public virtual LinkItemCollection Authors { get; set; }
  17:  
  18:     public bool IsViolated(out string reason)
  19:     {
  20:         reason = string.Empty;
  21:         if (!string.IsNullOrEmpty(Name) && Name.Contains("twilight"))
  22:         {
  23:             reason = "this bookstore does not allow the twilight books";
  24:             return true;
  25:         }
  26:         return false;
  27:     }
  28: }

Error messages are displayed in the standard yellow popup when trying to save or publish the page in edit mode and a EPiServerCancelException is thrown.

Source, tests, examples and compiled binary is available at github. Please let me know if you think this is a needed plugin and if you have ideas for improvements.

Source is compiled with EPiServer CMS R2 (6.1.379.0) and PageTypeBuilder 2.0.

May 24, 2012

Comments

Please login to comment.
Latest blogs
Troubleshooting with Azure Application Insights Using KQL

Users at least get access to Azure Application Insights even within minimum access level if you are requesting access to DXP management portals at...

K Khan | Dec 21, 2025

Looking back at Optimizely in 2025

Explore Optimizely's architectural shift in 2025, which removed coordination cost through a unified execution loop. Learn how agentic Opal AI and...

Andy Blyth | Dec 17, 2025 |

Cleaning Up Content Graph Webhooks in PaaS CMS: Scheduled Job

The Problem Bit of a niche issue, but we are building a headless solution where the presentation layer is hosted on Netlify, when in a regular...

Minesh Shah (Netcel) | Dec 17, 2025

A day in the life of an Optimizely OMVP - OptiGraphExtensions v2.0: Enhanced Search Control with Language Support and Synonym Slots

Supercharge your Optimizely Graph search experience with powerful new features for multilingual sites and fine-grained search tuning. As search...

Graham Carr | Dec 16, 2025