Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / Windows Forms

Visual Application Launcher

Rate me:
Please Sign up or sign in to vote.
4.91/5 (37 votes)
23 Jan 2012CPOL23 min read 106.4K   3.7K   116   35
A WinForms UI using WCF services, Entity Framework, repository data access, repository caching, Unit of Work, Dependency Injection, and every other buzz work you can think of!

Introduction

The Visual Application Launcher (VAL) is a WinForms application that allows delivery of icons to users that can be double clicked to launch a file.

Image 1

Background

VAL is useful for delivering bespoke applications, databases, and spreadsheets to users. In a corporate environment, it is very common to have data in formats such as these that must be referred to by different departments.

E.g., Amy works in accounts, she needs access to the 'Budget Data' spreadsheet, an accounting Access database, an Intranet .NET web application, and an in-house .NET WinForms application. John works in marketing, he needs access to the 'Budget Data' spreadsheet and the Intranet .NET web application. Dylan works in IT and needs access to everything! How would you maintain all of this information? If the budget data spreadsheet is on a shared network drive, how will the user know where to go to launch the file? This can be emailed around to the different users, but wouldn't it be easier if all of this could be centrally maintained and a list of 'shortcuts' delivered to the users in a fancy user interface?

This is something that is often centrally managed for the main functionality of a user profile. Using the 'Amy' example, the domain administrator would grant permissions on the account to deliver finance specific software that may be available to select from the user's 'Start' menu. However, this will rarely go to the granular level of particular workbooks located on shares.

This is where VAL comes in - VAL has the concept of Groups, Users, and Files. Users and Files can be assigned to Groups and the system determines which Files the User has access to by calculating their overall group membership.

Since the database schema for this application is simple, I decided to create an application that tests many of the current technologies and Design Patterns being used in .NET today. While this works, it's massively over engineered for what it needs to achieve!

VAL is meant as a complete example of a number of .NET technologies and Design Patterns.

The solution contains examples of the following...

  • Using Entity Framework 4.2 (EF) to access data in a SQL Server database
  • Using the Repository pattern to access data
  • Using Dependency Injection (DI) to create loosely coupled domain services
  • Using StructureMap to inject dependencies
  • Using WCF services to control access to domain services
  • Using the ChannelFactory to invoke instances of WCF services
  • Handling Exceptions in the WCF service
  • Creating custom behaviours for WCF to control StructureMap and Errors
  • Creating custom Faults, throwing Faults to our client application
  • Creating a domain model using POCO objects
  • Using Data Annotations to provide instance validation of our POCO objects
  • Tests
  • Using Moq with fake data to test method implementations

Prerequisites

You'll need at least the following to use the application...

  • SQL Server 2005 or later
  • A machine (local or remote) with IIS
  • Visual Studio 2010
  • .NET 4.0

VAL also uses a number of Open Source libraries which are included as compiled assemblies and are located in the packages directory of the solution. You can use the compiled assemblies, replace them with your own versions, or download source \ binaries from the associated web sites. The packages directory was created by NuGet, so you can simply update the packages if you are using NuGet.

If you don't want to use the compiled assemblies included in this download, just drop the replacements into packages/lib and the solution will pick them up when you compile:

Getting Started

The first thing you'll need to do is create the database for the application, the virtual directory in IIS for the WCF services, and make a few changes to the app configuration.

I've included a ReadMe.txt that describes each setting and its impact on the system; please make sure you read through and follow the guide when trying to get the application ready to run. Spending a few minutes going through the config is important.

Application Screens and Overview

The main user interface is a simple window that displays icons, this is the only screen that the majority of users of the system will ever see.

There are also administration screens that allow you to configure the different properties of VAL. Only members of the 'Administration Group' will have access to these screens.

Admin MDI Window

The admin window allows you to launch the various maintenance windows by clicking on the icons in the Toolbar. Each screen maintains a particular group of data.

Image 2

User Maintenance

The user maintenance screen is where you create user profiles for use with VAL. Either manually type or use the 'Active Directory Browser' section to pick a user from your AD.

Image 3

File Maintenance

File maintenance is where you define icons to be delivered to end users. Configure the location of the programs to launch, their types, the icon to display, and various other options.

Image 4

Group Maintenance

The group maintenance screen is where you create groups and where you assign users and files to particular groups. This determines the set of overall permissions (and therefore the icons displayed to the user).

Image 5

