Tips and Tricks

Labels
AJAX(112) App Studio(9) Apple(1) Application Builder(245) Application Factory(207) ASP.NET(95) ASP.NET 3.5(45) ASP.NET Code Generator(72) ASP.NET Membership(28) Azure(18) Barcode(2) Barcodes(3) BLOB(18) Business Rules(1) Business Rules/Logic(140) BYOD(13) Caching(2) Calendar(5) Charts(29) Cloud(14) Cloud On Time(2) Cloud On Time for Windows 7(2) Code Generator(54) Collaboration(11) command line(1) Conflict Detection(1) Content Management System(12) COT Tools for Excel(26) CRUD(1) Custom Actions(1) Data Aquarium Framework(122) Data Sheet(9) Data Sources(22) Database Lookups(50) Deployment(22) Designer(178) Device(1) DotNetNuke(12) EASE(20) Email(6) Features(101) Firebird(1) Form Builder(14) Globalization and Localization(6) How To(1) Hypermedia(2) Inline Editing(1) Installation(5) JavaScript(20) Kiosk(1) Low Code(3) Mac(1) Many-To-Many(4) Maps(6) Master/Detail(36) Microservices(4) Mobile(63) Mode Builder(3) Model Builder(3) MySQL(10) Native Apps(5) News(18) OAuth(9) OAuth Scopes(1) OAuth2(13) Offline(20) Offline Apps(4) Offline Sync(5) Oracle(11) PKCE(2) Postgre SQL(1) PostgreSQL(2) PWA(2) QR codes(2) Rapid Application Development(5) Reading Pane(2) Release Notes(184) Reports(48) REST(29) RESTful(29) RESTful Workshop(15) RFID tags(1) SaaS(7) Security(81) SharePoint(12) SPA(6) SQL Anywhere(3) SQL Server(26) SSO(1) Stored Procedure(4) Teamwork(15) Tips and Tricks(87) Tools for Excel(3) Touch UI(93) Transactions(5) Tutorials(183) Universal Windows Platform(3) User Interface(338) Video Tutorial(37) Web 2.0(100) Web App Generator(101) Web Application Generator(607) Web Form Builder(40) Web.Config(9) Workflow(28)
Archive
Blog
Tips and Tricks
Tuesday, July 26, 2011PrintSubscribe
Access Control Rules

Web app generator Code On Time introduces Access Control Rules, the first component of EASE (Enterprise Application Services Engine). EASE components simplify implementation of enterprise-class features in line-of-business web applications created with Code On Time and require little or no programming at all.

Introduction

Why do we need access control rules?

Many line-of-business applications start with a simple spreadsheet that allows performing data analysis or calculations to satisfy a specific business requirement. The spreadsheet turns in an application as soon as business users realize that the spreadsheet must accept data input from multiple users. A database is set up and application user interface is built on top of it.

In a few instances the entire database contents may be available to all authorized application end users. In most situations there is a need to create a set of restrictions that would separate slices of data that are available to individual users or users in a given business role.

For example, a web application administrator may be authorized to see the entire list of customers. A sales person will likely be allowed to see only the customers that he or she has a relationship with.

Another example is a line-of-business web application integrated with a content management system such as DotNetNuke or Microsoft SharePoint. The application data must be isolated by a portal or site name. Data created by all  CMS users is stored in the same database tables but under no circumstances shall the users see each other’s records.

Preparing Database for Access Control

A common approach to facilitate access control implementation is to add columns reflecting ownership of data. For example, you can implement User ID column in each table of your database.

If more than one user has a relationship with a data record then developers opt to implement a dedicated table linking a a data record with multiple users.

A typical relational database structure provides certain natural means of establishing access control based on database table relationships. It may be sufficient to implement just one ownership column. For example, consider the following snippet from the Northwind Sample.

Access Control Rules

Table Orders has a reference to an Employee. If a web application user is in the role of Sales then one can make the following assumptions:

  • an employee shall see only his or her orders
  • an employee shall see only customers that have matching orders placed by the employee
  • a subset of order details are naturally visible to an employee when an order is selected
  • if an employee is viewing the global list of order details then only orders of her customers shall be accessible.

The described access control rules can be implemented if we associate each Employees table record with a User ID.

Code On Time allows integrating Microsoft ASP.NET Membership in the generated applications.  User accounts and roles are stored in dedicated tables and user and role management APIs are already built in ASP.NET. Let’s incorporate ASP.NET Membership plumbing in the application database.

Change Employees table to implement two additional columns UserID and UserName. Notice that if you are developing with a database other than Microsoft SQL Server then you may need to choose a different types for UserID and UserName columns. For example, MySQL implementation of ASP.NET Membership uses int as the type of the membership user ID.

Access Control Rules 2

Start Code On Time and begin creating a new Web Site Factory project . On the Database Connection page of project wizard press on a button next to connection string input field.

image

Set the database connection string and press the Add button to create membership tables and stored procedures in the application database.

image

Continue application configuration until you reach the Data Controllers summary page in the project wizard.

Creating a Business Rules Class

Access Control Rules in Code On Time web applications are implemented as methods in  business rules classes. These methods can be shared by all data controllers of your application or designed to address the needs of a specific data controller.

