Language preference:

Phase 2 / ColumnRestrictionAttribute and TableRestrictionAttribute

When your application offers a login feature, you may want to restrict who can see and edit various tables and even their columns. The DESDA.ColumnRestrictionAttribute and DESDA.TableRestrictionAttribute describe how much access a user with a specific security role has. BLD's user interface automatically conforms to these attributes when setup.

BLD provides support for ASP.NET Forms Authentication. If you use a different security model, you can support it by subclassing from PeterBlum.DES.BLD.BaseRestrictionManager.

These attributes define their restrictions with the PeterBlum.DES.DataAnnotations.DenyAccess type. It has these values:

  • None – This does not impose a security restriction. It is often used as a way for methods in this system indicate that there is no security error.
  • View – Cannot view the data in single record or list views. Can still edit, insert, or delete. If you do not want to view, edit, insert or delete, choose DenyAccess.All.
  • Edit – Cannot go into edit mode on an existing record or edit the DataField. You can still insert. If you want to prevent edit and insert, choose DenyAccess.Write.
  • Insert – Cannot go into insert mode (create a new record)
  • Delete – Cannot delete a record. Not supported by DESDA.ColumnRestrictionAttribute.
  • Identify – Cannot see any presence of this table or DataField, even in a list of available tables or through a foreign key.
  • Write – Cannot go into edit or insert modes. Effectively the same as DenyAccess.Edit OR DenyAccess.Insert (a bitset OR)
  • AllActions – Cannot take any of the actions: View, Edit, Insert, or Delete. Can still see the presence of the table or DataField. (The DenyAcess.Identity restriction is not imposed.) Not supported by DESDA.ColumnRestrictionAttribute.
  • All – Cannot take any action with the table or DataField, including viewing it.

Here are DESDA.ColumnRestrictionAttributes and DESDA.TableRestrictionAttributes associated with the Product Entity class.

[DESDA.TableRestriction("Admin", DESDA.DenyAccess.None)]
[DESDA.TableRestriction("Customer", DESDA.DenyAccess.Edit | DESDA.DenyAccess.Delete | DESDA.DenyAccess.Insert)]
public class ProductMetadata
{
   [DESDA.ColumnRestriction("Customer", DESDA.DenyAccess.View)]
   public object UnitsInStock { get; set; }

   [DESDA.ColumnRestriction("Customer", DESDA.DenyAccess.View)]
   public object UnitsOnOrder { get; set; }

   [DESDA.ColumnRestriction("Customer", DESDA.DenyAccess.View)]
   public object ReorderLevel { get; set; }

   [DESDA.ColumnRestriction("Customer", DESDA.DenyAccess.All)]
   public object Order_Details { get; set; }

}

The Source Code Browser shows completed DataAnnotations. The DESDA.ColumnRestrictionAttributes and DESDA.TableRestrictionAttributes have been highlighted.

The attributes are meaningless without the user interface supplying a login system. Run this sample application. It reflects modifications to the Page Templates and Master Page. Explore the code using it's Source Code Browser and explore the files of the BLD Templates folder. You will see that none of their BLD features have changed. Instead, new code handles FormsAuthentication logins and redirects to other pages if the user role does not permit access.


Show the Application with Login Features

Show the code for this application with Login Features

In the next topic, we'll conclude Phase 2 with a few more DataAnnotation attributes.



