Application Factory

Labels
AJAX(112) App Studio(7) 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(177) 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(183) 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(2) 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
Application Factory
Sunday, March 18, 2012PrintSubscribe
Export to Spreadsheet

Code On Time web applications offer the Export to Spreadsheet action. The feature is available in all grid views of your application by default.

'Export to Spreadsheet' action in Code On Time web applications

Select this option and you will be prompted to download a file. The following screen shot shows the prompt displayed by Internet Explorer 9 at the bottom of the web browser window.

Prompt displayed in IE9 in response to 'Export to Spreadsheet' action in Code On Time web application

The file has the “*.iqy” extension. This file format is recognized and supported by Microsoft Excel.

If you save the file to the hard drive and open it in the Notepad then you will see a cryptic-looking URL that points to your web application.

If you choose Open option instead then Microsoft Excel will start if you have it installed on your computer. You will see a prompt similar to the one in the next picture.

The prompt to download '*.iqy' file displayed by Microsoft Excel

Microsoft Excel wants to let you know that it is going to download the content, which is not located on your computer. You have to authorize that by clicking on Enable button. Nothing dangerous is coming your way. The content is an XML file produced by your application in response to the URL in the “*.iqy” file.

XML content produced in response to the URL specified in '*.iqy' file

Click Enable and you will see the prompt shown next.

This time Microsoft Excel contacts your web application. Your application needs to know the identity of the user and requests this information from Excel. That is why you are seeing the prompt.

The prompt to enter the application user id/password displayed in response to 'Export to Spreadsheet' action in Code On Time web application

Enter the identity of the user registered in the membership database. For example,enter admin/admin123% or user/user123% – these are two default users accounts created automatically in the baseline application.

Click OK button and you will see the data from your web application display in Microsoft Excel.

Application data exported to Microsoft Excel

Select Insert tab on the ribbon and choose to insert the PivotTable.

'Insert PivotTable' option in Microsoft Excel

Dialog Create PivotTable is displayed.

'Create PivotTable' dialog in Microsoft Excel

Click OK to insert a new sheet with the pivot table.

Empty pivot table created in Microsoft Excel

The pivot table PivotTable1 is empty. Notice the list of fields on the right-hand side of the window. All visible fields from the Orders screen are listed there.

Drag Customer Company Name field to the “Values” box and you will see the total number of orders in the database.

Total number of orders in the application database

Drag Employee Last Name to the “Row Labels” box and you will see that the most product employee is Ms. Peacock. She has placed 156 orders.

The most productive employee in the pivot table

Drag Ship Via Company Name field to “Column Labels” box and you will see the breakdown of orders by employees and shippers delivering the goods to customers.

Breakdown of orders by employees and shipping companies

Numbers don’t always tell the story.

Choose PivotChart item on the right-hand side of the Options ribbon. Select 3-D Cylinder chart and click OK button.

'Insert Chart' dialog in Microsoft Excel

We can now clearly see that Ms. Peacock is heavily utilizing the shipping company United Package. If this is the most expensive shipping option then we should call Ms. Peacock and advise to switch to another freight operator. If this this is the least expensive option then a bonus is due in recognition of an employee thinking about the business bottom line.

Ms. Peacock is heavily utilizing the shipping company 'United Package'

You can save this spreadsheet to the hard drive.  Open the spreadsheet a week a later and choose Data | Refresh All and you will be prompted to re-enter user name and password – the new data feed will be produced by your application and the charts and cross tables will automatically refresh.

'Data | Refresh All' option in Excel will contact your web application and retreive a fresh data set, which will result in updated charts and cross tables.

You web application created with Code On Time becomes an information hub of your company. Business users can do ad hoc data analysis with the tool they know best. The business decision makers don’t even need to sign in the web application to know what’s going on. Microsoft Excel charts and pivot table swill let them know everything they need.

Friday, March 9, 2012PrintSubscribe
Using ConnectionStringSettingsFactory in “Database per Tenant” Apps

Multi-tenant web applications can be constructed using one of the two methods:

  • Tenants co-exist in the same database. Data is segregated based on the user identity.
  • Each tenant has a private database.

Code On Time web applications offer excellent support for multi-tenant data segregation.

Let’s discuss how to implement a multi-tenant application with private databases.

Internet users arrive to your web application protected with ASP.NET Membership. If the user is not authenticated then there is no way to know, which private database is the final destination for this user. Therefore you either need to implement a single database of user accounts (for example an ASP.NET Membership database) or rely on the elements of the user name (for example the domain portion of the email address) to authenticate the user.