We will consider both situations and will show the real-live examples of custom and shared access control rules.

We will start by creating a business rules class associated with Customers data controller.

Click on Start Designer  button and select Customers data controller on All Controllers tab of Project Designer.

Enter CustomersBusinessRules in Handler field under Business Rules section and click OK button to persist changes.

image

Click Exit button at the top of the screen and click Next to generate the project.

The implementation of business rules will require source code text editing and can be done in Notepad or a development tool such as Visual Studio or Visual Web Developer.

If you do not have a development tool on your computer then click “open” under Actions column of the Code On Time start page. 

image

Select App_Code\Rules\CustomersBusinessRules.cs(vb) file in Windows Explorer and open the file in Notepad.

If you do have a development tool listed above then simply click “develop” link to activate the development tool and open the same file in Solution Explorer.

image

The extension of the file depends on your programming language. Double click the file name to open the file in the editor.

Next we will show examples in both C# and Visual Basic demonstrating various way of implementing access control rules.

Challenges in Implementing Access Control Rules

Modern database software make it exceptionally easy to select data. The application developer writes a data selection statement in declarative language SQL. Such statements typically list the source tables, table columns, and table join instructions. 

Data selection statements are frequently enhanced with filters to present the data that users would like to see.

There is this pesky requirement to segment the data based on user identity or business role. A developer will have to incorporate access control filters in every single data selection statement and foresee all sorts of business requirements that may call for exceptions to some of the filters.

Because of that, developers end up writing their data selection statement as stored procedures persisted in the database. These stored procedures are fundamentally basic selection statements enhanced with numerous checks and conditions to ensure proper access control.

Code On Time applications create SQL selection statements on-the-fly and incorporate user-defined adaptive filters, search criteria, and sort order. Selection statements are enhanced with parameters to prevent any possibility of an injection attack that plagues many applications with hand-written SQL.

It is impossible to write a stored procedure that accepts an unknown number of filtering and sorting parameters to match on-the-fly SQL statements created by your Code On Time web application. Access Control Rules are designed specifically to address the need to apply access control restrictions to dynamic SQL statements.

Next we will show you several examples of Access Control Rules and will implement filtering based on user identity and business role.

Restricting Access by a Single Value

Consider the list of customers presented in the following screenshot. We can see a list of customers from many countries.

List of customers without restrictions

Let’s limit this list of customers to USA only and have this rule apply to all application users.

Enter the following method in the CustomersBusinessRules.cs(vb) and save the file.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class CustomersBusinessRules : MyCompany.Data.BusinessRules
    {
        [AccessControl("Customers", "Country")]
        public void CountryFilterThatAppliesToEverybody()
        {
            RestrictAccess("USA");
        }
    }
}

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq

Namespace MyCompany.Rules

    Partial Public Class CustomersBusinessRules
        Inherits MyCompany.Data.BusinessRules

        <AccessControl("Customers", "Country")> _
        Public Sub CountryFilterThatAppliesToEverybody()
            RestrictAccess("USA")
        End Sub
    End Class
End Namespace

The name of the method plays no role. Use you imagination to ensure that it will be easy to understand the purpose of the method in the future. The method is decorated with AccessControl attribute .

The first parameter of the attribute constructor indicates the data controller that will use this access control method. The second parameter indicates the field that will be filtered by the access control method.

If the data controller is requested to retrieve data then it will scan the list of methods of the associated business rules class and find those that are decorated with the matching AccessControl attribute. By default the data controller will assume that the method is designed to allow access to data. The data controller will invoke the method and if any calls to RestrictAccess are executed then every restriction will be incorporated in the access control filter of SQL data selection statement. In this instance the method will ensure that all users are restricted to see customers from USA regardless of their identity or business role.

List of customers with access restricted to USA only.

Notice that the list of options available in the adaptive filter is considerably shorter. Any user-defined filters will be applied on top of the filters produced by Access Control Rules. Access control filter in the example is activated in all instances of requests to retrieve a list of customers.

Access control methods can utilize any sort of logic to decide if a restriction is applicable at any given moment. For example, a filter can prevent access to the list of customers during non-working hours. The following access control method will allow access to the list of customer from 9 AM though 5:30 PM. If the data is being accessed outside of this time period then a restriction by a non-exiting country is activated.

C#:

[AccessControl("Customers", "Country")]
private void CountryFilterThatAppliesToEverybody()
{
    DateTime today = DateTime.Now;
    DateTime workDayBegins = new DateTime(today.Year, today.Month, today.Day, 9, 00, 00);
    DateTime workDayEnds = new DateTime(today.Year, today.Month, today.Day, 17, 30, 00);
    if (workDayBegins <= today && today <= workDayEnds)
        RestrictAccess("USA");
    else
        RestrictAccess("*****");

}

Visual Basic:

<AccessControl("Customers", "Country")> _
Private Sub CountryFilterThatAppliesToEverybody()
    Dim today As DateTime = DateTime.Now
    Dim workdayBegins As DateTime = New DateTime(today.Year, today.Month, today.Day, 9, 0, 0)
    Dim workdayEnds As DateTime = New DateTime(today.Year, today.Month, today.Day, 17, 30, 0)
    If (workdayBegins <= today And today <= workdayEnds) Then
        RestrictAccess("USA")
    Else
        RestrictAccess("*****")
    End If