That's a quick overview of the different screens available in VAL. Please see the associated help file included with the application which provides full details of each maintenance screen.

Using the Code

There's a lot of code to describe, so consider the following logical diagram first. This describes the data flow process from the highest level (the UI) across application boundaries (WCF communication) to the lowest level (the SQL database).

Image 6

This article will describe the process from the lowest level first.

The Database

There's very little to describe in the database, there are only tables, indexes, and relationships to consider in the schema. There are no views or procedures used by the application source code. Cascade operations will occur on related tables during a Delete operation.

The Entity Framework and the Repository Pattern

Access to data has been controlled using the Repository Pattern.

The class EntityRepository<t> is the generic class used for repository access. The interface IRepository is used in all service constructs, so we can use Dependency Injection to provide different implementations at run time. This release (29/12/2012) has changed significantly from the previous version which all used concrete repository classes and restricted access based on the requirements of individual repositories. This approach removes a large number of classes from the solution, simplifies the overall project structure, and improves the ease of unit testing.

C#
public interface IRepository<T>
{
    // Method definitions
}

public class EntityRepository&: 
    EntityRepository<T> where T : PocoEntityBase
{
    // Implementation of IRepository

}

The repository exposes an implementation of IQueryable<t> which provides access to the underlying data using standard LINQ syntax.

C#
var query = from g in Repository.Table
            where g.GroupUsers.Any(gu => gu.UserID == userId && g.IsGroupActive == true)
            select g;

The Domain Services

In VAL, the domain services are where most of the magic happens. The services here will perform a number of functions such as:

  • Tracing and logging
  • Parameter validation and exception raising
  • Repository access to perform data retrieval \ updates

The domain services have been designed with Dependency Injection in mind. A typical class signature for a service will read as follows. The DomainServiceBase class gives access to some basic logging functionality to be shared by all service classes. The only construct on the service requires objects that implement IRepository, which will allow us to test this service with fake data at a later point.

C#
public class GroupDomainService : DomainServiceBase, IGroupService
{
    public GroupDomainService(IRepository<Group> repository, 
        IRepository<GroupUser> groupUserRepository, 
        IRepository<Permission> permissionsRepository, 
        IDbContext dataContext)
    {
        this.Repository = repository;
        this.GroupUserRepository = groupUserRepository;
        this.PermissionRepository = permissionsRepository;
        this.DataContext = dataContext;
    }

    private IRepository<Group> Repository { get; set; }
    private IRepository<GroupUser> GroupUserRepository { get; set; }
    private IRepository<Permission> PermissionRepository { get; set; }
    private IDbContext DataContext { get; set; }
}

A typical method in the domain services layer will do the following...

  • Debug-Log entering the method
  • Validate the required parameters
  • Some sort of Repository access (Retrieve \ Update)
  • Info-Log method values \ variables
  • Debug-Log exiting the method
  • Return data (if applicable)
C#
public Group[] GetUserGroups(int userId)
{
    #region Enter method Tracing
    if (log.IsDebugEnabled)
    {
        log.Debug("Entered " + this.GetType().ToString() + " - " + 
          System.Reflection.MethodBase.GetCurrentMethod().ToString());
    }
    #endregion

    #region Guard Parameter Validation
    Guard.Against<ArgumentException>(userId <= 0, "User id must be greater than zero");
    #endregion

    var query = from g in Repository.Table
                where g.GroupUsers.Any(gu => gu.UserID == userId && g.IsGroupActive == true)
                select g;

    var groups = query.ToArray();

    #region Exit Method Tracing
    if (log.IsDebugEnabled)
    {
        log.Debug("Completed " + this.GetType().ToString() + " - " + 
          System.Reflection.MethodBase.GetCurrentMethod().ToString());
    }
    #endregion

    return groups;
}

The Application Services (WCF)

The WCF layer provides communication endpoints for the consumers to access the underlying subsystems. The WCF layer is responsible for the following operations:

  • Security check the identity of the caller
  • Access the particular domain service
  • Return data (if applicable)
  • Catch expected exceptions and convert into faults for the client

A method in the WCF services layer that will handle Exceptions and faults will read as follows:

C#
[VALAdministrators(SecurityAction.Demand)]
public User SaveUser(User user)
{          
    try
    {
        user = Service.SaveUser(user);
    }
    catch (BusinessRuleException ex)
    {
        var fault = new BusinessRulesFault(ex);
        throw new FaultException<BusinessRulesFault>(
                  fault, new FaultReason(fault.Message));
    }                 
    catch (UserAlreadyExistsException ex)
    {
        var fault = new UserFault(ex, user.WindowsIdentityName);
        throw new FaultException<UserFault>(fault, new FaultReason(fault.Message));
    }
    return user;
}

All WCF services will have a class signature that reads similar to the following:

C#
public class StandardUserService : WcfServiceBase, IUserService
{
    #region IUserService Members

    #region Ctor

    public StandardUserService(IFileService fileService, 
        IUserService userService)
    {
        this.FileService = fileService;
        this.UserService = userService;
    }

    #endregion
}

Some important points to note about the signature:

  • It inherits from WcfServiceBase, a simple base class to provide logging functionality via the protected log4net.ILog instance
  • The WCF service construct requires arguments

The last point is important, WCF will normally take care of creating service objects and it expects a parameterless construct. If we want to use the services with StructureMap for Dependency Injection, then we need to be able to pass parameters to the construct. We therefore need a way of allowing parameters and having them wire up with StructureMap automagically.

The good thing about WCF services is that they are highly customizable, we can create Service Behaviours, and configure our WCF environment to use them. Between Jimmy Bogard and Scott Griffin, I borrowed the solution they found for the problem. :o)

The four classes in the ServiceBehaviour folder (StructureMapInstanceProvider, StructureMapServiceBehavior, StructureMapServiceHostFactory, StructureMapServiceHost) provide the functionality for wiring up WCF services to StructureMap, allowing us to specify the dependencies to be injected into the constructs.

Dependency Chain

There is a dependency chain in the system that is resolved by StructureMap. When an instance of an object is requested, if it has a parameterised construct, then StructureMap will also attempt to inject an instance of *that* object into the instance.

In VAL, the dependency resolution begins when a request to a WCF service method is received. This initiates a dependency chain that looks similar to this graphic.

Image 7

Once the DataSettings class has been initialised, all dependencies are fulfilled and StructureMap is able to create instances of every object in the chain. It's important to understand this concept, if there is an error in one of the classes deeper into the chain, it will cause an Exception in Structuremap.

Unit of Work Behaviour

Entity Framework 4.2 uses the new DbContext API, which is a 'Unit of Work'. During its scope (which is per HTTP request) we can make numerous changes to the entities which are change tracked by the context. Until we call SaveChanges, none of these changes are actually committed to the database. StructureMap will create this single instance per request and inject into any other object that requires an instance as part of its construct.

In order to aid testing and to ensure our services remain loosely coupled, we can abstract out the core functionality we require from the data context into a new interface, IDbContext.

C#
public interface IDbContext
{
    IDbSet<TEntity> Set<TEntity>() where TEntity : PocoEntityBase;

    int SaveChanges();

    EntityState GetState(object entity);

    void SetModifed(object original, object updated);

    void SetEntityState(object entity, EntityState state);
}

The method signatures for IDbContext allow us a way to retrieve and update data. The concrete implementation of this interface is then handled in the DataContext (in project VAL.Data).

Service Behaviours

In order for StructureMap to create instances of the WCF services and resolve all dependencies, we need to attach the behaviour into the WCF service. We can apply this as part of the service instantiation within our implementation of IServiceBehavior. The class StructureMapServiceBehavior implements this interface, and has a method body for ApplyDispatchBehavior.

C#
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
                                  ServiceHostBase serviceHostBase)
{
    var errorHandler = new ErrorHandler();

    foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher cd = cdb as ChannelDispatcher;
        if (cd != null)
        {
            cd.ErrorHandlers.Add(errorHandler);

            foreach (EndpointDispatcher ed in cd.Endpoints)
            {
                ed.DispatchRuntime.InstanceProvider =
                    new StructureMapInstanceProvider(serviceDescription.ServiceType);
            }
        }
    }
}

In the above code, we create an instance of ErrorHandler which provides our custom service behaviours. We attach the error handler instance to the ChannelDispatcher and add new instances of StructureMapInstanceProvider to the InstanceProvider, this allows StructureMap to intercept the request for the service and inject the required dependencies. Now, every time StructureMap is asked to create an instance of a WCF service, it will apply our custom behaviours.

Service Contracts

In VAL, we control the codebase for both the client and the server - I wanted to share the definitions of the services between both.

