C#: Building a Useful, Extensible .NET Console Application Template for Development and Testing

Posted on September 7 2014 02:26 PM by jatten in C#, CodeProject   ||   Comments (1)

charles-degaulle-airport-500How often do you find yourself tossing together a console application for the purpose of trying out code, "testing" in the sense of seeing what works best, or, possibly, as a means to demo some library of function?

I do, a lot. And I usually end up with some horrid mess which, while never intended for public use, still becomes a pain to deal with.

In preparing material for another post, I found myself once again working with the Console, hacking together some demonstration code to illustrate working with the Identity in the context of WebApi. The premise was to create a console application to demonstrate remote identity management via WebApi, and once again I found myself throwing together a minimal, ugly series of Console.WriteLine(sometext) statements, and in general writing code that was "illustration-only." And, Ugly as sin.

Image by Christopher Rose  | Some Rights Reserved

I wanted to put something together which functioned a little more like a shell terminal a la the Linux Bash. You know, enter a command, tell the computer to do something, the on to the next. Interactive.

How do I make the C# Console behave like a Shell, at least within the context of my application?

Lest you have any illusions, no, I am not trying to write a shell. But when working with my little demo and testing applications, I thought it would be nice if the console environment behaved in some (at least, reasonably) familiar, interactive ways, and without having to write a bunch of boilerplate code every time.

What I came up with is pretty functional for half an afternoon's work. It's basic, and easily extensible. Most importantly, it makes if easy to plug in some code and go, be it for testing purposes, and to demo a library. Or, possibly, you just might find you need a console-based application, and this may solve your problem.

Source Code on GitHub

We're going to build out the structure of this application framework here in the post. However, you can also clone the completed source from Github:

Building Blocks

The goal here was not to emulate a fully-functioning Shell or terminal. I wanted to be able to:

  • Run the program, be greeted with a prompt (customizable, in this case), and then enter commands corresponding to various methods defined in a specific area of the application.
  • Receive feedback (where appropriate), error messages, and such
  • Easily add/remove commands
  • Generally be able to quickly put together a functioning console application, with predictable and familiar interactive behavior, without re-inventing the wheel every time. 

We are going to set up the basic input/output functionality first. It will be easier to understand the basic structure of the program if we start with some very basic program flow, then move on to command parsing, and finally command execution.

Console Basics - The Interactive Input and Output Loop

Before the console can do anything else, it needs to be able to take input from the user, and provide feedback where needed. We are all familiar with Console.Write() or Console.WriteLine(), and also Console.Read() and Console.ReadLine(). However, these methods in and of themselves are utilitarian at best, and don't quite get us to the interactive Input/output loop we are looking for.

Also, the .NET Console does not directly afford us the ability to add a custom prompt.

We'll start with a very basic, but functional skeleton which will set up the basic input/output loop, and provide us with a means of adding a text prompt for input:

The Basic Interactive Console Skeleton Code:
class Program
{
    static void Main(string[] args)
    {
        Console.Title = typeof(Program).Name;
  
        // We will add some set-up stuff here later...
        Run();
    }
    static void Run()
    {
        while (true)
        {  
            var consoleInput = ReadFromConsole();
            if (string.IsNullOrWhiteSpace(consoleInput)) continue;
  
            try
            {
                // Execute the command:
                string result = Execute(consoleInput);
      
                // Write out the result:
                WriteToConsole(result);
            }
            catch (Exception ex)
            {
                // OOPS! Something went wrong - Write out the problem:
                WriteToConsole(ex.Message);
            }
        }
    }
  
  
    static string Execute(string command)
    {
        // We'll make this more interesting shortly:
        return string.Format("Executed the {0} Command", command);
    }
  
  
    public static void WriteToConsole(string message = "")
    {
        if(message.Length > 0)
        {
            Console.WriteLine(message);
        }
    }
  
  
    const string _readPrompt = "console> ";
    public static string ReadFromConsole(string promptMessage = "")
    {
        // Show a prompt, and get input:
        Console.Write(_readPrompt + promptMessage);
        return Console.ReadLine();
    }
}

 

We can see in the above that execution is quickly passed from the entry point Main() method to the Run() method. In just a little bit, we'll be adding some configuration and set-up code to main, which needs to run once, upon application startup.

Once execution is in our Run() method, we begin to see how our basic interactive IO loop works. Before we look at Run() , however, let's look at the other two methods we see, at the bottom of the class. WriteToConsole() and ReadFromConsole() look suspiciously like Console.WriteLine() and Console.ReadLine() and in fact, we wrap those two methods within each of our own.

The primary purpose of the ReadFromConsole() method is to show a prompt, and collect user input. Note the use of Console.Write() as opposed to Console.WriteLine() here. This way, our custom prompt is displayed on the same line that input will occur. For some unknown reason, the .NET Console does not provide a means of adding or customizing the standard prompt, so this is how we have to do things.

9/8/2014 NOTE: Thanks to a few intrepid commenters from r/csharp on Reddit, I have been saved from my own stupidity here. I had originally set up the Run() method discussed below with a recursive call to itself to create the interactive "loop" we are seeking. This would have created, over time, a big fat balloon of a memory leak. I have now repaired the damage before the universe collapsed in on itself, and updated the code in this post.

See I am a Programmer, and I can Complicate the #@$% out of a Ball Bearing for an examination of this idiocy…

Instead, we are going to use the (should have been obvious!) while loop.

Now, back to that Run() method. It is easy to see how things work here. Once control is passed to the Run() method, ReadFromConsole() is called, and the program awaits input from the user. Once text has been entered, control returns to Run().

Now, back to the Run() method. The Run() method features what is essentially an infinite while loop. Control is passed to ReadFromConsole() to get input from the user. When control returns (when the user hits Enter), the input is examined. If the string returned is null or empty, the loop resumes from the top again.

The next call from within Run() is to the Execute() method, which doesn't actually DO anything just yet, but we'll flesh that out in a minute. For the moment, execute returns the result of whatever command was called back to Run(). If something goes wrong, any exception thrown during Execute() will be caught in the catch block, and (hopefully!) a meaningful message will be displayed, informing the user about the nature of the problem.

Finally, Run() calls itself, recursively, thus completing our interactive loop, and the process starts anew.

When control once again returns to Run() from WriteToConsole(), the loop resumes.

Run the Bare-Bones IO Loop

If we run our little application at this point, we see that it doesn't do much besides take input, repeat what we told it to do back to us, and return to the input prompt. But for now, that's the point - getting the interactive IO loop in place.

Console Application with Minimal IO loop at Start-up:

io-loop-only-console-open

If we go ahead and type a "command" in and hit enter:

Console Application with Minimal IO Loop After Entering a Command:

io-loop-only-execute-command-text

As we can see - not terribly interesting yet, with the exception that we can now interact with our console in a familiar way. We'll fix that soon.

Basic Application Architecture

With the basic IO loop in place, we now need to take a look at how to set things up architecturally. Recall one of the primary goals of the project is to make it easy to plug in commands or sets of commands.

We will achieve this by creating a special project folder and namespace, Commands. We will enforce some (arbitrary, but good for consistency) rules for how commands will be provided to the project.

  • Methods representing Console commands will be defined as public static methods which always return a string, and are defined on public static classes.
  • Classes containing methods representing Console commands will be located in the Commands namespace, and in the Commands folder.
  • There will always be a static class named DefaultCommands from which methods may be invoked from the Console directly by name. For many console project, this will likely be sufficient.
  • Commands defined on classes other than DefaultCommands will be invoked from the console using the familiar dot syntax: ClassName.CommandName.

We will execute commands from the terminal and call our Command methods using Reflection. Yeah, I know - we're supposed to be hyper-sensitive about possible performance penalties when using reflection. In our case, this is negligible. Most of the so-called heavy lifting  will be done at application startup. Also, it's 2014. Computers are fast.

We'll look more closely at the Reflection aspect in a bit. For now, understand how this plays into how we parse text entered by the user into Commands and Arguments.

Parsing Console Input into Commands and Arguments

Now that we can interact with our console in a meaningful way, let's look at parsing the text input from the console into some sort of meaningful structure of commands and arguments.

Here, we need to make some decisions. For the purpose of this very basic application template, I made some assumptions:

  • The text entered will always consist of a command followed by zero or more arguments, separated by spaces.
  • Quoted text will be preserved as a single string argument, and may contain spaces.
  • Text arguments without spaces will be treated as strings, quotes are optional.

To manage to collection of commands and arguments, we're going to build a ConsoleCommand class:

The ConsoleCommand Class:
public class ConsoleCommand
{
    public ConsoleCommand(string input)
    {
        // Ugly regex to split string on spaces, but preserve quoted text intact:
        var stringArray = 
            Regex.Split(input, 
                "(?<=^[^\"]*(?:\"[^\"]*\"[^\"]*)*) (?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
  
        _arguments = new List<string>();
        for (int i = 0; i < stringArray.Length; i++)
        {
            // The first element is always the command:
            if (i == 0)
            {
                this.Name = stringArray[i];
  
                // Set the default:
                this.LibraryClassName = "DefaultCommands";
                string[] s = stringArray[0].Split('.');
                if (s.Length == 2)
                {
                    this.LibraryClassName = s[0];
                    this.Name = s[1];
                }
            }
            else
            {
                var inputArgument = stringArray[i];
                string argument = inputArgument;
  
                // Is the argument a quoted text string?
                var regex = new Regex("\"(.*?)\"", RegexOptions.Singleline);
                var match = regex.Match(inputArgument);
  
                if (match.Captures.Count > 0)
                {
                    // Get the unquoted text:
                    var captureQuotedText = new Regex("[^\"]*[^\"]");
                    var quoted = captureQuotedText.Match(match.Captures[0].Value);
                    argument = quoted.Captures[0].Value;
                }
                _arguments.Add(argument);
            }
        }
    }
  
    public string Name { get; set; }
    public string LibraryClassName { get; set; }
  
    private List<string> _arguments;
    public IEnumerable<string> Arguments
    {
        get
        {
            return _arguments;
        }
    }
}
 

To achieve proper parsing of all this, we will need to engage everyone's favorite, Regular Expressions. That big, long, ugly Regular Expression near the top of the ConsoleCommand class is used to parse incoming text by splitting the text string where spaces occur, but to leave quoted text string intact.

I am not a Regex genius, and I found this Regular Expression on stack overflow, in an answer by someone who clearly IS a Regex Genius.

That is a big constructor method, and could possibly be refactored into a few smaller methods. But maybe not. In any case, let's take a look at what happens here:

Parse the Command from the Input Text