End Sub

Multiple AccessControl attributes can be applied to the same method.

If several methods with AccessControl attribute are discovered then the data controller will incorporate SQL data selection statement restrictions by concentrating them with “and” logic.

Restricting Access by Multiple Values

If more than one value must be used to filter out the data then simply call RestrictAccess method multiple times and pass the values that are compatible with the data type of the field specified in AccessControl attribute.

The following method will limit the list of customers to three specific IDs when presenting the list to users with roles other than Administrators.

C#:

[AccessControl("CustomerID", AccessPermission.Allow)]
protected void NonAdministrativeUsersAreAuthorizedToSeeOnlyThreeCustomers()
{
    if (UserIsInRole("Administrators"))
        UnrestrictedAccess();
    else
    {
        RestrictAccess("GREAL");
        RestrictAccess("OLDWO");
        RestrictAccess("THEBI");
    }
}

Visual Basic:

<AccessControl("CustomerID", AccessPermission.Allow)> _
Protected Sub NonAdministrativeUsersAreAuthorizedToSeeOnlyThreeCustomers()
    If UserIsInRole("Administrators") Then
        UnrestrictedAccess()
    Else
        RestrictAccess("GREAL")
        RestrictAccess("OLDWO")
        RestrictAccess("THEBI")
    End If
End Sub
This example is using UnrestrictedAccess method to indicate that the restriction does not apply to Administrators. Access control methods will only result in data filtering if at least one call to RestrictAccess has been made. You can use UnrestrictedAccess method to negate the result of the access restriction previously applied within the same method execution path.

Here is the slightly shorter version of the method restricting access to specific customer accounts for non-administrative users. If a user does not belong to the role Administrators, then no restrictions will be imposed on the the list of customers.

C#:

[AccessControl("CustomerID", AccessPermission.Allow)]
protected void NonAdministrativeUsersAreAuthorizedToSeeOnlyThreeCustomers()
{
    if (!UserIsInRole("Administrators"))
    {
        RestrictAccess("GREAL");
        RestrictAccess("OLDWO");
        RestrictAccess("THEBI");
    }
}

Visual Basic:

<AccessControl("CustomerID", AccessPermission.Allow)> _
Protected Sub NonAdministrativeUsersAreAuthorizedToSeeOnlyThreeCustomers()
    If Not UserIsInRole("Administrators") Then
        RestrictAccess("GREAL")
        RestrictAccess("OLDWO")
        RestrictAccess("THEBI")
    End If
End Sub

The screenshot shows the list of customers presented to a user with role Users.

image

Denying Access

The previous examples of access control rules are specifying AccessPermission.Allow value passed as a parameter of AccessControl attribute. This is default permission of access control rules and can be omitted.

There is always a possibility that a data access exception must be implemented. For example, you may need to prevent administrators from being able to access The Big Cheese customer from USA. The following method does just that.

C#:

[AccessControl("CustomerID", AccessPermission.Deny)]
public void ExceptionForAdministrators()
{
    if (!UserIsInRole("Administrators"))
        UnrestrictedAccess();
    else
        RestrictAccess("THEBI");
}

Visual Basic:

<AccessControl("CustomerID", AccessPermission.Deny)> _
Public Sub ExceptionForAdministrators()
     If (Not UserIsInRole("Administrators")) Then
        UnrestrictedAccess()
    Else
        RestrictAccess("THEBI")
    End If
End Sub

If both “allow” and “deny” access control rules are imposing restrictions at runtime, then the data controller will compose an access control filter such as the one below.

(List of  “Allow” restrictions) and Not (List of “Deny” restrictions)

Restricting Access via Parameterized SQL

Our examples are using static values to compose restrictions. In real world situations you may need to examine a user identity and figure the appropriate restriction value on the fly.

It may be impractical to invoke RestrictAccess method for each restriction due to a very large number of such restrictions.

You can solve both problems by incorporating SQL statements in the definition of AccessControl attribute.

For example, you may want to consider restricting non-administrative accounts to see only those customers that they have a relationship with. The following method makes an assumption that the user with User Name = ‘user’ is a sales person and shall see only customers matching Employee ID = 1.

The total number of records in the Customers table of Northwind database is 91. If you implement the method presented next then the user with the name “user” will only see 65 records. Make sure to comment out or delete any previously defined access control methods described above to prevent the cumulative effect of access control restrictions.

C#:

[AccessControl("CustomerID", Sql = 
"select distinct CustomerID from Orders where EmployeeID = @EmployeeID")] public void LimitUserToSeeOnlyHerCustomers() { if (!UserIsInRole("Administrators")) if (Context.User.Identity.Name == "user") RestrictAccess("@EmployeeID", 1); else UnrestrictedAccess(); }

Visual Basic:

<AccessControl("Customers", "CustomerID", _
    "select distinct CustomerID from Orders where EmployeeID = @EmployeeID", _
    AccessPermission.Allow)> _
Public Sub LimitUserToSeeOnlyHerCustomers()
    If (Not UserIsInRole("Administrators")) Then
        If Context.User.Identity.Name = "user" Then
            RestrictAccess("@EmployeeID", 1)
        Else
            UnrestrictedAccess()
        End If
    End If
