Lightswitch for the Enterprise

Make things as simple as possible but no simpler.

  • Home
RSS

Dynamically hiding columns based on modelview info rather than column header names.

Posted on 2012/05/04 by paul van bladel
4 Comments

Introduction

It can happen that certain columns in a grid need to be hidden based on particular process logic.

You can find solutions on the LightSwitch forum where the column header name is used for retrieving the column to hide. So, that means using “magic string”.

I’m proposing here an approach that:

  • firstly uses the “backing viewmodel” rather than column names. This means that the column to be hidden is identified by the name how it is known in the domain model. So, if I want to hide the City column, I specify “City” although the column header maybe something completely different.
  • secondly, avoids the usage of magic strings so that we can leverage compile time checking.

Note that hiding columns should never be done solely due to security reasons. Don’t forget that even if you hide a column, the data is still going over the line.

 

Proposed solution

Let’s stick to an easy example. We have an editable grid screen showing customers having 2 fields: LastName and City.

Our sophisticated process logic dictates us that we should hide the City column.

 

 

 

 

 

 

 

 

 

 

 

Let’s first show the approach how we hide the City column based on the City field as known in the domain model. We still hardcode  the value in the variable propertyToHide.

public partial class EditableCustomersGrid
    {
        partial void EditableCustomersGrid_InitializeDataWorkspace(List<IDataService> saveChangesTo)
        {
            string propertyToHide = "City";

            this.FindControl("grid").ControlAvailable += new EventHandler<ControlAvailableEventArgs>((s1, e1) =>
              {
                  IContentItem contentItem = (e1.Control as Control).DataContext as IContentItem;

                  foreach (var item in contentItem.ChildItems.First().ChildItems)
                  {
                      if (item.BindingPath == propertyToHide)
                      {
                          item.IsVisible = false;
                      }
                  }
              });
        }
    }

Pretty easy, no?

So, try to rename the control name into something else and you’ll see it still works :)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

In a next step we want to get rid of the “magic string” City:

public partial class EditableCustomersGrid
    {
        partial void EditableCustomersGrid_InitializeDataWorkspace(List<IDataService> saveChangesTo)
        {
            string propertyToHide = Application.CreateDataWorkspace().ApplicationData.Customers.AddNew().Details.Properties.City.Name;
            //the above line could be simpler if we were able to presume that there is at least one record available in the customers screen collection
            //in that case we could simply retrieve the fieldName as follows:
            //string propertyToHide = this.Customers.First().Details.Properties.FirstName.Name

            this.FindControl("grid").ControlAvailable += new EventHandler<ControlAvailableEventArgs>((s1, e1) =>
              {
                  IContentItem contentItem = (e1.Control as Control).DataContext as IContentItem;

                  foreach (var item in contentItem.ChildItems.First().ChildItems)
                  {
                      if (item.BindingPath == propertyToHide)
                      {
                          item.IsVisible = false;
                      }
                  }
              });
        }
    }

So, you might wonder what is the added value of this ?

 string propertyToHide = Application.CreateDataWorkspace().ApplicationData.Customers.AddNew().Details.Properties.City.Name;

We can illustrate this by renaming in our domain model the City column into “Town”.

 

 

 

 

 

 

 

 

 

Compile now and you will get following compilation error message:

 

 

I’m still in search for a better way, the the one below, to get the property name. In this version I need to do a CreateDataWorkspace, which is not optimal:

string propertyToHide = Application.CreateDataWorkspace().ApplicationData.Customers.AddNew().Details.Properties.City.Name;

 

Conclusion

Well, sometimes you should be happy with an error message. At least we know now that we should update some code to get the column hiding working again. When using magic strings we only will get the error message when visiting the screen, or worser, when the application has been deployed.

So, with a minimal effort we can bring in some additional robustness in LightSwitch.

 

 

 

Categories: Uncategorized

A Generic Audit trail solution – revisited.

Posted on 2012/04/13 by paul van bladel
1 Comment

Introduction

My post on audit trails  has gotten some amount of attention. So, it seems that auditing is perceived as something important in LightSwitch.

A drawback of my initial approach is that it could only work with an intrinsic database and entities with a single-Id KeySegment (i.e. the entity is identified solely by an integer Id field).

In the mean time, I was able, based on some initial input of Matt Evans, to improve the existing solution.

What are our requirements/goals?

  • We want that all changes (every insert, update and delete) to data in our application is monitored (audited) on field level.
  • we want that  extending the application with additional tables requires a minimum effort. Maintenance should be simple.
  • Therefor, we want that all auditing information is collected in one audit table with a corresponding Search audit screen.
  • Having audit information at your disposal is fine, but it should be consultable in an easy way. Therefor, we want to be able to navigate from the search audit screen to the corresponding entity.
  • We want to be able to make it possible that we can navigate from an entity to the corresponding audit history, again, with a minimum coding effort when we extend the application with an additional table.

This boils down following UI.

The Audit Search Screen

Contains all audit information. The user can jump to the audit detail or to the corresponding entity. If the existing entity doesn’t exist any longer the user receives a message box.

 

The Audit Detail Screen

Navigating to the audit history for an enity “under audit”

 

How to hook up auditing?

Let’s see how we hook up auditing and let’s make a distinction between client and server side. We’ll start server side.

Server Side

I have in my example 2 distinct data sources. The intrinsic database (containing a customer table) and an external data source (a sql server database) with one table called “ExternalTable”.

The ApplicationDataService goes as follows:

public partial class ApplicationDataService
    {
        partial void SaveChanges_Executing()
        {
            EntityChangeSet changes = this.DataWorkspace.ApplicationData.Details.GetChanges();

            foreach (var modifiedItem in changes.ModifiedEntities)
            {
                AuditHelper.CreateAuditTrailForUpdate(modifiedItem, this.AuditTrails);
            }

            foreach (var deletedItem in changes.DeletedEntities)
            {
                AuditHelper.CreateAuditTrailForDelete(deletedItem, this.AuditTrails);
            }
        }

        //the _Inserted method needs to be repeated for all entities where auditing is desired
        partial void Customers_Inserted(Customer entity)
        {
            AuditHelper.CreateAuditTrailForInsert(entity, this.AuditTrails);
        }
    }

 

 

As you can see the Delete and Modified audits are handled during the SaveChanges_Executing method. We still need for every insert a dedicated call. The reason why we can’t do the insert audit during the SaveChanges_Executing will be come clear later.

The DataService of my external datasource goes as follows:

 public partial class SecurityDbDataService
    {
        partial void SaveChanges_Executing()
        {
            EntityChangeSet changes = this.DataWorkspace.SecurityDbData.Details.GetChanges();

            foreach (var modifiedItem in changes.ModifiedEntities)
            {
                AuditHelper.CreateAuditTrailForUpdate(modifiedItem, this.DataWorkspace.ApplicationData.AuditTrails);
            }

            foreach (var deletedItem in changes.DeletedEntities)
            {
                AuditHelper.CreateAuditTrailForDelete(deletedItem, this.DataWorkspace.ApplicationData.AuditTrails);
            }

            this.DataWorkspace.ApplicationData.SaveChanges();
        }

        partial void ExternalTables_Inserted(ExternalTable entity)
        {
            AuditHelper.CreateAuditTrailForInsert(entity, this.DataWorkspace.ApplicationData.AuditTrails);
        }
    }

This looks of course very similar, except that we do an explicit SaveChanges() on the ApplicationDataService. This is because my audit table is in the Intrinsic database. So, this my differ if you want to store the auditing in a completely different database.

So, the “low-level” handling is present in the AuditHelper class:

public static class AuditHelper
    {
        public static void CreateAuditTrailForUpdate<E>(E entity, EntitySet<AuditTrail> auditTrailEntitySet) where E : IEntityObject
        {
            StringBuilder newValues = new StringBuilder();
            StringBuilder oldValues = new StringBuilder();

            foreach (var prop in entity.Details.Properties.All().OfType<IEntityStorageProperty>())
            {
                if (!(Object.Equals(prop.Value, prop.OriginalValue)))
                {
                    oldValues.AppendLine(string.Format("{0}: {1}", prop.Name, prop.OriginalValue));
                    newValues.AppendLine(string.Format("{0}: {1}", prop.Name, prop.Value));
                }
            }

            foreach (var prop in entity.Details.Properties.All().OfType<IEntityReferenceProperty>())
            {
                if (!(Object.Equals(prop.Value, prop.OriginalValue)))
                {
                    oldValues.AppendLine(string.Format("{0}: {1}", prop.Name, prop.OriginalValue));
                    newValues.AppendLine(string.Format("{0}: {1}", prop.Name, prop.Value));
                }
            }

            CreateAuditRecord<E>(entity, auditTrailEntitySet, newValues, oldValues, "Updated");
        }

        public static void CreateAuditTrailForInsert<E>(E entity, EntitySet<AuditTrail> auditTrailEntitySet) where E : IEntityObject
        {
            StringBuilder newValues = new StringBuilder();
            StringBuilder oldValues = new StringBuilder("The record has been newly created");

            foreach (var prop in entity.Details.Properties.All().OfType<IEntityStorageProperty>())
            {
                newValues.AppendLine(string.Format("{0}: {1}", prop.Name, prop.Value));
            }

            foreach (var prop in entity.Details.Properties.All().OfType<IEntityReferenceProperty>())
            {
                newValues.AppendLine(string.Format("{0}: {1}", prop.Name, prop.Value));
            }

            var auditRecord = CreateAuditRecord<E>(entity, auditTrailEntitySet, newValues, oldValues, "Inserted");

            //savechanges needs only be done if the datasource of the entity under audit is different from the datasource of the audittable itself
            string auditTableDataSource = auditRecord.Details.EntitySet.Details.DataService.Details.Name;
            if (auditRecord.DataSource != auditTableDataSource)
            {
                auditRecord.Details.EntitySet.Details.DataService.SaveChanges();
            }
        }

        public static void CreateAuditTrailForDelete<E>(E entity, EntitySet<AuditTrail> auditTrailEntitySet) where E : IEntityObject
        {
            StringBuilder oldValues = new StringBuilder();
            StringBuilder newValues = new StringBuilder("The record has been deleted");

            foreach (var prop in entity.Details.Properties.All().OfType<IEntityStorageProperty>())
            {
                oldValues.AppendLine(string.Format("{0}: {1}", prop.Name, prop.Value));
            }

            foreach (var prop in entity.Details.Properties.All().OfType<IEntityStorageProperty>())
            {
                oldValues.AppendLine(string.Format("{0}: {1}", prop.Name, prop.Value));
            }

            CreateAuditRecord<E>(entity, auditTrailEntitySet, newValues, oldValues, "Deleted");
        }
        private static AuditTrail CreateAuditRecord<E>(E entity, EntitySet<AuditTrail> auditTrailEntitySet, StringBuilder newValues, StringBuilder oldValues, string changeType) where E : IEntityObject
        {
            AuditTrail auditRecord = auditTrailEntitySet.AddNew();
            auditRecord.ChangeType = changeType;
            auditRecord.KeySegment = CommonAuditHelper.SerializeKeySegments(entity);
            auditRecord.ReferenceType = entity.Details.EntitySet.Details.Name;
            auditRecord.DataSource = entity.Details.EntitySet.Details.DataService.Details.Name;
            auditRecord.Updated = DateTime.Now;
            auditRecord.ChangedBy = Application.Current.User.FullName;
            if (oldValues != null)
            {
                auditRecord.OriginalValues = oldValues.ToString();
            }
            if (newValues != null)
            {
                auditRecord.NewValues = newValues.ToString();
            }

            return auditRecord;
        }
    }

 