  • The string passed as the constructor argument is first split into a string array, delimited by spaces, with the exception that quoted text is preserved intact (the quotes are also preserves as part of the string, but we'll take care of that momentarily).
  • We then iterate over the array elements. One of the assumptions we have made about our program is that the first element will always be a command, so we grab element 0 on our first pass and assume it is the command.

Recall from earlier that we have some rules about command names:

  • We will always have a set of default commands available to our Console directly using the corresponding method name as defined on the class DefaultCommands.
  • We MAY have commands defined on other special classes within the Commands namespace, but from the console, we need to access any of these with the ClassName.CommandName dot notation.

At this point in our constructor is where we implement this rule. Our constructor code assumes that, time, a Console application will likely be using the default commands class, and assigns the LibraryClassName property as "DefaultCommands" accordingly. Then, the code checks to see if the command can be split on a period. If so, the new string array resulting from the check will have two elements, so the LibraryClassName property is set to match the first array element in the array (element[0]). The second array element is then assumed to be the actual command name, and is assigned to the Command property accordingly.

Parse the Arguments from the Input Text

Once the command has been properly parsed, and the LibraryClassName and Command properties have been set, we complete the iteration by parsing the arguments:

  • Each array element that is not element[0] is examined (once again using Regex, but not as bad this time) to see if it is a quoted string.
  • If the element is a quoted string, the text between the quotes is extracted, and added in its entirety to the Arguments list.
  • Otherwise, the string element is simply added to the Arguments list as-is.

Update the Run() Method to Use ConsoleCommand Class

Now that we have an object to represent a command issued by the user from the console, let's update our Run() and Execute() methods to use that. In Run() we will create an instance of our ConsoleCommand class, and then pass that to Execute():

Update Run Method to Consume ConsoleCommand Class:
static void Run()
{
    while (true)
    {  
        var consoleInput = ReadFromConsole();
        if (string.IsNullOrWhiteSpace(consoleInput)) continue;
        try
        {
            // Create a ConsoleCommand instance:
            var cmd = new ConsoleCommand(consoleInput);
            // Execute the command:
            string result = Execute(cmd);
            // Write out the result:
            WriteToConsole(result);
        }
        catch (Exception ex)
        {
            // OOPS! Something went wrong - Write out the problem:
            WriteToConsole(ex.Message);
        }
    }
}

 

And we will modify the signature of the Execute() method to accept an argument of type ConsoleCommand (and make a silly modification to the body code for simple demo purposes at this point):

Update Execute Method to Consume ConsoleCommand Class:
static string Execute(ConsoleCommand command)
{
    // We'll make this more interesting shortly. 
  
    // For now, output the command details:
    var sb = new StringBuilder();
    sb.AppendLine(string.Format("Executed the {0}.{1} Command", 
        command.LibraryClassName, command.Name));
    for(int i = 0; i < command.Arguments.Count(); i++)
    {
        sb.AppendLine(ConsoleFormatting.Indent(4) + 
            string.Format("Argument{0} value: {1}", i, command.Arguments.ElementAt(i)));
    }
    return sb.ToString();
}

 

Run the Application with Command Parsing in Place

If we run the application now, we can see, from the values written to the console, how our input is being parsed. We can input some reasonable approximation of a command with arguments, and view the result:

Command and Arguments are Parsed and Displayed:

command-parsing-execute-command

Now that we can effectively parse commands and arguments, how are we going to define commands available to our program, validate the arguments, and execute? Why, Reflection, of course.

Using Reflection to Load and Validate Commands and Arguments

We have set up our application structure specifically so we can easily use Reflection to load information about the commands we wish to make available to our application, and also to execute commands from the Console. This way, when we wish to add or change the commands available, all we need do is add the relevant code within the Commands folder/namespace, and our application will properly load everything it needs.

Let's recall the rules we established for defining commands within our application:

  • Methods representing Console commands will be defined as public static methods which always return a string, and are defined on public static classes.
  • Classes containing methods representing Console commands will be located in the Commands namespace, and in the Commands folder.
  • There will always be a static class named DefaultCommands from which methods may be invoked from the Console directly by name. For many console project, this will likely be sufficient.
  • Commands defined on classes other than DefaultCommands will be invoked from the console using the familiar dot syntax: ClassName.CommandName.

If we haven't already, add a folder to the project named Commands. Within this folder we will define at least one static class and name it DefaultCommands. It is within this class that we will define the core set of commands available to our application. Commands within this class will be invokeable from the Console using only the method name, no need to prefix with the class name using dot notation.

As an aside, some thought should be put into naming the methods we define as our commands. Short, easily remembered (and easily typed) method names will go a long way towards the usability factor of any Console application.

For now, we will set up a couple trivial commands as default commands to use while we work out the details:

Add the DefautCommands Class in the Commands Folder:
// All console commands must be in the sub-namespace Commands:
namespace ConsoleApplicationBase.Commands
{
    // Must be a public static class:
    public static class DefaultCommands
    {
        // Methods used as console commands must be public and must return a string
  
        public static string DoSomething(int id, string data)
        {
            return string.Format(
                "I did something to the record Id {0} and save the data {1}", id, data);
        }
  
  
        public static string DoSomethingElse(DateTime date)
        {
            return string.Format("I did something else on {0}", date);
        }
    }
}

While it may not seem intuitive at first, any command available to our console returns a string because, well, that's what the console can use. It may be an empty string, but a string nonetheless. This gives us a consistent return type to work with, easily consumed by our console.

The assumption here is that most of the real work to be done by any command will likely be sourced off into other classes or libraries. Once the command has completed its work, what our Console application is interested in is either a result it can display, or an empty string.

I my case, most of the time when a command succeeds, we either have asked for output to be displayed, or instructed the application to perform some function which succeeds or fails, but does not produce output. In the former case, we expect to see the results of our request. In the latter, we are going to assume that no feedback indicates successful execution, and return an empty string. If something goes wrong in the latter case, feedback will inform us of the details.

Loading Commands at Application Startup

First, we need to add a reference to System.Reflection in the using statements of our Program class:

Add System.Reflection to Using Statements:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

 

Next, we need to add a couple variables for use by the code we will be adding next. First, we want a constant defining the full Command namespace, and next we want a static dictionary to hold key-value pairs representing the classes defined in our Commands namespace. Each dictionary entry will use the class name as a key, and the value will be a nested dictionary containing information about the methods defined within that class.

Add Class-Level Variables to Program Class:
class Program
{
    const string _commandNamespace = "ConsoleApplicationBase.Commands";
    static Dictionary<string, Dictionary<string, IEnumerable<ParameterInfo>>> _commandLibraries;
  
    // ... The rest of the Program class code ...
}

 

Next, let's update our Main() method as follows:

Updated Main() method Loading Command Information Using Reflection:
static void Main(string[] args)
{
    Console.Title = typeof(Program).Name;
  
    // Any static classes containing commands for use from the 
    // console are located in the Commands namespace. Load 
    // references to each type in that namespace via reflection:
    _commandLibraries = new Dictionary<string, Dictionary<string, 
            IEnumerable<ParameterInfo>>>();
  
    // Use reflection to load all of the classes in the Commands namespace:
    var q = from t in Assembly.GetExecutingAssembly().GetTypes()
            where t.IsClass && t.Namespace == _commandNamespace
            select t;
    var commandClasses = q.ToList();
  
    foreach (var commandClass in commandClasses)
    {
        // Load the method info from each class into a dictionary:
        var methods = commandClass.GetMethods(BindingFlags.Static | BindingFlags.Public);
        var methodDictionary = new Dictionary<string, IEnumerable<ParameterInfo>>();
        foreach (var method in methods)
        {
            string commandName = method.Name;
            methodDictionary.Add(commandName, method.GetParameters());
        }
        // Add the dictionary of methods for the current class into a dictionary of command classes:
        _commandLibraries.Add(commandClass.Name, methodDictionary);
    }
    Run();
}

 

We load the information we need into the _commandLibrariesDictionary as follows:

  • We load all the Types contained in the _commandNamespace into a IEnumerable<Type> using Linq
  • Iterate over each Type in the list, use the GetMethods() method to retreive an IEnumerable<MethodInfo> for the methods in the class which meet the criteria specified in the call to GetMethods() (in this case, methods which are both public and static).
  • We create a Dictionary<string, IEnumerable<ParameterInfo>> for each method on a given class, and add a key-value pair for the method. The key, in this case, is the method name, and the corresponding value for each key is the IEnumerable<ParameterInfo> for the method.
  • Add each of these dictionaries, in nested fashion, into the _commandLibrariesDictionary and use the class name as the key.

With this in place, we can now update our Execute() method, and begin to get it doing something useful.

Executing Console Commands Using Reflection

Once again, we need to do a lot within this method, and there is likely potential for refactoring here. Within the Execute() method, we need to accomplish, broadly, three things:

  • Validate that the command passed in corresponds to a class and method defined in our Commands namespace
  • Make sure that all required method arguments are provided with the command.
  • Make sure that the arguments passed from the Console are all properly converted to the Type specified in the method signature (coming from the Console, all arguments are passed as strings).
  • Set up to execute the command using Reflection
  • Return the result of the execution, or catch exceptions and return a meaningful exception message.
Updated Execute() Method:
  
    // Validate the command name:
    if(!_commandLibraries.ContainsKey(command.LibraryClassName))
    {
        return badCommandMessage;
    }
    var methodDictionary = _commandLibraries[command.LibraryClassName];
    if (!methodDictionary.ContainsKey(command.Name))
    {
        return badCommandMessage;
    }
    
    // Make sure the corret number of required arguments are provided:
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  
    var methodParameterValueList = new List<object>();
    IEnumerable<ParameterInfo> paramInfoList = methodDictionary[command.Name].ToList();
  
    // Validate proper # of required arguments provided. Some may be optional:
    var requiredParams = paramInfoList.Where(p => p.IsOptional == false);
    var optionalParams = paramInfoList.Where(p => p.IsOptional == true);
    int requiredCount = requiredParams.Count();
    int optionalCount = optionalParams.Count();
    int providedCount = command.Arguments.Count();
  
    if (requiredCount > providedCount)
    {
        if (requiredCount > providedCount)
        {
            return string.Format(
                "Missing required argument. {0} required, {1} optional, {2} provided",
                requiredCount, optionalCount, providedCount);
        }
    }
  
    // Make sure all arguments are coerced to the proper type, and that there is a 
    // value for every emthod parameter. The InvokeMember method fails if the number 
    // of arguments provided does not match the number of parameters in the 
    // method signature, even if some are optional:
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  
    if (paramInfoList.Count() > 0)
    {
        // Populate the list with default values:
        foreach (var param in paramInfoList)
        {
            // This will either add a null object reference if the param is required 
            // by the method, or will set a default value for optional parameters. in 
            // any case, there will be a value or null for each method argument 
            // in the method signature:
            methodParameterValueList.Add(param.DefaultValue);
        }
  
        // Now walk through all the arguments passed from the console and assign 
        // accordingly. Any optional arguments not provided have already been set to 
        // the default specified by the method signature:
        for (int i = 0; i < command.Arguments.Count(); i++)
        {
            var methodParam = paramInfoList.ElementAt(i);
            var typeRequired = methodParam.ParameterType;
            object value = null;
            try
            {
                // Coming from the Console, all of our arguments are passed in as 
                // strings. Coerce to the type to match the method paramter:
                value = CoerceArgument(typeRequired, command.Arguments.ElementAt(i));
                methodParameterValueList.RemoveAt(i);
                methodParameterValueList.Insert(i, value);
            }
            catch (ArgumentException ex)
            {
                string argumentName = methodParam.Name;
                string argumentTypeName = typeRequired.Name;
                string message = 
                    string.Format(""
                    + "The value passed for argument '{0}' cannot be parsed to type '{1}'", 
                    argumentName, argumentTypeName);
                throw new ArgumentException(message);
            }
        }
    }
  
    // Set up to invoke the method using reflection:
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  
    Assembly current = typeof(Program).Assembly;
  
    // Need the full Namespace for this:
    Type commandLibaryClass = 
        current.GetType(_commandNamespace + "." + command.LibraryClassName);
  
    object[] inputArgs = null;
    if (methodParameterValueList.Count > 0)
    {
        inputArgs = methodParameterValueList.ToArray();
    }
    var typeInfo = commandLibaryClass;
  
    // This will throw if the number of arguments provided does not match the number 
    // required by the method signature, even if some are optional:
    try
    {
        var result = typeInfo.InvokeMember(
            command.Name, 
            BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, 
            null, null, inputArgs);        
        return result.ToString();
    }
    catch (TargetInvocationException ex)
    {
        throw ex.InnerException;
    }
}

 

As we can see, we use the InvokeMember() method to execute our command. A good deal of the code above deals with setting up for this. There are a few things to be aware of here.

First off, we need to make sure the text passed as the the LibraryClassName and the Command Name properties are both valid, and correspond to appropriate objects within our Commands namespace. If not, we want to return the error information to the console immediately.

Next, we need to be aware of matching our arguments to method parameters. Some things to deal with here:

Our methods may have zero or more parameters defined within the method signature. Some may be required, and some may be "optional" (meaning they have been provided with default values within the method signature itself). In all cases, we want to fail immediately if the number of arguments provided doesn't at least match the number of required method parameters. So we check for that using reflection.

The InvokeMember() method attempts to match the requested method by matching the method name, and also by matching the method signature to the number of arguments provided as part of the call. Because of this, even though some of the parameters specified within the target method signature may be optional, InvokeMember() still needs to have arguments for them. Therefore, we need to provide values for ALL the parameters in the method, optional or not.

To accomplish this, we iterate once over the complete list of method parameters contained in paramInfoList, and add a corresponding object to methodParametersValueList. In the case of optional method parameters, the default value from the method signature will be used. All other values will be added simply as empty objects.

Once we have a value in methodParametersValueList for every corresponding method parameter, we can walk through the arguments passed in from the Console, coerce them to the proper type required by the parameter, and swap them into the list at the proper index.

We do the type coercion using the CoerceArgument() method discussed momentarily.

Once the list of arguments needed to call the method are assembled, we use InvokeMethod() to call into our command and return the result of the call as a string.

Coercing Console Arguments to the Proper Type

Note the call to CoerceArgument() about midway through our revised Execute() method. We need to be able to parse the incoming string representation of our argument values to the proper type required by the corresponding method parameter.

There may be more elegant ways to write this method, but this is pretty straightforward boiler plate code. The switch statement examines the type passed in and attempts to parse the string value to that type. If the method succeeds, the value is returned as an object. If the method fails, an ArgumentException is thrown, and caught by the calling code.

Add the following method to the Program class:

The CoerceArgument() Method:
static object CoerceArgument(Type requiredType, string inputValue)
{
    var requiredTypeCode = Type.GetTypeCode(requiredType);
    string exceptionMessage = 
        string.Format("Cannnot coerce the input argument {0} to required type {1}", 
        inputValue, requiredType.Name);
  