End Sub

The data controller will inject SQL statement defined in AccessControl attribute in the data selection statement.

The value of parameter @EmployeeID is assigned by the code in the access control method.

If you call method RestrictAccess with two arguments then the first argument is the name of the parameter in Sql statement of the attribute and the second argument is its value.

The data controller will compose a restriction that looks as follows.

“Customers.CustomerID” in (select distinct CustomerID from Orders where EmployeeID = @EmployeeID)

The expression in AccessControl attribute is inserted as-is. It is up to you to ensure that the expression is valid and will return a correct list of values.

Implementing Restrictions for Northwind Sample

Let’s implement access control rules in the Northwind sample that rely on columns User ID and User Name, implemented previously in this article.

We will make a few changes to the web application design.

Run Code On Time web application generator and select your project.

Click the Next button twice to reach Business Logic Layer page in the project wizard. Select “Generate a shared business rules class …” check box and continue pressing Next until you reach the summary of Data Controllers in your project.

Click Start Designer button to activate the project designer.

Select Employees data controller and activate Fields tab.

Select UserID field, activate Field tab, and change its Items Style to User ID Lookup under Lookup section.

Enter UserName=UserName in the Copy field right under the Items Style. This instruction will ensure that the value of UserName field from Membership Manager will be copied into Employees.UserName field.

image

Scroll down and modify the Security section if you want to ensure that only Administrators are able to assign/create users while editing the Employees record. The following configuration will put no restrictions on who can see the user name and will enable business users with role Administrators to change the user account associated with the employee.

image

Click OK and select this field one more time. Activate Data Fields tab.

Change both instances of the UserID data field to reference UserName field as an alias of UserID. Select each binding (data field) and edit Alias property under General section.

image

Bind UserID field to grid1 view to see the User Name associated with each record in Employees table when viewing the list of employees. Click New|New Data Field and proceed to create a data field (binding) linking field UserID to view grid1. Make sure to leave the field Category blank and choose UserName as the field’s Alias. Press OK.

Your list of data fields associated with field UserID will look as follows.

image

Select Controller:Employees link in the path of the Designer and select UserName field on the Fields tab.

Delete both bindings of UserName field to views editForm1 and createForm1. These bindings are shown prior to deletion in the next screenshot.

image

Exit the Project Designer and generate the project.

Sign-in as admin/admin123% and associate user account user with the employee with the last name of Davolio.

image

Open the CustomersBusinessRules.cs(vb) file in the text editor and delete any previously defined access control rules. Also, replace the base class with MyCompany.Rules.SharedBusinessRules. This will ensure that any global shared access control rules will be inherited in custom business rules associated with Customers data controller.

The new version of CustomersBusinessRules is presented next.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class CustomersBusinessRules :
MyCompany.Rules.SharedBusinessRules { } }

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq

Namespace MyCompany.Rules

    Partial Public Class CustomersBusinessRules
        Inherits MyCompany.Rules.SharedBusinessRules

    End Class

End Namespace

Refresh the project tree in Solution Explorer if you are using Visual Studio or Visual Web Developer.

Open file ~/App_Code/Rules/SharedBusinessRules.cs(vb) in the editor.

This file implements MyCompany.Rules.SharedBusinessRules class. The data controller implementation of your web application will create an instance of this class when preparing to process requests from any data controllers of your project if a dedicated data controller is not available.