The tricky part in this implementation is the way how we handle compound KeySegments. Remember, that one of our requirements is being able to jump from an audit record to the corresponding entity. That means that we have to store in our audit table the “KeySegment” of our initial record.  When you use only the intrinsic database, a keysegment in always a simple primary key (an integer Id field). This is not the case when you use external datasources where a keysegment can be any combination of fields and types.

In order to test this as good as possible, I constructed a table (in my external datasource) which has only one field, but which a compound primary key consisting of 6 different fields and each key field has different type.

 

So, we need to be able to store inside our audit table a kind of serialized Key-Type-Value combination of the keysegment. This is handled by following class (in the common project):

public static class CommonAuditHelper
    {
        public static string SerializeKeySegments(IEntityObject entity)
        {
            string keyString = "";
            object propVal = null;

            foreach (Microsoft.LightSwitch.Model.IKeyPropertyDefinition keyPropertyDefinition in entity.Details.GetModel().KeyProperties)
            {
                if (!string.IsNullOrEmpty(keyString))
                    keyString += ", ";

                keyString += keyPropertyDefinition.Name;

                keyString += ";";
                propVal = entity.Details.Properties[keyPropertyDefinition.Name].Value;
                if (null != propVal)
                    keyString += Convert.ToString(propVal, CultureInfo.InvariantCulture);
                else
                    keyString += "(null)";

                keyString += ";";
                IPrimitiveType definitionType = keyPropertyDefinition.PropertyType as IPrimitiveType;

                string definitionTypeName;
                if (definitionType != null)
                {
                    definitionTypeName = definitionType.ClrType.Name;
                }
                else
                {
                    ISemanticType semanticDefType = keyPropertyDefinition.PropertyType as ISemanticType;
                    if (semanticDefType != null)
                    {
                        definitionTypeName = semanticDefType.UnderlyingType.Name;
                    }
                    else
                    {
                        definitionTypeName = "unknowType"; //Houston-->problem
                    }
                }
                keyString += definitionTypeName;
            }
            return keyString;
        }
        public static object[] DeserializeKeySegments(string keySegmentString)
        {
            string[] rawSegments = keySegmentString.Split(',');
            object[] segments = new Object[rawSegments.Count()];

            for (int i = 0; i < rawSegments.Count(); i++)
            {
                var currentSegment = rawSegments[i].Split(';');
                string segmentValue = currentSegment[1];
                string segmentType = currentSegment[2];

                switch (segmentType)
                {
                    case "Int16":
                        segments[i] = Convert.ToInt16(segmentValue, CultureInfo.InvariantCulture);
                        break;
                    case "Int32":
                        segments[i] = Convert.ToInt32(segmentValue, CultureInfo.InvariantCulture);
                        break;
                    case "Int64":
                        segments[i] = Convert.ToInt64(segmentValue, CultureInfo.InvariantCulture);
                        break;
                    case "String":
                        segments[i] = segmentValue;
                        break;
                    case "Decimal":
                        segments[i] = Convert.ToDecimal(segmentValue, CultureInfo.InvariantCulture);
                        break;
                    case "DateTime":
                        segments[i] = Convert.ToDateTime(segmentValue, CultureInfo.InvariantCulture);
                        break;
                    case "Guid":
                        segments[i] = new Guid(segmentValue);
                        break;
                    default:
                        segments[i] = segmentValue;
                        break;
                }
            }
            return segments;
        }
    }

 

So, there is both a Serialize and Deserialize method.  We’ll need the Deserialize method client side when we want to jump to the original record. That’s why this class is in the common project.

Note that another option would be here, instead of storing the keysegment in a serialized string, to store the key segment in a related table to the audit table. I have chosen for the serialization approach because I believe it might be better from a performance perspective.

 

Client Side

We want to call the related record from the audit record. The code of the Show_Execute() method is very simple:

 partial void Show_Execute()
        {
            AuditTrail auditTrail = this.AuditTrails.SelectedItem;
            bool result = false;
            ClientAuditHelper.TryShowRecordFromAuditRecord(auditTrail, this.DataWorkspace, out  result);
            if (result == false)
            {
                this.ShowMessageBox("Sorry, the record is no longer available...");
            }
        }

 

It makes use of following ClientAuditHelper class:

 

public static class ClientAuditHelper
    {
        public static void TryShowRecordFromAuditRecord(AuditTrail auditTrail, IDataWorkspace dataWorkSpace, out bool result)
        {

            string entitySetName = auditTrail.ReferenceType;
            string dataSourceName = auditTrail.DataSource;
            string serializedKeySegment = auditTrail.KeySegment;

            object[] keySegments = CommonAuditHelper.DeserializeKeySegments(serializedKeySegment);

            IDataService dataService = dataWorkSpace.Details.Properties[dataSourceName].Value as IDataService;

            IEntityObject entityObject = null;
            entityObject = GetEntityByKey(dataService, entitySetName, keySegments);

            if (entityObject != null)
            {

                Application.Current.ShowDefaultScreen((IEntityObject)entityObject);
                result = true;
            }
            else
            {

                result = false;
            }
        }

        private static IEntityObject GetEntityByKey(IDataService dataService, string entitySetName, params object[] keySegments)
        {
            ICreateQueryMethod singleOrDefaultQuery = dataService.Details.Methods[entitySetName + "_SingleOrDefault"] as ICreateQueryMethod;

            if (singleOrDefaultQuery == null)
            {
                throw new ArgumentException("An EntitySet SingleOfDefault query does not exist for the specified EntitySet.", entitySetName);
            }

            ICreateQueryMethodInvocation singleOrDefaultQueryInvocation = singleOrDefaultQuery.CreateInvocation(keySegments);
            return singleOrDefaultQueryInvocation.Execute() as IEntityObject;
        }

        public static void ShowRelatedAuditRecords(IEntityObject entity)
        {
            string serializedKeySegment = CommonAuditHelper.SerializeKeySegments(entity);

            Application.Current.ShowSearchAuditTrails(serializedKeySegment, entity.Details.EntitySet.Details.Name);
        }
    }

 

And finally, we want to navigate from a detail record to the list of related audit records.

This is again calling our ClientAuditHelper:

 partial void ShowAuditRecords_Execute()
        {
            ClientAuditHelper.ShowRelatedAuditRecords(this.Customers.SelectedItem);
        }

Sample

You can find a sample over here : AuditTrail. You first need to create a database called ExternalDb and then run the .sql script present in the solution folder.

Conclusion

This post presents a improved approach for audit trails, but basically it’s a good illustration of the power of using the  “weakly-typed” API style in LightSwitch

Hopefully, the presented extensions to the audit solution are useful for you. Please drop me a line in the LightSwitch forum in case you find bugs or ideas for improvement :

http://social.msdn.microsoft.com/Forums/en-US/lightswitch/thread/b59554fe-bdf9-44e2-a045-228a58aff7d7

 

 

 

Categories: AuditTrail, Uncategorized

LightSwitch custom control: Mimic datagrid structure with undefined amount of columns

Posted on 2012/03/05 by paul van bladel
No Comments

 RoomReservation

Categories: Uncategorized

Nuget Package for setting up an internal RIA Service for your LightSwitch project.

Posted on 2012/02/26 by paul van bladel
1 Comment

Introduction

I like to automate as much as possible repetitive tasks mainly because I don’t like repetitive work but also because automation can reduce the number of errors and it just gives a nice feeling when one can get rid of something annoying.

Adding a RIA service to a LightSwitch project is such a repetitive task…

Therefore, I created a Nuget Package for setting up an internal RIA Service that can get attached via a .Net class library project to your LightSwitch solution.

Why an internal RIA service ?

Often, the pure entity driven approach of LightSwitch is not sufficient and you want to create Data Transfer Objects (DTO) which make up a kind of aggregation between different entities. A good example of such a DTO is an aggregation of a Customer, Order and City entity:

public class CustomerDTO
    {
        [Key]
        public int Id { get; set; }
        public string CustomerName { get; set; }
        public string City { get; set; }
        public decimal? TotalOrdersQuantity { get; set; }
    }

In many cases, “computed properties” can handle such aggregations as well, but there can be reasons (e.g. performance) why you do not want to use these.

Why is setting up an internal RIA service repetitive?

  • the RIA service class library needs a lot of assembly references (e.g. System.ComponentModel.DataAnnotations.dll)
  • the RIA service class library needs a linked file reference to ApplicationData.cs from the ServerGeneratedProject
  • it has practical advantages to refactor base functionality of each DomainService in a base class called LightSwitchDomainServiceBase, which inherits from DomainService. Basically, this base class takes care of the low level plumbing of the domain context and carries the Count() method.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.DomainServices.Server;
using ApplicationData.Implementation;
using System.Data.EntityClient;

namespace RiaService
{
    public  class LightSwitchDomainServiceBase:DomainService
    {
        #region Database connection
        private ApplicationDataObjectContext m_context;
        public ApplicationDataObjectContext Context
        {
            get
            {
                if (this.m_context == null)
                {
                    string connString =
                        System.Web.Configuration.WebConfigurationManager
                        .ConnectionStrings["_IntrinsicData"].ConnectionString;
                    EntityConnectionStringBuilder builder = new EntityConnectionStringBuilder();
                    builder.Metadata =
                        "res://*/ApplicationData.csdl|res://*/ApplicationData.ssdl|res://*/ApplicationData.msl";
                    builder.Provider =
                        "System.Data.SqlClient";
                    builder.ProviderConnectionString = connString;
                    this.m_context = new ApplicationDataObjectContext(builder.ConnectionString);
                }
                return this.m_context;
            }
        }
        #endregion
        // Override the Count method in order for paging to work correctly
        protected override int Count<T>(IQueryable<T> query)
        {
            return query.Count();
        }
    }
}
  • A class library has by default a class called class1.cs.  You want to remove this, don’t you?
  • I included as well 2 classes which serve as a (minimal) example on how to create a custom domain service and a DTO. The DTO is enlisted already above. This is the example domain service:
namespace RiaService
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using RiaService.DTO;
    using System.ServiceModel.DomainServices.Server;

    public class CustomerDomainService : LightSwitchDomainServiceBase
    {

        [Query(IsDefault = true)]
        public IQueryable<CustomerDTO> GetCustomersDTO()
        {
            return from c in this.Context.Customers
                   select new CustomerDTO
                   {
                       Id = c.Id,
                       CustomerName = c.LastName,
                       City = c.City.CityName,
                       TotalOrdersQuantity = c.Orders.Sum(o => o.Quantity)
                   };
        }
    }
}
The NuGet package I created, is doing the above automatically for you.

How to use the RiaLightSwitch Nuget Package?

In case you don’t know what NuGet is, I suggest you go to nuget.org.

Step 1: Install NuGet (in case you don’t have it yet)

Go in visual studio to the extension manager (from the tools menu  - Extension Manager) and install NuGet.

 Step 2: In a LightSwitch solution add a plain .net 4 class library project.

The name of the class library doens’t matter (I realise now that I have only foreseen a scenario for c#).

You will see now in the project explorer your newly added class library. It has the class1.cs, but the NuGet package will remove this :)

Step 3: install the RiaLightSwitch package

Rightclick your newly added class library and select “Manage NuGet Packages”, this will open again the extension manager, but this time for installing the NuGet package.

This will trigger the package installation.

Alternatively you could do the same, via the  package manager console (go to Tools – Library Package Manager – Package Manager Console). Make sure your class library is in the default project dropdown and type “Install-Package RiaLightSwitch”

 

The package makes sures that there are folders for the DTO’s and the Services. Also the link to the ApplicationData.cs is present.

 

 

The internals

In case you interested in the internal of the NuGet Package, you could download it via the Nuget package explorer.

My RiaLightSwitch package is simple, but nonetheless, it contains a powershell script, where the most tricky part is the addition of the ApplicationData.cs link from the ServerGenerated project to the class Library.

Here is the code of the Install.ps1 powershell script:

param($installPath, $toolsPath, $package, $project)

Add-Type -Assembly EnvDTE100

#if your naming convention system means having class names like Class1 you have a problem now
ForEach ($item in $project.ProjectItems)
{
	if($item.Name -eq "Class1.cs")
	{
		$item.Remove()
	}
} 

$applicationDataFileExists = $false
ForEach ($item in $project.ProjectItems)
{
	if($item.Name -eq "ApplicationData.cs")
	{
		$applicationDataFileExists = $true
	}
} 

if($applicationDataFileExists -eq $false)
{
	$solution = get-interface $dte.solution ([EnvDTE100.Solution4])
	$solutionRoot = split-path $solution.filename
	$solutionFolderName = (Get-Item (Split-Path $solution.fileName -parent)).Name
	$project.ProjectItems.AddFromFile($solutionRoot + "\" + $solutionFolderName + "\ServerGenerated\GeneratedArtifacts\ApplicationData.cs");
}

My powershell skills are poor, so I will not feel offended if you have suggestions for improvement.

Conclusion

This was my first NuGet package. Although it works on my machine,  I hardly can’t imagine that there will be situations in which the package doesn’t work. Drop me a line, and I’ll look into it.

Categories: Uncategorized

Row level security.

Posted on 2012/01/29 by paul van bladel
No Comments

Introduction

The previous post covered table level security. Let’s focus now on row level security: which rows can a user see depending on her permission set.

Let’s take in mind the app for managing purchase requests. I have an PurchaseOrder which is initially requested by a company employee (we call them requestors). Before being sent eventually to the vendor, the purchase order first needs to be approved by several other parties:  a procurement officers and and Adviser.

Let’s imagine that following row level security rules needs to be implemented:

  • requestors can see only their own requests
  • procurement officers can only see requests to which they are assigned as procurement officer
  • advisers can see only those requests to which they are assigned as adviser.
An important requirement is that it is perfectly possible that users have different roles at the same time: e.g. an adviser can hand in as well a purchase request. This means that she can see requests handed in by her and requests in which she particapates as adviser.

Preprocess query method and permissions

It is clear that the row level security that we want to implement is handled in the ProProcessQuery method associated with the PurchaseRequest entity. Furthermore, the LightSwitch permission mechanism is used as “entry point” to the security implementation.

Let’s keep the data model as simple as possible:

 

We define 3 permissions in the Access Control Tab of the application properties:

  • RLS_ApplyFilteringOnAssociatedRequestor
  • RLS_ApplyFilteringOnAssociatedProcurementOfficer
  • RLS_ApplyFilteringOnAssociatedAdviser

The RLS stands for Row Level Security.  By using this prefix the permissions can be easily identified in the permission list. That’s practical, because row level security permissions have a slightly different semantic than other permissions. A “normal” permission is a kind of additional right in the application. So, more permissions, more rights. That’s kind of different with the row level security permissions. I you have a certain RLS permission, additional filtering on data is applied, so in a way, you have less rights.

What is wrong with the following approach

What is the problem with following code?

partial void PurchaseRequests_All_PreprocessQuery(ref IQueryable<LightSwitchApplication.PurchaseRequest> query)
        {
            bool applyRequestorFiltering= this.Application.User.HasPermission(Permissions.RLS_ApplyFilteringOnAssociatedRequestor);
            bool applyProcurementOfficerFiltering = this.Application.User.HasPermission(Permissions.RLS_ApplyFilteringOnAssociatedProcurementOfficer);
            bool applyAdviserFiltering = this.Application.User.HasPermission(Permissions.RLS_ApplyFilteringOnAssociatedAdviser);

            if (applyRequestorFiltering)
            {
                query = query.Where(r => r.RequestorUserName == this.Application.User.Name);
            }

            if (applyProcurementOfficerFiltering)
            {
                query = query.Where(r => r.ProcurementOfficerUserName == this.Application.User.Name);
            }

            if (applyAdviserFiltering)
            {
                query = query.Where(r => r.AdviserUserName == this.Application.User.Name);
            }
        }

What is happening here? We first take the 3 permissions in a simple boolean value (e.g. applyRequestorFiltering). Depending on the active filters, a specific predicate will be applied. This all makes sense and this works also perfectly under the assumption that the user has either:

  • one of the 3 possible permissions: a single where clause is applied.
  • no permission at all: in this case the query will just be returned untouched.

What happens when the user has e.g. both the requestor and the adviser permission? Well, .. the 2 filters are applied but only the Intersection is returned and that’s not what we want. We want the records where the current user is requestor OR where the current user is Adviser.

It’s very tempting to try to combine the 3 predicates in one where clause, but you’ll see you end up with other trouble.

 

Introducing the ConditionalOr Operator

Try to see what’s happening here:

 partial void PurchaseRequests_All_PreprocessQuery(ref IQueryable<LightSwitchApplication.PurchaseRequest> query)
        {
            bool applyRequestorFiltering = this.Application.User.HasPermission(Permissions.RLS_ApplyFilteringOnAssociatedRequestor);
            bool applyProcurementOfficerFiltering = this.Application.User.HasPermission(Permissions.RLS_ApplyFilteringOnAssociatedProcurementOfficer);
            bool applyAdviserFiltering = this.Application.User.HasPermission(Permissions.RLS_ApplyFilteringOnAssociatedAdviser);

            if (!applyRequestorFiltering && !applyProcurementOfficerFiltering && !applyAdviserFiltering)
            {
                return;
            }

            Expression<Func<PurchaseRequest, bool>> StartWith = r => false;
            Expression<Func<PurchaseRequest, bool>> requestorPredicate = r => r.RequestorUserName == this.Application.User.Name;
            Expression<Func<PurchaseRequest, bool>> procurementOfficerPredicate = r => r.ProcurementOfficerUserName == this.Application.User.Name;
            Expression<Func<PurchaseRequest, bool>> adviserPredicate = r => r.AdviserUserName == this.Application.User.Name;

            Expression<Func<PurchaseRequest, bool>> predicate =
                StartWith
                .ConditionalOr(requestorPredicate, applyRequestorFiltering)
                .ConditionalOr(procurementOfficerPredicate, applyProcurementOfficerFiltering)
                .ConditionalOr(adviserPredicate, applyAdviserFiltering);
            query = query.Where(predicate);
        }

So, we split the 3 permissions into 3 Expressions and we start with a query that’s returning nothing by applying the StartWith expression. Afterwards each filter is applied in a conditional way and Or-ed with the previous expression.

This will give the result we expect: in case a user has multiple permissions, the correct result set is returned.

Of course, we still need the code for the ConditionalOr operator (I found this approach here: http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx):

public static class Utility
    {
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // apply composition of lambda expression bodies to parameters from the first expression
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }

        public static Expression<Func<T, bool>> ConditionalOr<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second, bool? doCompose)
        {
            if (doCompose.Value == true)
            {
                return first.Compose(second, Expression.Or);
            }

            else
            {
                return first;
            }
        }
    }
    public class ParameterRebinder : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;

        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new ParameterRebinder(map).Visit(exp);
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            ParameterExpression replacement;
            if (map.TryGetValue(p, out replacement))
            {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }

 

