Anton Kallenberg
May 24, 2012
  2954
(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
Opti ID overview

Opti ID allows you to log in once and switch between Optimizely products using Okta, Entra ID, or a local account. You can also manage all your use...

K Khan | Jul 26, 2024

Getting Started with Optimizely SaaS using Next.js Starter App - Extend a component - Part 3

This is the final part of our Optimizely SaaS CMS proof-of-concept (POC) blog series. In this post, we'll dive into extending a component within th...

Raghavendra Murthy | Jul 23, 2024 | Syndicated blog

Optimizely Graph – Faceting with Geta Categories

Overview As Optimizely Graph (and Content Cloud SaaS) makes its global debut, it is known that there are going to be some bugs and quirks. One of t...

Eric Markson | Jul 22, 2024 | Syndicated blog

Integration Bynder (DAM) with Optimizely

Bynder is a comprehensive digital asset management (DAM) platform that enables businesses to efficiently manage, store, organize, and share their...

Sanjay Kumar | Jul 22, 2024