We will implement a collection of access control rules to perform consistent data selection restrictions based on user identity.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class SharedBusinessRules : MyCompany.Data.BusinessRules
    {

        public object UserID
        {
            get
            {
                return System.Web.Security.Membership.GetUser().ProviderUserKey;
            }
        }

        [AccessControl("EmployeeID", Sql =
            "select EmployeeID from Employees where UserID = @UserId")]
        [AccessControl("CustomerID", Sql =
            @"select distinct CustomerID from Orders 
            inner join Employees 
            on Orders.EmployeeID = Employees.EmployeeID 
            where Employees.UserID = @UserID")]
        public void RestrictByEmployeeIdAndCustomerId()
        {
            if (!UserIsInRole("Administrators"))
                RestrictAccess("@UserID", UserID);
        }
    }
}

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq

Namespace MyCompany.Rules
    
    Partial Public Class SharedBusinessRules
        Inherits MyCompany.Data.BusinessRules

        Public ReadOnly Property UserID As Object
            Get
                Return System.Web.Security.Membership.GetUser().ProviderUserKey
            End Get
        End Property

        <AccessControl(Nothing, "EmployeeID", _
            "select EmployeeID from Employees where UserID = @UserID")> _
        <AccessControl(Nothing, "CustomerID", _
            "select distinct CustomerID from Orders " & _
            "inner join Employees " & _
            "on Orders.EmployeeID = Employees.EmployeeID " & _
            "where Employees.UserID = @UserID")> _
        Public Sub RestrictByEmployeeIdAndCustomerId()
            If Not UserIsInRole("Administrators") Then
                RestrictAccess("@UserID", UserID)
            End If
        End Sub

    End Class
End Namespace

Paste the source code defined above in the ~/App_Code/Rules/SharedBusinessRules.cs(vb), save it and try it in action by signing in as user/user123%.

You will notice that this simple restriction affects uniformly Customers, Order Details, Employees, Employee Territories, and Customer Demographics.

Any data view that has either CustomerID or EmployeeID field will be automatically restricted if the current is not a member of Administrators role.

You can widen the reach of shared restrictions to Order Details if you simply add EmployeeID field to this data controller and mark it as Hidden.

The command text of Order Details data controller is defined as follows. The command is a simple SQL selection statement joining relevant tables with main table dbo.[Order Details].

select
    "OrderDetails"."OrderID" "OrderID"
    ,"Order"."CustomerID" "OrderCustomerID"
    ,"OrderCustomer"."CompanyName" "OrderCustomerCompanyName"
    ,"OrderEmployee"."LastName" "OrderEmployeeLastName"
    ,"OrderShipVia"."CompanyName" "OrderShipViaCompanyName"
    ,"OrderDetails"."ProductID" "ProductID"
    ,"Product"."ProductName" "ProductProductName"
    ,"ProductCategory"."CategoryName" "ProductCategoryCategoryName"
    ,"ProductSupplier"."CompanyName" "ProductSupplierCompanyName"
    ,"OrderDetails"."UnitPrice" "UnitPrice"
    ,"OrderDetails"."Quantity" "Quantity"
    ,"OrderDetails"."Discount" "Discount"
from "dbo"."Order Details" "OrderDetails"
    left join "dbo"."Orders" "Order" on "OrderDetails"."OrderID" = "Order"."OrderID"
    left join "dbo"."Customers" "OrderCustomer" on "Order"."CustomerID" = "OrderCustomer"."CustomerID"
    left join "dbo"."Employees" "OrderEmployee" on "Order"."EmployeeID" = "OrderEmployee"."EmployeeID"
    left join "dbo"."Shippers" "OrderShipVia" on "Order"."ShipVia" = "OrderShipVia"."ShipperID"
    left join "dbo"."Products" "Product" on "OrderDetails"."ProductID" = "Product"."ProductID"
    left join "dbo"."Categories" "ProductCategory" on "Product"."CategoryID" = "ProductCategory"."CategoryID"
    left join "dbo"."Suppliers" "ProductSupplier" on "Product"."SupplierID" = "ProductSupplier"."SupplierID"

We will create a field that uses Order alias of table dbo.Orders to reference Orders.EmployeeID column in SQL Formula.

Start the Project Designer and select Order Details data controller. Activate Fields tab and and choose New|New Field option on the action bar. Enter the following properties for the new field under New Field section and save its settings by pressing OK button.

Notice that we have wrapped the word Order with double quotes to make sure that it will not be misinterpreted by the database server as SQL keyword “order” .

image

The field will be automatically available in all data views of controller Order Details but remain hidden from end users. The presence of the field will allow it to participate in the access control rules.

Generate the application and observe that shared business rules now extend to Order Details as well. Non-administrative users will only see the order details of orders that they have placed in the system.

Availability

The new mechanism of access control rules greatly simplifies creation of a consistent data segmentation in multi-tenant applications that require isolation of database content created by users.

Access Control Rules are available in the Premium and Unlimited editions of Code On Time web application generator. Owners of Free and Standard editions can use Filter Expressions as discussed later.

What’s Next?

Unlimited edition of Code On Time generator will soon be offering Dynamic Access Control List, another powerful component of EASE (Enterprise Application Services Engine).

Dynamic Access Control List is designed to complement Access Control Rules and allow luxury of defining precise access control at runtime. The real world business processes make it difficult to foresee all possible access control restrictions, and most importantly exceptions to the rules at design time.

Dynamic Access Control List will maintain access control rules in the application database. Administrative user interface will enable dynamic creation of access control rules to respond to the business requirements at runtime without making any changes to the application code.

Filter Expressions

Developers working with Free and Standard editions can use Filter Expressions to implement access control rules in their applications.

Filter expressions are defined on a level of a view and can reference properties of business rules classes as parameters.

For example, if you make changes to dbo.Employees table as described at the top of the article and add UserID and UserName columns then you can filter Orders by EmployeeID. You will have to enable shared business rules and define filter expressions on grid1 and editForm1, followed by implementation of EmployeeID property in the SharedBusinessRules class.

Select your project on the start page of Code On Time web application generator, click Design button, select Orders data controller on All Controllers tab, and activate Views tab.

Change both grid1 and editForm1 views to have the following Filter Expression under Sort and Filter section of the property page.

image

Generate your project and change ~/App_Code/Rules/SharedBusinessRules.cs(vb) file to have the following code.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class SharedBusinessRules : MyCompany.Data.BusinessRules
    {

        public object UserID
        {
            get
            {
                return System.Web.Security.Membership.GetUser().ProviderUserKey;
            }
        }

        public int EmployeeID
        {
            get
            {
                using (SqlText findEmployee = new SqlText(
                    "select EmployeeID from Employees where UserID = @UserId"))
                {
                    findEmployee.AddParameter("@UserId", UserID);
                    object id = findEmployee.ExecuteScalar();
                    if (DBNull.Value.Equals(id))
                        return -1;
                    else
                        return Convert.ToInt32(id);
                }
            }
        }

    }
}

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq

Namespace MyCompany.Rules
    
    Partial Public Class SharedBusinessRules
        Inherits MyCompany.Data.BusinessRules

        Public ReadOnly Property UserID As Object
            Get
                Return System.Web.Security.Membership.GetUser().ProviderUserKey
            End Get
        End Property

        Public ReadOnly Property EmployeeID As Integer
            Get
                Using findEmployee As SqlText = New SqlText( _
                    "select EmployeeID from Employees where UserID = @UserId")
                    findEmployee.AddParameter("@UserId", UserID)
                    Dim id As Object = findEmployee.ExecuteScalar()
                    If DBNull.Value.Equals(id) Then
                        Return -1
                    Else
                        Return Convert.ToInt32(id)
                    End If
                End Using
            End Get
        End Property

    End Class
End Namespace

The value of property EmployeeID will be automatically evaluated and passed as a parameter in SQL statements created to render data for presentation in grid1 and editForm1 views of Orders data controller. Here is how a user associated with employee Davolio may see a list of Orders.

image

Class SharedBusinessRules is created to handle requests for all data controllers. This allows referencing EmployeeID as a parameter in other data controllers including EmployeeTerritories, Employees, and Order Details.

Filter expressions lack the flexibility of conditional restrictions available with Access Control Rules and Dynamic Access Control List. You will need to compose filter expressions that use more than one parameter to accomplish an effect of conditional filtering.

If you need to return more than one value for filtering purposes, then change the type of property to be a list of values or an array. Also refer to the property values as shown in the example below.

EmployeeID in @EmployeeID

Filter expressions can be used with Access Control Rules when needed if a presentation of data in a specific view requires additional filtering.

Tuesday, June 28, 2011PrintSubscribe
Highlighting “Past Due” and “Within” Data Rows

Code On Time generates applications that can be styled with CSS rules to adjust logo, background, and provide conditional formatting of the data views.

The latest enhancements to the client library allow creating complex rules that depend on current date and time.

The screenshot below shows a list of orders from the Nortwhind database sample presented in a Data Sheet view. Conditional styling rules are applied to the view.

The second row from the top has a pink “FiveDays” background since the required date for this order is 7/2/2011 and the date in the screen shot is 6/28/2011, which places the order within five days of the required date.

There are four rows with red background. The “PastDue” background is applied to these rows since the first row has been shipped after the required date and the other three were not shipped.

image

Here is how you can set this up in your own application.

Start the code generator, select your project, click Design button, activate All Controllers tab and locate your controller.

Select the controller and activate Views tab.

Select view grid1 and activate Styles tab.

Enter two styling rules shown in the picture

image

The test formulas shown below are referring to the Orders table from the Northwind sample.

Css Class Test
FiveDays [ShippedDate] == null && Date.$within([RequiredDate], 5)
Past Due Date.$pastDue([ShippedDate], [RequiredDate])

Tests are written in JavaScript. Data controller field names are specified in square brackets. The client library will substitute references to the fields names with calls to a function that will return the field values. The expressions will be evaluated for each data row to see if a custom CSS class must be assigned to the table row.

Client Library extensions to the Date class are being used to test the dates. The extensions are Date.$within and $Date.pastDue.

Exist the designer and generate your application. Open your project in Visual Studio via File | Open Website option if your app is a Web Site Factory project or File | Open Project if your app is a Web App Factory project.

Add new CSS style sheet in your project under the ~/App_Themes folder.

image

Double click your new style sheet and enter the following CSS rule.

.FiveDays td.Cell
{
    background-color: #FFCCCC!important;
    color: Black !important;
}

.PastDue td.Cell
{
    background-color: #CC3300!important;
    color: Black !important;
}

It is very important to use “!important” after the CSS property values to override the default style sheets of your application.

Run your application in a web browser and make sure to hit the refresh button to ensure that the styles are taking effect.

Monday, June 27, 2011PrintSubscribe
Globalization and Localization

Code On Time web application generator creates standard ASP.NET web projects and web sites, which take full advantage of ASP.NET globalization infrastructure. Globalization in ASP.NET has been perfected by Microsoft to allow creation of web applications that work with all cultures and languages. The building blocks are there and our web application generator puts them together to offer a great solution for your globalization and localization needs.

image

Globalization

Code On Time applications offer full support for world cultures, including date and time, calendar, numeric, and currency formats. Your application code may use one culture for the server side business logic and a different culture for presentation.

Globalization settings are configured in the project wizard on the page titled Globalization and Localization. The screen shot below shows this page in a new project.

image

Culture and UI Culture drop down lists are automatically configured to match the locale of your computer. The screen shot above shows both inputs set to en-US, English (United States) .

The selections in these drop downs are the primary culture set of your application where Culture is governing the culture used in the code executed on the server and UI Culture is controlling the user interface culture aspects of the application.

If you are not planning to create applications for other locales then do not make any changes and simply click Next to continue project configuration.

If you are developing an application for a locale that is different then the one selected in Culture and UI Culture then make sure to change the selections accordingly.

Unlimited edition of web application generator allows specifying multiple culture sets.

You can define multiple culture sets by selecting  combinations in Culture / UI Culture drop downs and pressing Add Cultures button.