    object result = null;
    switch (requiredTypeCode)
    {
        case TypeCode.String:
            result = inputValue;
            break;
        case TypeCode.Int16:
            short number16;
            if (Int16.TryParse(inputValue, out number16))
            {
                result = number16;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.Int32:
            int number32;
            if (Int32.TryParse(inputValue, out number32))
            {
                result = number32;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.Int64:
            long number64;
            if (Int64.TryParse(inputValue, out number64))
            {
                result = number64;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.Boolean:
            bool trueFalse;
            if (bool.TryParse(inputValue, out trueFalse))
            {
                result = trueFalse;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.Byte:
            byte byteValue;
            if (byte.TryParse(inputValue, out byteValue))
            {
                result = byteValue;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.Char:
            char charValue;
            if (char.TryParse(inputValue, out charValue))
            {
                result = charValue;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.DateTime:
            DateTime dateValue;
            if (DateTime.TryParse(inputValue, out dateValue))
            {
                result = dateValue;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.Decimal:
            Decimal decimalValue;
            if (Decimal.TryParse(inputValue, out decimalValue))
            {
                result = decimalValue;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.Double:
            Double doubleValue;
            if (Double.TryParse(inputValue, out doubleValue))
            {
                result = doubleValue;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.Single:
            Single singleValue;
            if (Single.TryParse(inputValue, out singleValue))
            {
                result = singleValue;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.UInt16:
            UInt16 uInt16Value;
            if (UInt16.TryParse(inputValue, out uInt16Value))
            {
                result = uInt16Value;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.UInt32:
            UInt32 uInt32Value;
            if (UInt32.TryParse(inputValue, out uInt32Value))
            {
                result = uInt32Value;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        case TypeCode.UInt64:
            UInt64 uInt64Value;
            if (UInt64.TryParse(inputValue, out uInt64Value))
            {
                result = uInt64Value;
            }
            else
            {
                throw new ArgumentException(exceptionMessage);
            }
            break;
        default:
            throw new ArgumentException(exceptionMessage);
    }
    return result;
}

 

Now, before we take this for another test spin, let's add one more silly demonstration method to our set of default commands. Add the following method, which includes and optional method parameter, to the DefaultCommands class:

Add a Method with Optional Parameters to Default Commands:
public static string DoSomethingOptional(int id, string data = "No Data Provided")
{
    var result = string.Format(
        "I did something to the record Id {0} and save the data {1}", id, data);
  
    if(data == "No Data Provided")
    {
        result = string.Format(
        "I did something to the record Id {0} but the optinal parameter "
        + "was not provided, so I saved the value '{1}'", id, data);
    }
    return result;
}

 

This will give us an opportunity to examine the behavior of our application when a method with an optional parameter is called.

Running the Console Application and Executing Commands

We now have what should be a working prototype of our Console Application Template. While the current example commands are simplistic, we can run this current version and make sure things are functioning as expected.

When we run the application, we have three commands to choose from. We enter the command name, followed by the arguments, separated by spaces. Recall that in cases where we wish to enter strings with spaces, we need to use quotes.

Our current method signatures look like this:

Reminder: Sample Method Signatures:
DoSomething(int id, string data)
DoSomethingElse(DateTime date)
DoSomethingOptional(int id, string data = "No Data Provided")

 

We'll start with the DoSomething() method. The method signature requires an id argument of type int, and a data argument of type string. We might enter something like this at the console:

Example Console Input for the DoSomething Command:
console> DoSomething 23 "My Data"

 

If we enter that, we see what happens:

Execute the DoSomething() method:

execute-dosomething-command

We can do similarly for the DoSomethingElse() command, which expects a date argument:

Execute the DoSomethingElse() Command:

execute-dosomethingelse-command

Likewise, the DoSomethingOptional() Command (we will provide the second, optional argument this pass):

Execute the DoSomethingOptional() Command:

execute-dosomethingoptional-with-optionalparam-command

If we enter the DoSomethingOptional() Command again, but this time omit the optional second argument, the command still executes, using the default value provided in our original method signature:

Execute the DoSomethingOptional() Command Omitting Optional Argument:

execute-dosomethingoptional-without-optionalparam-command

In this last case, we can see that command execution uses the default value defined in the method signature for the optional parameter, and takes the alternate execution path indicated by the conditional we implemented in the method itself.

Adding Additional Classes and Commands to the Commands Namespace

Depending upon our application requirements, the DefaultCommands class may not be sufficient to our needs. For any number of reasons, we may want to break our commands out into discreet classes. As we have seen, we have set up our Console application to handle this, so long as we observe the rules we defined.

Let's add a little bit business logic, and a new commands class to our application.

Add a Models Folder to your project, and add the following code in a file named SampleData.cs:

The SampleData Code File:
namespace ConsoleApplicationBase.Models
{
    // We'll use this for our examples:
    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
  
  
    public static class SampleData
    {
        private static List<User> _userData;
        public static List<User> Users
        {
            get
            {
                // List will be initialized with data the first time the
                // static property is accessed:
                if(_userData == null)
                {
                    _userData = CreateInitialUsers();
                }
                return _userData;
            }
        }
  
  
        // Some test data:
        static List<User> CreateInitialUsers()
        {
            var initialUsers = new List<User>()
            {
                new User { Id = 1, FirstName = "John", LastName = "Lennon" },
                new User { Id = 2, FirstName = "Paul", LastName = "McCartney" },
                new User { Id = 3, FirstName = "George", LastName = "Harrison" },
                new User { Id = 4, FirstName = "Ringo", LastName = "Starr" },
            };
            return initialUsers;
        }
    }
}

 

Here, we are going to use the SampleData class as a mock database, and work against that with a couple new commands. We can now add a Users class to our Commands namespace and folder.

Add A Users Class to the Commands Namespace

Add a class named Users to the Commands folder in Solution Explorer. Make sure it is in the Commands namespace:

The Users Command class:
public static class Users
{
    public static string Create(string firstName, string lastName)
    {
        Nullable<int> maxId = (from u in SampleData.Users
                              select u.Id).Max();
        int newId = 0;
        if(maxId.HasValue)
        {
            newId = maxId.Value + 1;
        }
  
        var newUser = new User 
        { 
            Id = newId, 
            FirstName = firstName, 
            LastName = lastName 
        };
        SampleData.Users.Add(newUser);
        return "";
    }
  
  
    public static string Get()
    {
        var sb = new StringBuilder();
        foreach(var user in SampleData.Users)
        {
            sb.AppendLine(ConsoleFormatting.Indent(2) 
                + string.Format("Id:{0} {1} {2}", 
                user.Id, user.FirstName, user.LastName));
        }
        return sb.ToString();
    }
}

 

Above, we have added two rather simplistic methods, Users.Create() and Users.Get(). We had to take some liberties faking up an auto-incrementing Id for the new users, but you get the idea.

With that, we can now try out our application with an added commands library. Recall that to call commands other than those defined in our DefaultCommands class, we need to user the dot syntax ClassName.CommandName.

Running the Project with Additional Command Classes

We can now invoke our User-related commands from the Console. The Console syntax for these two new commands would be similar to:

Console Syntax for the Users.Get() Method:
Users.Get

 

Console Syntax for the Users.Create() Method:
Users.Create Joe Strummer

 

If we run the application and execute the Users.Get() command, we get the following result:

Execute Users.Get() Command from the Console:

execute-users-get-command

Above, we see what we'd expect - the sample data we cooked up. If we then execute the Create() command, and then follow that up by executing Get() again, we see the added user data:

Execute Users.Create() Followed by Users.Get():

execute-users-create-then-users-get

Exception Handling for Console Command Execution

I don't claim to have handled every exception case we might run into here, but I believe I've provided a good start. The goal is to handle exceptions and errors and provide useful feedback to the user to know what went wrong, and how to correct things.

Note, however, that using InvokeMember adds a few minor wrinkles to the exception handling equation:

Invoke member catches any Exceptions thrown by the target method, and then throws a new TargetInvocationException. Our code needs to catch TargetInvocationException and retreive the inner exception  to obtain meaningful information about what happened.

Therefore, carefully thinking through how exceptions are defined and processed by your command code, and any code called by your command code is in order. The goal is to pass meaningful exception messages up through the call stack such that, when TargetInvocationException is caught, there will be sufficient meaningful information present to display on the Console.

TargetInvocationException Always Breaks at the Exception Source

Unlike the standard experience in Visual Studio, where user-handled exceptions are, well, handled according to how the code is written, when using InvokeMember, Visual Studio always breaks at the exception source, regardless of any exception handling in the code. In other words, user-handled exceptions thrown within the context of a call via InvokeMember will always cause  VS to drop into debug mode, unless you employ certain workarounds in your VS settings. More information about this in this StackOverflow Answer by Jon Skeet.

The upside of all that is, when the application .exe is run normally, exceptions sourced by calls to InvokeMember propagate normally up the call stack, and messages will be output correctly to the console.

Next Steps - Building Out a Real Console Application

Of course, the commands we have defined here are simplistic, used for demonstration and to make sure our application, in its nascent form, is working correctly in terms of parsing out commands and arguments, and then executing those commands.

As we plug in some actual business logic, there are some things we want to bear in mind.

The purpose of each command defined within the Commands namespace is primarily to receive instruction from the console, and essentially act as the go-between for the console and the business layer of an application. In other words, like controllers in an ASP.NET MVC application, most of the business logic (and work) performed by our application proper would likely not occur within the command itself.

The command receives input from the console in terms of arguments, and a directive to execute. The code within the command should then call out to whatever other methods or libraries it needs in order to do the work requested, receive the result, and then translate to the desired console output string (if any).

Think of it as MVC for the Console

Within certain limits, what we are trying to achieve here is essentially an MVC-like separation of concerns. The classes within the Commands namespace provide a function similar to that performed by Controllers in an MVC application. The Commands we define on those classes work in a similar manner to Controller actions.

in both cases, the function of the class is to be an intermediary between the presentation layer of the application (in our case, the "presentation layer" is the Console) and the Models that make up the business logic.

We can easily add additional Command classes and methods, and our application framework will properly "discover" them, provided we adhere to the rules we defined at the beginning (or, of course, we can change the rules to suit our needs).

It's Not Done…

Most of the basic application structure is in place here, but really, there is a lot more that could be done, depending upon how extensive your needs are.

I certainly haven't exhaustively tested things here (in fact, you'll note, there are no tests at all!), and there are myriad areas ripe with potential for improvement.

The basic idea, for me, was to get some sort of basic structure in place so that, whether I am trying out ideas, demoing library code, or actually building a real console application, I can plug in the code I want to work with and go. Without messing with, or thinking much about writing to and from the console itself.

Ideas, Issues, and Pull Requests Welcome!

The project code for this is hosted on my Github account, and I welcome all suggestions, Issues, and Pull Requests.

Additional Resources and Items of Interest

 

Posted on September 7 2014 02:26 PM by jatten     

Comments (1)

ASP.NET Identity 2.0: Implementing Group-Based Permissions Management

Posted on August 10 2014 07:35 PM by jatten in ASP.NET MVC, ASP.Net, C#, CodeProject   ||   Comments (15)

authorized-personel-onlyEarlier this year we looked at Implementing Group-Based Permissions Management using the ASP.NET Identity 1.0 framework. The objective of that project was to gain a little more granular control of application authorization, by treating the now-familiar Identity Role as more of a "permission" which could be granted to members of a group.

With the release of Identity 2.0 in March 2014 came some breaking changes, and a significant expansion of features and complexity. Identity 2.0 is now a full-grown authorization and authentication system for ASP.NET. However, the added features came at a price. There is a lot to get your mind around to start using Identity 2.0 effectively.

 

Image by Thomas Hawk  |  Some Rights Reserved

In previous articles, we have covered some of the basics:

The code we used to implement Group-based permissions under Identity 1.0 breaks in moving to Identity 2.0. Simply too much has changed between the two versions to make a clean, easy upgrade. In this article, we will revisit the Group-Based Permission idea, and implement the concept under the Identity 2.0 framework.

A Quick Review

Under the familiar ASP.NET Identity structure, Users are assigned one or more Roles. Traditionally, access to certain application functionality is governed, through the use of the [Authorize] attribute, by restricting access to certain controllers or controller methods to members of certain roles. This is effective as far as it goes, but does not lend itself to efficient management of more granular access permissions.

The Group-Based Permissions project attempts to find a middle ground between the complexities of a full-blown, Claims-based authorization scheme, and the simplicity (and limitations) of the authorization offered by Identity out of the box. Here, we will implement another familiar idea - Users are assigned to one or more Groups. Groups are granted set of permissions corresponding to the various authorizations required by group members to perform their function.

I discussed the overall concept, and security concerns in the previous article, so I won't rehash all that here. For reference, the following topics might be worth a quick visit:

In this article, we will implement a similar structure using Identity 2.0.

Getting Started - Clone a Handy Project Template

We're going to start with a handy, ready-to-customize project template based on the Identity Samples project created by the Identity team. I've used what we've learned in the past few posts to create a ready to extend project which can be cloned from my Github account (and hopefully soon, from Nuget!).

Or, Clone the Finished Source for this Project

Or, if you like, you can clone the source for the completed Group Permissions project, also on Github:

If you are starting with the template project and following along, it's probably best to rename the directory and project files to reflect the current effort. if you do this, make sure to also update the Web.Config file as well. You may want to update the connection string so that when the back-end database is created, the name reflects the Group Permissions application.

You MUST update the appSettings => owin:AppStartup element so that the startup assembly name matches the name you provide in Project => Properties => Assembly Name. In my case, I set my connection string and owin:appStartup as follows:

Update Web.Config Connection String and owin:appStartup elements:
<connectionStrings>
    <add name="DefaultConnection" 
         connectionString="Data Source=(LocalDb)\v11.0;
           Initial Catalog=AspNetIdentity2GroupPermissions-5;
           Integrated Security=SSPI" 
         providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings>
    <add key="owin:AppStartup" value="IdentitySample.Startup,AspNetIdentity2GRoupPermissions" />
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings></configuration>

 

Adding the Group Models

Let's get right to it. We can start by adding some new models to the Models => IdentityModels.cs file. We will be defining three different classes here: ApplicationGroup, which is the core Group model, as well as two additional classes which map ApplicationUser and ApplicationRole as collections within ApplicationGroup. Add the following to the IdentityModels.cs file:

The ApplicationGroup and Related Classes:
public class ApplicationGroup
{
    public ApplicationGroup()
    {
        this.Id = Guid.NewGuid().ToString();
        this.ApplicationRoles = new List<ApplicationGroupRole>();
        this.ApplicationUsers = new List<ApplicationUserGroup>();
    }
  
    public ApplicationGroup(string name)
        : this()
    {
        this.Name = name;
    }
  
    public ApplicationGroup(string name, string description)
        : this(name)
    {
        this.Description = description;
    }
  
    [Key]
    public string Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<ApplicationGroupRole> ApplicationRoles { get; set; }
    public virtual ICollection<ApplicationUserGroup> ApplicationUsers { get; set; }
}
  
  
public class ApplicationUserGroup
{
    public string ApplicationUserId { get; set; }
    public string ApplicationGroupId { get; set; }
}
  
public class ApplicationGroupRole
{
    public string ApplicationGroupId { get; set; }
    public string ApplicationRoleId { get; set; }
}

 

Groups have a many-to-many relationship with both Users, and Roles. One user can belong to zero or more Groups, and one group can have zero or more users. Likewise with roles. A single role can be assigned to zero or more groups, and a single Group can have zero or more roles.

When we need to manage this type of relationship using EntityFramework, we can create what are essentially mapping classes, in this case, ApplicationUserGroup, and ApplicationGroupRole. The way we are doing this is similar to the structure used by the Identity team in defining Users, Roles, and UserRoles. For example, our ApplicationUser class is derived from IdentityUser, which defines a Roles property. Note that the Roles property of IdentityUser is not a collection of IdentityRole object, but instead a collection of IdentityUserRole objects. The difference being, the IdentityUserRole class defines only a UserId property, and a RoleId property.

We are doing the same thing here.We need to allow EF to manage the many-to-many relationships we described above by adding mapping classes between the collections defined on ApplicationGroup, and the domain objects involved in each relationship.

Override OnModelCreating in the DbContext Class

EntityFramework will not figure out our many-to-many relationships on its own, nor will it determine the proper table structures to create in our database. We need to help things along by overriding the OnModelCreating method in the ApplicationDbContext class. Also, we need to add ApplicationGroups as a property on our DbContext so that we can access our Groups from within our application.  Update the ApplicationDbContext class as follows:

Update ApplicationDbContext and Override OnModelCreating:
public class ApplicationDbContext
    : IdentityDbContext<ApplicationUser, ApplicationRole,
    string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
  
    static ApplicationDbContext()
    {
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }
  
    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
  
    // Add the ApplicationGroups property:
    public virtual IDbSet<ApplicationGroup> ApplicationGroups { get; set; }
  
    // Override OnModelsCreating:
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Make sure to call the base method first:
        base.OnModelCreating(modelBuilder);
        
        // Map Users to Groups:
        modelBuilder.Entity<ApplicationGroup>()
            .HasMany<ApplicationUserGroup>((ApplicationGroup g) => g.ApplicationUsers)
            .WithRequired()
            .HasForeignKey<string>((ApplicationUserGroup ag) => ag.ApplicationGroupId);
        modelBuilder.Entity<ApplicationUserGroup>()
            .HasKey((ApplicationUserGroup r) => 
                new 
                { 
                    ApplicationUserId = r.ApplicationUserId, 
                    ApplicationGroupId = r.ApplicationGroupId 
                }).ToTable("ApplicationUserGroups");
  
        // Map Roles to Groups:
        modelBuilder.Entity<ApplicationGroup>()
            .HasMany<ApplicationGroupRole>((ApplicationGroup g) => g.ApplicationRoles)
            .WithRequired()
            .HasForeignKey<string>((ApplicationGroupRole ap) => ap.ApplicationGroupId);
        modelBuilder.Entity<ApplicationGroupRole>().HasKey((ApplicationGroupRole gr) => 
            new 
            { 
                ApplicationRoleId = gr.ApplicationRoleId, 
                ApplicationGroupId = gr.ApplicationGroupId 
            }).ToTable("ApplicationGroupRoles");
    }
}

 

With these humble beginnings in place, let's run the project, and see if everything works the way we expect.

Running the Project and Confirming Database Creation

If we run the project now, we will be greeted by the standard MVC page. Recall that with the Entity Framework Code-First model, database creation will occur at first data access. In other words, as things stand, we need to log in.

To this point, we haven't added any explicit functionality to our front-end site - when we log in, there will be no evidence that our underlying model has changed. We simply want to see if the site starts up properly, and that the database and table we expect are in fact created.

Once we have run the project and logged in, we should be able to stop, and use  the Visual Studio Server Explorer to see what our database tables look like. We should see something like the following:

Server Explorer with Additional Tables for Groups:

server-explorer-with-group-tables

We see from the above that our ApplicationGroup and related classes are now represented by tables in our back-end database, along with the expected columns and primary keys. So far, so good!

Investigation: Building a Consistent Asynchronous Model Architecture

APS.NET Identity 2.0 offers a fully async model architecture. We are going to do our best to follow the conventions established by the Identity team in building up our Groups management structure, using similar abstractions to create a Group store, and Group Manager with fully async methods. In other words, perhaps we can take a look at how the Identity 2.0 team built the basic UserStore and RoleStore abstractions (including the async methods) and simply model up our own GroupStore along the same lines.

If we take a close look at the structure used by the Identity team to build up the basic UserStore and RoleStore classes within the Identity framework, we find that each are composed around an instance of a class called EntityStore<TEntity> , which wraps the most basic behaviors expected of a persistence store.

For example, if we look inside the RoleStore<TRole, TKey, TUserRole> class defined as part of the Identity 2.0 framework, we find the following:

Decomposing the RoleStore Class:
public class RoleStore<TRole, TKey, TUserRole> : 
    IQueryableRoleStore<TRole, TKey>, IRoleStore<TRole, TKey>, IDisposable
    where TRole : IdentityRole<TKey, TUserRole>, new()
    where TUserRole : IdentityUserRole<TKey>, new()
{
    private bool _disposed;
    private EntityStore<TRole> _roleStore;
  
    public DbContext Context { get; private set; }
    public bool DisposeContext {get; set; }
  
    public IQueryable<TRole> Roles
    {
        get
        {
            return this._roleStore.EntitySet;
        }
    }
  
    public RoleStore(DbContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        this.Context = context;
        this._roleStore = new EntityStore<TRole>(context);
    }
  
    public virtual async Task CreateAsync(TRole role)
    {
        this.ThrowIfDisposed();
        if (role == null)
        {
            throw new ArgumentNullException("role");
        }
        this._roleStore.Create(role);
        TaskExtensions.CultureAwaiter<int> cultureAwaiter = 
            this.Context.SaveChangesAsync().WithCurrentCulture<int>();
        await cultureAwaiter;
    }
  
    public virtual async Task DeleteAsync(TRole role)
    {
        this.ThrowIfDisposed();
        if (role == null)
        {
            throw new ArgumentNullException("role");
        }
        this._roleStore.Delete(role);
        TaskExtensions.CultureAwaiter<int> cultureAwaiter = 
            this.Context.SaveChangesAsync().WithCurrentCulture<int>();
        await cultureAwaiter;
    }
  
    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }
  
    protected virtual void Dispose(bool disposing)
    {
        if (this.DisposeContext && disposing && this.Context != null)
        {
            this.Context.Dispose();
        }
        this._disposed = true;
        this.Context = null;
        this._roleStore = null;
    }
  
    public Task<TRole> FindByIdAsync(TKey roleId)
    {
        this.ThrowIfDisposed();
        return this._roleStore.GetByIdAsync(roleId);
    }
  
    public Task<TRole> FindByNameAsync(string roleName)
    {
        this.ThrowIfDisposed();
        return QueryableExtensions
            .FirstOrDefaultAsync<TRole>(this._roleStore.EntitySet, 
                (TRole u) => u.Name.ToUpper() == roleName.ToUpper());
    }
  
    private void ThrowIfDisposed()
    {
        if (this._disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }
  
    public virtual async Task UpdateAsync(TRole role)
    {
        this.ThrowIfDisposed();
        if (role == null)
        {
            throw new ArgumentNullException("role");
        }
        this._roleStore.Update(role);
        TaskExtensions.CultureAwaiter<int> cultureAwaiter = 
            this.Context.SaveChangesAsync().WithCurrentCulture<int>();
        await cultureAwaiter;
    }
}

 

The code above is interesting, and we will be looking more closely in a bit. For the moment, notice the highlighted item. RoleStore wraps an instance of EntityStore<TRole> . If we follow our noses a little further, we find a definition for EntityStore as well:

The EntityStore Class from Identity 2.0 Framework:
internal class EntityStore<TEntity>
where TEntity : class
{
    public DbContext Context { get; private set; }
    public DbSet<TEntity> DbEntitySet { get; private set; }
    public IQueryable<TEntity> EntitySet
    {
        get
        {
            return this.DbEntitySet;
        }
    }
    public EntityStore(DbContext context)
    {
        this.Context = context;
        this.DbEntitySet = context.Set<TEntity>();
    }
    public void Create(TEntity entity)
    {
        this.DbEntitySet.Add(entity);
    }
    public void Delete(TEntity entity)
    {
        this.DbEntitySet.Remove(entity);
    }
    public virtual Task<TEntity> GetByIdAsync(object id)
    {
        return this.DbEntitySet.FindAsync(new object[] { id });
    }
    public virtual void Update(TEntity entity)
    {
        if (entity != null)
        {
            this.Context.Entry<TEntity>(entity).State = EntityState.Modified;
        }
    }

 

This code is also of great interest, despite its simplicity. Unfortunately, we cannot directly use the EntityStore class within our project. Note the internal modifier in the class declaration - this means EntityStore is only available to classes within the Microsoft.AspNet.Identity.EntityFramework assembly. In other words, we can't consume EntityStore in order to build up our own GroupStore implementation. Instead, we will take the time-honored approach of stealing/copying.

Building an Asynchronous GroupStore

We are going to apply the same conventions used by the Identity team in building out a GroupStore class, and then, in similar fashion, wrap a GroupManager class around THAT, much the same as Identity Framework wraps a RoleManager class around an instance of RoleStore.

But first, we need to deal with the EntityStore problem. in order to properly mimic the structure used to build up RoleStore and UserStore, we need to basically create our own implementation of EntityStore. In our case, we don't need a generically-typed class - we can create a non-generic implementation specific to our needs.

Mimicking EntityStore - Building the GroupStoreBase Class

We can basically steal most of the code from the EntityStore<TEntity> class shown above, and adapt it to suit our needs by removing the generic type arguments for the class itself, and by passing non-generic arguments where needed. Add a class named GroupStoreBase to the Models folder of your project, and then use the following code for the class itself. First, you will need the following using statements a the top of your code file:

Required Assembly References for the GroupStoreBase Class:
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;

 

The GroupStoreBase Class:
public class GroupStoreBase
{
    public DbContext Context { get; private set; }
    public DbSet<ApplicationGroup> DbEntitySet { get; private set; }
  
  
    public IQueryable<ApplicationGroup> EntitySet
    {
        get
        {
            return this.DbEntitySet;
        }
    }
  
  
    public GroupStoreBase(DbContext context)
    {
        this.Context = context;
        this.DbEntitySet = context.Set<ApplicationGroup>();
    }
  
  
    public void Create(ApplicationGroup entity)
    {
        this.DbEntitySet.Add(entity);
    }
  
  
    public void Delete(ApplicationGroup entity)
    {
        this.DbEntitySet.Remove(entity);
    }
  
  
    public virtual Task<ApplicationGroup> GetByIdAsync(object id)
    {
        return this.DbEntitySet.FindAsync(new object[] { id });
    }
  
  
    public virtual ApplicationGroup GetById(object id)
    {
        return this.DbEntitySet.Find(new object[] { id });
    }
  
  
    public virtual void Update(ApplicationGroup entity)
    {
        if (entity != null)
        {
            this.Context.Entry<ApplicationGroup>(entity).State = EntityState.Modified;
        }
    }
}

 

Note the structure here. the GroupStoreBase provides methods for working with a DbSet<ApplicationGroup> , but performs no persistence to the backside database directly. Persistence will be controlled by the next class in our structure, ApplicationGroupStore.

Building the Primary ApplicationGroupStore Class

In following the pattern used by UserStore and RoleStore, we will now build out a GroupStore class, which will be composed around our new GroupStoreBase class. Add another class to the models folder named ApplicationGroupStore, and add the following code:

The ApplicationGroupStore Class:
public class ApplicationGroupStore : IDisposable
{
    private bool _disposed;
    private GroupStoreBase _groupStore;
  
  
    public ApplicationGroupStore(DbContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        this.Context = context;
        this._groupStore = new GroupStoreBase(context);
    }
  
  
    public IQueryable<ApplicationGroup> Groups
    {
        get
        {
            return this._groupStore.EntitySet;
        }
    }
    
    public DbContext Context
    {
        get;
        private set;
    }
  
  
    public virtual void Create(ApplicationGroup group)
    {
        this.ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException("group");
        }
        this._groupStore.Create(group);
        this.Context.SaveChanges();
    }
  
  
    public virtual async Task CreateAsync(ApplicationGroup group)
    {
        this.ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException("group");
        }
        this._groupStore.Create(group);
        await this.Context.SaveChangesAsync();
    }
  
  
    public virtual async Task DeleteAsync(ApplicationGroup group)
    {
        this.ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException("group");
        }
        this._groupStore.Delete(group);
        await this.Context.SaveChangesAsync();
    }
  
  
    public virtual void Delete(ApplicationGroup group)
    {
        this.ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException("group");
        }
        this._groupStore.Delete(group);
        this.Context.SaveChanges();
    }
  
  
    public Task<ApplicationGroup> FindByIdAsync(string roleId)
    {
        this.ThrowIfDisposed();
        return this._groupStore.GetByIdAsync(roleId);
    }
  
  
    public ApplicationGroup FindById(string roleId)
    {
        this.ThrowIfDisposed();
        return this._groupStore.GetById(roleId);
    }
  
  
    public Task<ApplicationGroup> FindByNameAsync(string groupName)
    {
        this.ThrowIfDisposed();
        return QueryableExtensions
            .FirstOrDefaultAsync<ApplicationGroup>(this._groupStore.EntitySet, 
                (ApplicationGroup u) => u.Name.ToUpper() == groupName.ToUpper());
    }
  
  
    public virtual async Task UpdateAsync(ApplicationGroup group)
    {
        this.ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException("group");
        }
        this._groupStore.Update(group);
        await this.Context.SaveChangesAsync();
    }
  
  
    public virtual void Update(ApplicationGroup group)
    {
        this.ThrowIfDisposed();
        if (group == null)
        {
            throw new ArgumentNullException("group");
        }
        this._groupStore.Update(group);
        this.Context.SaveChanges();
    }
  
  
    // DISPOSE STUFF: ===============================================
  
    public bool DisposeContext
    {
        get;
        set;
    }
  
  
    private void ThrowIfDisposed()
    {
        if (this._disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }
  
   
    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }
  
  
    protected virtual void Dispose(bool disposing)
    {
        if (this.DisposeContext && disposing && this.Context != null)
        {
            this.Context.Dispose();
        }
        this._disposed = true;
        this.Context = null;
        this._groupStore = null;
    }
}

 

Some things to note about the ApplicationGroupStore class. First, notice that this class takes care of all the actual persistence to the backing store, by virtue of calls to .SaveChanges() or .SaveChangesAsync(). Also, we have provided both an async and a synchronous implementation for each of the methods.

However, we're not quite done yet. While the ApplicationGroupStore manages the basics of persistence for Groups, what it does NOT do is handle the complexities introduced by the relationships between Groups, Users, and Roles. Here, we can perform the basic CRUD operations on our groups, but we have no control over the relationships between the classes.

This becomes the job of the ApplicationGroupManager class.

Managing Complex Relationships - The ApplicationGroupManager Class

The relationships between Users, Groups, and Roles, for the sake of our application, are more complex than they might first appear.

Our Users-Groups-Roles structure is actually performing a bit of an illusion. It may appear, when we are done, that Roles will "belong" to groups, and that Users, by virtue of membership in a particular group, gain access to the Roles of that Group. However, what is really going on is that, when a User is assigned to a particular group, our application is then adding that user to each Role within the Group.

This is a subtle, but important distinction.

Let's assume we have an existing Group with two Roles assigned - "CanEditAccount" and "CanViewAccount." Let's further assume that there are three users in this group. Finally, let's say we want to add another (already existing) Role to this group - "CanDeleteAccount." What needs to happen?

  1. We assign the role to the group
  2. We need to add each member or the group to the new role

On the face of it, that is relatively straightforward. However, each User can belong to more than one group. Also, a Role can be assigned to more than one group. What if we want to remove a Role from a group?

  1. Remove each User in the Group from the Role, except when they are also a member of another group which also has that same role
  2. Remove the Role from the Group

This is a little more complicated. A similar situation arises if we wish to remove a User from a Group:

  1. Remove the User from all Roles in the Group, except when the user also belongs to another Group with the same role
  2. Remove the User from the Group

And so on. In order to get the predictable, intuitive behavior from our application which will be expected by the end user, there is more going on than meets the eye.

It will be the job of the ApplicationGroupManager to handle these types of problems for us, and afford a clear API against which our controllers can work between the user and the backside data.

We have created the GroupStore class to handle the basic persistence of Group data, and we have the ApplicationUserManager and ApplicationRoleManager classes to handle the relationships between Users, Roles, and persistence. For the most part, the ApplicationGroupManager's job will mainly be governing the interactions of these three stores, and occasionally the DbContext directly.

We will accomplish this by defining an API which, similar to the Identity base classes UserManager and RoleManager, affords us the intuitive methods we need to deal with Group-based Role management. We will provide both synchronous and asynchronous implementations.

Add another class to the Models folder, and name it ApplicationGroupManager. You will need the following using statements at the top of your code file:

Required Assembly References for ApplicationGroupManager:
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

 

Now, add the following code:

The ApplicationGroupManager Class:
public class ApplicationGroupManager
{
    private ApplicationGroupStore _groupStore;
    private ApplicationDbContext _db;
    private ApplicationUserManager _userManager;
    private ApplicationRoleManager _roleManager;
  
    public ApplicationGroupManager()
    {
        _db = HttpContext.Current
            .GetOwinContext().Get<ApplicationDbContext>();
        _userManager = HttpContext.Current
            .GetOwinContext().GetUserManager<ApplicationUserManager>();
        _roleManager = HttpContext.Current
            .GetOwinContext().Get<ApplicationRoleManager>();
        _groupStore = new ApplicationGroupStore(_db);
    }
  
  
    public IQueryable<ApplicationGroup> Groups
    {
        get
        {
            return _groupStore.Groups;
        }
    }
  
  
    public async Task<IdentityResult> CreateGroupAsync(ApplicationGroup group)
    {
        await _groupStore.CreateAsync(group);
        return IdentityResult.Success;
    }
  
 
    public IdentityResult CreateGroup(ApplicationGroup group)
    {
        _groupStore.Create(group);
        return IdentityResult.Success;
    }
  
  
    public IdentityResult SetGroupRoles(string groupId, params string[] roleNames)
    {
        // Clear all the roles associated with this group:
        var thisGroup = this.FindById(groupId);
        thisGroup.ApplicationRoles.Clear();
        _db.SaveChanges();
  
        // Add the new roles passed in:
        var newRoles = _roleManager.Roles.Where(r => roleNames.Any(n => n == r.Name));
        foreach(var role in newRoles)
        {
            thisGroup.ApplicationRoles.Add(new ApplicationGroupRole 
            { 
                ApplicationGroupId = groupId, 
                ApplicationRoleId = role.Id 
            });
        }
        _db.SaveChanges();
  
        // Reset the roles for all affected users:
        foreach(var groupUser in thisGroup.ApplicationUsers)
        {
            this.RefreshUserGroupRoles(groupUser.ApplicationUserId);
        }
        return IdentityResult.Success;
    }
  
  
    public async Task<IdentityResult> SetGroupRolesAsync(
        string groupId, params string[] roleNames)
    {
        // Clear all the roles associated with this group:
        var thisGroup = await this.FindByIdAsync(groupId);
        thisGroup.ApplicationRoles.Clear();
        await _db.SaveChangesAsync();
  
        // Add the new roles passed in:
        var newRoles = _roleManager.Roles
                        .Where(r => roleNames.Any(n => n == r.Name));
        foreach (var role in newRoles)
        {
            thisGroup.ApplicationRoles.Add(new ApplicationGroupRole 
            { 
                ApplicationGroupId = groupId, 
                ApplicationRoleId = role.Id 
            });
        }
        await _db.SaveChangesAsync();
  
        // Reset the roles for all affected users:
        foreach (var groupUser in thisGroup.ApplicationUsers)
        {
            await this.RefreshUserGroupRolesAsync(groupUser.ApplicationUserId);
        }
        return IdentityResult.Success;
    }
  
  
    public async Task<IdentityResult> SetUserGroupsAsync(
        string userId, params string[] groupIds)
    {
        // Clear current group membership:
        var currentGroups = await this.GetUserGroupsAsync(userId);
        foreach (var group in currentGroups)
        {
            group.ApplicationUsers
                .Remove(group.ApplicationUsers
                .FirstOrDefault(gr => gr.ApplicationUserId == userId
            ));
        }
        await _db.SaveChangesAsync();
  
        // Add the user to the new groups:
        foreach (string groupId in groupIds)
        {
            var newGroup = await this.FindByIdAsync(groupId);
            newGroup.ApplicationUsers.Add(new ApplicationUserGroup 
            { 
                ApplicationUserId = userId, 
                ApplicationGroupId = groupId 
            });
        }
        await _db.SaveChangesAsync();
  
        await this.RefreshUserGroupRolesAsync(userId);
        return IdentityResult.Success;
    }
  
  
    public IdentityResult SetUserGroups(string userId, params string[] groupIds)
    {
        // Clear current group membership:
        var currentGroups = this.GetUserGroups(userId);
        foreach(var group in currentGroups)
        {
            group.ApplicationUsers
                .Remove(group.ApplicationUsers
                .FirstOrDefault(gr => gr.ApplicationUserId == userId
            ));
        }
        _db.SaveChanges();
  
        // Add the user to the new groups:
        foreach(string groupId in groupIds)
        {
            var newGroup = this.FindById(groupId);
            newGroup.ApplicationUsers.Add(new ApplicationUserGroup 
            { 
                ApplicationUserId = userId, 
                ApplicationGroupId = groupId 
            });
        }
        _db.SaveChanges();
  
        this.RefreshUserGroupRoles(userId);
        return IdentityResult.Success;
    }
  
  
    public IdentityResult RefreshUserGroupRoles(string userId)
    {
        var user = _userManager.FindById(userId);
        if(user == null)
        {
            throw new ArgumentNullException("User");
        }
        // Remove user from previous roles:
        var oldUserRoles = _userManager.GetRoles(userId);
        if(oldUserRoles.Count > 0)
        {
            _userManager.RemoveFromRoles(userId, oldUserRoles.ToArray());
        }
  
        // Find teh roles this user is entitled to from group membership:
        var newGroupRoles = this.GetUserGroupRoles(userId);
  
        // Get the damn role names:
        var allRoles = _roleManager.Roles.ToList();
        var addTheseRoles = allRoles
            .Where(r => newGroupRoles.Any(gr => gr.ApplicationRoleId == r.Id
        ));
        var roleNames = addTheseRoles.Select(n => n.Name).ToArray();
  
        // Add the user to the proper roles
        _userManager.AddToRoles(userId, roleNames);
  
        return IdentityResult.Success;
    }
  
  
    public async Task<IdentityResult> RefreshUserGroupRolesAsync(string userId)
    {
        var user = await _userManager.FindByIdAsync(userId);
        if (user == null)
        {
            throw new ArgumentNullException("User");
        }
        // Remove user from previous roles:
        var oldUserRoles = await _userManager.GetRolesAsync(userId);
        if (oldUserRoles.Count > 0)
        {
            await _userManager.RemoveFromRolesAsync(userId, oldUserRoles.ToArray());
        }
  
        // Find the roles this user is entitled to from group membership:
        var newGroupRoles = await this.GetUserGroupRolesAsync(userId);
  
        // Get the damn role names:
        var allRoles = await _roleManager.Roles.ToListAsync();
        var addTheseRoles = allRoles
            .Where(r => newGroupRoles.Any(gr => gr.ApplicationRoleId == r.Id
        ));
        var roleNames = addTheseRoles.Select(n => n.Name).ToArray();
  
        // Add the user to the proper roles
        await _userManager.AddToRolesAsync(userId, roleNames);
  
        return IdentityResult.Success;
    }
  
  
    public async Task<IdentityResult> DeleteGroupAsync(string groupId)
    {
        var group = await this.FindByIdAsync(groupId);
        if (group == null)
        {
            throw new ArgumentNullException("User");
        }
  
        var currentGroupMembers = (await this.GetGroupUsersAsync(groupId)).ToList();
        // remove the roles from the group:
        group.ApplicationRoles.Clear();
  
        // Remove all the users:
        group.ApplicationUsers.Clear();
  
        // Remove the group itself:
        _db.ApplicationGroups.Remove(group);
  
        await _db.SaveChangesAsync();
  
        // Reset all the user roles:
        foreach (var user in currentGroupMembers)
        {
            await this.RefreshUserGroupRolesAsync(user.Id);
        }
        return IdentityResult.Success;
    }
  
  
    public IdentityResult DeleteGroup(string groupId)
    {
        var group = this.FindById(groupId);
        if(group == null)
        {
            throw new ArgumentNullException("User");
        }
  
        var currentGroupMembers = this.GetGroupUsers(groupId).ToList();
        // remove the roles from the group:
        group.ApplicationRoles.Clear();
  
        // Remove all the users:
        group.ApplicationUsers.Clear();
  
        // Remove the group itself:
        _db.ApplicationGroups.Remove(group);
  
        _db.SaveChanges();
  
        // Reset all the user roles:
        foreach(var user in currentGroupMembers)
        {
            this.RefreshUserGroupRoles(user.Id);
        }
        return IdentityResult.Success;
    }
  
  
    public async Task<IdentityResult> UpdateGroupAsync(ApplicationGroup group)
    {
        await _groupStore.UpdateAsync(group);
        foreach (var groupUser in group.ApplicationUsers)
        {
            await this.RefreshUserGroupRolesAsync(groupUser.ApplicationUserId);
        }
        return IdentityResult.Success;
    }
  
  
    public IdentityResult UpdateGroup(ApplicationGroup group)
    {
        _groupStore.Update(group);
        foreach(var groupUser in group.ApplicationUsers)
        {
            this.RefreshUserGroupRoles(groupUser.ApplicationUserId);
        }
        return IdentityResult.Success;
    }
  
  
    public IdentityResult ClearUserGroups(string userId)
    {
        return this.SetUserGroups(userId, new string[] { });
    }
  
  
    public async Task<IdentityResult> ClearUserGroupsAsync(string userId)
    {
        return await this.SetUserGroupsAsync(userId, new string[] { });
    }
  
  
    public async Task<IEnumerable<ApplicationGroup>> GetUserGroupsAsync(string userId)
    {
        var result = new List<ApplicationGroup>();
        var userGroups = (from g in this.Groups
                          where g.ApplicationUsers
                            .Any(u => u.ApplicationUserId == userId)
                          select g).ToListAsync();
        return await userGroups;
    }
  
  
    public IEnumerable<ApplicationGroup> GetUserGroups(string userId)
    {
        var result = new List<ApplicationGroup>();
        var userGroups = (from g in this.Groups
                          where g.ApplicationUsers
                            .Any(u => u.ApplicationUserId == userId)
                          select g).ToList();
        return userGroups;
    }
  
  
    public async Task<IEnumerable<ApplicationRole>> GetGroupRolesAsync(
        string groupId)
    {
        var grp = await _db.ApplicationGroups
            .FirstOrDefaultAsync(g => g.Id == groupId);
        var roles = await _roleManager.Roles.ToListAsync();
        var groupRoles = (from r in roles
                          where grp.ApplicationRoles
                            .Any(ap => ap.ApplicationRoleId == r.Id)
                          select r).ToList();
        return groupRoles;
    }
  
  
    public IEnumerable<ApplicationRole> GetGroupRoles(string groupId)
    {
        var grp = _db.ApplicationGroups.FirstOrDefault(g => g.Id == groupId);
        var roles = _roleManager.Roles.ToList();
        var groupRoles = from r in roles
                         where grp.ApplicationRoles
                            .Any(ap => ap.ApplicationRoleId == r.Id)
                         select r;
        return groupRoles;
    }
  
  
    public IEnumerable<ApplicationUser> GetGroupUsers(string groupId)
    {
        var group = this.FindById(groupId);
        var users = new List<ApplicationUser>();
        foreach (var groupUser in group.ApplicationUsers)
        {
            var user = _db.Users.Find(groupUser.ApplicationUserId);
            users.Add(user);
        }
        return users;
    }
  
  
    public async Task<IEnumerable<ApplicationUser>> GetGroupUsersAsync(string groupId)
    {
        var group = await this.FindByIdAsync(groupId);
        var users = new List<ApplicationUser>();
        foreach (var groupUser in group.ApplicationUsers)
        {
            var user = await _db.Users
                .FirstOrDefaultAsync(u => u.Id == groupUser.ApplicationUserId);
            users.Add(user);
        }
        return users;
    }
  
  
    public IEnumerable<ApplicationGroupRole> GetUserGroupRoles(string userId)
    {
        var userGroups = this.GetUserGroups(userId);
        var userGroupRoles = new List<ApplicationGroupRole>();
        foreach(var group in userGroups)
        {
            userGroupRoles.AddRange(group.ApplicationRoles.ToArray());
        }
        return userGroupRoles;
    }
  
  
    public async Task<IEnumerable<ApplicationGroupRole>> GetUserGroupRolesAsync(
        string userId)
    {
        var userGroups = await this.GetUserGroupsAsync(userId);
        var userGroupRoles = new List<ApplicationGroupRole>();
        foreach (var group in userGroups)
        {
            userGroupRoles.AddRange(group.ApplicationRoles.ToArray());
        }
        return userGroupRoles;
    }
  
  
    public async Task<ApplicationGroup> FindByIdAsync(string id)
    {
        return await _groupStore.FindByIdAsync(id);
    }
  
  
    public ApplicationGroup FindById(string id)
    {
        return _groupStore.FindById(id);
    }
}

 

As we can see from the above, there's a lot of code there. However, much of it is due to duplication between synchronous and asynchronous method implementations. With the above, we now have an API to work against from our controllers, and we can now get down to brass tacks, and add the Groups functionality to our site.

We've kept things as simple as possible in terms of adding and removing Users from Groups, Roles to and from Groups, and such. As you can see, each time we change the groups a user belongs to, we change all the group assignments at once, by calling SetUserGroups() and passing in an array of Group Id's. Similarly, we assign all of the Roles to a group in one shot by calling SetGroupRoles() and again, passing in an array of Role names, representing all of the roles assigned to a particular group.

We do these in this manner because, when we modify a User's Group membership, we need to essentially re-set all of the User's Roles anyway. In like manner, when we modify the Roles assigned to a particular group, we need to refresh the Roles assigned to every user within that group.

This is also handy because, when we receive the User and/or Role selections made by the user from either of the Admin Views, we get them as an array anyway. We'll see this more closely in a bit.

Adding a GroupViewModel

We will need a view model for passing group data between various controller methods and their associated Views. in the Models => AdminViewModel.cs file, add the following class:

The GroupVeiwModel Class:
public class GroupViewModel
{
    public GroupViewModel()
    {
        this.UsersList = new List<SelectListItem>();
        this.PermissionsList = new List<SelectListItem>();
    }
    [Required(AllowEmptyStrings = false)]
    public string Id { get; set; }
    [Required(AllowEmptyStrings = false)]
    public string Name { get; set; }
    public string Description { get; set; }
    public ICollection<SelectListItem> UsersList { get; set; }
    public ICollection<SelectListItem> RolesList { get; set; }
}

 

Notice here that we are passing ICollection<SelectListItem> to represent Users and Roles assigned to a Group. This way, we can pass a list of user names or or role names out to a view, allow the user to select one or more items from the list, and then process the selection choices made when the form data is submitted back to the controller via the HTTP POST method.

Update the EditUserViewModel

While we have the AdminViewModel.cs file open, let's also modify the EditUserViewModel, and add a collection property for Groups:

Add a GroupsList Property to EditUserViewModel:
public class EditUserViewModel
{
    public EditUserViewModel()
    {
        this.RolesList = new List<SelectListItem>();
        this.GroupsList = new List<SelectListItem>();
    }
    public string Id { get; set; }
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "Email")]
    [EmailAddress]
    public string Email { get; set; }
  
    // We will still use this, so leave it here:
    public ICollection<SelectListItem> RolesList { get; set; }
  
    // Add a GroupsList Property:
    public ICollection<SelectListItem> GroupsList { get; set; }
}

 

We will keep the RolesList collection as well. Even though we won't be assigning Roles directly to Users anymore, we may want to create a View which allows us to see what Roles a user has by virtue of membership in various Groups. This way, we can use the same ViewModel.

Now that we have our ViewModel all tuned up, let's add a GroupsAdminController.

Building the GroupsAdminController

Similar to the existing UserAdminController and the RolesAdminController, we need to provide a Controller to work with our new Groups functionality.

We need to add a controller in the Controllers directory. Instead of using the Context Menu => Add Controller method, just add a class, named GroupsAdminController, and add the following code:

Adding the GroupsAdminController:
public class GroupsAdminController : Controller
{
    private ApplicationDbContext db = new ApplicationDbContext();
  
    private ApplicationGroupManager _groupManager;
    public ApplicationGroupManager GroupManager
    {
        get
        {
            return _groupManager ?? new ApplicationGroupManager();
        }
        private set
        {
            _groupManager = value;
        }
    }
  
    private ApplicationRoleManager _roleManager;
    public ApplicationRoleManager RoleManager
    {
        get
        {
            return _roleManager ?? HttpContext.GetOwinContext()
                .Get<ApplicationRoleManager>();
        }
        private set
        {
            _roleManager = value;
        }
    }
  
  
    public ActionResult Index()
    {
        return View(this.GroupManager.Groups.ToList());
    } 
  
  
    public async Task<ActionResult> Details(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        ApplicationGroup applicationgroup =
            await this.GroupManager.Groups.FirstOrDefaultAsync(g => g.Id == id);
        if (applicationgroup == null)
        {
            return HttpNotFound();
        }
        var groupRoles = this.GroupManager.GetGroupRoles(applicationgroup.Id);
        string[] RoleNames = groupRoles.Select(p => p.Name).ToArray();
        ViewBag.RolesList = RoleNames;
        ViewBag.RolesCount = RoleNames.Count();
        return View(applicationgroup);
    }
  
  
    public ActionResult Create()
    {
        //Get a SelectList of Roles to choose from in the View:
        ViewBag.RolesList = new SelectList(
            this.RoleManager.Roles.ToList(), "Id", "Name");
        return View();
    }
  
  
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Create(
        [Bind(Include = "Name,Description")] ApplicationGroup applicationgroup, 
        params string[] selectedRoles)
    {
        if (ModelState.IsValid)
        {
            // Create the new Group:
            var result = await this.GroupManager.CreateGroupAsync(applicationgroup);
            if (result.Succeeded)
            {
                selectedRoles = selectedRoles ?? new string[] { };
  
                // Add the roles selected:
                await this.GroupManager
                    .SetGroupRolesAsync(applicationgroup.Id, selectedRoles);
            }
            return RedirectToAction("Index");
        }
 
        // Otherwise, start over:
        ViewBag.RoleId = new SelectList(
            this.RoleManager.Roles.ToList(), "Id", "Name");
        return View(applicationgroup);
    }
  
  
    public async Task<ActionResult> Edit(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        ApplicationGroup applicationgroup = await this.GroupManager.FindByIdAsync(id);
        if (applicationgroup == null)
        {
            return HttpNotFound();
        }
  
        // Get a list, not a DbSet or queryable:
        var allRoles = await this.RoleManager.Roles.ToListAsync();
        var groupRoles = await this.GroupManager.GetGroupRolesAsync(id);
  
        var model = new GroupViewModel()
        {
            Id = applicationgroup.Id,
            Name = applicationgroup.Name,
            Description = applicationgroup.Description
        };
  
        // load the roles/Roles for selection in the form:
        foreach (var Role in allRoles)
        {
            var listItem = new SelectListItem()
            {
                Text = Role.Name,
                Value = Role.Id,
                Selected = groupRoles.Any(g => g.Id == Role.Id)
            };
            model.RolesList.Add(listItem);
        }
        return View(model);
    }
  
  
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Edit(
        [Bind(Include = "Id,Name,Description")] GroupViewModel model, 
        params string[] selectedRoles)
    {
        var group = await this.GroupManager.FindByIdAsync(model.Id);
        if (group == null)
        {
            return HttpNotFound();
        }
        if (ModelState.IsValid)
        {
            group.Name = model.Name;
            group.Description = model.Description;
            await this.GroupManager.UpdateGroupAsync(group);
  
            selectedRoles = selectedRoles ?? new string[] { };
            await this.GroupManager.SetGroupRolesAsync(group.Id, selectedRoles);
            return RedirectToAction("Index");
        }
        return View(model);
    }
  
  
    public async Task<ActionResult> Delete(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        ApplicationGroup applicationgroup = await this.GroupManager.FindByIdAsync(id);
        if (applicationgroup == null)
        {
            return HttpNotFound();
        }
        return View(applicationgroup);
    }
  
  
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> DeleteConfirmed(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        ApplicationGroup applicationgroup = await this.GroupManager.FindByIdAsync(id);
        await this.GroupManager.DeleteGroupAsync(id);
        return RedirectToAction("Index");
    }
  
  
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }
}

 

As we can see, we use the GroupsAdminController to manage the creation of groups, and the assignment of roles to various groups. Now, we need to modify the UsersAdminController so that instead of assigning Users to Roles directly, we are instead assigning Users to Groups, through which they gain access to the roles assigned to each group.

Modify the UsersAdminController

We need to make a few adjustments to the UsersAdminController to reflect the manner in which we are managing Groups and Roles. As mentioned previously, we are now assigning users to Groups instead of directly to roles, and our UsersAdminController needs to reflect this.

First off, we need to add an instance of ApplicationGroupManager to the controller. Next, we need to update all of our controller methods to consume Groups instead of Roles. When we create a new User, we want the View to include a list of available Groups to which the User might be assigned. When we Edit an existing User, we want the option to modify group assignments. When we Delete a user, we need to make sure the corresponding Group relationships are deleted as well.

The Following is the updated code for the entire UsersAdminController. It is easier to copy the entire thing in than to wade through it piece by piece. Then you can eyeball things, and fairly easily understand what is going on in each controller method. 

The Modified UsersAdminController:
[Authorize(Roles = "Admin")]
public class UsersAdminController : Controller
{
    public UsersAdminController()
    {
    }
  
    public UsersAdminController(ApplicationUserManager userManager, 
        ApplicationRoleManager roleManager)
    {
        UserManager = userManager;
        RoleManager = roleManager;
    }
  
    private ApplicationUserManager _userManager;
    public ApplicationUserManager UserManager
    {
        get
        {
            return _userManager ?? HttpContext.GetOwinContext()
                .GetUserManager<ApplicationUserManager>();
        }
        private set
        {
            _userManager = value;
        }
    }
  
    // Add the Group Manager (NOTE: only access through the public
    // Property, not by the instance variable!)
    private ApplicationGroupManager _groupManager;
    public ApplicationGroupManager GroupManager
    {
        get
        {
            return _groupManager ?? new ApplicationGroupManager();
        }
        private set
        {
            _groupManager = value;
        }
    }
  
    private ApplicationRoleManager _roleManager;
    public ApplicationRoleManager RoleManager
    {
        get
        {
            return _roleManager ?? HttpContext.GetOwinContext()
                .Get<ApplicationRoleManager>();
        }
        private set
        {
            _roleManager = value;
        }
    }
  
      
    public async Task<ActionResult> Index()
    {
        return View(await UserManager.Users.ToListAsync());
    }
  
  
    public async Task<ActionResult> Details(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        var user = await UserManager.FindByIdAsync(id);
  
        // Show the groups the user belongs to:
        var userGroups = await this.GroupManager.GetUserGroupsAsync(id);
        ViewBag.GroupNames = userGroups.Select(u => u.Name).ToList();
        return View(user);
    }
  
  
    public ActionResult Create()
    {
        // Show a list of available groups:
        ViewBag.GroupsList = 
            new SelectList(this.GroupManager.Groups, "Id", "Name");
        return View();
    }
  
  
    [HttpPost]
    public async Task<ActionResult> Create(RegisterViewModel userViewModel, 
        params string[] selectedGroups)
    {
        if (ModelState.IsValid)
        {
            var user = new ApplicationUser 
            { 
                UserName = userViewModel.Email, 
                Email = userViewModel.Email 
            };
            var adminresult = await UserManager
                .CreateAsync(user, userViewModel.Password);
  
            //Add User to the selected Groups 
            if (adminresult.Succeeded)
            {
                if (selectedGroups != null)
                {
                    selectedGroups = selectedGroups ?? new string[] { };
                    await this.GroupManager
                        .SetUserGroupsAsync(user.Id, selectedGroups);
                }
                return RedirectToAction("Index");
            }
        }
        ViewBag.Groups = new SelectList(
            await RoleManager.Roles.ToListAsync(), "Id", "Name");
        return View();
    }
  
  
    public async Task<ActionResult> Edit(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        var user = await UserManager.FindByIdAsync(id);
        if (user == null)
        {
            return HttpNotFound();
        }
  
        // Display a list of available Groups:
        var allGroups = this.GroupManager.Groups;
        var userGroups = await this.GroupManager.GetUserGroupsAsync(id);
  
        var model = new EditUserViewModel()
        {
            Id = user.Id,
            Email = user.Email
        };
  
        foreach (var group in allGroups)
        {
            var listItem = new SelectListItem()
            {
                Text = group.Name,
                Value = group.Id,
                Selected = userGroups.Any(g => g.Id == group.Id)
            };
            model.GroupsList.Add(listItem);
        }
        return View(model);
    }
  
  
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Edit(
        [Bind(Include = "Email,Id")] EditUserViewModel editUser, 
        params string[] selectedGroups)
    {
        if (ModelState.IsValid)
        {
            var user = await UserManager.FindByIdAsync(editUser.Id);
            if (user == null)
            {
                return HttpNotFound();
            }
  
            // Update the User:
            user.UserName = editUser.Email;
            user.Email = editUser.Email;
            await this.UserManager.UpdateAsync(user);
  
            // Update the Groups:
            selectedGroups = selectedGroups ?? new string[] { };
            await this.GroupManager.SetUserGroupsAsync(user.Id, selectedGroups);
            return RedirectToAction("Index");
        }
        ModelState.AddModelError("", "Something failed.");
        return View();
    }
  
  
    public async Task<ActionResult> Delete(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        var user = await UserManager.FindByIdAsync(id);
        if (user == null)
        {
            return HttpNotFound();
        }
        return View(user);
    }
  
  
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> DeleteConfirmed(string id)
    {
        if (ModelState.IsValid)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
  
            var user = await UserManager.FindByIdAsync(id);
            if (user == null)
            {
                return HttpNotFound();
            }
  
            // Remove all the User Group references:
            await this.GroupManager.ClearUserGroupsAsync(id);
  
            // Then Delete the User:
            var result = await UserManager.DeleteAsync(user);
            if (!result.Succeeded)
            {
                ModelState.AddModelError("", result.Errors.First());
                return View();
            }
            return RedirectToAction("Index");
        }
        return View();
    }
}

 

Now that we have all of our Controllers in place, we need to add and/or update some Views.

Add Groups Admin as a Menu Item in the Main Layout View

In order to access our new Groups, we will need to add a menu item to Views => Shared => _Layout.cshtml. Since we only want Admin users to access menu item, we want to tuck it in with the other links to Admin-type Views. Modify the _Layout.cshtml file as follows (I've only included the relevant section of the View template below):

Add Groups Admin as a Link in the Main Layout View:
// Other View Code before...:
   
<ul class="nav navbar-nav">
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    <li>@Html.ActionLink("About", "About", "Home")</li>
    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
   
    @if (Request.IsAuthenticated && User.IsInRole("Admin")) {
        <li>@Html.ActionLink("RolesAdmin", "Index", "RolesAdmin")</li>
        <li>@Html.ActionLink("UsersAdmin", "Index", "UsersAdmin")</li>
        <li>@Html.ActionLink("GroupsAdmin", "Index", "GroupsAdmin")</li>
    }
</ul>
@Html.Partial("_LoginPartial")
   
// ...Other View Code After...

 

Adding Views for the GroupsAdminController

We're going to step through this fairly quickly, since there isn't a lot of discussion needed around View template code. Obviously, we need a view for each of our GroupsAdminController action methods.

Since Visual Studio won't generate quite what we need using the Add View Context Menu command, we'll do this manually.

Add a folder to the Views directory named GroupsAdmin. Now add the following Views:

The GroupsAdmin Index.cshtml View:
@model IEnumerable<IdentitySample.Models.ApplicationGroup>
@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Description)
        </th>
        <th></th>
    </tr>
  
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Description)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
            @Html.ActionLink("Details", "Details", new { id=item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Id })
        </td>
    </tr>
}
</table>

 

