Click here to Skip to main content
15,879,535 members
Articles / Web Development / HTML

SimpleRules.Net - Easy to use Rules Engine

Rate me:
Please Sign up or sign in to vote.
4.86/5 (29 votes)
13 Apr 2017CPOL18 min read 74.2K   956   83   21
SimpleRules.Net is a rules engine that works based on attributes decorated on the properties of a class

Agenda

Introduction

SimpleRules.Net is a rules engine, as you guessed probably based on the name! SimpleRules.Net was born out of a discussion I had at my workplace. The idea was to come up with a library that does not require me or anybody to write a gazillion lines of code to validate a set of instances (a List<T> or T specifically) and this led to the development of SimpleRules.Net. In order to define rules for a certain class or instance, all you have to do is decorate the class with pre-defined rule attributes. The basic usage section will get you started. And, it does not end there. Check out the sections after the basic usage section to know more!

SimpleRules.Net can be used in console, web applications or anything else for that matter. The sample included uses an MVC project to demonstrate how to use this library. SimpleRules.Net is NOT intended to be a replacement for the data annotations features that MVC provides, which are part of the System.ComponentModel.DataAnnotations namespace see Using data annotations.

Installation

To install SimpleRules.Net from NuGet, run the following command in the package manager console or from the nuget user interface.

Shell
PM> Install-Package SimpleRules

Terminology

Before we get on with anything, I would like to define some terminology that will be used across this file. Consider the snippet below:

C#
public class User
{
    public string Password { get; set; }

    [EqualTo("Password")]
    public string ConfirmPassword { get; set; }
}

In the above class the property that is decorated with the EqualTo attribute will be referred to as the "source" and the property identified by the argument to this attribute (Password in this case) will be referred to as the "target". For any source there could be multiple targets (i.e. rules).

Basic Usage

Lets say you have a User object and you need to validate if the Password property matches the ConfirmPassword property and also if the EmailAddress and PhoneNumber properties match their appropriate pattern, as suggested by their names. Here is what you could do:

C#
public class User
{
    public string Username { get; set; }
    public string Password { get; set; }
    [EqualTo("Password")]
    public string ConfirmPassword { get; set; }
    [EmailMatchRegex]
    public string EmailAddress { get; set; }
    [UsPhoneNumberRegex]
    public string PhoneNumber { get; set; }
}

As evident from the snippet above, all you had to do was decorate the ConfirmPassword property with the EqualTo attribute, by passing in the name of the property that has to be matched with, in this case Password. And for the EmailAddress and PhoneNumber properties, use the in-built EmailMatchRegex and UsPhoneNumberRegex attributes! With this done, when you have an instance of user, it can be validated as shown below:

C#
var user = new User { 
     Username = "jdoe", 
     Password = "john", 
     ConfirmPassword = "johndoe", 
     EmailAddress = "abc", 
     PhoneNumber = "123" 
};
var simpleRulesEngine = new SimpleRulesEngine();
var result = simpleRulesEngine.Validate(user);

Needless to say, you can also pass in a list in order to validate a list of User objects (List<User>). An illustration is shown below:

C#
var users = ...; // Method that returns a list of users: List<User>
var simpleRulesEngine = new SimpleRulesEngine();
var results = simpleRulesEngine.Validate(users);

The result will be a ValidationResult that contains an Errors property with 3 errors in this case - one each for mismatching passwords, invalid email address and invalid phone number. In the next section, you will see how the rule metadata can be seperated out of the class to keep the entities and the rules separate, in order to achive loose coupling.

One important thing to note is that when a rule is applied on a certain property (the source), the data types of the "target" property (or properties) should match that of the source. This is applicable when validating against a constant too. Simply, if the EqualTo attribute is applied on a DateTime property, the "target" property should also be a DateTime (or DateTime?).

Specify Metadata Using an External Class

In the earlier section you saw how easy it is to setup and run a validation using the SimpleRules engine. In this section, we are going to see how the rules can be declared externally, like I said in the last section to support loose coupling. To do this, just create a metadata class called UserMetadata and decorate your class with the RuleMetadata attribute, as shown below:

C#
[RuleMetadata(typeof(UserMetadata))]
public class User
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string ConfirmPassword { get; set; }
    public string EmailAddress { get; set; }
    public string PhoneNumber { get; set; }
}

public class UserMetadata
{
    public string Username { get; set; }
    public string Password { get; set; }
    [EqualTo("Password")]
    public string ConfirmPassword { get; set; }
    [EmailMatchRegex]
    public string EmailAddress { get; set; }
    [UsPhoneNumberRegex]
    public string PhoneNumber { get; set; }
}

As of now the rule metadata class has to contain the same set of properties (or more) that are present in the class that you intend to validate (User). And with respect to the data types, it can also be just an object. When you make the same call to Validate as before with a list of users, you will get the same results as before, an Errors property with 3 errors. Next section deals how the rules engine can be informed of the link between the class to be validated and the metadata.

Specify Metadata During Setup

Let's say you don't want to specify the metadata class in the class you want to validate. In that case, you just have to use the RegisterMetadata extension to register the metadata for a class, as shown below:

C#
var user = new User { 
     Username = "jdoe", 
     Password = "john", 
     ConfirmPassword = "johndoe", 
     EmailAddress = "abc", 
     PhoneNumber = "123" 
};
var simpleRulesEngine = new SimpleRulesEngine()
                            .RegisterMetadata<User, UserMetadata>();
var result = simpleRulesEngine.Validate(user);

The result of the validation is just the same as discussed before! Okay, we have seen enough of validation. Note that if you attempt to register the same concrete type, metadata type again, it will throw a DuplicateMetadataRegistrationException exception. Lets move on to possibilities of extending the rules engine using custom rules. With the help of custom rule handlers, there is no limit what can be validated!

Creating Custom Rules

SimpleRules.Net supports extensibility with the help of "handlers" to define new rules. That was one of the most important design considerations for me - to support the ability to define custom rules easily. As you probably guessed this rule engine is based on expressions. So to get futher with the handlers, you need to have a working knowledge of expressions. Let me try to explain the ability to create custom rules with the help of a rule that validates if a certain property is between a certain minimum and maximum value. In order to do this, first you need to create the attribute that contains the metadata for the rule - RangeRuleAttribute.

C#
public class RangeRuleAttribute : BaseRuleAttribute
{
    public RangeRuleAttribute(int minValue, int maxValue)
    {
        MinValue = minValue;
        MaxValue = maxValue;
    }

    public int MinValue { get; set; }
    public int MaxValue { get; set; }
}

As evident from the snippet above, the rule attribute defines a MinValue and a MaxValue to get the necessary metadata for the rule. The next step is to define a handler that implements the IHandler interface, as shown below:

C#
public class RangeRuleHandler : IHandler
{
    public EvaluatedRule GenerateEvaluatedRule<TConcrete>(BaseRuleAttribute attribute, PropertyInfo targetProp)
    {
        var rangeAttr = attribute as RangeRuleAttribute;
        var input = Expression.Parameter(typeof(TConcrete), "a");
        var propName = targetProp.Name;
        var comparison = Expression.And(
                Expression.GreaterThan(Expression.PropertyOrField(input, propName), Expression.Constant(rangeAttr.MinValue)),
                Expression.LessThan(Expression.PropertyOrField(input, propName), Expression.Constant(rangeAttr.MaxValue))
            );
        var lambda = Expression.Lambda(comparison, input);
        return new EvaluatedRule
        {
            MessageFormat = string.Format("{0} should be between {1} and {2}", propName.AddSpaces(), rangeAttr.MinValue, rangeAttr.MaxValue),
            RuleType = RuleType.Error,
            Expression = lambda
        };
    }

    public bool Handles(BaseRuleAttribute attribute)
    {
        return typeof(RangeRuleAttribute).IsAssignableFrom(attribute.GetType());
    }
}

The Handles method simply returns a boolean to indicate that this handler infact deals with a RangeRuleAttribute. And the GenerateEvaluatedRule method constructs an expression which uses the rule metadata to construct an expression tree that evaluates the same. In order to explain the expression tree generated by this method, let me define a class that will use this rule.

C#
public class Activity
{
    public int Id { get; set; }
    public string Name { get; set; }
    [RangeRule(10, 30)]
    public int Capacity { get; set; }
}