I therefore created an assembly named VAL.Contracts that contains definitions of the service methods. A service interface will read similar to:

C#
[ServiceContract()]
public interface IStandardUserService
{
    [OperationContract]
    File[] GetUserFiles(int userId);

    // additional methods...
}

The Interfaces provide the contracts between the client and the server that allow them to communicate. The WCF services implement these interfaces and the client can invoke these implementations using the WCFServiceClient code to locate the contract endpoints as configured in the client app.config.

Handling Security in the WCF Service

VAL is designed to be run in a secure 'trusted' environment such as a corporate network. Additionally, the information transferred by VAL cannot be classified as sensitive. Therefore, we can choose a security model based on the following:

  • System security can be roughly grouped into 'Administrator' and 'Non Administrator' functionality
  • We don't need encrypted transport
  • We need to enforce group membership of the user account running the VAL client

We could use WSHttpBinding here, but for this model, BasicHttpBinding is fine. Much like the classic 'Web Service' model, we just need to call a method and make sure that the user is allowed access to that method.

We can accomplish this by setting up the configuration bindings to transfer the user account details. However, we need to initialise the security credentials within the client proxy base.

This happens within the WCFServiceClient class in the VAL UI project. The client provides a custom implementation of ClientBase which allows us to set the credentials and also provide a Dispose pattern for handling clean up.

C#
public class WCFServiceClient<T> : ClientBase<T>,
        IDisposable where T : class
{
    #region ctors
    public WCFServiceClient()
    {
        this.ClientCredentials.Windows.AllowedImpersonationLevel = 
          System.Security.Principal.TokenImpersonationLevel.Impersonation;
        this.ClientCredentials.Windows.ClientCredential = 
          System.Net.CredentialCache.DefaultNetworkCredentials;
    }

    #endregion ctors

    void IDisposable.Dispose()
    {
        if (State == CommunicationState.Faulted)
        {
            Abort();
        }
        else
        {
            try
            {
                Close();
            }
            catch
            {
                Abort();
            }
        }
    }
}
XML
<security mode="TransportCredentialOnly">
    <transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />

    <message clientCredentialType="UserName" algorithmSuite="Default" />
</security>

Custom Security Attributes

Because security is a configuration value, we can keep the code clean by defining our own implementation of CodeAccessSecurityAttribute which allows us to read a string from the config and apply that as the 'Require group membership' value.

C#
public class VALAdministratorsAttribute : PrincipalPermissionExAttribute
{
    public VALAdministratorsAttribute(SecurityAction action)
        : base(action, "AdministratorGroup")
    {
    }
}

The VALAdministratorsAttribute class constructs an instance of PrincipalPermissionExAttribute and instructs it to read the value of AdministratorGroup from the config file. This value is then used to enforce the Security.Demand call.

Any permissions that fail are reported back to the client application as a SecurityFault.

Handling Faults in the WCF Service

There are a number of circumstances where the domain services will throw exceptions. One such example is object validation, which should occur on the client application but will also happen on the server as part of 'Save' operations. If the object is invalid, an exception will be raised.

In cases where faults are expected, we need to return this information to the client so it can be displayed to the user. In all other cases, we want to log the exception details on the server but shield the exception details from the user. Expected exceptions are caught by the WCF service and converted into Faults that are then returned to the client. The client is responsible for catching and handling the expected Faults.

The Domain Model

The model was generated using the 'Reverse Engineer Code First' context menu that is available after installing Entity Framework Power Tools. Once the entities had been generated, a number of amendments were made.

  • All domain model objects inherit from PocoEntityBase

The PocoEntityBase class provides some functionality for enforcing simple instance rules and taking advantage of the DataAnnotations syntax that is used in MVC. The class has anumber of properties, Id, IsValid and ErrorMessage, that will allow you to check for broken rules and display the appropriate error message to the caller.

C#
[IgnoreDataMember()]
public bool IsValid
{
    get
    {
        this.errors = DataValidator.Validate(this);
        return (!errors.Any());
    }
}

[IgnoreDataMember()]
public string ErrorMessage
{
    get
    {
        var errorText = new StringBuilder();
        foreach (var error in errors)
        {
            errorText.Append(error.ErrorMessage + Environment.NewLine);
        }
        return errorText.ToString();
    }
}

To use this functionality, a Model class must have associated MetaData that provides the rules to be enforced. Due to the Model classes being generated by templates, a bit of a hacky approach is used by creating Buddy Classes that provide only metadata in a separate partial class. This allows you to auto generate your main model, while keeping your custom data annotations for model validation safe.