Note that you can get the same result by using the excellent LinqKit Predicatebuilder (http://www.albahari.com/nutshell/predicatebuilder.aspx).

 

 Conclusion

Row level security is key in every decent enterprise application. LightSwitch allows to implement row level security in an excellent way. Extending Linq with the conditionalOr operator allows to really tune the row level security at any level.

 

Categories: Uncategorized

How to organize entity-level, row-level and state-based security in a work-flow-alike scenario. (part 1)

Posted on 2012/01/15 by paul van bladel
1 Comment

Introduction

Let’s start from the following real-live enterprise scenario.

Image, I’m builing an app for managing purchase requests. I have an PurchaseOrder which is initially requested by a company employee. Before being sent eventually to the vendor, the purchase order first needs to be approved by several other parties (the direct management of the requestor, some advising parties, a procurement officer, the board of directors, etc. …)  in the company.  All parties involved will enrich the purchase order with additional information in such a way we can speak of some kind of workflow. From a security perspective, the data which is enriching the original purchase order may not be edited by the original requestor, or may even not be seen by the original requestor of other “enriching parties”.

How can we make this doable in LightSwitch and how can we make this really secure.

The first part only covers the table-level security. The row-level security and the state-driven workflow/security aspects are covered in  later episodes.

The scenario for a small proof of concept

Data involved

Let’s stick, for the small proof of concept w’ll working out here, to the scenario where we have a purchase request which is handed in by a “requestor” en enriched by a procurement officer and finally enriched, approved or rejected by the board of directors.

So, we have 3 parties, all enriching the Purchase request entity. Let’s start with a “naive” representation of this:

In order to make the data model not too heavy, I’m using the field SeveralOtherFieldsOfProcurementOfficer and SeveralOtherFieldsBoardOfDirectors as a “placeholder” for a whole bunch of fields. This keeps our example manageable.

The State property makes that we speak of a kind of workflow:

  • when the requester finished her request, she can put to state to “ready for PO validation“, in such a way the request can be picked up by the Procurement Officer (PO). The requestor can also leave the request in state “Draft“. As long as it is in Draft mode, other parties can’t see the request.
  • The Procurement Officer can validate the request and put in state “ready for board of directors approval” or can put it back in state “Draft”.
  • Finally, the board of directors can approve or decline the request.

The requestor has following rights with respect to the purchase order:

  • creating a purchase order
  • may not see (nor edit)  the fields added by the procurement officer
  • may read (but not edit) the fields added by the procurement officer.
The Procurement officer has following rights:
  • may view (but not edit) the original purchase request from the requester
  • may create/update/read the procurement specific fields
  • may read (but not edit), the specific fields that the board of directors will add
The board of directors has following rights:
  • may view (but not edit) the original purchase request from the requester
  • may view (but not edit) the specific fields from the procurement officer
  • may edit/read the specific fields that the board of directors add.
It’s clear that we will end up with 3 roles in the application, which is handled inside the application with 3 permissions on the request entity. Furthermore the notion of the request state is also deeply involved in the application security.

Why is the above “naive” datamodel not workable

There are several reasons why the above datamodel will sooner are later give trouble:

  • the request entity is never created as a whole: the requester provides only a subset of fields. The trouble is that the requester will not be able to save the record because there are mandatory fields which can, given our requirements, not be provided at requester-create-time. Ok, we could make the PO and board of director specific fields optional, but that would make our data model weaker and would require to implement the mandatory field logic in code, rather than on data level.
  • How will be enable the security? Since everything is in one table, we can only leverage field level security and even only in a partial way. We could use the _isReadOnly method to ensure that given a users role and request state, certain fields are read-only, but it will not be possible in an easy way to make sure that certain fields can not be read and that is is ensured in a secure way.
  • Please note that hiding fields client side has nothing to do with security. Security happens server side, where you have to protect your assets. Making controls read-only or invisible client side has more to do with cosmetics and application comfort but nothing with data security.
  • Even if we could elaborate a way to implement this field-level security, it would be very tedious to maintain this application because adding a new field (due to evolving requirements) would mean going through the application logic and add the logic for the newly added field.
It’s clear that we want to have the security implemented on the right level of granularity: table-level.

What type of UI do we have in mind?

The tabs layout, is the most applicable way that LightSwitch offers out-of-the-box for handling the above scenario.

The generic purchase request fields are in the upper part of the screen. The lower part contains the tabs with the PO and board of directors specific fields. When our server side logic is in place, the tabs can be very elegantly shown or hidden to ensure further user comfort.

Towards a more normalized data model.

The following datamodel is closer to our goal of a more manageable security implementation. Since the procurement officer and the board of directors data related to a purchase request, should be secured differently they are taken out of the purchase request.

These sub entities get a 0..1 to 1 relationship with the purchase request entity. This means that they can exist and if they exist only one instance can exist. That fits perfectly our goal.

Since the Procurement Officer and the board of directors fields are isolated now in separate tables, we can manage in a much better way the security.

Some Definitions

Table level security

The subject of this first part on lightswitch security. Entity level security handles the authorization aspects of a table in isolation, so only related to a related permission. In LightSwitch this type of authorization security is handled in the domain model by following access control methods:

Since the information carried in these methods is on domain level, the client can re-use this information, by automatically enabling/disabling buttons related to these CRUD (Create, read, update and delete) methods. This type of security is coarse-grained.

Table level security is involed in both the query (CanRead) and the save pipeline  (CanDelete, CanUpdate, CanInsert).

Row level security

Row level security is only involved in the query pipeline. It will tell you which records (which rows) a user can see depending on her permission level. 

The _PreprocessQuery methods are key when it comes to row level security.

State based security

State based security happens on a more functional level and it related to actions that users apply on data. Let’s take the example of the PurchaseRequests. When a procurement officer decides that that a request is ok, she can put it in state ready for board of directors approval.  From then, the request is no longer editable for the procurement officer, neither for the original requestor. The change in authorization is due to the change of a particular field of the entity (in casu, the RequestState). The different states and the potential transitions between these states are typically represented in a Finite State Table.

 

Bringing the Purchase Request detail screen in line with a workable server-side security approach

Our final goal is that we can protect the RequestPOEnrichtment and the RequestBoardEnrichment entity via table level security:

 partial void RequestBoardEnrichments_CanInsert(ref bool result)
        {
            result = this.Application.User.HasPermission(Permissions.BoardPermission);
        }

        partial void RequestBoardEnrichments_CanRead(ref bool result)
        {
            //result = ...
        }

        partial void RequestBoardEnrichments_CanUpdate(ref bool result)
        {
            result = this.Application.User.HasPermission(Permissions.BoardPermission);
        }

We start with following layout of our Request detail screen. Note that the 2 tabs have an edit button, so that e.g. a procurement officer can decide to fill in the procurement officer specific fields. The databinding of the tabs is done as follows:   As you can see, the tab is bound to the “main” PurchaseRequest property. We’ll see in a minute or two that this will lead to trouble. We foresee following code for when a procurement officer wants to provide her specific fields:

partial void EditByProcurementOfficer_Execute()
        {
            // Instantiate PO Enrichment entity
            if (PurchaseRequest.RequestPOEnrichment ==null)
            {
                RequestPOEnrichment POEnrichment = new RequestPOEnrichment();
                PurchaseRequest.RequestPOEnrichment = POEnrichment;
            }
        }

So, the RequestPOEnrichment is simply instantiated if it doesn’t exist yet.

Now, just for testing, we insert following code in the ApplicationDataService partial class:

        partial void RequestPOEnrichments_CanRead(ref bool result)
        {
            result = false;
        }

What we expect now, is that we can still read the request entity, but that we would get an error (a red cross) in the PO tab. We don’t care right now getting errors, because we didn’t do anything specific client side to avoid, cosmetically, that the user would run into this error.

Unfortunately, we’ll run into this screen:

 

I can not really tell you why this error happens, but it is what it is. So, let’s try to find a solution.

Instead of  using a query for retrieving the current PurchaseRequest, we use a Property for storing the current PurchaseRequest and in the InitializeDataWorkspace method run an query and assign a value to the PurchaseRequest property. For one reason or another, by doing so, the problem solved.

 

 

    partial void PurchaseRequestDetail_InitializeDataWorkspace(List<IDataService> saveChangesTo)
        {
            this.PurchaseRequestProperty =
            this.DataWorkspace.ApplicationData.PurchaseRequests_SingleOrDefault(this.PurchaseRequestId);
        }

 

Now, let’s test again the situation where the user has no rights to see the ProcurementOfficer specific data (so, the _CanRead method returns false:


 

That’s really what we want: the user can see the purchase request details but not the PO specific data. For the moment we don’t care about the red crosses, because that’s only client-side cosmetics. I love to see the red crosses, because it really means that no data which had to be protected went over the line.

 

Now, we can go in client-side cosmectic mode and make following update:
   partial void PurchaseRequestDetail_Created()
        {
            this.FindControl("RequestPOEnrichment").IsVisible = this.DataWorkspace.ApplicationData.RequestPOEnrichments.CanRead;
            this.FindControl("RequestBoardEnrichment").IsVisible = this.DataWorkspace.ApplicationData.RequestBoardEnrichments.CanRead;
        }

 

In order to start editing the PO specific fields we adapt the Execute code of the button as follows:

partial void EditBoard_Execute()
        {
            if (PurchaseRequestProperty.RequestBoardEnrichment == null)
            {
                PurchaseRequestProperty.RequestBoardEnrichment = new RequestBoardEnrichment();
            }
        }

        partial void EditProcurementOfficer_Execute()
        {
            if (PurchaseRequestProperty.RequestPOEnrichment == null)
            {
                PurchaseRequestProperty.RequestPOEnrichment = new RequestPOEnrichment();
            }
        }
The CanExecute methods becomes this:

 partial void EditProcurementOfficer_CanExecute(ref bool result)
        {
            result = (this.DataWorkspace.ApplicationData.RequestPOEnrichments.CanInsert
                && this.PurchaseRequestProperty.RequestPOEnrichment == null);
        }

        partial void EditBoard_CanExecute(ref bool result)
        {
            result = (this.DataWorkspace.ApplicationData.RequestBoardEnrichments.CanInsert
                && this.PurchaseRequestProperty.RequestBoardEnrichment == null);
        }

 

This leads to the result we want: in case I may not see the PO, i won’t see them. All security in place is server side and on table level.

Obviously, you can tune the UI experience, and maybe hide the tab if the user may not see the data.

In order to make the picture complete, we have to implement the requirements described initially:

 

Everything is now secured with respect to entity-level security.

 

Conclusion

Securing an application starts on the server side. Making use of 1 to 0..1 relationships is the basis for for a state driven workflow where the entity enrichment takes places in sub entities.

Part II will cover the state based and the row level security aspects.

Categories: Uncategorized

Attaching tags to any entity in a generic way.

Posted on 2012/01/15 by paul van bladel
4 Comments

Introduction

I like the concept of tags as a means of categorizing things. It’s very flexible and you can take unions, intersections, etc.

It’s quite simple in lightswitch to foresee a generic approach, which is perfectly reusable in many other situations.

The ingredients are:

  • generics,
  • implementing an interface,
  • the dynamic keyword and
  • extension methods.
The approach is completely inspired by the work of Kostas Christodoulou (http://code.msdn.microsoft.com/silverlight/Managing-Custom-AddEdit-5772ab80). In his approach, a modal window is used for adding and editing an entity. Here we use a similar approach for many-to-many handling. To make things not too generic, I focus here on tagging, as an example of many-to-many handling.
The source of my solution is here: GenericTaggingModalWindow

The User Interface

Of course we need a screen for managing tags, but it’s not worth providing a screenshot. You just need a ListDetail screen for a table having one field (tagname).

So, let’s focus on the customer detail screen which contains a serialized list of assigned tags.

I agree that my representation of the assigned tags is rather poor. It’s clear the kind of custom control (a colored stackpanel) could improve the “user interface experience”.

So, the user can click on the “Edit Tags” button, which opens following modal screen:

 

You’ll find many examples of this approach. The screen has basically 2 grids: Assigned tags and available tags. The 2 buttons in the middle allow to assign or unassign tags. Of course, we try to leverage all the comfort that Silverlight is offering here:

  • The command pattern (the Execute and CanExecute methods) allows us to disable the buttons when appropriate.
  • The full duplex databinding makes sure that even the AssignedTag list on the main customer detail screen is always in sync with the situation on the picker screen.

Data Model

Let’s stick to the simple example of a customer table on which we apply tagging. So conceptually, each customer has an assigned list of tags. Tags can be anything: points of interest, participation in holidays, etc. …

First of all we need a Tag table:

Of course, we have our Customer table:

The customer table has a computed field named AssignedTagList, which is in fact the serialized representation of the next table, the AssignedTags Table. The code for the field computation is very simple:

 public partial class Customer
    {
        partial void AssignedTagList_Compute(ref string result)
        {
            if (this.AssignedTags != null)
            {
                result = string.Join<AssignedTag>(" | ", this.AssignedTags);
            }
        }
    }

The AssignedTag table goes as follows:

The AssignedTag table has also a computed field: TagName.

public partial class AssignedTag

    {
        partial void TagName_Compute(ref string result)
        {
            if (this.Tag!=null)
            {
                result = this.Tag.TagName;
            }
        }
    }

 

The “base infrastructure”

The code in the customer detail screen can be kept very simple, because I use a technique that I borrowed from Kostas.

In fact we implement an interface called IScreenWithAvailailablTags:

public interface IScreenWithAvailableTags<TEntity, TDetails, SEntity, SDetails> : IScreenObject
        where TEntity : EntityObject<TEntity, TDetails>
        where SEntity : EntityObject<SEntity, SDetails>
        where SDetails : EntityDetails<SEntity, SDetails>, new()
        where TDetails : EntityDetails<TEntity, TDetails>, new()
    {
        VisualCollection<TEntity> TagList { get; }

        VisualCollection<SEntity> AssignedTagList { get; }

        string TagFieldName { get; }
    }

This interface makes sure that our detail screen will have 2 VisualCollections (one for the TagList and one for the AssignedTagList. Furthermore it makes sure that we have a name for the field in the AssignedTagList refering to the Tag table. By doing so, this approach can be used for any many to many screen.

The interface definition is the base for a set of extension methods which will basically do the handling of the assignment and un-assignment of tags in our final customer details screen.

 

 public static class TagExtensions
    {

        public static void DoAssignThisTag<TEntity, TDetails, SEntity, SDetails>(this IScreenWithAvailableTags<TEntity, TDetails, SEntity, SDetails> screen)
            where TEntity : EntityObject<TEntity, TDetails>
            where TDetails : EntityDetails<TEntity, TDetails>, new()
            where SEntity : EntityObject<SEntity, SDetails>
            where SDetails : EntityDetails<SEntity, SDetails>, new()
        {
            if (screen.TagList.SelectedItem != null)
            {
                dynamic newAssignedTag = screen.AssignedTagList.AddNew();
                newAssignedTag.Details.Properties[screen.TagFieldName].Value = screen.TagList.SelectedItem; //Tag property is dynamically assigned !
                MoveToNextItemInCollection<TEntity>(screen.TagList);
            }
        }

        public static bool DoAssignThisTag_CanExecute<TEntity, TDetails, SEntity, SDetails>(this IScreenWithAvailableTags<TEntity, TDetails, SEntity, SDetails> screen)
            where TEntity : EntityObject<TEntity, TDetails>
            where TDetails : EntityDetails<TEntity, TDetails>, new()
            where SEntity : EntityObject<SEntity, SDetails>
            where SDetails : EntityDetails<SEntity, SDetails>, new()
        {
            bool result;
            if (screen.TagList.SelectedItem != null)
            {
                result = !(screen.AssignedTagList.Where(at =>
                {
                    dynamic at2 = at as dynamic;
                    return at2.Details.Properties[screen.TagFieldName].Value == screen.TagList.SelectedItem;
                }).Any()
                    );
            }
            else
            {
                result = false;
            }
            return result;
        }

        public static void DoUnAssignThisTag<TEntity, TDetails, SEntity, SDetails>(this IScreenWithAvailableTags<TEntity, TDetails, SEntity, SDetails> screen)
            where TEntity : EntityObject<TEntity, TDetails>
            where TDetails : EntityDetails<TEntity, TDetails>, new()
            where SEntity : EntityObject<SEntity, SDetails>
            where SDetails : EntityDetails<SEntity, SDetails>, new()
        {
            if (screen.AssignedTagList.SelectedItem != null)
            {
                if (screen.AssignedTagList.SelectedItem.Details.EntityState != EntityState.Deleted)
                {
                    screen.AssignedTagList.DeleteSelected();
                    screen.AssignedTagList.Refresh();
                }
            }
        }

        public static bool DoUnAssignThisTag_CanExecute<TEntity, TDetails, SEntity, SDetails>(this IScreenWithAvailableTags<TEntity, TDetails, SEntity, SDetails> screen)
            where TEntity : EntityObject<TEntity, TDetails>
            where TDetails : EntityDetails<TEntity, TDetails>, new()
            where SEntity : EntityObject<SEntity, SDetails>
            where SDetails : EntityDetails<SEntity, SDetails>, new()
        {
            bool result;

            if (screen.AssignedTagList.SelectedItem != null)
            {
                result = screen.AssignedTagList.SelectedItem.Details.EntityState != EntityState.Deleted;
            }
            else
            {
                result = false;
            }
            return result;
        }

        private static void MoveToNextItemInCollection<T>(VisualCollection<T> collection) where T : class, IEntityObject
        {
            if (!collection.SelectedItem.Equals(collection.Last()))
            {
                int currentIndex = collection.ToList().IndexOf(collection.SelectedItem);
                T nextElementInCollection = collection.ToList()[currentIndex + 1];
                if (nextElementInCollection != null)
                {
                    collection.SelectedItem = nextElementInCollection;
                }
            }
        }
    }

The customer detail screen code handling

Both the interface and the extension method make that the customerdetail screen handling is very straightforward

    public partial class CustomerDetail : IScreenWithAvailableTags<Tag, Tag.DetailsClass, AssignedTag, AssignedTag.DetailsClass>
    {
        private string _modalWindowControlName = "PickerModalWindow";

        partial void Customer_Loaded(bool succeeded)
        {
            this.SetDisplayNameFromEntity(this.Customer);
        }

        partial void Customer_Changed()
        {
            this.SetDisplayNameFromEntity(this.Customer);
        }

        partial void CustomerDetail_Saved()
        {
            this.SetDisplayNameFromEntity(this.Customer);
        }

        #region Tag (un)assignment button handling
        partial void AssignThisTag_Execute()
        {
            this.DoAssignThisTag();
        }

        partial void AssignThisTag_CanExecute(ref bool result)
        {
            result = this.DoAssignThisTag_CanExecute();
        }

        partial void UnassignThisTag_Execute()
        {
            this.DoUnAssignThisTag();
        }

        partial void UnassignThisTag_CanExecute(ref bool result)
        {
            result = this.DoUnAssignThisTag_CanExecute();
        }
        #endregion

        partial void EditTags_Execute()
        {

            this.OpenModalWindow(_modalWindowControlName);
        }

        #region IScreenWithAvailableTags implementation
        public VisualCollection<Tag> TagList
        {
            get { return this.AvailableTags; }
        }

        public VisualCollection<AssignedTag> AssignedTagList
        {
            get { return this.AssignedTags; }
        }

        public string TagFieldName
        {
            get { return "Tag"; }
        }
        #endregion
    }
}

Using the attached solution

The source of my solution is here: GenericTaggingModalWindow.

The source can be used directly, but contains no database, so you have to create some sample data. Create first some tags in the Tag management screen and create a customer.

Conclusion

LightSwitch allows to design screens in a very simple way. Nonetheless, for some screen designs, it can be useful to foresee kind of base infrastructure, like the one shown over here.

 

Categories: Uncategorized

A straightforward approach for a create and detail screen for entity inheritance.

Posted on 2011/11/26 by paul van bladel
1 Comment

Introduction

Quite often in business apps, one has to deal with object oriented relationships between entities making up your domain model. For example, a person can be a student or a teacher or administration personnel. They share some common characteristics, but depending on their role, they have some specific properties.

Setting up such a data structure and corresponding detail screen  in LightSwitch is not difficult, but making things both flexible (in the sense that it should be easy to add other specialized tables) and maintainable, providing some kind of basic infrastructure for entity specialization can help.

We’ll focus on a very simple sample (read: entities without not too many fields).

Data modelling entity specialization

From a data perspective, there are multiple tables for each “specialization” category (e.g. Student, Teacher), carrying only the fields relevant for the specialized entity. Each specialised entity inherits a series of properties of a “master” entity (e.g. Person).

As you know, object orientation and relational database models doesn’t go well together. Obviously, the best way to make a perfect object oriented model of you data, is without a database, but then you might run into persistence problems :)  So, the idea is to get the best of both worlds. There is enough literature about this subject and what’s important for me, is how can Lightswitch cope as best as possible with entity inheritance.

A simple inheritance data model

As announced in the introduction, we will deal with a Person, Student and Teacher table. I’ll warn you immediately, later in this post you’ll see some code which might look quite involved, and which might look a bit over-kill for the simple setting I present you. I trust on your imagination to transpose this example to a more extended scenario where a master table can be inherited by 15 sub types and where each sub type has 56 fields.

You can see a simple master table (Person) with some fields (some of them mandatory).  A very important field is the Type field. It really drives the specialization relationships. The Type field has a choice list attached with following values:

The precise naming is extremely important. Students and Teachers refer to the “specialization” entity sets, which you’ll see here:

Note that both Student and Teacher have a mix of mandatory and optional fields. Both Teacher and Student refer via a 0..1 –>1 relationship to person.  So, database-technically,  a person can exist as a person as such. Again, database-technically, a person could be both a Teacher and a Student. Nonetheless, that’s not what we want here: we presume that upon creation of a person,  a clear choice of the person’s nature has been defined: a person as such can not exist and should be either a student, either a teacher (in other words, the specialization categories are mutual exclusive). These are prerequisites of my example.

The user interface for a student and teacher detail screen

We want only one screen which can handle both a student and a teacher entity. The reason why we want one screen is that we want to be “open for extension”.  The properties of the master entity (person) are on the first half of the screen, and the specialized properties ont he second half.

 

The specialized entities are organised with a tab control. Depending on the type property in the master entity, a particular tab is activated. The other tabs are set invisible, they have anyhow no data. Remember, the sub types are mutual exclusive.

Setting the correct tabs visible, and the incorrect invisble, is all possible with the .FindControl() method, but is quite cumbersome to do. Especially, in the scenario where additional sub types are introduced, controls being renamed, etc. So, a more generic approach is desirable.

 

 

 

A two-step approach for entity creation

A basic problem with the creation of entities based on specialization is introduced by the “type field” in the master entity (person). The problem, in the context of entity creation (on a create screen)  is that the type field defines the nature of the sub type, but obviously, the user can change her mind and change the type field to another value. What will you do when the user entered already 25 fields of the previous sub type? Delete the sub type and create another one? But maybe she will change back to the original sub type?

In short, that’s not a practical way of organizing things. Keep it simple and go for a two-step approach when it comes to creating specialized entities:

Step 1: present only the master entity fields and let the user choose a sub type (via dropdown or something else).

Step 2: from the moment the user clicked on save, the entity detail screen is opened with the correct sub type is shown in the tab control and the user can provide the specific sub type fields. The user can still edit the master entity fields except the type field.

In step 2, the user can cancel the save operation. When doing so, the master entity will still be there, so that later on, she can continue entering the sub type fields.

This 2 step create way, is an approach that works for me.  Nonetheless, the code I provide later in the post, can be easily adapted to cope with another transaction schema.

The Search Screen

Here again, keep it sample and restrict the list of fields in the result grid to the fields of the master entity. If that’s not good enough, mix everything together.

 

 The Server side

Nothing specific is necessary on the server side.

The domain model (the middle tier)

Also, nothing special to be foreseen, except that for making it possible that the entity type field in the people entity becomes read-only when the state of the entity in no longer in add mode. This can be done as follows:

 public partial class Person
    {
        partial void Type_IsReadOnly(ref bool result)
        {
            result = (this.Details.EntityState != EntityState.Added);
        }
    }

Next, we need a helper class in the middle tier. I’ll explain the purpose later, but provide the code here. Create a new class in the Common Project with following static method:

 public static class ApplicationCommonActivatorHelper
    {
        public static object CreateEntityInstance(string typeName)
        {
            Type t = Type.GetType(typeName);
            if (t != null)
                return Activator.CreateInstance(t);
            return null;
        }
    }

 The client side

The create new Person screen

It boils down to the following. Make sure to include to student nor teacher specific fields (they are provided on the detail screen) and that the choice list of the type field is up to date. The code behind class doesn’t contain anything specific.

The search screen

Again, very straightforward and no specific code behind content.

The detail screen

This one is more involved. We kept the most interesting (I hope at least) for the end. The screen design of the detail screen is very simple but the code behind contains some advanced techniques.

The tabs layout contains the two (mutual exclusive) sub types (student and teacher). The easiest way to get them there, is dragging and dropping from the PersonProperty in the viewmodel.

Ok, now we come to the most important part, the code behind of the detail screen.

My purpose is clear and simple :when a new sub type is introduced (e.g. adminPersonnel) I don’t want to touch the code behind of the detail screen, it should be simply self containing.

The main tasks of the code behind for this screen is:

  • make sure the correct tab is visible and gets the focus and make sure the non-relevant tabs are hidden.
  • make sure to instantiate the correct sub type in case it has not been instiantiated yet.
As said, the code behind is generic, it has no reference to teachers and students :)
        public string EntityTypeName { get; set; }
        public string ParentPropertyName { get; set; }
        public string ChildTabGroupName { get; set; }

partial void PersonDetail_InitializeDataWorkspace(List<IDataService> saveChangesTo)
        {
            ParentPropertyName = "PersonProperty";
            ChildTabGroupName = "PersonTabGroup";

            EntityTypeName = this.DataWorkspace.ApplicationData.Details.GetModel().EntitySets.Where(s => s.Name.ToUpper() == this.PersonProperty.Type.ToUpper()).Single().EntityType.Name;

            this.FindControl(ChildTabGroupName).ControlAvailable += (s, e) =>
            {
                var ctrl = e.Control as System.Windows.Controls.Control;
                var contentItem = ctrl.DataContext as Microsoft.LightSwitch.Presentation.IContentItem;
                foreach (var child in contentItem.ChildItems)
                {
                    IEntityReferenceProperty subEntity = child.Details as IEntityReferenceProperty;
                    if (subEntity.Name.ToUpper() == EntityTypeName.ToUpper())
                    {
                        child.IsVisible = true;
                        this.FindControl(child.ContentItemDefinition.Name).Focus();
                    }
                    else
                    {
                        child.IsVisible = false;
                    }
                }
            };

            InstantiateEntitySubTypeWhenNull(this.PersonProperty.Id, this.PersonProperty.Type, this.PersonProperty);
        }

        private void InstantiateEntitySubTypeWhenNull(int entityId, string entitySubTypeName, IEntityObject parentProperty)
        {
            IDataService dataService = this.DataWorkspace.ApplicationData;
            ICreateQueryMethod singleOrDefaultQuery = dataService.Details.Methods[entitySubTypeName + "_SingleOrDefault"] as ICreateQueryMethod;
            ICreateQueryMethodInvocation singleOrDefaultQueryInvocation = singleOrDefaultQuery.CreateInvocation(entityId);
            IEntityObject entityObject = singleOrDefaultQueryInvocation.Execute() as IEntityObject;

            if (entityObject == null)
            {
                //makes use of static class in common dll.
                parentProperty.Details.Properties[EntityTypeName].Value = ApplicationCommonActivatorHelper.CreateEntityInstance("LightSwitchApplication." + EntityTypeName);
            }
        }

 

As you see, everything is happening in the InitializeDataWorkspace method.

The first part is making sure that the correct tab is visible and gets the focus. The nice thing is that everything can be processed without any intimate knowlegde of the sub entities involved.

The second part is encapsulated in the InstantiateEntitySubTypeWhenNull private method. We first try to find out, if we still need to instantiate the sub type. Since we know, deliberately,  the sub type only by it’s name we have to use reflection to instantiate the entity type. For doing this, we need the ApplicationCommonActivatorHelper (I got this helper class via the lightswitch forum from LS__, many thanks !)  in the common project. The reason why we put this method in the common project is that the entity types are also stored over there. In silverlight it’s quite complicated to get in a generic way a reference to a referenced DLL via reflection. The problem is that you have to use the full qualified name containing also a version number which changes all the time.

The only 2 “entry points” in the code behind are the values of 2 properties:

 ParentPropertyName = "PersonProperty";
  ChildTabGroupName = "PersonTabGroup"

Extending the master entity with another sub type

The proof of the pudding is in the eating. Let’s try now to introduce a new sub type “AdminStaff” and check what’s the work to be done:

Create the AdminStaff entity and create relation to Person

Update the choice list with AdminStaffs (the entity set name of the new sub type)

 

Update the person detail screen

Just drag and drop the adminStaff navigation property from the viewmodel to the tabslayout.

That’s all, as you can see from following screen:

Conclusion

It’s perfectly possible in LightSwitch to make things a bit more generic, reusable and maintainable even without using the extension framework. So far, I have little experience with the LightSwitch extension framework, but probably the above would be a reasonable starting point for an “Inheritance based detail screen template extension“.

 

Categories: Uncategorized

Tweaking the LightSwitch webdeploy package with a simple script.

Posted on 2011/10/22 by paul van bladel
7 Comments
Select Create a package on disk during Publish

Introduction

Visual studio 2010 contains powerful features when it comes to tweaking the generated web deploy package with respect to the web.config file and the parameters involved. Basically, there is functionality for changing the shape of the web.config file (this is called web.config transformations) during build time and functionality for making environment specific (e.g. different values in staging and production) web.config values injected on deploy time. This last feature is called: web deploy parameterization. In you want to compare both techniques, read this post: http://vishaljoshi.blogspot.com/2010/06/parameterization-vs-webconfig.html

web.config transformations

Here you can find more information about web.config transformations: http://go.microsoft.com/fwlink/?LinkId=125889.  You often find “environment specific connection strings” as an example of a web.config transformation. In my view, that’s not the true purpose of a web.config transformation. You should better use web deploy parameterization for doing this. The point is that the web.config transformation happens at build time. So imagine you have 3 environments (staging, acceptance, production), you would have to create 3 packages where depending on the solution configuration (debug, release, etc. …) a dedicated web.config is generated. Very powerful, but there is no possibility to tweak afterwards the package since your connection string is hard coded in the web.config file. Well, what then the true purpose of the web.config file? The answer is: changing the shape of your web.config file. So, all types of changes which are not “environment value specific”. In most cases this boils down to removing sections in the web.config file which are only applicable in the development environment and which should be removed for security reasons.

In case you want to use web.config transformations in a LightSwitch project, I have some very bad new for you: that’s NOT possible.

Web Deploy Parameterization in a visual studio web application project

Here you can find an excellent introduction on web deploy parameterization: http://vishaljoshi.blogspot.com/2010/07/web-deploy-parameterization-in-action.html. I ‘m a big fan of web deploy. Ok, it has a steep learning curve and there is a very high level of abstraction involved, but it is so powerful.

In case web deploy (parameterization) is completely new for you, I recommend that your first read the post of Vishal Joshi. Based on that, you will understand that there is a difference between the declaration of parameters (which can be done by adding a parameters.xml file in your solution) and actually injection environment specific parameter values during deploy time (either via entering the values when you deploy manually from IIS, either via attaching a SetParameterValues.xml file to the msdeploy command when you deploy via de command prompt). In vishal’s post, all this is explained in the context of a normal web application project. Note also that the content of parameter file is not restricted to parameters which involve a transformation in the web.config file. Also IIS infrastructure related parameters (like setting the application pool) can be handled via the parameter.xml file.

Here again, this is not working in LightSwitch, but fortunately we still can use a lot of the functionality when we deploy via the command line. I will explain in the next paragraph how all this can be done.

Web Deploy Parameterization in a LightSwitch project via MsDeploy from the command line.

I’ll demonstrate the power of web deploy parameterization in the context of a LightSwitch project with following goals in mind.

  1. Although LightSwitch allows to deploy the database from IIS, I’m not using this because (because my Hosting provider doesn’t support this) and as a result I want to remove all database related things from my web deploy package. Neither I care about creating a security admin during deployment, I prefer to create this via a simple sql script, rather than installing all LightSwitch Server Prereqs (which is basically doing not more than installing the security admin !)
  2. I want to be able to set my _instrinsicData connection string during deploy time and I want to read the actual connection string value from a SetParameterValues.xml file rather than typing it over and over again.
  3. I want to the extend the standard set of web deploy parameters of a LightSwitch project with a new parameter. I use a dedicated security database (apart from the “application database”); as a result I need in my app also the connection string towards the security database.
So, we assume that the database has been deployed already and a security admin is already in place. If you have problems creating these yourself I have following suggestion: deploy ONCE your test application as you normally do and make sure it works (both database and security admin are installed). You can remove then the application from IIS but leave the database intact. By doing so, you have clean starting point for what’s coming in this blog post.
The above defined goals, should normally cover everything you want to do with a LightSwitch project when it comes to web deploy parameterization.  We’ll proceed as follows:
  1. As a starting point, I’ll explain first how you can simply deploy from the command line a vanilla Lightswitch project (without the above mentioned “tweaks”).
  2. Next, we want to replace the parameters.xml file which resides in the LightSwitch web deploy package (the .zip) with our own version.
  3. Finally, we want to inject our own (environment-specific) values during deployment to IIS.

How to deploy a vanilla LightSwitch project from the command line.

We make the following assumption here: we presume that you have full access as an administrator to your IIS server and that you execute everything in a command line box with administrator rights. Obviously, you can use webdeploy also remotely and even configure IIS in such a way that a non-admin can deploy packages. That’s all cool, but it involves a lot of things which are not making the subject of this post more clear.

Furthermore, you may use any version of web deploy (1.1 or above), and the LightSwitch Server Prereqs does not have to be installed on the server !

First make sure you have a LightSwitch project (which compiles nicely) and for which you want to generate a web deploy package.

Since we want to use an additional parameter during deployment (see goal 3), we need to slightly adapt the web.config file. Open the web.config file which can be found in the ServerGenerated project and add a second connection string. (_SecurityData)

  <connectionStrings>
    <add name="_IntrinsicData" connectionString="Data Source=|SqlExpressInstanceName|;AttachDbFilename=|ApplicationDatabasePath|;Integrated Security=True;Connect Timeout=30;User Instance=True;MultipleActiveResultSets=True" />
    <add name="_SecurityData" connectionString="Data Source=|SqlExpressInstanceName|;AttachDbFilename=|ApplicationDatabasePath|;Integrated Security=True;Connect Timeout=30;User Instance=True;MultipleActiveResultSets=True" />
  </connectionStrings>

The precise value of the connection string is completely irrelevant, because we’ll set the value during deploy time. The only thing that matters is that there is at least the <add> node with the attribues name and ConnectionString.

Make sure to unselect “IIS has LightSwith prereqs installed”. We don’t need these because we don’t generate an admin user during deployment.

 

Unselect IIS has LightSwitch server prereqs installed.

 

Obviously, we want to create a package on disk rather than publishing it directly from visual studio.

 

Select Create a package on disk during Publish

 

 That’s all on the visual studio side. So, click publish and locate the .zip file on disk because that’s what we need.

 Now, drop the following lines in a .cmd file and adjust the _sourcePackagePath to the path where your .zip is located.

SET _sourcePackagePath="D:\VS2010\temp\Application2\Application2\Publish\application2.zip"

"C:\Program Files\IIS\Microsoft Web Deploy\msdeploy.exe" -source:package=%_sourcePackagePath%  -dest:auto,IncludeAcls='False',AuthType='Basic' -verb:sync -allowUntrusted skip:ObjectName=dbFullSql

pause

 Open a command line prompt (and use run as administrator !), and just run the script. Note the skip dbFullSql verb. This avoids that a database is installed. We will remove this skip verb later, when we completely tweaked the parameter file.

Tweak the existing parameters.xlm of the original .zip package

We first need to replace the existing parameters.xml file with our own version. This is done by creating a new .zip based on the old one. The two .zip files are completely identical except for the parameter file.

So, first create in the same folder where your .cmd file is a new xlm file and call it exactly “declareparameters.xml”.

Give it following content:

<parameters>
  <parameter name="ApplicationDataConnectionString" defaultValue="No default value" tags="Hidden">
    <parameterEntry kind="XmlFile" scope="web.config" match="//connectionStrings/add[@name='_IntrinsicData']/@connectionString" />
  </parameter>
<parameter name="SecurityDataConnectionString" defaultValue="No default value" tags="Hidden">
    <parameterEntry kind="XmlFile" scope="web.config" match="//connectionStrings/add[@name='_SecurityData']/@connectionString" />
  </parameter>
  <parameter name="IisWebApplication" description="IIS Web Application content location" defaultValue="NO default" tags="IisApp">
    <parameterEntry kind="ProviderPath" scope="IisApp" match="^.*\\app\.publish\\$" />
  </parameter>
</parameters>

Compare this file with the original parameters.xml file present in the .zip package:

<parameters>
  <parameter name="DatabaseAdministratorConnectionString" description="Connection used to create or update the application database." defaultValue="" tags="SQLConnectionString" />
  <parameter name="DatabaseServer" description="Name of the server that hosts the application database. Must match the server specified in the connection string." defaultValue="" tags="SQL" />
  <parameter name="DatabaseName" description="Name of the application database. Must match the database specified in the connection string." defaultValue="Application2" tags="SQL">
    <parameterEntry kind="SqlCommandVariable" scope="Application2.sql" match="DatabaseName" />
  </parameter>
  <parameter name="DatabaseUserName" description="User name that the application will use to connect to the application database." defaultValue="" tags="SQL">
    <parameterEntry kind="SqlCommandVariable" scope="Application2.sql" match="DatabaseUserName" />
  </parameter>
  <parameter name="DatabaseUserPassword" description="Password for the database user name." defaultValue="" tags="SQL,Password,New">
    <parameterEntry kind="SqlCommandVariable" scope="Application2.sql" match="DatabaseUserPassword" />
  </parameter>
  <parameter name="dbFullSql_Path" defaultValue="{DatabaseAdministratorConnectionString}" tags="Hidden">
    <parameterEntry kind="ProviderPath" scope="dbFullSql" match="Application2.sql" />
  </parameter>
  <parameter name="Update web.config connection string" defaultValue="Data Source={DatabaseServer};Database={DatabaseName};uid={DatabaseUserName};Pwd={DatabaseUserPassword};" tags="Hidden">
    <parameterEntry kind="XmlFile" scope="web.config" match="//connectionStrings/add[@name='_IntrinsicData']/@connectionString" />
  </parameter>
  <parameter name="Application2_IisWebApplication" description="IIS Web Application content location" defaultValue="Default Web Site/Application2" tags="IisApp">
    <parameterEntry kind="ProviderPath" scope="IisApp" match="^d:\\VS2010\\temp\\Application2\\Application2\\Bin\\Debug\\app\.publish\\$" />
  </parameter>
</parameters>

Note that everything (except the connection string) related to the database deployment has disappeared.  I renamed also the “Application2_IisWebApplication parameter to something more generic (by doing so, I can use the same script for all my lightswitch projects) and tweaked also the match attribute to something more generic. Basically, the match will be done now only based on

^.*\\app\.publish\\$

rather than the full path which is too application specific. Also our second connection string is now a parameter!

Ok we can now update our original .cmd file as follows (clean first the complete file):

SET _sourcePackagePath="D:\VS2010\temp\Application2\Application2\Publish\application2.zip"
SET _targetpackagePath="D:\VS2010\temp\Application2\Application2\Publish\application2_ReadyToDeploy.zip"

"C:\Program Files\IIS\Microsoft Web Deploy\msdeploy.exe" -verb:sync -source:package=%_sourcePackagePath% -dest:package=%_targetPackagePath% -declareParamFile="declareparameters.xml"

So, this is doing nothing more than throwing away the original parameters.xml file and replace it by declareParameters.xml. Currently, I didn’t find a more elegant way to do this. I admit, we have now 2 .zip files. Note that so far, nothing is deployed !

The last step is now to inject our own parameter values during deployment.

Injecting environment specific values during deployment.

In order to do this, we need a second .xml file for storing the environment specific values. So, create a new .xml file and call it SetParameterValues.xml and give it following content:

<?xml version="1.0" encoding="utf-8"?>
<parameters>
  <setParameter name="IisWebApplication" value="Default Web Site/Application2" />
  <setParameter name="ApplicationDataConnectionString" value="Data Source=.\sqlexpress;Initial Catalog=Application2;Integrated security=true" />
 <setParameter name="SecurityDataConnectionString" value="this is my second connection string" />
</parameters>

As you see, the 3 parameter values are there. One for the IisWebApplication and the two connection strings. For the securityDataConnectionString, I just used a dummy value, but it should have of course the shape of the other connection string.

Finally, we have to adapt our .cmd file to do the actual deployment based on the parameter values in this xml file.

SET _sourcePackagePath="D:\VS2010\temp\Application2\Application2\Publish\application2.zip"
SET _targetpackagePath="D:\VS2010\temp\Application2\Application2\Publish\application2_ReadyToDeploy.zip"
"C:\Program Files\IIS\Microsoft Web Deploy\msdeploy.exe" -verb:sync -source:package=%_sourcePackagePath% -dest:package=%_targetPackagePath% -declareParamFile="declareparameters.xml"
"C:\Program Files\IIS\Microsoft Web Deploy\msdeploy.exe" -source:package=%_targetPackagePath%  -dest:auto,IncludeAcls='False',AuthType='Basic' -verb:sync -allowUntrusted -setParamFile:"SetParametervalues.xml"
pause

 

As you see, we just added the last line which is doing the actual deployment to IIS. It uses of course the .._ReadyToDeploy package rather than the original one and injects the SetParameterValues.xml file during deployment. Note also that the -skip verb for database deployment is gone now, this could be done because there are no database deployment params any longer.

Conclusion

LightSwitch has unfortunately not the same flexibility to tweak package generation as a normal web application project (but has so many other nice things !). Luckily, we can perfectly tweak things via command line deployment.

You can use these scripts “manually” or from a TFS build server, so that a new version can automatically be pushed towards a staging environment.

Hope this helps.

-paul.

Categories: Uncategorized

Integrating a LightSwitch app with a third-party authentication system and use single sign-on.

Posted on 2011/07/31 by paul van bladel
No Comments

Introduction

Large enterprises often make use of a company-wide authentication system for all their internal applications.

This approach has major advantages:

  • all code and business logic related to authentication is centralised in one place;
  • the authentication mechanism can be integrated on the most suitable application layer;
  • the management of users is very convenient: when an employee leaves the company, his permissions for all applications she was previously subscribed, can be withdrawn by one mouse-click;
  • …

Nonetheless, there is an important drawback: each individual application (and typically, in a major enterprise, there are a lot of applications types: Mainframe, Java, .Net, …) needs to integrate in one way or another with this third-party authentication system.

Obviously, it is much easier to make this integration exercise when your application framework is more or less build on custom coding. The other side of the spectrum, a product, is much more difficult to integrate with the third-party authentication system.

Lightswitch keeps in a sense the middle between a product and an application development framework. Anyhow, it’s a very easy to do the kind of integration we are envisioning here.

The security context

Of course, the topic I cover here is rather generic. So let’s make following assumptions:

  • I can retrieve the user for which I have to enforce a single sign on, on the server side of my application, before the xap is accessed.
  • This makes it possible that I transform the security context of this user towards my LightSwitch app via a simple .aspx file.
  • For the rest, I’m just relying on the build-in security which LightSwitch offers (which is based on the classical aspnet membership database.

Include a transformSecurity.aspx file in the LS project

Open your LightSwitch solution and add a new file to the ServerGenerated project. This file has to be an aspx file.

You may remove the code behind file because I handle everything in the aspx file itself.

Give following content to this aspx file:

<%@ Page Title="Transform security" Language="C#" %>

<h2>

Your userId is not know for this application

</h2>

<script runat="server">

protected void Page_Load(object sender, EventArgs e)

{

string applicationName = "Application1"; //must match 

string userId= RetrieveUserFromThirdPartySystem();

if (Membership.ValidateUser(userId, "genericPassword"))

{

FormsAuthentication.SetAuthCookie(userId, false);
string url = @"~/default.htm";

Response.Redirect(this.ResolveUrl(url));

}

}

</script>

 

Note the RetrieveUserFromThirdPartySystem() method. This content of this method depends of course on your third party authentication system. In most case, the authenticated user will be taken from the Http Header. Since we are playing here in the web world (rather than the silverlight), we can execute stuff like:  userId = HttpContext.Current.Request.Headers["mySpecialHeaderTag"];

Note also the call to Membership.ValidateUser(userId, “genericPassword”),

where as password “genericPassword” is used. This seems to look as if every user in my security database will have the same password. I’ll tell you: that’s true and from a security perspective no problem at all. The reason is that the actual authentication is already done by the third-party authentication system.  The

FormsAuthentication.SetAuthCookie(userId, false);

will then make sure that the single sign-on really happens: the cookie is created based on the credentials of the already authenticated user.

When the user is authenticated, the default.htm page is opened. (that’ the one where the silverlight component is stored).

We need to make a slight adjustment to the web.config file in order to make sure that our aspx file is opened before the default.htm file  (which is normally accessed first).

 <defaultDocument>
      <files>
        <clear />
        <add value="transformSecurity.aspx" />
        <add value="default.htm" />
      </files>
    </defaultDocument>

You won’t believe it, but that’s all except one thing. How can we deploy the dedicated aspx file? Thanks to William Stacey, I know now that you can adjust the project file and make this happen. I will not reproduce here how to do this, you can read it on :  http://ourbizforward.com/LightSwitchTips/?ID=114. Thank a lot William.

 

Hope this helps.

paul.

Categories: Uncategorized
Previous Entries
© Lightswitch for the Enterprise. Proudly Powered by WordPress | Nest Theme by YChong