The Func given below explains the intended effect I wish to achieve. Given an activity, check if the property Capacity is between the numbers 10 and 30.

C#
Func<Activity, bool> func = a => a.Capacity > 10 && a.Capacity < 30;

The return value of the GenerateEvaluatedRule method needs to return an object of type EvaluatedRule which contains the evaluated rule itself along with other additional properties to help the core rules engine decide on things, like the message to be displayed if the rule is not met and whether its an error or a warning. If you notice the line where the message is constructed, I have used the extension method AddSpaces to make the property names readable, rather than being a string of text. For example StartDate will be transformed to "Start Date" and so on. Its not over once you create the rule attribute and the handler. You need to register it with the rules engine. More on this is in the next section.

Discover Handlers Dynamically Using Assembly Markers

In the previous section you saw have new rules/handlers can be defined. In order to put them to use, there are couple of ways. First one is by registering it with the rules engine as shown below:

C#
var activities = new List<Activity> { new Activity { Id = 1, Name = "Indoor", Capcaity = 45 } };
var engine = new SimpleRulesEngine()
                .RegisterCustomRule<RangeRuleHandler>();
var results = engine.Validate<Activity>(acitivities);

This might get tedious if you have multiple handlers defined. So, in order to let the rules engine automatically discover the handlers defined, use the DiscoverHandlers method, as shown below:

C#
var engine = new SimpleRulesEngine()
                .DiscoverHandlers(new[] { typeof(Marker) });                

Marker is simply a class that exists in the assembly that contains the defined handlers. With this method called, the handlers are all automatically discovered and registered and note that during the life cycle of the rules engine instance it may be called any number of times (handlers already registered will be ignored).

Create new Rules for Existing Handlers

Its also possible to extend existing rules in order to support reuse. For example, consider the MatchRegexRule. This can be used to validate the value of a property against a regular expression. There are already rules like the EmailMatchRegexRule, UsPhoneNumberRegex etc to validate properties, but you can create your own rules based on this too. For example, the following code creates a rule that validates a password. I lifted the password validation regex straight out of google and it validates if the "password matching expression. match all alphanumeric character and predefined wild characters. password must consists of at least 8 characters and not more than 15 characters".

C#
public class PasswordMatchRegexAttribute : MatchRegexAttribute
{
    public PasswordMatchRegexAttribute()
        : base(@"^([a-zA-Z0-9@*#]{8,15})$")
    {
    }
}

With this rule you can validate a class that contains this rule decorated on a particular property.

Multiple Validation Rules

You can also decorate a property with multiple rule attributes in order to validate it against a number of other properties. A sample is shown below:

C#
public class Student
{
    [EntityKey]
    [GreaterThan("", constantValue: 100)]
    public int Id { get; set; }

    [LessThan("StartDate")]
    public DateTime RegistrationDate { get; set; }

    public DateTime StartDate { get; set; }

    [LessThan("StartDate", canBeNull: true)]
    public DateTime? EndDate { get; set; }

    [LessThan("RegistrationDate")]
    [LessThan("StartDate")]
    [LessThan("EndDate")]
    public DateTime DateOfBirth { get; set; }
}

In the above snippet note the DateOfBirth property. It has been decorated with 3 validation rules to check whether it is less than 3 other properties: RegistrationDate, StartDate and EndDate.

Another thing of interest in the above snippet is the EntityKey attribute decorated on the Id property. You can use this attribute on a property to indicate that this value has to be returned as the value for the Key property in every ValidationResult instance returned with the results of the validation of a certain entity.

Usage in a .Net Core MVC Project

The sample project provided in the solution has an example of how the rules engine can be used in a MVC project. This section provides a quick introduction of the same. In this case, the Startup class is used to create an instance of the rules engine as a singleton and configured in the ConfigureServices method, as shown below:

C#
public void ConfigureServices(IServiceCollection services)
{
    // ...
    var simpleRulesEngine = new SimpleRulesEngine()
                                .DiscoverHandlers(new [] { typeof(Startup) })
                                .RegisterMetadata<Registration, RegistrationMetadata>()
                                .RegisterMetadata<Activity, ActivityMetadata>();
    services.AddSingleton(typeof (SimpleRulesEngine), simpleRulesEngine);
    // ...
}