The GroupsAdmin Create.chtml View:
@model IdentitySample.Models.ApplicationGroup
  
@{
    ViewBag.Title = "Create";
}
  
<h2>Create</h2>
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
     
    <div class="form-horizontal">
        <h4>ApplicationGroup</h4>
        <hr />
        @Html.ValidationSummary(true)
        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name)
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>
  
        <div class="form-group">
            @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description)
                @Html.ValidationMessageFor(model => model.Description)
            </div>
        </div>
        <div class="form-group">
            <label class="col-md-2 control-label">
                Select Group Roles
            </label>
            <div class="col-md-10">
                @foreach (var item in (SelectList)ViewBag.RolesList)
                {
                    <div>
                        <input type="checkbox" name="SelectedRoles" value="@item.Text" class="checkbox-inline" />
                        @Html.Label(item.Text, new { @class = "control-label" })
                    </div>
                }
            </div>
        </div>
  
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

The GroupsAdmin Edit.cshtml View:
@model IdentitySample.Models.GroupViewModel
@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
     
    <div class="form-horizontal">
        <h4>ApplicationGroup</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.Id)
  
        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name)
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>
  
        <div class="form-group">
            @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description)
                @Html.ValidationMessageFor(model => model.Description)
            </div>
        </div>
        <div class="form-group">
            @Html.Label("Permissions", new { @class = "control-label col-md-2" })
            <span class=" col-md-10">
                @foreach (var item in Model.RolesList)
                {
                    <div>
                        <input type="checkbox" name="selectedRoles" value="@item.Text" checked="@item.Selected" class="checkbox-inline" />
                        @Html.Label(item.Text, new { @class = "control-label" })
                    </div>
                }
            </span>
        </div>
  
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

