Home > Developing Using CodeFluent Entities, Producers > Dissecting the ASP.NET Identity Producer – Part 2

Dissecting the ASP.NET Identity Producer – Part 2


In our last post, we talked about some generalities regarding writing a custom producer.
Today we’ll see how does the ASP.NET Identity Producer generate C# code. Let’s remind that the full source code is available on our GitHub repository.

First and before producing code, we have to find the Role and the User entities in the model.

Do you remember the NamespaceUri? It allows you to add custom attributes in the xml file which represents the model.

<cf:entity name="User" d2p1:entityType="User" xmlns:d2p1=
"http://www.softfluent.com/codefluent/producers.aspNetIdentityProducer/2014/1"> 

</cf:entity> 

We add the entityType attribute to the entity. Xml Namespace are useful to avoid naming conflict between producers.

To find the User entity we can do the following, create an enumeration named “EntityType”:

public enum EntityType 
{ 
    None, 
    User, 
    Role, 
    UserRole, 
    Claim, 
    Login 
} 

Then, read the attribute value in the following way:

foreach (var entity in project.Entities) 
{ 
    if (entity.GetAttributeValue("entityType", 
               Constants.NamespaceUri, EntityType.None) == EntityType.User) 
    { 
        return entity; 
    } 
} 

The Produce method is a blank method so you can do what you want. When the output file is simple (with no complex logic), the easiest way is to use a template. When the logic is more complex you may want to use other mechanism such as CodeDom.

In the ASP.NET Identity producer we decided to use two templates: one for UserRole and the other one for RoleStore. CodeFluent Runtime already provides everything you need to use template. We’ll use here the SimpleTemplateProducer abstract class to simplify templates mechanisms (loading, parsing, processing). This producer takes directly the template from the resources of the producer, so you’ll have only one DLL to release. Of course the template has access to the producer instance, and so to its properties and methods.

AspNet Identity Producer

Tips: Keep the maximum of the logic in the producer, your templates will be much simpler to read

Here’s an extract from the template:

public System.Threading.Tasks.Task CreateAsync([%=TemplateProducer.IdentityRole.Entity.ClrFullTypeName%] role)
{
	if(role == null)
		throw new System.ArgumentNullException("role");

    return System.Threading.Tasks.Task.FromResult(role.Save());
}

And the code to run the template

public class RoleStoreProducer : SimpleTemplateProducer
{
    public IdentityRole IdentityRole { get; set; }
    
    protected override string DefaultNamespace
    {
        get { return Producer.Project.DefaultNamespace + Producer.WebNamespaceSuffix + ".Security"; }
    }

    protected override string DefaultTypeName
    {
        get { return "RoleStore"; }
    }

    protected override Template CreateTemplate()
    {
        var template = base.CreateTemplate();

        template.AddReferenceDirective(typeof(CodeDomBaseProducer));
        template.AddReferenceDirective(typeof(UserStoreProducer));

        return template;
    }

    public override string TargetPath
    {
        get
        {
            string path = ConvertUtilities.Nullify(XmlUtilities.GetAttribute(Producer.Element, ConvertUtilities.Camel(this.TargetName) + "TargetPath", (string)null), true);
            if (path == null)
                return BaseType.GetFilePath(Producer.TargetBaseNamespace, TypeName, Namespace, Producer.FullTargetDirectory, null);

            return Producer.GetFullRelativeDirectoryPath(path);
        }
    }
}

Note: We omit some code to make this implementation simpler

The template file is found based on the DefaultTypeName, so we don’t need to specify anything else. Instantiating the RoleStoreProducer and calling its Produce method will load the template from resources, and run it.

To implement the IUser and IRole interfaces, we need to edit the code generated by the CodeDom Producer. To do so, the way to go is to write a CodeDomSubProducer. But you can also get the instance of the BOM producer and register to the CodeDomProduction event:

var producer = project.Producers.GetProducerInstance<CodeDomProducer>();
if (producer == null)
    return;
producer.CodeDomProduction += CodeDomProducer_CodeDomProduction;

We can now handle the event and add the interface implementation:

private void CodeDomProducer_CodeDomProduction(object sender, CodeDomProductionEventArgs e)
{
    if (e.EventType == CodeDomProductionEventType.EntityCommitting)
    {
        CodeCompileUnit unit = e.Argument as CodeCompileUnit;
        if (unit == null)
            return;

        foreach (CodeNamespace ns in unit.Namespaces)
        {
            foreach (CodeTypeDeclaration typeDeclaration in ns.Types)
            {
                BaseType type = UserData.GetBaseType(typeDeclaration);
                if (type.GetAttributeValue("entityType", NamespaceUri, EntityType.None) == EntityType.User)
                {
                    // Implements IUser<TKey> & IUser
                    if (_identityUser.MustImplementGenericInterface)
                    {
                        ImplementIUser(typeDeclaration, true);
                    }

                    ImplementIUser(typeDeclaration, false);
                }
            }
        }
    }
}

private void ImplementIUser(CodeTypeDeclaration typeDeclaration, bool generic)
{
    string keyTypeName = generic ? _identityUser.KeyTypeName : typeof(string).FullName;
    var iuserCodeTypeReference = new CodeTypeReference("Microsoft.AspNet.Identity.IUser");
    var iuserGenericCodeTypeReference = new CodeTypeReference("Microsoft.AspNet.Identity.IUser");
    iuserGenericCodeTypeReference.TypeArguments.Add(keyTypeName);
    if (generic)
    {
        iuserCodeTypeReference.TypeArguments.Add(keyTypeName);
    }

    typeDeclaration.BaseTypes.Add(iuserCodeTypeReference);

    CodeMemberProperty idProperty = new CodeMemberProperty();
    idProperty.PrivateImplementationType = iuserGenericCodeTypeReference;
    idProperty.Type = new CodeTypeReference(keyTypeName);
    idProperty.Name = "Id";
    idProperty.HasSet = false;
    idProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), generic ? _identityUser.KeyPropertyName : "EntityKey")));
    typeDeclaration.Members.Add(idProperty);
}

The CodeDom can be traversed to find class declarations. The CodeDom producer annotates CodeDom elements, so we can get model concept from the CodeDom element by using UserData class. In this case we know if a class corresponds to the User entity by using UserData.GetBaseEntity.

In the last part, we’ll explain how to integrate this producer in Visual Studio and how to debug it.

The R&D Team.

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s