The second solution is more complex when it comes to implementing the user authentication. We will consider the first scenario and assume that the main database of the application has ASP.NET Membership installed already. Follow instructions at http://codeontime.com/learn/sample-applications/northwind to create an application with the membership feature.

The main application database will have the application tables and views. We will call it a “master” database. You will develop your application using the master database. Create additional databases with the same schema but do not include the membership infrastructure in them.

Private Database Derived From DNS Records

Deploy your application. Create CNAME records in the DNS configuration of your Internet domain for each client that must have a private database. Point the CNAME record of each client to the name of your web application. Instruct your clients to sign in the application using their dedicated domain name defined in the CNAME record.

For example, if your main application name is myapp.contoso.com then a client with a dedicate database can access your application as clientname.contoso.com. Also make sure that the name of the master database is myapp.constoso.com and the private database names for the clients use the format clientname.contoso.com.

Unauthenticated users will be authenticated against the master database membership. If you do nothing else then the authenticated users will see the contents of the master database while navigating through the application pages.

Implement the following class in your application to redirect authenticated users to the private database.

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration;
using System.Data.SqlClient;
namespace MyCompany.Data
{
    public partial class ConnectionStringSettingsFactory
    {
        protected override ConnectionStringSettings CreateSettings(string connectionStringName)
        {
            ConnectionStringSettings settings = base.CreateSettings(connectionStringName);
            // if the user is not authenticated then use the default database
            if (HttpContext.Current.User.Identity.IsAuthenticated &&
                HttpContext.Current.Request.Url.Host.Contains("contoso.com"))
            {
                SqlConnectionStringBuilder csb = 
                    new SqlConnectionStringBuilder(settings.ConnectionString);
                csb.InitialCatalog = HttpContext.Current.Request.Url.Host;
                settings = new ConnectionStringSettings(null, csb.ToString(), 
settings.ProviderName); } return settings; } } }

Visual Basic:

Imports Microsoft.VisualBasic
Imports MyCompany.Data
Imports System.Data.SqlClient

Namespace MyCompany.Data
    Partial Public Class ConnectionStringSettingsFactory
        Protected Overrides Function CreateSettings(
            connectionStringName As String) As System.Configuration.ConnectionStringSettings
            Dim settings As ConnectionStringSettings = 
MyBase.CreateSettings(connectionStringName) If (HttpContext.Current.User.Identity.IsAuthenticated AndAlso HttpContext.Current.Request.Url.Host.Contains(".contoso.com")) Then Dim csb As SqlConnectionStringBuilder = New SqlConnectionStringBuilder(settings.ConnectionString) csb.InitialCatalog = HttpContext.Current.Request.Url.Host settings = New ConnectionStringSettings(Nothing, csb.ToString(),
settings.ProviderName) End If Return settings End Function End Class End Namespace

This code inspects the name of the host specified in the URL of the current request. If the authenticated user is trying to access the production deployment and “*.contoso.com” is detected in the host name then the code will create a database-specific connection string and change the database name to the name of the host.

Our example uses Microsoft SQL Server database. That is why we are creating SqlConnectionStringBuilder class instance. Use the class that matches your back-end database. The name of the SQL Server database is specified in InitialiCatalog property. Implementations of connection string builder for other database engines may use a different property for the same purpose.

If your clients do have a web presence then you can instruct them to create their private CNAME Records that point to your application. Make sure to use the client’s CNAME Record as the name of the private database instance. Your clients will be able to access your web application as myapp.clientdomain.com where “clientdomain” is the client’s own domain name.

Private Database Derived From User identity

You can also implement a table in the master database that will associate the user accounts with the private databases available in the app. This table will effectively play the role of the DNS for your own application. Users will access the application via the same URL.

create table UserDatabases
(
    UserName varchar(50),
    DatabaseName varchar(50),
    primary key (UserName, DatabaseName)
)