This MetaData is automatically wired into MVC, but in WinForms, we need to attempt to retrieve the MetaData ourselves. The DataValidator class exposes a single method that will attempt to validate an object by wiring up its MetaData and calling the Validator.TryValidateObject method.

C#
public class DataValidator
{
    public static List<ValidationResult> Validate(object instance)
    {
        Type instanceType = instance.GetType();
        Type metaData = null;
        var metaAttr = (MetadataTypeAttribute[])
          instanceType.GetCustomAttributes(typeof(MetadataTypeAttribute), true);

        if (metaAttr.Count() > 0)
        {
            metaData = metaAttr[0].MetadataClassType;
        }
        else
        {
            throw new InvalidOperationException(
              "Cannot validate object, no metadata " + 
              "assoicated with the specified type");
        }

        TypeDescriptor.AddProviderTransparent(
            new AssociatedMetadataTypeTypeDescriptionProvider(
            instanceType, metaData), instanceType);

        var results = new List<ValidationResult>();
        ValidationContext ctx = new ValidationContext(instance, null, null);

        bool valid = Validator.TryValidateObject(instance, ctx, results, true);
        return results;
    }
}

You can now create MetaData classes for your model objects in the standard format.

C#
[MetadataType(typeof(GroupMetaData))]
public partial class Group
{
    public Group()
    {
    }

    #region Internal MetaData class

    internal class GroupMetaData
    {
        #region Primitive Properties

        [Required(ErrorMessage = "You must enter a description for the group")]
        [DisplayName("Description:")]
        [StringLength(50)]
        public virtual string Description {get; set;}

        #endregion
    }

    #endregion
}

This keeps all of the 'simple' rules in one place in the domain model. Any consumers of our Model will have validation available to them with very little effort. Our WinForms application enforces the rules in the following manner:

C#
if (!this.group.IsValid)
{
    Messaging.ShowError(group.ErrorMessage);
    return;
}

The Model classes really are the key to the system. They provide access to the data in strongly typed form, they form the basis of the messages to be transferred over the wire, and they provide information about themselves to the client for validation.

WCF Service Invocation

Using svcutil, you can create client side proxy classes to invoke your service.

We can then change the generated class to use our custom base class that provides the security initialisation and Dispose pattern. The standard using pattern is then adhered to when calling our service methods.

C#
using (var service = new VAL.ClientServices.UserServiceClient())
{
    files = service.GetUserFiles(Convert.ToInt32(e.Argument));   
}

Unit Tests - Domain Classes

The last project in the VAL solution contains a number of unit tests. The tests make use of the Moq framework and some dummy data which we can query and return results from using our existing domain services.

Within the unit test initialisation code, we create the Mock objects and Setup the repository methods to use our fake data:

C#
[TestInitialize]
public void TestInitialize()
{
    var list = TestDataObjectCreator.GetFilesList();

    // Setup the mock repository to use a list of objects as it's query source
    _fileRepository.Setup(f => f.Table).Returns(list.AsQueryable());

    _fileRepository.Setup(f => f.GetById(It.IsAny<int>())).Returns
        ((int id) => list.Find(i => i.Id == id));

    // Setup the delete method to remove the item from the list we created above
    _fileRepository.Setup(f => f.Delete(It.IsAny<File>())).Callback
        ((File item) => list.Remove(item));

    // Setup the add method
    _fileRepository.Setup(f => f.Add(It.IsAny<File>())).Callback
        ((File item) =>
        {
            if (item.Id == 0)
            {
                list.Add(item);
            }
        });

    // We only need to setup the repository access for file types
    _fileTypeRepository.Setup(f => f.Table).Returns(
                  TestDataObjectCreator.GetFileTypes().AsQueryable());

    // Pass in the mock objects to our service ctor
    this.Service = new FileDomainService(this._fileRepository.Object, 
        this._fileTypeRepository.Object, _dataContext.Object);
}

I'm using the standard built-in 'Visual Studio Unit Testing Framework' for the test setups:

C#
[TestMethod()]
[ExpectedException(typeof(UserAlreadyExistsException))]
public void Same_Windows_Identity_Throws_Exception()
{
    var someUser = new User
    {
        Forename = "Dylan",
        Surname = "Morley",
        WindowsIdentityName = ExistingUserName,
        Id = 0,
        IsActive = true
    };

    someUser = Service.SaveUser(someUser);
}