For example, if you are developing a line-of-business application that is expected to have users primarily in United States then you may anticipate that users from two neighbor countries Canada and Mexico may need to be supported as well. In this age of glo,bal commerce it should not be surprising that the business users of your web application may need to interact with partners from a far away country such as Taiwan.

Given the example above your application must support English, French, Spanish, and Traditional Chinese. The screen shot below shows the configuration of the corresponding supported culture sets.

image

You can also enter the support culture sets directly into the textbox as follows:

en-US, en-US; es-MX, es-MX; en-CA, en-CA; fr-CA, fr-CA; zh-TW, zh-TW;

Multiple culture sets are separated by semicolon or line breaks. Culture is separated from UI Culture by a comma within each culture set definition. If both Culture and UI Culture are the same then enter just one culture. UI Culture may be non-specific and defined by two letters of the language (fr, es, en).

This particular example assumes that users in different locales do not share the server culture.

This is all that is necessary to ensure that your application will correctly present and process date, time, calendars, numbers, and currency values in one or more locales.

Localization

Code On Time web application generator performs automatic localization of all standard application resources in the languages matched to the UI cultures. Our elegant localization system makes supporting multiple localized resources exceptionally simple and accessible.

Localization is one of the most complex aspects of application development. Various text fragments are typically dispersed in static application files such as pages, menus, reports, help system. Text messages are also emitted by business rules to report all sorts of errors and instructions to the end users of a web application.

ASP.NET does provide standard means of externalizing application resources and creating localized versions of each resource file. A developer must maintain all resource files in sync and embed references to specific resources whenever a fragment of text needs to be referenced.  In fact a developer has to translate their entire application in the language of resource IDs that are subsequently translated into actual text written in the natural language. If your application is AJAX-based then you need to employ additional resource DLLs to ensure that localized text resources are available to the client scripts, which makes the localization process even more complex. Resource files in ASP.NET web applications have XML format, which requires users to exercise great care when changing them.

We have decided to eliminate the complexity from this necessity.

Every generated Code On Time application includes several kinds of files commonly found in many hand-coded ASP.NET web applications:

  • Ajax Client Library resources (*.js files)
  • Data Controller Descriptors (*.xml files)
  • Web Pages (*.aspx files)
  • User Controls (*.ascx files)
  • Site Maps (*.sitemap files) B
  • Core Library and Business Logic (*.cs or*.vb files)

We use a refreshingly simple and consistent method of localizing the application source code. If you open the generated application source code in Visual Studio or Windows Explorer then you will find a collection of text files that include the names of UI cultures supported in your web application.

The screen shot shows the partial contents of the root folder of a generated application that supports cultures listed in the discussion of a globalization example above.

image

The screen shot shows ClientLibrary.*.txt, Resources.*.txt, and Web.Sitemap.*.txt groups of text files.

The first group defines the localized Client Library resources for all supported locales.

The second group defines localized resources used in the business rules and core library of the generated application.

The third group defines a collection of localized resources found in the Web.Sitemap, the file that describes the navigation hierarchy of the application.

You will find a few other clusters of localized resources if you browse the contents of the project. Notice that all of these clusters are associated with a specific static source file of your project much like Web.Sitemap and its satellite resources.

Let’s take a look inside.

Here is the first three lines from ClientLibrary.en-US.txt.

^About^About^About^

^ActionBarActionsHeaderText^Actions^ActionBarActionsHeaderText^

^ActionBarCancelActionHeaderText^Cancel^ActionBarCancelActionHeaderText^

Next example shows the first three lines from ClientLibrary.fr-CA.txt.

^About^À propos^About^

^ActionBarActionsHeaderText^Actions^ActionBarActionsHeaderText^

^ActionBarCancelActionHeaderText^Annuler^ActionBarCancelActionHeaderText^

This snippet shows the first three lines from ClientLibrary.zh-TW.txt.

^About^關於^About^

^ActionBarActionsHeaderText^動作^ActionBarActionsHeaderText^

^ActionBarCancelActionHeaderText^取消^ActionBarCancelActionHeaderText^

You have probably noticed the pattern that includes localization brackets on both sides of a localized resource. A localization bracket must start and end with “^” and may contain any combination of alphanumeric characters, such as ^About^, ^Label1^, and ^23^. We call the combination of matching brackets and text between them a localization token.

Here is an example of localization tokens SiteHome, HomePath, and HomeDesc found in Web.Sitemap.

  <siteMapNode url="~/Default.aspx" title="^SiteHome^Home^SiteHome^" description="">
    <siteMapNode title="^HomePath^Home^HomePath^" 
description="^HomeDesc^Application home page^HomeDesc^"
url="~/Pages/Home.aspx" />

Sample code using localization token RecordChangedByAnotherUser is shown next. Method Replace of class Localizer automatically adds “^” to the localization token name and wraps it around the text fragment. This class is the core class of your application. You may find yourself using Localizer.Replace if you need to write a custom business logic in a multi-lingual web application.

if (result.RowsAffected == 0)
{
    result.RowNotFound = true;
    result.Errors.Add(Localizer.Replace("RecordChangedByAnotherUser", 
"The record has been changed by another user.")); }