The GroupsAdmin Details.cshtml View:
@model IdentitySample.Models.ApplicationGroup
  
@{
    ViewBag.Title = "Details";
}
  
<h2>Details</h2>
  
<div>
    <h4>ApplicationGroup</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>
  
        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>
  
        <dt>
            @Html.DisplayNameFor(model => model.Description)
        </dt>
  
        <dd>
            @Html.DisplayFor(model => model.Description)
        </dd>
  
    </dl>
</div>
  
<h4>List of permissions granted this group</h4>
@if (ViewBag.PermissionsCount == 0)
{
    <hr />
    <p>No users found in this role.</p>
}
  
<table class="table">
  
    @foreach (var item in ViewBag.RolesList)
    {
        <tr>
            <td>
                @item
            </td>
        </tr>
    }
</table>
<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
    @Html.ActionLink("Back to List", "Index")
</p>

 

The GroupsAdmin Delete.cshtml View:
@model IdentitySample.Models.ApplicationGroup
  
@{
    ViewBag.Title = "Delete";
}
  
<h2>Delete</h2>
  
<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>ApplicationGroup</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Description)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Description)
        </dd>
    </dl>
  
    @using (Html.BeginForm()) {
        @Html.AntiForgeryToken()
  
        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            @Html.ActionLink("Back to List", "Index")
        </div>
    }