With this done, the SimpleRulesEngine can be injected in to any controller where you intend to do validation, like it is done in the case of the HomeController.

C#
public class HomeController : Controller
{
    private readonly SimpleRulesEngine _rulesEngine;

    public HomeController(SimpleRulesEngine rulesEngine)
    {
        _rulesEngine = rulesEngine;
    }

    // ...
}

Internals

Based on what you have seen so far, at the core of the rules engine various attributes and expression trees are used extensively to interpret the rules defined on a class. When a class is declared and decorated with even one rule attribute, a lot of things happen behind the scenes. This section intends to discuss about some of these things in moderate detail. The subsequent sections are NOT intended to be a tutorial on expression trees. Rather, the intention is to show how it is used to get the required results. Read on!

Rule Attributes & their Hierarchy

Since rule attributes play an important role in this library, its important that we take a look in to them and understand them better. Consider the illustration below that describes the attributes already available to you:

                  System.Attribute [fw]
                        |
                    BaseRuleAttribute                        -----> Should be used for custom rule attributes
                    /           \
              RuleAttribute [a] RegexRuleAttribute [a]
                  |                   |
          |       |    |        |     |      |
GreaterThanAttribute ...  EmailRegexRuleAttribute ...

Legend:

     fw -> framework
     a  -> abstract

At the root of the attribute based rule engine framework of the api is the BaseRuleAttribute, which is an abstract class that inherits from System.Attribute class that comes with the .net framework. Every other rule attribute has to derive from the BaseRuleAttribute in order to be discoverable by the rule engine core. The reason would be evident if you look at the GetRules method (and its overload) in the SimpleRulesEngineExtensions class. One of them is shown below:

C#
public static IEnumerable<Tuple<BaseRuleAttribute, PropertyInfo>> GetRules(this Type srcType)
{            
    var attrList = srcType.GetProperties(publicPropFlags)
                          .SelectMany(c => c.GetCustomAttributes<BaseRuleAttribute>(), (prop, attr) => new { prop, attr })
                          .Select(p => new Tuple<BaseRuleAttribute, PropertyInfo>(p.attr, p.prop));
    return attrList;
}

The generic type constraint to the GetCustomAttributes method is the BaseRuleAttribute class, so that for the given concrete type all the rule attributes decorated on various properties are identified. So when you are creating custom handlers, you need to ensure that the attribute used in the Handles method uses a class that derives from the BaseRuleAttribute or any other class that derives from the BaseRuleAttribute class - for example, say, the MatchRegexAttribute. A more detailed discussion on this follows further down this document and it delves in to a lot more expressions and reflection.

Sequence of Operations

The most important methods exposed to the world by the rules engine are:

  • Validate<TConcrete>
  • RegisterMetadata<TConcrete, TMeta>
  • RegisterCustomRule<Rhandler>
  • DiscoverHandlers

To begin with, lets start with the Validate<TConcret> method since other methods can be considered "add ons". When you call the Validate<TConcrete> method, Every possible rule declared in various properties of the TConcrete class are discovered using the GetRules method. The rules could have been provided using a number of methods, like, using inline rule attributes or using a metadata class declared inline in the class or externally using the RegisterMetadata method. For the sake of simplicity, lets assume the rules are declared inline in the class itself, like this:

C#
public class Activity
{
    [EntityKey]
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime StartDate { get; set; }
    [GreaterThan("StartDate")]
    public DateTime EndDate { get; set; }
    [LesserThan("MaxCapacity")]
    public int Capacity { get; set; }
    public int MaxCapacity { get; set; }
}

The GetRules (SimpleRulesEngine) method first checks a local cache that may contain the list of evaluated rules. In this case, since the method is being called for the first time, the cache won't have an entry for the TConcrete type and so it must move on to the next step of discovering the rules attributes.