You can see that the localization token name is present along with the default text fragment in both use cases. This makes it much easier to understand the intended result. Your web application will remove the localization token at runtime and try to find the resource associated with the token in a file matched to the current web request UI culture. If the localized resource is found then it is used in place of the default value. If the exact match of a specific culture such as “fr-CA” is not found then the localizer will try to see if there is a file that matches a non-specific culture “fr”.

All resource text fragments of the core server and client libraries are written in English. Your can replace them if you change the localization files with the corresponding culture. For example, if your culture is en-US then change the files that end with *.en-US.txt to replace the default English fragments. Do not change the corresponding source code files directly.

If you change the value between localization brackets and save the file then you have effectively changed the localized text representation of the corresponding physical resource of your web application. Simply run your application and observe your changes to resource files in action.

If you application is based on a Visual Studio solution file (if you are developing a Web App Factory, Azure Factory, or SharePoint Factory project) then you will need to compile your application.

Files ClientLibrary.*.txt are not a part of your application. The web application generator will use the contents of these files to customize the JavaScript library of your application. If you change any definitions in ClientLibrary.*.txt file set then make sure to re-generate your project for the changes to take effect. Make sure to hit Refresh button of your browser to ensure that the most current version of localized resources is loaded in the web browser window.

Other localization file sets found in your project must be deployed along with the application.

Localization files found in the class libraries of your project (applies to Web App Factory, Azure Factory, SharePoint Factory) will have the culture component in the file name using “_” instead of “-“.

Here is a brief description of all standard localization files sets.

File Set Folder Description
ClientLibrary.*.txt ~/ JavaScript Client Library resources.
Resources.*.txt ~/ Resources used in the core application classes and business rules of application.
Web.Sitemap.*.txt ~/ Text and description of navigation nodes presented in application menu.
aspnet_Membership.xml.*.txt ~/Controllers Membership manager resources.
aspnet_Roles.xml.*.txt ~/Controllers Role manager resources.
TableOfContents.ascx.*.txt ~/Controls Text fragments used in the standard table of contents that presents the site map as a tree.
Welcome.ascx.*.txt ~/Controls Text fragments used in the welcome message.
Home.aspx.*.txt ~/Pages Resources definitions found in the home page.
Membership.aspx.*.txt ~/Pages Resource definitions found in the membership manager page.
Template.xslt.*.txt ~/Reports Resource definitions used in RDLC report template.

Modified localization files are preserved by web application generator. If a new localization token is introduced in the core library and you have an existing application that does not have such a token then the code generator will insert the token in the file with value equal to the default text fragment from the code generation library.

Multiple Cultures / Languages

Your application may support any number of user interface languages with the same code base. The application framework offers easy-to-use localization of static resources and simple API to render localized messages produced by your custom business rules.  We have shown examples of localized static resources and business rules in the previous topic.

This capability is available in the Unlimited edition of Code On Time.

Automatic Translation

Wrap localization brackets around any text. Web application code generator will perform full translation in all languages supported by your application.

For example, the screen shot below shows the list of fields in Customers data controller. You can see that the field labels in the third column from the right have been changed to include localization brackets. Numerical and named tokens such as ^ContName^ and ^3^ are being used for localization.

image

We have also changed various text properties of Customers page in designer.

image

The following versions of Customers screen are presented if we generate our application and select languages corresponding to fr-CA and zh-TW cultures.

image

image

If you inspect the generated application then you will notice the following new culture sets that were created by Code On Time web application generator.

Localization files for ~/Pages/Customers.aspx

image
Localization files for ~/Controllers/Customers.xml

image

Feel free to open any of these text files to refine the localized text fragments. Generated applications monitor localization files and will start using the fresh content when you save the changes.

Language Detection

Code On Time web applications will automatically detect the supported culture. If the client browser culture is supported by your  app then the appropriate localized resources are utilized to render the pages without any user involvement.

Web browsers send language preferences to web servers with each request. Culture manager of the generated web application will automatically match a supported culture set with the languages accepted by the user’s browser. If a match is found then the culture set is automatically selected.

If the matching culture is not found then the application will use the default culture set of your application that was selected on Globalization and Localization page of project wizard.

Language Selector

Membership bar offers a list of languages supported in your application with native names presented to application users. Language selection is automatically memorized and maintained with a sliding expiration.

Language selector complements automatic language detection.

We Need Your Help

Automatic translation of localized resources is performed via Google Translate. The result of translation may not meet your expectations and we apologize for that.

We need your help with creating high quality localized standard resources. If you do make changes to any of the localized files listed below then please contribute your translations to benefit the developer community. You will find additional instructions in ClientLibrary.*.txt files in the root of your applications.

We are looking for assistance with the following files:

  • ClientLibrary.*.txt
  • Resources.*.txt
  • Web.Sitemap.*.txt
  • aspnet_Membership.xml.*.txt
  • aspnet_Roles.xml.*.txt
  • TableOfContents.ascx.*.txt
  • Welcome.ascx.*.txt
  • Home.aspx.*.txt
  • Membership.aspx.*.txt
  • Template.xslt.*.txt

Your contribution will be included in the general distribution of the code generation library. We will post the names of contributors on our blog with a link to contributor’s website if requested.