Anton Kallenberg
May 24, 2012
  3794
(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
Accelerating Optimizely CMS and Commerce upgrades with agentic AI (Part 2 of 2)

The Real Transformation in Optimizely CMS 13: Why the Upgrade Itself Is the Easy Part. A field-tested playbook for enterprise teams moving from...

Hung Le Hoang | May 18, 2026

Is the most powerful AI model really the best value?

Artificial Intelligence is already becoming part of everyday software development. Developers now use AI tools to generate code, write documentatio...

K Khan | May 16, 2026

Optimizely London Dev Meetup 2026

Well, everyone, it's that time of the year again, and we have another London Developer meet up coming for this summer. The date is set for the 2nd ...

Scott Reed | May 15, 2026

Building a Custom RAG for Optimizely Opal

How to design a standalone RAG service for documents that don't belong in Optimizely One, and expose it to Opal and other AI tools without coupling...

Michał Mitas | May 14, 2026 |

Building a Custom RAG for Optimizely Opal

Opal's built-in knowledge is limited to content inside Optimizely One. Here's how to design a custom RAG service for documents that live outside th...

Michał Mitas | May 14, 2026 |

Semantic Search - Deep Dive

Deep dive into semantic search with Optimizely Graph

Michał Mitas | May 14, 2026 |