The GetMetadataType (SimpleRulesEngineExtensions) is an extension method that simply checks and returns all the rules decorated for every property in the class. It takes in two parameters as the input - one is a dictionary that acts as a cache of discovered rule attributes and the next is the concrete type itself. The order in which GetMetadataType discovers the rule attributes are:

  1. Passed in dictionary is checked first, to return the type that contains the rule metadata. This would be true if the rule metadata was registered using the RegisterMetadata method
  2. The type's (note, not properties) attributes are checked and if there is a RuleMetadata attribute, that is returned
  3. Finally, if 1 & 2 fails, the type's properties are investigated to find out if they have the rule attributes. If so they are returned

If all the 3 steps fail, a null is returned to indicate no rules were discovered, causing an exception to be thrown to inform the user that rule validation cannot on a class without any rule attributes.

In the case of our Activity class, there are rule attributes, so we move on to the next step. If you go back to the Activity class, you will notice that the Id property has the EntityKey attribute decoration. This just indicates that the value of this property must be used to populate the ValidationResult.Key property in the result returned for every entry in the input. This helps you build a more helpful message to the end user.

As of now, I have a list of EvaluatedRule instances and so I am ready to move on to the actual process of generating the expressions!

Expression Generation using Handlers

With the help of the list of rules, I now use the ProcessRule (ExpressionGenerationExtensions) extension to identify the associated instance of IHandler. If a handler is not found, an exception is thrown and execution is halted. In this case the GreaterThan and LessThan attributes are used and so these two will evaluate to be handled by the SimpleRuleHandler. With this in tow the ProcessRule method calls the SimpleRuleHandler.GenerateEvaluatedRule method to create an instance of EvaluatedRule, which contains the evaluated expression tree, a message that has to be displayed if the rule is not matched, rule type (error or warning). It also contains a read only Delegate that will be used during the actual evaluation of the rules.

With the rules evaluated, they are stored in the evaluated rules cache and returned to the Validate<TConcrete> method. At this point all that remains is to cast the delegates to a Func<TConcrete, bool> and evaluate them! Every rule is evaluated and the results are aggregated in to a ValidationResult, either in the Errors property or the Warnings property based on the rule type.

Closer look at the RegexRuleHandler

It might be interesting at this point to look at the RegexRuleHandler in more detail. This handler deals with validating a certain property in a class against a regular expression. The Handles method acknowledges that any attribute that uses the RegexRuleAttribute is acceptable for this handler. Here is the definition of the RegexRuleHandler.

C#
public abstract class RegexRuleAttribute : BaseRuleAttribute
{
    public abstract string RegularExpression { get; }
}

Its an abstract class so that it cannot be directly used. Instead the MatchRegexAttribute has to be used to decorate propeties to validate against a regular expression. The handler uses the regular expression passed to construct an expression block that operates on the TConcrete instance. Here is the complete handler for these type of attributes.

C#
public class RegexRuleHandler : IHandler
{
    public EvaluatedRule GenerateEvaluatedRule<TConcrete>(BaseRuleAttribute attribute, PropertyInfo targetProp)
    {
        var regexAttr = attribute as RegexRuleAttribute;
        var input = Expression.Parameter(typeof(TConcrete), "i");
        var ctor = typeof(Regex).GetConstructors().SingleOrDefault(c => c.GetParameters().Count() == 1);
        var method = typeof(Regex).GetMethods()
                                  .SingleOrDefault(m => m.Name == "IsMatch" && 
                                                        !m.IsStatic && 
                                                        m.GetParameters().Count() == 1);
        var leftExpr = Expression.PropertyOrField(input, targetProp.Name);
        var block = Expression.Block(
                        Expression.Call(
                            Expression.New(
                                ctor, 
                                Expression.Constant(regexAttr.RegularExpression)), method, leftExpr)
                    );
        var expression = Expression.Lambda(block, input);
        return new EvaluatedRule
        {
            RuleType = RuleType.Error,
            MessageFormat = string.Format("{0} does not match the expected format", targetProp.Name.AddSpaces(), regexAttr.RegularExpression),
            Expression = expression
        };
    }

    // ...
}

Note the expression generation step, the block variable. It simply generates an instance of the Regex object by passing the regular expression defined as a constructor to the MatchRegexAttribute and calls the IsMatch method by passing the property that has to be compared against the regular expression.

There are some more attributes like UsPhoneNumberRegexAttribute, EmailMatchRegexAttribute are syntactic sugars for the MatchRegexAttribute to validate a property against the regular expression for US phone number and email respectively.