</div>

 

Updating the UserAdmin Views

We need to make a few minor changes to the UserAdmin Views. In the existing project, the UserAdmin Create and Edit Views allow us to assign Roles to the user. Instead, we want to assign the User to one or more Groups. Update the Create and Edit Views as follows:

Pay close attention when modifying the view code, and the names of Viewbag properties matter. 

The Modified UserAdmin Create.cshtml View:
@model IdentitySample.Models.RegisterViewModel
@{
    ViewBag.Title = "Create";
}
  
<h2>@ViewBag.Title.</h2>
  
@using (Html.BeginForm("Create", "UsersAdmin", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-error" })
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <label class="col-md-2 control-label">
            Select User Groups
        </label>
        <div class="col-md-10">
            @foreach (var item in (SelectList)ViewBag.GroupsList)
            {
                <div>
                    <input type="checkbox" name="selectedGroups" value="@item.Value" class="checkbox-inline" />
                    @Html.Label(item.Text, new { @class = "control-label" })
                </div>
            }
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Create" />
        </div>
    </div>
}
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

The highlighted area calls out the primary impacted code.

Next, we will similarly modify the UsersAdmin => Edit.cshtml file.

The Modified UsersAdmin Edit.cshtml View:
@model IdentitySample.Models.EditUserViewModel
  
@{
    ViewBag.Title = "Edit";
}
  
<h2>Edit.</h2>
  
  
@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
  
    <div class="form-horizontal">
        <h4>Edit User Form.</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.Id)
  
        <div class="form-group">
            @Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
               @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
               @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>
        <div class="form-group">
            @Html.Label("This User belongs to the following Groups", new { @class = "control-label col-md-2" })
            <span class=" col-md-10">
                @foreach (var item in Model.GroupsList)
                {
                    <div>
                        <input type="checkbox" name="selectedGroups" value="@item.Value" checked="@item.Selected" class="checkbox-inline" />
                        @Html.Label(item.Text, new { @class = "control-label" })
                    </div>
                }
            </span>
        </div>
  
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

The UsersAdmin Details.cshtml View displays a list of Roles assigned to the currently selected User. We will instead display a list of Groups:

The Modified UsersAdmin Details.cshtml View:
@model IdentitySample.Models.ApplicationUser
@{
    ViewBag.Title = "Details";
}
  
<h2>Details.</h2>
  
<div>
    <h4>User</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.UserName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.UserName)
        </dd>
    </dl>
</div>
<h4>List ofGroups this user belongs to:</h4>
@if (ViewBag.GroupNames.Count == 0)
{
    <hr />
    <p>No Groups found for this user.</p>
}
  
<table class="table">
    @foreach (var item in ViewBag.GroupNames)
    {
        <tr>
            <td>
                @item
            </td>
        </tr>
    }
</table>
<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
    @Html.ActionLink("Back to List", "Index")
</p>

 

Of course, there is more we could do with the Views in this project, and we could add a few more for displaying User effective permissions (the sum permissions a user holds by virtue of membership in the assigned Groups) for example. For now, though, we'll just get things working, and then you can fine-tune things to the needs of your project.

Modify the Identity.Config File and the DbInitializer

In the previous project, when we set up Group-Based Permissions Management under Identity 1.0, we used EF Migrations to perform the database creation and Code-First generation. This time around, we are going to continue using the DbInitializer from the IdentitySamples project.

The ApplicationDbInitializer is defined in App_Start => IdentityConfig.cs. I have set it up for development purposes to inherit from DropCreateDatabaseAlways. However, you can easily change this to DropCreateDatabaseIfModelChanges.

Of course, we want our application to run with a basic configuration ready to go. Currently, the DbInitializer sets things up by creating a default User, an Admin Role, and then assigns the default user to that role.

We want to create the same default user, but then also create a default group. Then, we will create the default Admin role and assign it to the default group. Finally, we'll add the user to that group.

Open the App_Start => Identity.Config file, and make the following changes to the InitializeIdentityForEF() Method in the ApplicationDbInitializer class:

The InitializeIdentityForEF Method:
public static void InitializeIdentityForEF(ApplicationDbContext db) {
    var userManager = HttpContext.Current
        .GetOwinContext().GetUserManager<ApplicationUserManager>();
    var roleManager = HttpContext.Current
        .GetOwinContext().Get<ApplicationRoleManager>();
    const string name = "admin@example.com";
    const string password = "Admin@123456";
    const string roleName = "Admin";
  
    //Create Role Admin if it does not exist
    var role = roleManager.FindByName(roleName);
    if (role == null) {
        role = new ApplicationRole(roleName);
        var roleresult = roleManager.Create(role);
    }
  
    var user = userManager.FindByName(name);
    if (user == null) {
        user = new ApplicationUser 
        { 
            UserName = name, 
            Email = name, 
            EmailConfirmed = true
        };
        var result = userManager.Create(user, password);
        result = userManager.SetLockoutEnabled(user.Id, false);
    }
  
    var groupManager = new ApplicationGroupManager();
    var newGroup = new ApplicationGroup("SuperAdmins", "Full Access to All");
  
    groupManager.CreateGroup(newGroup);
    groupManager.SetUserGroups(user.Id, new string[] { newGroup.Id });
    groupManager.SetGroupRoles(newGroup.Id, new string[] { role.Name });
}

 

With that, we should be ready to run the application.

Running the Application

Once we've started the application and logged in, we should be able to navigate through the various admin functions. To make things interesting, let's add a few Roles, Groups, and Users and see what's what:

If we have added some additional Roles, Users, and Groups, we begin to see how this might work in the context of a real-world application. Lets take two fictitious departments, Sales and Purchasing. We might have some Roles at a relatively granular level (perhaps, at the level of our basic controller Actions) for each function:

Basic Roles for Sales and Purchasing Departments

sales-purchasing-roles

