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
  1214
(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
A day in the life of an Optimizely OMVP: Learning Optimizely Just Got Easier: Introducing the Optimizely Learning Centre

On the back of my last post about the Opti Graph Learning Centre, I am now happy to announce a revamped interactive learning platform that makes...

Graham Carr | Jan 31, 2026

Scheduled job for deleting content types and all related content

In my previous blog post which was about getting an overview of your sites content https://world.optimizely.com/blogs/Per-Nergard/Dates/2026/1/sche...

Per Nergård (MVP) | Jan 30, 2026

Working With Applications in Optimizely CMS 13

💡 Note:  The following content has been written based on Optimizely CMS 13 Preview 2 and may not accurately reflect the final release version. As...

Mark Stott | Jan 30, 2026

Experimentation at Speed Using Optimizely Opal and Web Experimentation

If you are working in experimentation, you will know that speed matters. The quicker you can go from idea to implementation, the faster you can...

Minesh Shah (Netcel) | Jan 30, 2026