Dealing with Nullable Properties

Even though I don't want to go over the handler that deals with simple relational operators, I would like to throw light on how I deal with nullable properties. For the sake of this section, lets call the property that has the rule attribute decoration as the "source" and the property its compared against the "target". With that said, there are few cases to consider.

  1. "source" is nullable, "target" is not
  2. "target" is nullable, "source" is not
  3. both "source" and "target" are nullable

In all these cases, even if one of source or target is null, its important to use the Convert method of the Expression class to convert the properties to have the same PropertyType as shown below (lines 3 & 4 in the method body):

C#
public static BinaryExpression CreateBinaryExpression(this Expression leftExpr, 
                                                           Expression rightExpr, 
                                                           ExpressionType exprType, 
                                                           PropertyInfo propertyInfo,
                                                           PropertyInfo otherPropInfo = null)
{
    var isNullable = propertyInfo.IsNullable() || (otherPropInfo != null ? otherPropInfo.IsNullable() : true);
    var propType = propertyInfo.PropertyType;
    var leftExprFinal = isNullable ? Expression.Convert(leftExpr, propertyInfo.PropertyType) : leftExpr;
    var rightExprFinal = isNullable ? Expression.Convert(rightExpr, propertyInfo.PropertyType) : rightExpr;
    return Expression.MakeBinary(
                exprType,
                leftExprFinal,
                rightExprFinal
           );
}

Closer look into RegisterCustomRule Methods

Next up, lets look at the RegisterCustomRule (SimpleRulesEngine) method. There is not much to it in terms of implementation. But it plays an important role in how all the things come together. The RegisterCustomRule method is a generic method that takes in a single type constraint, which is the class that implements IHandler. Notice the new () constraint added so that I can ensure that there is a parameterless constructor for this class. With the type constraint passed, I have another dictionary that acts as a cache to stop users from registering the same handler multiple times. The handlers have a Handles method that takes the responsibility of identifying or subscribing to the type of attributes this handler will process. Its important what attribute you use in this method. Consider the case of SimpleRuleHandler as shown below:

C#
public bool Handles(BaseRuleAttribute attribute)
{
    return typeof(RelationalOperatorAttribute).IsAssignableFrom(attribute.GetType());
}

Attributes like GreaterThan, LessThan etc derive from the RelationalOperatorAttribute class, which in turn derives from the BaseRuleAttribute. BaseRuleAttribute should be the class that any rule derives from to be able to be used in the rules engine. Getting back to the Handles method, rather than checking if GreaterThan, LessThan attributes satisfy the IsAssignableFrom check, you should check if the RelationalOperatorAttribute attribute satisfies the requirement.

Handler Discovery

In order to automatically identify custom handlers, the rules engine provides the DiscoverHandlers method and it looks like this:

C#
public SimpleRulesEngine DiscoverHandlers(params Type[] assemblyMarkers)
{
    var discoveredHandlerTypes = assemblyMarkers.FindHandlerTypesInAssemblies();
    foreach (var handler in discoveredHandlerTypes)
    {
        if (!handlerMapping.ContainsKey(handler))
            handlerMapping.Add(handler, handler.CreateInstance());
    }
    return this;
}

The FindHandlerTypesInAssemblies extension method is used to "look" in to the assemblies passed and identify any types that implements the IHandler interface. This method uses reflection to achieve this. Once the types are inferred, its passed on to the calling method so that they can be registered in the dictionary that caches the handlers for quick retrieval. The rules engine itself uses this method to discover the handlers that are provided by default like the one for relational operator rules and regular expression rules. Here is the corresponding method from the SimpleRulesEngine class:

C#
private void AddDefaultHandlers()
{
    this.DiscoverHandlers(typeof (SimpleRulesEngine));
}

Some pointers on Compatibility

I wanted this library to be available for multiple frameworks - namely .net standard 1.6, .net 4.5 and .net 4.6. In order to achieve this I had to do a few things. One of them is in the project.json file where the frameworks property has all the frameworks listed. The most important thing of interest is the Compatibility directory in the simple rules engine project. Take a look at that - here is the hierarchy of that directory:

Compatibility
  |
  | - GENERAL
  | - NETSTANDARD

This is needed because the classes under the System.Reflection namespace has been changed in the .net standard framework. For example you cannot use the IsInterface property of a System.Type object to identify if the type is an interface anymore. Instead, the System.Type instance has a GetTypeInfo method to get all of the information about the type like whether its an interface, or whether its generic and so on. In order to deal with this and not duplicate code, the compatibility directory has been created and if you recall looking at the project.json file, you will notice that being indicated, as shown below (see 'define' property):

JSON
{
  "net461": {
      "buildOptions": {
        "define": [ "GENERAL" ]
      },
      "dependencies": {
        "NETStandard.Library": "1.6.0",
        "System.Reflection": "4.0.10"
      }
    },
    "netstandard1.6": {
      "imports": "dnxcore50",
      "buildOptions": {
        "define": [ "NETSTANDARD" ]
      },

    }
}

License

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


Written By
Software Developer (Senior)
United States United States
Just another passionate software developer!

Some of the contributions to the open source world - a blog engine written in MVC 4 - sBlog.Net. Check it out here. For the codeproject article regarding sBlog.Net click here!

(Figuring out this section!)

Comments and Discussions

 
QuestionI think it's cool Pin
Xequence26-Jan-20 20:03
Xequence26-Jan-20 20:03 
QuestionError implementing in vb.net Pin
LeCoqDeMort25-Apr-17 12:13
LeCoqDeMort25-Apr-17 12:13 
AnswerRe: Error implementing in vb.net Pin
Karthik. A26-Apr-17 3:45
Karthik. A26-Apr-17 3:45 
Praisevery nice Pin
BillW3321-Apr-17 11:07
professionalBillW3321-Apr-17 11:07 
GeneralRe: very nice Pin
Karthik. A26-Apr-17 3:46
Karthik. A26-Apr-17 3:46 
QuestionAre there non-attribute ways to use this framework? Pin
Paulo Zemek12-Apr-17 18:01
mvaPaulo Zemek12-Apr-17 18:01 
AnswerRe: Are there non-attribute ways to use this framework? Pin
Karthik. A13-Apr-17 7:34
Karthik. A13-Apr-17 7:34 
GeneralRe: Are there non-attribute ways to use this framework? Pin
Paulo Zemek13-Apr-17 11:36
mvaPaulo Zemek13-Apr-17 11:36 
GeneralRe: Are there non-attribute ways to use this framework? Pin
Karthik. A13-Apr-17 18:11
Karthik. A13-Apr-17 18:11 
QuestionRules Engine or Validation Library? Pin
dale.newman12-Apr-17 4:05
dale.newman12-Apr-17 4:05 
AnswerRe: Rules Engine or Validation Library? Pin
Karthik. A12-Apr-17 5:21
Karthik. A12-Apr-17 5:21 
QuestionExternal rules Pin
dazinator11-Apr-17 15:04
dazinator11-Apr-17 15:04 
AnswerRe: External rules Pin
Karthik. A12-Apr-17 3:53
Karthik. A12-Apr-17 3:53 
GeneralMy vote of 3 Pin
dmjm-h10-Apr-17 13:09
dmjm-h10-Apr-17 13:09 
QuestionTypo? Pin
pharry2210-Apr-17 12:09
pharry2210-Apr-17 12:09 
AnswerRe: Typo? Pin
Karthik. A10-Apr-17 14:22
Karthik. A10-Apr-17 14:22 
QuestionGreat article! Is there a way to extend for dependent rules Pin
dkurok10-Apr-17 2:15
dkurok10-Apr-17 2:15 
AnswerRe: Great article! Is there a way to extend for dependent rules Pin
Karthik. A10-Apr-17 6:15
Karthik. A10-Apr-17 6:15 
GeneralRe: Great article! Is there a way to extend for dependent rules! 6 of 5 if I could Pin
dkurok11-Apr-17 3:55
dkurok11-Apr-17 3:55 
GeneralRe: Great article! Is there a way to extend for dependent rules! 6 of 5 if I could Pin
Karthik. A11-Apr-17 5:17
Karthik. A11-Apr-17 5:17 

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

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