Now, this, and our other views could most likely use some help from a designer, or at least some presentation controls which would allow for better groupings, or something. Nonetheless, we see we have added some Roles here which correspond roughly with hypothetical controller actions we might find on a CustomersController and a ProductsController (OK - we're simplifying a little for the purpose of this example, but you get the idea).

Now, we might also define some Groups:

Basic Groups for Sales and Purchasing:

sales-purchasing-groups

Again, if we were worried about design aesthetics here, we might find our Groups management View a little lacking. But you can see that we have defined a few Groups related to the functions of the Sales and Purchasing departments.

Now, if we edit one of these groups, we can assign the appropriate Roles to the group:

Assigning Group Roles When Editing Group:

select-roles-for-group

Here, we have decided that users in the SalesAdmin Group should be able to add/edit/view/delete Customer data, as well as view (but not modify) product data.

Again, we might add some improvements to the display and grouping of the available roles here, but this works for demonstration purposes.

Now, if we save the SalesAdmin Group like this, we can then assign one or more users, who will then get all of the permissions associated with this group.

Creating a New User with Group Assignments:

create-user-with-group-assignments

Once we save, Jim will be a member of the SalesAdmin Group, and will have all of the Role permissions we assigned to that group.

Controlling Access with the [Authorize] Attribute

Of course, none of this does us any good if we don't put these granular Role permissions to use.

First off, we probably want to add some access control to our GroupsAdminController itself. We might want to add an [Authorize] decoration to the entire controller, similar to the UserAdminController, so that only those with the Admin Role can modify Groups.

Beyond that, though, let's expand upon our example above. Consider a hypothetical CustomersController. We might decorate the basic controller methods as follows, in keeping with the Roles we defined:

Hypothetical CustomersController:
public class CustomerController
{
    [Authorize(Roles = "CanViewCustomers")]
    public async ActionResult Index()
    {
        // Code...
    }
  
  
    [Authorize(Roles = "CanAddCustomers")]
    public async ActionResult Create()
    {
        // Code...
    }
  
  
    [HttpPost]
    [ValidateAntiForgeryToken]
    [Authorize(Roles = "CanAddCustomers")]
    public async Task<ActionResult> Create(SomeArguments)
    {
        // Code...
    }
  
  
    [Authorize(Roles = "CanEditCustomers")]
    public async ActionResult Edit(int id)
    {
        // Code...
    }
  
  
    [HttpPost]
    [ValidateAntiForgeryToken]
    [Authorize(Roles = "CanEditCustomers")]
    public async Task<ActionResult> Edit(SomeArguments)
    {
        // Code...
    }
  
  
    [Authorize(Roles = "CanDeleteCustomers")]
    public async Task<ActionResult> Delete(id)
    {
        // Code...
    }
  
  
    [HttpPost]
    [ValidateAntiForgeryToken]
    [Authorize(Roles = "CanDeleteCustomers")]
    public async Task<ActionResult> Delete(SomeArguments)
    {
        // Code...
    }
}

 

We can see that we have now implemented some authorization control which corresponds to some of the Roles we defined. The roles themselves are not specific to any one type of user. instead, Roles can be assigned to different Groups. From there, it is a simple matter to add or remove users from one or more groups, depending upon their function and access requirements.

Some Thoughts on Authorization and Security

The concepts in this project are sort of a middle ground between the basic, but highly functional authorization mechanism offered by Identity 2.0 out of the box, and more complex implementations using Claims or Active Directory.

The system illustrated here will afford a more granular control over Roles and Authorization to access and execute code. However, there is a practical limit.

Designing a robust application authorization matrix requires careful thought, and choosing the correct tools (as with most things in development). Planning carefully upfront will go a long ways towards helping you create a solid, maintainable application.

If you get too fine-grained in your Authorizations and Role definitions, you will have a difficult to manage mess. Not going granular enough results in a clunky, limited authorization scheme where you may find yourself giving users too much or too little access.

Errata, Ideas, and Pull Requests

It is entirely possible I have missed something in putting the code together for this article. Also, as noted, there is plenty of room for improvement in the design here. If you find something broken, have an idea to improve the concept, or (Especially) would like to improve the Views with respect to organizing Roles, Groups, etc, please open an issue or send me a Pull Request.

Additional Resources and Items of Interest

 

Posted on August 10 2014 07:35 PM by jatten     

Comments (15)

ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings

Posted on July 13 2014 10:45 AM by jatten in ASP.NET MVC, ASP.Net, C#, CodeProject   ||   Comments (20)

Identity-240The ASP.NET Identity framework was released to manufacture on March 20 2014, bringing with it a slew of long-awaited enhancements, delivering a fully-formed authentication and authorization platform to the ASP.NET developer community.

In previous posts, we have taken a broad look at the structure of the new framework, and how it differs from the 1.0 release. We've also walked through implementing email account confirmation and two-factor authentication, as well as extending the basic User and Role models (which requires a bit more effort than you might think).

Image by Josh Cowper  | Some Rights Reserved

In this post we're going take a deeper look at extending the core set of models afforded by the Identity 2.0 framework, and re-implementing the basic Identity Samples project using integer keys for all of our models, instead of the default string keys which are the default.

Source Code on Github

In the course of this article, we will basically re-implement the Identity Samples project with integer keys. you can clone the completed source code from my Github repo. Also, if you find bugs and/or have suggestions, please do open an issue and/or shoot me a pull request!

Why Does ASP.NET Identity Use String Keys in the First Place?

A popular, and somewhat confounding question is "why did the Identity team choose string keys as the default for the Identity framework models? Many of us who grew up using databases tend towards easy, auto-incrementing integers as database primary keys, because it's easy, and at least in theory, there are some performance advantages with respect to table indexes and such.

The decision of the Identity Team to use strings as keys is best summarized in a Stack Overflow answer by Rick Anderson, writer for ASP.NET at Microsoft:

  1. The Identity runtime prefers strings for the user ID because we don’t want to be in the business of figuring out proper serialization of the user IDs (we use strings for claims as well for the same reason), e.g. all (or most) of the Identity interfaces refer to user ID as a string.
  2. People that customize the persistence layer, e.g. the entity types, can choose whatever type they want for keys, but then they own providing us with a string representation of the keys.
  3. By default we use the string representation of GUIDs for each new user, but that is just because it provides a very easy way for us to automatically generate unique IDs.

The decision is not without its detractors in the community. The default string key described above is essentially a string representation of a Guid. As this discussion on Reddit illustrates, there is contention about the performance aspects of this against a relational database backend.

The concerns noted in the Reddit discussion focus mainly on database index performance, and are unlikely to be an issue for a large number of smaller sites and web applications, and particularly for learning projects and students. However, as noted previously, for many of us, the auto-incrementing integer is the database primary key of choice (even in cases where it is not the BEST choice), and we want our web application to follow suit.

Identity 2.0 Core Classes use Generic Type Arguments

As we discussed in the post on customizing ASP.NET Identity 2.0 Users and Roles, the framework is built up from a structure of generic Interfaces and base classes. At the lowest level, we find interfaces, such as IUser<TKey> and IRole<TKey>. These, and related Interfaces and base classes are defined in the Microsoft.AspNet.Identity.Core library.

Moving up a level of abstraction, we can look at the Microsoft.AspNet.Identity.EntityFramework library, which uses the components defined in …Identity.Core to build the useful, ready-to-use classes commonly used in applications, and in particular by the Identity Samples project we have been using to explore Identity 2.0.

The Identity.EntityFramework library gives us some Generic base classes, as well as a default concrete implementation for each. For example, Identity.EntityFramework gives us the following generic base implementation for a class IdentityRole:

Generic Base for IdentityRole:
public class IdentityRole<TKey, TUserRole> : IRole<TKey>
where TUserRole : IdentityUserRole<TKey>
{
    public TKey Id { get; set; }
    public string Name { get; set; }
    public ICollection<TUserRole> Users { get; set; }
    public IdentityRole()
    {
        this.Users = new List<TUserRole>();
    }
}

 

As we can see, the above defines IdentityRole in terms of generic type arguments for the key and UserRole, and must implement the interface IRole<TKey>. Note that Identity defines both an IdentityRole class, as well as an IdentityUserRole class, both of which are required to make things work. More on this later.

The Identity team also provides what amounts to a default implementation of this class:

Default Implementation of IdentityRole with non-generic type arguments:
public class IdentityRole : IdentityRole<string, IdentityUserRole>
{
    public IdentityRole()
    {
        base.Id = Guid.NewGuid().ToString();
    }
  
  
    public IdentityRole(string roleName) : this()
    {
        base.Name = roleName;
    }
}

 

Notice how the default implementation class is defined in terms of a string key and a specific implementation of IdentityUserRole?

This means that we can only pass strings as keys, and in fact the IdentityRole model will be defined in our database with a string-type primary key. It also means that the specific, non-generic implementation of IdentityUserRole will be what is passed to the type argument into the base class.

If we steal a page from the previous post, and take a look at the default type definitions provided by Identity 2.0, we find the following (it's not exhaustive, but these are what we will be dealing with later):

Default Identity 2.0 Class Signatures with Default Type Arguments:
public class IdentityUserRole 
    : IdentityUserRole<string>
  
public class IdentityRole 
    : IdentityRole<string, IdentityUserRole>
  
public class IdentityUserClaim 
    : IdentityUserClaim<string>
  
public class IdentityUserLogin 
    : IdentityUserLogin<string>
  
public class IdentityUser 
    : IdentityUser<string, IdentityUserLogin, 
        IdentityUserRole, IdentityUserClaim>, IUser, IUser<string>
  
public class IdentityDbContext 
    : IdentityDbContext<IdentityUser, IdentityRole, string, 
        IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
  
public class UserStore<TUser> 
    : UserStore<TUser, IdentityRole, string, IdentityUserLogin, 
        IdentityUserRole, IdentityUserClaim>, 
        IUserStore<TUser>, IUserStore<TUser, string>, IDisposable
    where TUser : IdentityUser
  
public class RoleStore<TRole> 
    : RoleStore<TRole, string, IdentityUserRole>, IQueryableRoleStore<TRole>, 
        IQueryableRoleStore<TRole, string>, IRoleStore<TRole, string>, IDisposable
    where TRole : IdentityRole, new()

 

We can see that, starting with IdentityUserRole, the types are defined with string keys, and as importantly, progressively defined in terms of the others. This means that if we want to use integer keys instead of string keys for all of our models (and corresponding database tables), we need to basically implement our own version of the stack above.

Implementing Integer Keys Using Identity 2.0 and the Identity Samples Project

As in previous posts, we are going to use the Identity Samples project as our base for creating an Identity 2.0 MVC application. The Identity team has put together the Identity Samples project primarily (I assume) as a demonstration platform, but in fact it contains everything one might need (after a few tweaks, anyway) in order to build out a complete ASP.NET MVC project using the Identity 2.0 framework.

The concepts we are going to look at here apply equally well if you are building up your own Identity-based application from scratch. The ways and means might vary according to your needs, but in general, much of what we see here will apply whether you are starting from the Identity Samples project as a base, or "rolling your own" so to speak.

The important thing to bear in mind is that the generic base types and interfaces provided by Identity framework allow great flexibility, but also introduced complexity related to the dependencies introduced by the generic type arguments. In particular, the type specified as the key for each model must propagate through the stack, or the compiler gets angry.

Getting Started - Installing the Identity Samples Project

The Identity Samples project is available on Nuget. First, create an empty ASP.NET Web Project (It is important that you use the "Empty" template here, not MVC, not Webforms, EMPTY). Then open the Package Manager console and type:

Install Identity Samples from the Package Manager Console:
PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre

 

This may take a minute or two to run. When complete, your will see a basic ASP.NET MVC project in the VS Solution Explorer. Take a good look around the Identity 2.0 Samples project, and become familiar with what things are and where they are at.

Re-Engineering the Basic Identity Models

To get started, we need to re-engineer the basic model classes defined in the Identity Samples project, as well as add a few new ones. Because Identity Samples uses string-based keys for entity models, the authors, in many cases get away with depending upon the default class implementations provided by the framework itself. Where they extend, they extend from the default classes, meaning the string-based keys are still baked in to the derived classes.

Since we want to use integer keys for all of our models, we get to provide our own implementations for most of the models.

In many cases, this isn't as bad as it sounds. For example, there are a handful of model classes we need only define in terms of the generic arguments, and from there the base class implementation does the rest of the work.

NOTE: As we proceed to modify/add new classes here, the error list in Visual Studio will begin to light up like a Christmas tree until we are done. Leave that be for the moment. If we do this correctly, there should be no errors left when we finish. IF there are, they will help us find things we missed.

In the Models => IdentityModels.cs file, we find the model classes used by the Identity Samples application. To get started, we are going to add our own definitions for IndentityUserLogin, IdentityUserClaim, and IdentityUserRole. The Identity Samples project simply depended upon the default framework implementations for these classes, and we need our own integer based versions. Add the following to the IdentityModels.cs file:

Integer-Based Definitions for UserLogin, UserClaim, and UserRole:
public class ApplicationUserLogin : IdentityUserLogin<int> { }
public class ApplicationUserClaim : IdentityUserClaim<int> { }
public class ApplicationUserRole : IdentityUserRole<int> { }

 

Now, with that out of the way, we can define our own implementation of IdentityRole. The Samples project also depended upon the framework version for IdentityRole, and we are going to provide our own again. This time, though, there's a little more to it:

Integer-Based Definition for IdentityRole:
public class ApplicationRole : IdentityRole<int, ApplicationUserRole>, IRole<int>
{
    public string Description { get; set; }
  
    public ApplicationRole() { }
    public ApplicationRole(string name)
        : this()
    {
        this.Name = name;
    }
  
    public ApplicationRole(string name, string description)
        : this(name)
    {
        this.Description = description;
    }
}

 

Notice above, we have defined ApplicationRole in terms of an integer key, and also in terms of our custom class ApplicationUserRole? This is important, and will continue on up the stack as we re-implement the Identity classes we need for the Identity Samples project to run as expected.

Next, we are going to modify the existing definition for ApplicationUser. Currently, the IdentitySamples.cs file includes a fairly simple definition for ApplicationUser which derives from the default IdentityUser class provided by the framework, which requires no type arguments because they have already been provided in the default implementation. We need to basically re-define ApplicationUser starting from the ground up.

The existing ApplicationUser class in the IdentityModels.cs file looks like this:

Existing ApplicationUser Class in IdentityModels.cs:
public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> 
        GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager
            .CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

 

We need to replace the above in its entirety with the following:

Custom Implementation for ApplicationUser:
public class ApplicationUser 
: IdentityUser<int, ApplicationUserLogin, 
    ApplicationUserRole, ApplicationUserClaim>, IUser<int>
{
    public async Task<ClaimsIdentity>
        GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
    {
        var userIdentity = await manager
            .CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

 

Once again, instead of deriving from the default Identity framework implementation for IdentityUser, we have instead used the generic base, and provided our own custom type arguments. Also again, we have defined our custom ApplicationUser in terms of an integer key, and our own custom types.

Modified Application Db Context

Also in the IdentityModels.cs file is an ApplicationDbContext class.

Now that we have built out the basic models we are going to need, we also need to re-define the ApplicationDbContext in terms of these new models. As previously, the existing ApplicationDbContext used in the Identity Samples application is expressed only in terms of ApplicationUser, relying (again) upon the default concrete implementation provided by the framework.

If we look under the covers, we find the ApplicationDbContext<ApplicationUser> actually inherits from IdentityDbContext<ApplicationUser>, which in turn is derived from:

IdentityDbContext<TUser, IdentityRole, string, IdentityUserLogin,
    IdentityUserRole, IdentityUserClaim>
    where TUser : Microsoft.AspNet.Identity.EntityFramework.IdentityUser

In other words, we once again have a default concrete implementation which is defined in terms of the other default framework types, all of which further depend upon a string-based key.

In order to define a DbContext which will work with our new custom types, we need to express our concrete class in terms of integer keys, and our own custom derived types.

Replace the existing ApplicationDbContext code with the following:

Modified ApplicationDbContext:
public class ApplicationDbContext 
    : IdentityDbContext<ApplicationUser, ApplicationRole, int, 
    ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
  
    static ApplicationDbContext()
    {
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }
  
    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

 

Once again, we have now expressed ApplicationDbContext in terms of our own custom types, all of which use an integer key instead of a string.

Custom User and Role Stores

I am willing to bet that if you take a look at the Visual Studio Error Window right now, it is likely a block of what seems to be endless red error indicators. As mentioned previously, that's fine for now - ignore it.

Identity framework defines the notion of User and Role stores for accessing user and role information. As with most everything else to this point, the default framework implementations for UserStore and RoleStore are defined in terms of the other default classes we have seen to this point - in other words, they won't work with our new custom classes. We need to express a custom User store, and a custom Role store, in terms of integer keys and our own custom classes.

Add the following to the IdentityModels.cs file:

Adding a Custom User Store:
    public class ApplicationUserStore : 
        UserStore<ApplicationUser, ApplicationRole, int,
        ApplicationUserLogin, ApplicationUserRole, 
        ApplicationUserClaim>, IUserStore<ApplicationUser, int>, 
        IDisposable
    {
        public ApplicationUserStore() : this(new IdentityDbContext())
        {
            base.DisposeContext = true;
        }
  
        public ApplicationUserStore(DbContext context)
            : base(context)
        {
        }
    }
  
  
    public class ApplicationRoleStore 
        : RoleStore<ApplicationRole, int, ApplicationUserRole>, 
        IQueryableRoleStore<ApplicationRole, int>, 
        IRoleStore<ApplicationRole, int>, IDisposable
    {
        public ApplicationRoleStore()
            : base(new IdentityDbContext())
        {
            base.DisposeContext = true;
        }
  
        public ApplicationRoleStore(DbContext context)
            : base(context)
        {
        }
    }

 

Re-Engineering Identity Configuration Classes

The Identity Samples project includes a file named App_Start => IdentityConfig.cs. In this file is a bunch of code which basically configures the Identity System for use in your application. The changes we introduced on our IdentityModels.cs file will cause issues here (and basically, throughout the application) until they are addressed in the client code.

In most cases, we will either be replacing a reference to a default Identity class with one of our new custom classes, and/or calling method overrides which allow the passing of custom type arguments.

In the IdentityConfig.cs file, we find an ApplicationUserManager class, which contains code commonly called by our application to, well, manage users and behaviors. we will replace the existing code with the following, which essentially expresses ApplicationUserManager in terms of integer keys, and our new custom UserStore. If you look closely, we have added an int type argument to many of the method calls.

Customized ApplicationUserManager Class:
// *** PASS IN TYPE ARGUMENT TO BASE CLASS:
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
    // *** ADD INT TYPE ARGUMENT TO CONSTRUCTOR CALL:
    public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
        : base(store)
    {
    }
  
    public static ApplicationUserManager Create(
        IdentityFactoryOptions<ApplicationUserManager> options,
        IOwinContext context)
    {
        // *** PASS CUSTOM APPLICATION USER STORE AS CONSTRUCTOR ARGUMENT:
        var manager = new ApplicationUserManager(
            new ApplicationUserStore(context.Get<ApplicationDbContext>()));
  
        // Configure validation logic for usernames
  
        // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };
  
        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };
  
        // Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;
   
        // Register two factor authentication providers. 
        // This application uses Phone and Emails as a step of receiving a 
        // code for verifying the user You can write your own provider and plug in here.
  
        // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        manager.RegisterTwoFactorProvider("PhoneCode", 
            new PhoneNumberTokenProvider<ApplicationUser, int>
        {
            MessageFormat = "Your security code is: {0}"
        });
  
          // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        manager.RegisterTwoFactorProvider("EmailCode", 
            new EmailTokenProvider<ApplicationUser, int>
        {
            Subject = "SecurityCode",
            BodyFormat = "Your security code is {0}"
        });
  
        manager.EmailService = new EmailService();
        manager.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
            manager.UserTokenProvider =
                new DataProtectorTokenProvider<ApplicationUser, int>(
                    dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }
}

 

That's a lot of code there. Fortunately, modifying the ApplicationRoleManager class is not such a big deal. We're essentially doing the same thing - expressing ApplicationRoleManager in terms of integer type arguments, and our custom classes.

Replace the ApplicationRoleManager code with the following:

Customized ApplicationRoleManager Class:
// PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO BASE:
public class ApplicationRoleManager : RoleManager<ApplicationRole, int>
{
    // PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO CONSTRUCTOR:
    public ApplicationRoleManager(IRoleStore<ApplicationRole, int> roleStore)
        : base(roleStore)
    {
    }
  
    // PASS CUSTOM APPLICATION ROLE AS TYPE ARGUMENT:
    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(
            new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
    }
}

 

Modify The Application Database Initializer and Sign-in Manager

The ApplicationDbInitializer class is what manages the creation and seeding of the backing database for our application. In this class we create a basic admin role user, and set up additional items such as the Email and SMS messaging providers.

The only thing we need to change here is where we initialize an instance of ApplicationRole. In the existing code, the ApplicationDbInitializer class instantiates an instance of IdentityRole, and we need to create an instance of our own ApplicationRole instead.

Replace the existing code with the following, or make the change highlighted below:

Modify the ApplicationDbInitializer Class:
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 
{
    protected override void Seed(ApplicationDbContext context) {
        InitializeIdentityForEF(context);
        base.Seed(context);
    }
  
    //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role        
    public static void InitializeIdentityForEF(ApplicationDbContext db) {
        var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
        var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
        const string name = "admin@example.com";
        const string password = "Admin@123456";
        const string roleName = "Admin";
  
        //Create Role Admin if it does not exist
        var role = roleManager.FindByName(roleName);
        if (role == null) {
            // *** INITIALIZE WITH CUSTOM APPLICATION ROLE CLASS:
            role = new ApplicationRole(roleName);
            var roleresult = roleManager.Create(role);
        }
  
        var user = userManager.FindByName(name);
        if (user == null) {
            user = new ApplicationUser { UserName = name, Email = name };
            var result = userManager.Create(user, password);
            result = userManager.SetLockoutEnabled(user.Id, false);
        }
  
        // Add user admin to Role Admin if not already added
        var rolesForUser = userManager.GetRoles(user.Id);
        if (!rolesForUser.Contains(role.Name)) {
            var result = userManager.AddToRole(user.Id, role.Name);
        }
    }
}

 

Fixing up the ApplicationSignInManager is even more simple. Just change the string type argument in the class declaration to int:

Modify the ApplicationSignInManager Class:
// PASS INT AS TYPE ARGUMENT TO BASE INSTEAD OF STRING:
public class ApplicationSignInManager : SignInManager<ApplicationUser, int>
{
    public ApplicationSignInManager(
        ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : 
        base(userManager, authenticationManager) { }
  
    public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
    {
        return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
    }
  
    public static ApplicationSignInManager Create(
     IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
    {
        return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
    }
}

 

Cookie Authentication Configuration

In the file App_Start => Startup.Auth there is a partial class definition, Startup. in the single method call defined in the partial class, there is a call to app.UseCookieAuthentication(). Now that our application is using integers as keys instead of strings, we need to make a modification to the way the CookieAuthenticationProvider is instantiated.

The existing call to app.UseCookieAuthentication (found smack in the middle of the middle of the ConfigureAuth() method) needs to be modified. Where the code calls OnVlidateIdentity the existing code passes ApplicationUserManager and ApplicationUser as type arguments. What is not obvious is that this is an override which assumes a third, string type argument for the key (yep - we're back to that whole string keys thing again).

We need to change this code to call another override, which accepts a third type argument, and pass it an int argument.

The existing code looks like this:

Existing Call to app.UseCookieAuthentication:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a 
        // password or add an external login to your account.  
        OnValidateIdentity = SecurityStampValidator
            .OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                validateInterval: TimeSpan.FromMinutes(30),
                regenerateIdentity: (manager, user) 
                    => user.GenerateUserIdentityAsync(manager))
    }
});

 