The following code will lookup the UserDatabases table for authenticated users and adjust the connection string accordingly.

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration;
using System.Data.SqlClient;
namespace MyCompany.Data
{
    public partial class ConnectionStringSettingsFactory
    {
        protected override ConnectionStringSettings CreateSettings(string connectionStringName)
        {
            ConnectionStringSettings settings = base.CreateSettings(connectionStringName);
            // if the user is not authenticated then use the default database
            if (HttpContext.Current.User.Identity.IsAuthenticated)
            {
                SqlConnectionStringBuilder csb =
                    new SqlConnectionStringBuilder(settings.ConnectionString);
                using (SqlConnection connection = new SqlConnection(settings.ConnectionString))
                {
                    connection.Open();
                    SqlCommand command = connection.CreateCommand();
                    command.CommandText =
                        "select DatabaseName from UserDatabases where UserName=@UserName";
                    SqlParameter p = command.CreateParameter();
                    command.Parameters.Add(p);
                    p.ParameterName = "@UserName";
                    p.Value = HttpContext.Current.User.Identity.Name;
                    csb.InitialCatalog = Convert.ToString(command.ExecuteScalar());
                }
                csb.InitialCatalog = HttpContext.Current.Request.Url.Host;
                settings = new ConnectionStringSettings(null, csb.ToString(), 
settings.ProviderName); } return settings; } } }

Visual Basic:

Imports Microsoft.VisualBasic
Imports MyCompany.Data
Imports System.Data.SqlClient

Namespace MyCompany.Data
    Partial Public Class ConnectionStringSettingsFactory
        Protected Overrides Function CreateSettings(
            connectionStringName As String) As System.Configuration.ConnectionStringSettings
            Dim settings As ConnectionStringSettings =
                MyBase.CreateSettings(connectionStringName)
            If (HttpContext.Current.User.Identity.IsAuthenticated) Then
                Dim csb As SqlConnectionStringBuilder =
                    New SqlConnectionStringBuilder(settings.ConnectionString)
                Using connection As SqlConnection =
                        New SqlConnection(settings.ConnectionString)
                    connection.Open()
                    Dim command As SqlCommand = connection.CreateCommand()
                    command.CommandText =
                        "select DatabaseName from UserDatabases where UserName=@UserName"
                    Dim p As SqlParameter = command.CreateParameter()
                    p.ParameterName = "@UserName"
                    p.Value = HttpContext.Current.User.Identity.Name
                    csb.InitialCatalog = Convert.ToString(command.ExecuteNonQuery())
                End Using
                csb.InitialCatalog = HttpContext.Current.Request.Url.Host
                settings = New ConnectionStringSettings(Nothing, csb.ToString(),
                    settings.ProviderName)
            End If
            Return settings
        End Function
    End Class
End Namespace

Notice that we are using the native ADO.NET classes that are specific to SQL Server: SqlConnection and SqlCommand. Code On Time web applications can take advantage of SqlText class that wraps the ADO.NET components in a compact database-independent implementation. The class is the component of the framework of the generated web application and takes advantage of ConnectionStringSettingsFactory. We have to use the native ADO.NET classes to prevent the re-entrance in the ConnectionStringSettingsFactory. Adjust the sample with the native ADO.NET classes that match your database backend if you are not programming with Microsoft SQL Server.

Saturday, March 3, 2012PrintSubscribe
Read-Only Database Applications

Data controllers of Code On Time web applications are equipped with a flexible set of data manipulation commands. Users can insert, duplicate, update, and delete data served by any data controller. The exceptionally simple and powerful action state machine makes very complex action sequences possible.

No doubt that the application administrator will be quite happy! What if you have a requirement to allow read-only access to data for a certain group of users or even a certain user account?

Let’s inventory the scope of work that needs to be performed.

Consider the following application built on top of the Northwind sample. Zero effort is required to enable full data editing in the Products list.

'Products'  list with all data manipulation capabilities enabled requires zero configuration

The context menu of data rows in the grid view offers Edit, Delete, Duplicate, and New commands.

Context menu in a grid view

The action bar offers New Products, Edit, Delete, and Import options.

Action bar offers New, Edit, Delete, and Import options.

If a user clicks on the link in the first column of any product row or chooses Select option from the context menu then the form view will be activated. Users are able to start editing or delete the row. Action bar also offers New Products and Import actions.

Form view allows  to start editing, delete, create new, or import a batch of records.

Code On Time web applications allow specifying a list of roles that are authorized to execute an action. The partial action state machine of the data controller Products is presented below.