In the above example test, we are ensuring that trying to save an object with the same WindowsIdentityName as an existing object will throw an exception named UserAlreadyExistsException.

This is one of the major benefits of using a Dependency Injection framework such as StructureMap. The time we have spent creating loosely coupled domain services using interfaces in our constructors is rewarded here, as we can send whatever test objects we like into the services and ensure the functionality within them works as expected without requiring any underlying real data access.

Moq provides a simple and concise way to swap out the concrete implementations with Mock objects to ensure our service logic is correct and functioning as expected.

Unit Tests - WCF Services

A well as testing our domain classes, there are tests for the WCF service implementations. The WCF services have a different concern to the domain classes, so we only want to test that they are doing their job as expected. We don't need to test 'over the wire' here, we can assume that this has all been tested by Microsoft! We just want to test that our code is operating correctly.

In these tests, we can simply Mock the domain service return values - we don't need to test these again, that's all been done with our previous tests. We can follow a strict Setup -> Act pattern for this stage of testing.

Consider the following test. All I want to test is that whenever an inactive user is requested, a FaultException is returned by the WCF service. We can therefore setup the domain service to simply return an Inactive user object we create in the test. All we are testing here is that the exception is raised and returned as expected.

C#
[TestMethod]
[ExpectedException(typeof(FaultException<UserFault>))]
public void Get_Inactive_User_Throws_Fault()
{
    // Setup
    string windowsName = "DMORLEY";
    var user = new User
    {
        WindowsIdentityName = windowsName,
        Id = 1,
        Forename = "Dylan",
        Surname = "Morley",
        IsActive = false
    };
    _userService.Setup(us => us.GetUser(windowsName)).Returns(user);
    
    // Act
    var result = this.Service.GetUser(windowsName);
}

Beyond Code - Application Usage

Forgetting the code, what else is VAL useful for?

Access Databases - Prior to Access 2007

VAL will help you manage Access database usage, particularly if you have databases being used in a thin client (Citrix \ Terminal Services) environment.

In Access 2007, Microsoft changed the security model for the new database format to remove user level security. However, Access 2007 is backwards compatible and can still open the .mdb format and work with user security. A number of organisations are still using older versions of Office or have invested time in creating user-level security based databases, so this functionality may still be useful.

VAL allows you to specify a Network workgroup. This should be an mdw file that is stored somewhere on your local network that all users of VAL would have access to. The network workgroup is the central location where you would manage user accounts and group membership.

Access is notorious for data corruption, especially so in a thin client environment. Imagine that several users are all connected to the same Citrix Server, which we'll call SERVER01. They haven't signed on to a particular workgroup and are all using the default user account which is 'Admin'. This means that Access will attempt to create record locks for both the default system.mdw workgroup in the 'Microsoft Office' installation directory on SERVER01 and also for the particular Access database. These are LDB files, which contain a record of the user and machine name locking the file and records.

Alarm bells should be ringing here. You have users all connected from machine SERVER01 all using the same credentials of Admin, sharing an ldb file on a disk where Microsoft Office is installed! This is a recipe for disaster and can lead to corruption of both the Access database and the system.mdw file.

There are a few rules to follow when using databases in this setup to avoid data corruption.

  • Databases should be split into compiled front end (MDE) and data back end (MDB) files
  • Users should launch their own copies of the front end
  • Users should sign on through a workgroup
  • Users should sign on to the workgroup using a user specific account, not using 'Admin'
  • Users should sign on to their own copy of the workgroup

I've found that when using the above rules, data corruption of Access databases is at an absolute minimum.

VAL is geared towards helping out with this scenario; you can define applications as 'Access databases' and have VAL automatically distribute the compiled front ends and workgroups to a user specific location. VAL will also attempt to sign the user on to the database transparently.

Workgroup Definition

For example, say you have your Network workgroup on a shared mapped drive, the Workgroup path may read something like K:\Public\AccessDatabases\Security\CompanyWorkgroup.mdw.

If you have defined your file in VAL as an Access database type and specified this workgroup, when you launch the Access database, VAL will take a copy of the mdw file from the network location and put it in a location specific to the user. This could be in something like, C:\Documents and Settings\username\Application Data\ or in any other location you like, as long as it is unique to the user.