We need to modify this code in a couple of non-obvious ways. First, as mentioned above, we need to add a third type argument specifying that TKey is an int.

Less obvious is that we also need to change the name of the second argument from regenerateIdentity to regenerateIdentityCallback. Same argument, but different name in the overload we are using.

Also less than obvious is the third Func we need to pass into the call as getUserIdCallback. Here, we need to retreive a user id from a claim, which stored the Id as a string. We need to parse the result back into an int.

Replace the existing code above with the following:

Modified Call to app.UseCookieAuthentication:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a 
        // password or add an external login to your account.  
        OnValidateIdentity = SecurityStampValidator
            // ADD AN INT AS A THIRD TYPE ARGUMENT:
            .OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
                validateInterval: TimeSpan.FromMinutes(30),
                // THE NAMED ARGUMENT IS DIFFERENT:
                regenerateIdentityCallback: (manager, user) 
                    => user.GenerateUserIdentityAsync(manager),
                    // Need to add THIS line because we added the third type argument (int) above:
                    getUserIdCallback:  (claim) => int.Parse(claim.GetUserId()))
    }
});

 

With that, most of the Identity infrastructure is in place. Now we need to update a few things within our application.

Update Admin View Models

The Models => AdminViewModels.cs file contains  class definitions for a RolesAdminViewModel and a UsersAdminViewModel. In both cases, we need to change the type of the Id property from string to int:

Modify the Admin View Models:
public class RoleViewModel
{
    // Change the Id type from string to int:
    public int Id { get; set; }
    
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "RoleName")]
    public string Name { get; set; }
}
  
public class EditUserViewModel
{
    // Change the Id Type from string to int:
    public int Id { get; set; }
  
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "Email")]
    [EmailAddress]
    public string Email { get; set; }
  
    public IEnumerable<SelectListItem> RolesList { get; set; }
}

 

Update Controller Method Parameter Arguments

A good many of the controller action methods currently expect an id argument of type string. We need to go through all of the methods in our controllers and change the type of the id argument from string to int.

In each of the following controllers, we need to change the existing Id from string to int as shown for the action methods indicated (we're only showing the modified method signatures here):

Account Controller:
public async Task<ActionResult> ConfirmEmail(int userId, string code)

 

Roles Admin Controller:
public async Task<ActionResult> Edit(int id)
public async Task<ActionResult> Details(int id)
public async Task<ActionResult> Delete(int id)
public async Task<ActionResult> DeleteConfirmed(int id, string deleteUser)

 

Users Admin Controller:
public async Task<ActionResult> Details(int id)
public async Task<ActionResult> Edit(int id)
public async Task<ActionResult> Delete(int id)
public async Task<ActionResult> DeleteConfirmed(int id)

 

Update the Create Method on Roles Admin Controller

Anywhere we are creating a new instance of a Role, we need to make sure we are using our new ApplicationRole instead of the default IdentityRole. Specifically, in the Create() method of the RolesAdminController:

Instantiate a new ApplicationRole Instead of IdentityRole:
[HttpPost]
public async Task<ActionResult> Create(RoleViewModel roleViewModel)
{
    if (ModelState.IsValid)
    {
        // Use ApplicationRole, not IdentityRole:
        var role = new ApplicationRole(roleViewModel.Name);
        var roleresult = await RoleManager.CreateAsync(role);
        if (!roleresult.Succeeded)
        {
            ModelState.AddModelError("", roleresult.Errors.First());
            return View();
        }
        return RedirectToAction("Index");
    }
    return View();
}

 

Add Integer Type Argument to GetUserId() Calls

If we take a look at our Error list now, we see the preponderance of errors are related to calls to User.Identity.GetUserId(). If we take a closer look at this method, we find that once again, the default version of GetUserId() returns a string, and that there is an overload which accepts a type argument which determines the return type.

Sadly, calls to GetUserId() are sprinkled liberally throughout ManageController, and a few places in AccountController as well. We need to change all of the calls to reflect the proper type argument, and the most efficient way to do this is an old fashioned Find/Replace.

Fortunately, you can use Find/Replace for the entire document on both ManageController and AccountController, and get the whole thing done in one fell swoop. Hit Ctrl + H, and in the "Find" box, enter the following:

Find all instances of:
Identity.GetUserId()

 

Replace with:
Identity.GetUserId<int>()

 

If we've done this properly, most of the glaring red errors in our error list should now be gone. There are a few stragglers, though. In these cases, we need to counter-intuitively convert the int Id back into a string.

Return a String Where Required

There are a handful of methods which call to GetUserId(), but regardless of the type the Id represents (in our case, now, an int) want a string representation of the Id passed as the argument. All of these methods are found on ManageController, and in each case, we just add a call to .ToString().

First, in the Index() method of ManageController, we find a call to AuthenticationManager.TwoFactorBrowserRemembered() . Add the call to .ToString() after the call to GetUserId():

Add Call to ToString() to TwoFactorBrowserRemembered:
public async Task<ActionResult> Index(ManageMessageId? message)
{
    ViewBag.StatusMessage =
        message == ManageMessageId.ChangePasswordSuccess ? 
            "Your password has been changed."
        : message == ManageMessageId.SetPasswordSuccess ? 
            "Your password has been set."
        : message == ManageMessageId.SetTwoFactorSuccess ? 
            "Your two factor provider has been set."
        : message == ManageMessageId.Error ? 
            "An error has occurred."
        : message == ManageMessageId.AddPhoneSuccess ? 
            "The phone number was added."
        : message == ManageMessageId.RemovePhoneSuccess ? 
            "Your phone number was removed."
        : "";
  
    var model = new IndexViewModel
    {
        HasPassword = HasPassword(),
        PhoneNumber = await UserManager.GetPhoneNumberAsync(User.Identity.GetUserId<int>()),
        TwoFactor = await UserManager.GetTwoFactorEnabledAsync(User.Identity.GetUserId<int>()),
        Logins = await UserManager.GetLoginsAsync(User.Identity.GetUserId<int>()),
  
        // *** Add .ToString() to call to GetUserId():
        BrowserRemembered = await AuthenticationManager
            .TwoFactorBrowserRememberedAsync(User.Identity.GetUserId<int>().ToString())
    };
    return View(model);
}

 

Similarly, do the same for the RememberBrowser method, also on ManageController:

Add Call to ToString() to RememberBrowser Method:
[HttpPost]
public ActionResult RememberBrowser()
{
    var rememberBrowserIdentity = AuthenticationManager
        .CreateTwoFactorRememberBrowserIdentity(
            // *** Add .ToString() to call to GetUserId():
            User.Identity.GetUserId<int>().ToString());
    AuthenticationManager.SignIn(
        new AuthenticationProperties { IsPersistent = true }, 
        rememberBrowserIdentity);
    return RedirectToAction("Index", "Manage");
}

 

Lastly,the same for the LinkLogin() and LinkLoginCallback() methods:

Add Call to ToString() to LinkLogin():
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider)
{
    return new AccountController
        .ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), 
            // *** Add .ToString() to call to GetUserId():
            User.Identity.GetUserId<int>().ToString());
}

 

Add Call to ToString() to LinkLoginCallback():
public async Task<ActionResult> LinkLoginCallback()
{
    var loginInfo = await AuthenticationManager
        .GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId<int>().ToString());
    if (loginInfo == null)
    {
        return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
    }
    var result = await UserManager
        // *** Add .ToString() to call to GetUserId():
        .AddLoginAsync(User.Identity.GetUserId<int>().ToString(), loginInfo.Login);
  
    return result.Succeeded ? RedirectToAction("ManageLogins") 
        : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
}

 

With that, we have addressed most of the egregious issues, and we basically taken a project built against a model set using all string keys and converted it to using integers. The integer types will be propagated as auto-incrementing integer primary keys in the database backend as well.

But there are still a few things to clean up.

Fix Null Checks Against Integer Types

Scattered throughout the primary identity controllers are a bunch of null checks against the Id values received as arguments in the method calls. If you rebuild the project, the error list window in Visual Studio should now contain a bunch of the yellow "warning" items about this very thing.

You can handle this in your preferred manner, but for me, I prefer to check for a positive integer value. We'll look at the Details() method from the UserAdminController as an example, and you can take it from there.

The existing code in the Details() method looks like this:

Existing Details() Method from UserAdminController:
public async Task<ActionResult> Details(int id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var user = await UserManager.FindByIdAsync(id);
    ViewBag.RoleNames = await UserManager.GetRolesAsync(user.Id);
    return View(user);
}

 

In the above, we can see that previously, the code checked for a null value for the (formerly) string-typed Id argument. Now that we are receiving an int, the check for null is meaningless. Instead, we want to check for a positive integer value. If the check is true, then we want to process accordingly. Otherwise, we want to return the BadRequest result.

In other words, we need to invert the method logic. Previously, if the conditional evaluated to true, we wanted to return the error code. Now, is the result is true, we want to proceed, and only return the error result if the conditional is false. So we're going to swap our logic around.

Replace the code with the following:

Modified Details() Method with Inverted Conditional Logic:
public async Task<ActionResult> Details(int id)
{
    if (id > 0)
    {
        // Process normally:
        var user = await UserManager.FindByIdAsync(id);
        ViewBag.RoleNames = await UserManager.GetRolesAsync(user.Id);
        return View(user);
    }
    // Return Error:
    return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}

 

We can do something similar for the other cases in UserAdminController, RolesAdminController, and AccountController. Think through the logic carefully, and all should be well.

Update Roles Admin Views

Several of the View Templates currently use the default IdentityRole model instead of our new, custom ApplicationRole. We need to update the Views in Views => RolesAdmin to reflect our new custom model.

The Create.cshtml and Edit.cshtml Views both depend upon the RoleViewModel, which is fine. However, the Index.cshtml, Details.cshtml, and Delete.cshtml Views all currently refer to IdentityRole. Update all three as follows

The Index.cshtml View currently expects an IEnumerable<IdentityRole> . We need to change this to expect an IEnumerable<ApplicationRole> . Note that we need to include the project Models namespace as well:

Update the RolesAdmin Index.cshtml View:
@model IEnumerable<IdentitySample.Models.ApplicationRole>
// ... All the view code ...

 

All we need to change here is the first line, so I omitted the rest of the View code.

Similarly, we need to update the Details.cshtml and Delete.cshtml Views to expect ApplicationRole instead of IdentityRole. Change the first line in each to match the following:

Update the Details.cshtml and Delete.cshtml Views:
@model IdentitySample.Models.ApplicationRole
// ... All the view code ...

 

Obviously, if your default project namespace is something other than IdentitySamples, change the above to suit.

Additional Extensions are Easy Now

Now that we have essentially re-implemented most of the Identity object models with our own derived types, it is easy to add custom properties to the ApplicationUser and/.or ApplicationRole models. All of our custom types already depend upon each other in terms of the interrelated generic type arguments, so we are free to simply add what properties we wish to add, and then update our Controllers, ViewModels, and Views accordingly.

To do so, review the previous post on extending Users and Roles, but realize all of the type structure stuff is already done. Review that post just to see what goes on with updating the Controllers, Views, and ViewModels.

A Note on Security

The basic Identity Samples application is a great starting point for building out your own Identity 2.0 application. However, realize that, as a demo, there are some things built in that should not be present in production code. For example, the database initialization currently includes hard-coded admin user credentials.

Also, the Email confirmation and two-factor authentication functionality currently circumvents the actual confirmation and two-factor process, by including links on each respective page which short-circuit the process.

The above items should be addressed before deploying an actual application based upon the Identity Samples project.

Wrapping Up

We've taken a rather exhaustive look at how to modify the Identity Samples application to use integer keys instead of strings. Along the way, we (hopefully) gained a deeper understanding of the underlying structure in an Identity 2.0 based application. There's a lot more there to learn, but this is a good start.

Additional Resources and Items of Interest

 

Posted on July 13 2014 10:45 AM by jatten     

Comments (20)

About the author

My name is John Atten, and my "handle" on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, JavaScript/Node, and databases of many flavors. Actively learning always. I dig web development. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten at typecastexception dot com

Web Hosting by