Open the Source Code Browser (C# only)
Source Code Browser
 
/* ------------------------------------------------
 * Describes the Entity class for: Category
 * Classes:
 *    Category - Entity class. Edit it for validation and to customize metadata at runtime
 *    CategoryMetadata - Entity Metadata class. It contains the DataAnnotations.
 *    CategoryDAO - BLD DataAccessObject format version of a Data Access Object.

 *    
 * Requires .net 4.0 and these references:
 *    System.ComponentModel.DataAnnotations
 *    PeterBlum.DES
 *    PeterBlum.DES.DataAnnotations
 * Generated: 7/8/2011 4:17:08 PM
 * ------------------------------------------------*/
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using PeterBlum.DES.DAO;
using PeterBlum.DES.DAO.Attributes;
using PeterBlum.DES.DAO.EntityDAO;
using PeterBlum.DES.DataAnnotations;

// Some members of this namespace have identical names to those in System.ComponentModel.DataAnnotations
// making it easy to switch from one to the other by adding the "DESDA." prefix
using DESDA = PeterBlum.DES.DataAnnotations;

namespace PeterBlum.WithDataAnnotations
{
   // --- ENTITY CLASS --------------------------------------------------
   /// <summary>
   /// Entity class. Edit it for validation and to customize metadata at runtime
   /// </summary>
   [EntityDAOType(typeof(CategoryDAO))]
   [MetadataType(typeof(CategoryMetadata))]
   public partial class Category
   {
/// <summary>
/// Associated with the CustomValidationAttribute on the Category.CategoryName property,
/// this uses LINQ to SQL to detect an existing Category with the same name
/// and reports it as an error.
/// </summary>
/// <remarks>
/// <para>You probably will create many of these methods. Consider using stored procedures
/// to search the database for duplicates. Write your code below to invoke the stored procedures.</para>
/// </remarks>
/// <param name="pNewName"></param>
/// <param name="pValidationContext"></param>
/// <returns></returns>
      public ValidationResult CheckForDuplicateCategoryNames(
         string pNewName, ValidationContext pValidationContext)
      {
         BaseDAOChangeEntityActionArgs vArgs = (BaseDAOChangeEntityActionArgs)pValidationContext.GetChangeEntityActionArgs();

         ChangeEntityAction vAction = vArgs != null ? vArgs.Action : ChangeEntityAction.Update;
         if ((vAction == ChangeEntityAction.Insert) || (vAction == ChangeEntityAction.Update))
         {
            Category vCategory = (Category)vArgs.Entity;
            int vThisCategoryID = (vAction == ChangeEntityAction.Insert) ? -1 : vCategory.CategoryID;
            NorthWindDataContext vDataContext = new NorthWindDataContext();
            System.Data.Linq.Table<Category> vTable = vDataContext.Categories;
            if (vTable.FirstOrDefault<Category>(category =>
               (String.Compare(category.CategoryName, pNewName, StringComparison.CurrentCultureIgnoreCase) == 0)
               && (category.CategoryID != vThisCategoryID)) != null)
            {
               DESDA.EntityValidationResult vResult = new DESDA.EntityValidationResult(
                  "CheckForDuplicateCategoryNames", // this is the Source parameter. It can be anything. It is used by the BLDPageManager.UpdateErrorMessage event. So make it useful for detecting this particular error
                  "This name already exists. Choose another.",
                  typeof(Category), "CategoryName", pNewName);
               vResult.SummaryErrorMessage = "{LABEL} already exists. Choose another.";
               return vResult;
            }

         }
         return ValidationResult.Success;
      }
   }  // class Category

   // --- ENTITY METADATA --------------------------------------------------
   /// <summary>
   /// Entity Metadata class.
   /// Companion to the Category Entity class that contains the DataAnnotations
   /// on properties with the same names as those in the actual Entity class.
   /// These properties do not require their types to match those in the Entity class.
   /// An Entity Metadata class allows the Entity class to be regenerated without
   /// overwriting DataAnnotations.
   /// </summary>
   [DESDA.InjectionSecurity(DetectScriptInjection=true, DetectSQLInjection=false)]  // impacts CategoryName, but not Description which has its own rules
   [DESDA.TableRestriction("Admin", DESDA.DenyAccess.None)]
   [DESDA.TableRestriction("Customer", DESDA.DenyAccess.Edit | DESDA.DenyAccess.Delete | DESDA.DenyAccess.Insert)]
   public class CategoryMetadata
   {

      [DESDA.Required()]
      [DESDA.CustomValidation(MethodName="CheckForDuplicateCategoryNames")]
      [DESDA.DisplayName("Name")]
      [DESDA.Filter(AutoGeneratePriority=DESDA.AutoGeneratePriority.Always, InMultiFieldSearch=true)]
      public object CategoryName { get; set; }
   
   /// <summary>
   /// In this example, explicitly make this a Multiline text element
   /// so it benefits from the MultilineText_Edit.ascx Field Template.
   /// Like most large textual fields, it should detect illegal hacking case.
   /// When the Peter's Input Security module is setup, the InjectionSecurityAttribute
   /// will block unwanted input.
   /// </summary>
      [DESDA.DataType(DataType.MultilineText)]
      [DESDA.Filter(InMultiFieldSearch=true)]
      [DESDA.InjectionSecurity(DetectScriptInjection=true, DetectSQLInjection=true, 
         SQLDetectionLevel=PeterBlum.DES.Web.SQLDetectionLevel.MediumLow, 
         HTMLTagMode=PeterBlum.DES.Web.HTMLTagMode.IllegalExceptTags,
         HTMLTags="br|img|span|div|a")]
      public object Description { get; set; }

      [DESDA.DbImageDataType(BadFormatErrorMessage="Bad format", ErrorMessage="Must be {EXTENSION}", SupportedFileTypes="jpg")]
      public object Picture { get; set; }

   }  // class CategoryMetadata

   // --- BLD DATAACCESSOBJECT  --------------------------------------------------
   /// <summary>
   /// BLD DataAccessObject class for the Category Entity class.
   /// It provides CRUD actions. The parent class already has default
   /// methods for Update(), Insert(), Delete() and several queries.
   /// </summary>
   /// <remarks>
   /// <para>For documentation, see the BLD DataAccessObject section of the Business Logic User's Guide.</para>
   /// </remarks>
   [TableName("Categories")]
   public class CategoryDAO : LINQtoSQLEntityDAO<Category>
   {
      public CategoryDAO() : base(typeof(NorthWindDataContext)) { }
      public CategoryDAO(object pDataContext) : base(pDataContext) { }

   }  // class CategoryDAO
}