VAL will then attempt to sign on via the copy of the workgroup which will now only ever contain one record lock for the individual user. VAL will also pass the Environment.UserName to the workgroup sign-on and attempt to sign the user into the workgroup. This ensures that the LDB file for the Access database contains information such as SERVER01 DMORLEY, SERVER01 ANOTHERUSER, SERVER01 THIRDUSER.

By forcing the user to sign on using their account name, the shared LDB file for the Access database will contain unique user identifiers, even when using the same server thin client.

Access Permissions

Managing access permissions can be a tedious process. If you setup a user in VAL, ensuring that the user is also created in your Network workgroup and assigned group membership adds another layer of complexity.

VAL allows you to clone a new user from an existing user. As well as creating all the icons for the user, it will attempt to copy Access database permissions from the source user, ensuring that the user account and permissions are all correctly assigned.

Image 8

This is achieved through ADODB and ADOX; please see the source file WorkgroupHelper which uses Reflection to 'late-bind' the COM functionality. Use the 'New User' wizard to step through the cloning process.

Image 9

Access Databases - Summary

The workgroup settings for the Network workgroup should be defined in the app.config VAL settings for enterpriseWorkgroupDirectory and enterpriseWorkgroupName. Only define these if you are using legacy Access database security models, otherwise they can be left blank.

Internet links

VAL is also useful for distributing web site addresses as icons. For example, you may have a number of in-house intranet applications that can be accessed from particular URLs. Individual users have to keep a record of these URLs in something like their browser favourites.

In VAL, you can create a new file and select the browser to launch, e.g., C:\Program Files\Internet Explorer\iexplore.exe. VAL will automatically retrieve the Internet Explorer icon and assign it to your file. Now you can replace this with any image of your choice and then create an entry in the command line section of the file maintenance screen and point it at any URL you like. Imagine we wanted an icon for the 'CodeProject', you would enter http://www.codeproject.com/.

When VAL attempts to launch this, it will build the following string to start the application: "C:\Program Files\Internet Explorer\iexplore.exe" http://www.codeproject.com/.

This is a very simple way of delivering web applications to users as an Icon that represents your link. If any of the URLs change, you have a central location to maintain the data.

Distributing Files - General

You can always distribute files, regardless of the underlying file type. As long as the account running the VAL client has access to the location specific in the File Maintenance screen, it can take a copy of the file and place in another location.

There are various reasons for wanting users to launch via a file copy, VAL lets you manage this easily.

Summary

This is a summary of the architecture of the Visual Application Launcher and its possible uses. As mentioned at the start of the article, it's an over-engineered solution for such a simple application, but the real purpose here is to show how we can use various tools at our disposal.

Acknowledgements

Like most programmers, I learn a lot from Internet resources and example projects. A lot of the code in this solution can probably be found on the Internet in certain forms. Where I've used complete classes, any headers and credits will point you to the original authors. In other cases, I've found snippets and solutions on message boards and incorporated them into larger classes. In these cases, I can't credit everyone because I've forgotten where everything came from!

A big thank you to people who take time out of their busy schedules to answer questions, point us towards resources, or simply make projects Open Source.

To Do

The next step here is to create a WPF front-end rather than WinForms and have it consume the same service model. That'll be in a future article :)

History

  • 29/12/2012 - Entity Framework update and code simplification.
    • Updated Entity Framework to version 4.2 'Code First' syntax.
    • Removed repository project \ Unit of Work implementation. Now using single generic repository and DbContext API.
    • Removed all concrete repositories and repository traits. Much simpler and cleaner approach.
    • Added NuGet packages.
    • Added Moq framework and updated all tests to use Moq.
  • 15/9/2011: Updated article to reflect changes made on 24/6/2011.
    • Removed WCF Service Invoker and ChannelFactoryManager details, now obsolete.
    • Added details about Unit of Work per Request pattern and how this is controlled from StructureMap.
    • Added information about custom security attributes.
  • 24/6/2011: Major changes to the overall structure of the project.
    • Added an implementation of IDispatchMessageInspector to handle 'before' and 'after' WCF events.
    • Unit of work now controlled through the IDispatchMessageInspector, a 'unit of work per request' approach. Simplifies code in the actual WCF service.
    • Simplified WCF Services into two svc files, one 'User' service and one 'Administrator' service.
    • Simplified the domain services and contracts, removed unnecessary classes.
    • Added attribute based security to the Administrator Service.
    • Removed WCF Service invoker proxy-generator. Access to WCF services now through ClientBase<>.
    • Generally tidied up the organisation of the project and various other improvements :)
  • 25/03/2011 - Edits. Grammar and readability.
  • 24/03/2011 - Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionVB 6.0 "Complete Error Handler" Pin