<actions>
  <actionGroup id="ag1" scope="Grid">
    <action id="a1" commandName="Select" commandArgument="editForm1" />
    <action id="a2" commandName="Edit" />
    <action id="a3" commandName="Delete" />
    <action id="a6" />
    <action id="a7" commandName="Duplicate" commandArgument="createForm1" />
    <action id="a8" commandName="New" commandArgument="grid1" />
  </actionGroup>
  <actionGroup id="ag2" scope="Form">
    <action id="a1" commandName="Edit" />
    <action id="a2" commandName="Delete" />
    . . . . .
  </actionGroup>
  <actionGroup id="ag3" scope="ActionBar" headerText="New" flat="true">
    <action id="a1" commandName="New" commandArgument="createForm1" 
            cssClass="NewIcon" />
  </actionGroup>
  <actionGroup id="ag4" scope="ActionBar" headerText="Edit/Delete" flat="true">
    <action id="a1" whenKeySelected="true" commandName="Edit" 
            commandArgument="editForm1" cssClass="EditIcon" whenView="grid1" />
    <action id="a2" whenKeySelected="true" commandName="Delete" 
            cssClass="DeleteIcon" whenView="grid1" />
  </actionGroup>
  <actionGroup id="ag5" scope="ActionBar" headerText="Actions">
    . . . . .
    <action id="a6" commandName="Import" commandArgument="createForm1" />
    . . . . .
  </actionGroup>
  . . . . .
</actions>

You can modify the data controller baseline and specify the “roles” attribute for each of the ten actions above.

<action id="a2" commandName="Edit" roles="Administrators" />

You can also use the  Project Designer and edit the the Roles property for each action there.

Action 'Roles' property in Project Designer

That may not be  a huge challenge in a small project but if you do need to configure a few dozen data controllers then you are probably thinking that there must be a better way.

It is possible to customize each data controller at runtime with the help of data controller virtualization.

Enable shared business rules in the Business Logic Layer settings of your web application and implement the SharedBusinessRules class as shown below.

C#:

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

namespace MyCompany.Rules
{
    public partial class SharedBusinessRules : MyCompany.Data.BusinessRules
    {
        public override bool SupportsVirtualization(string controllerName)
        {
            return !UserIsInRole("Administrators");
        }

        public override void VirtualizeController(string controllerName, 
            XPathNavigator navigator, XmlNamespaceManager resolver)
        {
            string[] restrictedCommands = { "Edit", "New", "Delete", "Duplicate", "Import" };
            XPathNodeIterator actionIterator = navigator.Select("//c:action", resolver);
            while (actionIterator.MoveNext())
            {
                string commandName = actionIterator.Current.GetAttribute(
                    "commandName", String.Empty);
                if (restrictedCommands.Contains(commandName))
                    actionIterator.Current.CreateAttribute(
                        String.Empty, "roles", String.Empty, "Administrators");
            }
        }
    }
}

Visual Basic:

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

Namespace MyCompany.Rules

    Partial Public Class SharedBusinessRules
        Inherits MyCompany.Data.BusinessRules

        Public Overrides Function SupportsVirtualization(controllerName As String) As Boolean
            Return Not UserIsInRole("Administrators")
        End Function

        Public Overrides Sub VirtualizeController(controllerName As String,
            navigator As XPathNavigator, resolver As XmlNamespaceManager)
            Dim restrictedCommands() As String =
                {"Edit", "New", "Delete", "Duplicate", "Import"}
            Dim actionIterator As XPathNodeIterator = navigator.Select("//c:action", resolver)
            While (actionIterator.MoveNext())
                Dim commandName As String = actionIterator.Current.GetAttribute(
                    "commandName", String.Empty)
                If (restrictedCommands.Contains(commandName)) Then
                    actionIterator.Current.CreateAttribute(
                        String.Empty, "roles", String.Empty, "Administrators")
                End If
            End While

        End Sub
    End Class
End Namespace

The method SupportsVirtualization returns True if the user performing the current web request is not an administrator.

Method VirtualizeController scans the list of actions and assigns “roles” attribute to actions that initiate commands Edit, New, Delete, Duplicate, and Import. The actual data controller file is not changed. The changes are discarded as soon as the current request is processed.

Notice that we “hide” the actions from the user account by assigning Administrators value to the “roles” attribute. You can assign any value instead of Administrators as long as the value does not represent the role assigned to the non-administrative user account. You can also delete the action from the data controller. The outcome will be exactly the same.

Run the application and login with the user account user/user123%. Notice that the ability to edit data is not available anymore.

Data controller virtualization allows implementing a single method that assignes a 'roles' attribute to each action that must have a restricted access.

Continue to Search Mode & Options