Dealing with Direct Object References in Web Api and Angular

I blogged here a long time ago about one way to deal with direct object references in ASP.NET MVC application.  Today I want to describe my solution to the same problem in an Angular application that is using ASP.NET Web Api for server data access.

Core approach is still the same, and I will still use the same encryption utility.  Since I do not have access to a specific controller type in this case, you have to decide what password you are going to use.  I am going to omit my process of picking a valid password.

Here is the outline of my solution

  • When I show the data on the search screen, my results are going to contain encrypted ids.
  • When an item is selected, I am going to pass this id to details Angular controller.
  • The controller will pass this id to the Web Api controller
  • I am going to inject a model binder that will dynamically decrypt the ID before Web Api controller gets it.
  • I will use a helper method inside my mapping API, based on AutoMapper that maps my Entity Framework objects to business objects, that will automatically encrypt primary key (integer).  To assist in this task I will add an interface to business objects that need this functionality

Let’s get started.

First of all, in the app I am working on I have separate business layer (custom objects) and database layer(Entity Framework Code first objects).  In most cases they are similar, and I use AutoMapper to map entity framework objects to returned business objects.  I have a helper class – MappingHelper that does the work.  In Map method it just calls AutoMapper Api.  I like the existence of the helper class because it allows me to inject extra code before and after the mappings.  In this case I am going to make it aware of the new interfaces:

    public interface IHaveEncryptedPrimaryKey : IHavePrimaryKey
    {
        string EncryptedId { get; set; }
    }
    public interface IHavePrimaryKey
    {
        int PrimaryKey { get; }
    }

Now, my mapping helper can take advantage of these in a simple fashion:

        public static TDestination Map<TDestination>(object source)
        {
            var returnValue = Mapper.Map<TDestination>(source);
            var itemWithEncryptedKey = returnValue as IHaveEncryptedPrimaryKey;
            if (itemWithEncryptedKey != null)
            {
                itemWithEncryptedKey.EncryptedId = EncryptionUtility.Encrypt(
                    itemWithEncryptedKey.PrimaryKey.ToString(CultureInfo.InvariantCulture), "your password here", true);
            }
            return returnValue;
        }

Very elegant, and I do not have to pepper many classes with the encryption code, just implement the interface on those classes that need it.  Here is an example:

    public class Role : IHaveEncryptedPrimaryKey
    {
        public Role()
        {
            Name = string.Empty;
            Description = string.Empty;
        }
        public int RoleId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        [JsonIgnore]
        public int PrimaryKey { get { return RoleId; }}


        public string EncryptedId { get; set; }
    }

I am using JsonIgnore not to output the primary key column, the rest is obvious. So, now I can dynamically encrypt primary keys as I am mapping database objects to business objects.

The code in Angular controller does not change much.  I simply need to pass encrypted id property instead of real ID from “search” Angular controller to “edit” Angular controller.  Something similar to the following (in TypeScript)

            $scope.editRole = (role: IRoleInfo) => {
                $location.path("/roles/edit/" + role.EncryptedId);
            };

Finally, when I am inside “edit” controller, I need to call Web Api controller, similar to the following:

  roleService.getRole(id, (result: IRole) => {
                    $scope.model = result;
                });

The last part is transparently handle the decryption of the ID before it is passed to the controller.  I will use model binders here, specifically I will write a class that implements IModelBinder.  The code is very simple.  If I expect an integer, but see something else, I will assume it is an encrypted ID.

    public class EncryptedIdModelBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (input != null && !string.IsNullOrEmpty(input.AttemptedValue))
            {
                if (bindingContext.ModelType == typeof(int))
                {
                    int id;
                    if (!int.TryParse(input.AttemptedValue, out id))
                    {
                        var decrypted =
                            EncryptionUtility.Decrypt(input.AttemptedValue, "your password here", true);
                        if (int.TryParse(decrypted, out id))
                        {
                            bindingContext.Model = id;
                        }
                        else
                        {
                           bindingContext.Model = int.MinValue;
                        }
                    }
                    else
                    {
                        bindingContext.Model = id;
                    }
                }
            }

            return true;
        }
    }

 

Now, my Web Api controller method does not change!  It will still have a parameter called “id” that is an integer. 

        [Route("find"), HttpGet]
        public ExecutionResult<Role> Find(int id)
        {
            return Execute(() =>
            {
                var role = MappingHelper.Map<Role>(Repository<IRoleRepository>().GetRole(id));
                return role;
            });

        }

The last step is to add my new binder to global configuration.

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.BindParameter(typeof(int), new EncryptedIdModelBinder());
        }
    }

That is all there is to it.

Enjoy.

One Comment

  1. Thank you very much for this series of Post, are of very good quality and served extremely helpful for who we are entering the world of angular.

    If you got a complete project that integrates everything you are posting is like the icing on the cake.

    Regards
    Nicolás

Leave a Reply

Your email address will not be published. Required fields are marked *