Member 133578675-Sep-18 21:51
Member 133578675-Sep-18 21:51 
GeneralMy vote of 2 Pin
gMagnus8712-Aug-17 11:27
gMagnus8712-Aug-17 11:27 
QuestionHey Dylan, I just wrote something similar (at least pattern wise) may be of interest Pin
Sacha Barber24-Jan-12 0:07
Sacha Barber24-Jan-12 0:07 
AnswerRe: Hey Dylan, I just wrote something similar (at least pattern wise) may be of interest Pin
Dylan Morley30-Jan-12 4:30
Dylan Morley30-Jan-12 4:30 
GeneralRe: Hey Dylan, I just wrote something similar (at least pattern wise) may be of interest Pin
Sacha Barber30-Jan-12 4:43
Sacha Barber30-Jan-12 4:43 
GeneralMy vote of 5 Pin
thatraja23-Jan-12 3:00
professionalthatraja23-Jan-12 3:00 
Questionwindows user profile service? Pin
VC Sekhar Parepalli19-Sep-11 5:35
VC Sekhar Parepalli19-Sep-11 5:35 
GeneralMy vote of 5 Pin
Sergio Andrés Gutiérrez Rojas15-Sep-11 15:53
Sergio Andrés Gutiérrez Rojas15-Sep-11 15:53 
GeneralMy vote of 5 Pin
Filip D'haene15-Sep-11 14:24
Filip D'haene15-Sep-11 14:24 
SuggestionArticle needs updating Pin
Claire Streb12-Sep-11 10:09
Claire Streb12-Sep-11 10:09 
GeneralRe: Article needs updating Pin
Dylan Morley12-Sep-11 22:23
Dylan Morley12-Sep-11 22:23 
GeneralRe: Article needs updating Pin
Claire Streb16-Sep-11 4:26
Claire Streb16-Sep-11 4:26 
GeneralRe: Article needs updating Pin
Dylan Morley16-Sep-11 4:39
Dylan Morley16-Sep-11 4:39 
Questionuse store proc with complex type Pin
Member 267080229-Aug-11 11:28
Member 267080229-Aug-11 11:28 
QuestionWhat does this mean? Pin
chr100721-Jun-11 5:43
chr100721-Jun-11 5:43 
AnswerRe: What does this mean? Pin
Dylan Morley21-Jun-11 5:49
Dylan Morley21-Jun-11 5:49 
AnswerRe: What does this mean? Pin
Dylan Morley21-Jun-11 5:50
Dylan Morley21-Jun-11 5:50 
GeneralWhat do i need to run the application Pin
chr100719-May-11 13:50
chr100719-May-11 13:50 
GeneralRe: What do i need to run the application Pin
Dylan Morley19-May-11 22:28
Dylan Morley19-May-11 22:28 
GeneralRe: What do i need to run the application Pin
chr100722-May-11 19:45
chr100722-May-11 19:45 
GeneralRe: What do i need to run the application Pin
Dylan Morley22-May-11 22:01
Dylan Morley22-May-11 22:01 
GeneralMy vote of 5 Pin
rawi28-Mar-11 21:51
rawi28-Mar-11 21:51 
QuestionError on compile Pin
rawi28-Mar-11 21:47
rawi28-Mar-11 21:47 
Hi Dylan,

this is a great article and I already found some good ideas for my current project.
If I compile the solution, I get the following error:

Error 49 Unable to resolve type 'C1.Win.C1FlexGrid.C1FlexGrid, C1.Win.C1FlexGrid, Version=2.5.20052.212, Culture=neutral, PublicKeyToken=c9c7ad9c0a5706c9' D:\Users\xxx\Documents\Visual Studio 2010\Projects\VAL\VAL.GUI\licenses.licx 1 VAL

I assume, there is a comercial component used, which I don't have.
So my Question, how can I work around this?

Thanks

Ralf
AnswerRe: Error on compile Pin
Dylan Morley28-Mar-11 22:49
Dylan Morley28-Mar-11 22:49 
GeneralMy vote of 5 Pin
linuxjr25-Mar-11 8:05
professionallinuxjr25-Mar-11 8:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.