DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations

Trending

  • How to LINQ Between Java and SQL With JPAStreamer
  • Avoiding Pitfalls With Java Optional: Common Mistakes and How To Fix Them [Video]
  • Extending Java APIs: Add Missing Features Without the Hassle
  • An Overview of Kubernetes Security Projects at KubeCon Europe 2023
  1. DZone
  2. Coding
  3. Frameworks
  4. Building a Simple Plug-ins System for ASP.NET Core

Building a Simple Plug-ins System for ASP.NET Core

In this post, we take a look at how to add plug-in support to your ASP.NET Core project. Read on for details and some example code!

Gunnar Peipman user avatar by
Gunnar Peipman
·
Jan. 26, 17 · Tutorial
Like (1)
Save
Tweet
Share
5.88K Views

Join the DZone community and get the full member experience.

Join For Free

Recently I built plug-ins support for my TemperatureStation IoT solution website. The code for .NET Core is different from what we have seen on full .NET Framework (application domains etc.) but there’s still nothing complex. This blog post describes how to build simple plug-ins support for ASP.NET Core web applications.

After some searching in web and some small experimenting I came out with a simple solution that covers the following:

  1. Loading types from assemblies
  2. Registering types automatically with built-in dependency injection
  3. Getting instances through built-in dependency injection

The code shown here is also kind of experimental and it is taken from my open-source IoT solution called TemperatureStation.

Calculator Plug-ins

TemperatureStation has plug-ins called Calculators. Calculators are classes that can be bound to measurements and when readings are reported then Calculators are run on readings. They provide different calculations like finding freezing point of liquid, estimating the time it takes for liquid to get to freezing point etc.

For calculators, there is ICalculator interface shown below.


public interface ICalculator
{
    double Calculate(SensorReadings readings, Measurement measurement);
    string DisplayValue(double value);
    bool ReturnsReading { get; }
    void SetParameters(string parameters);
}

Calculators use CalculatorAttribute to define some metadata.


[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class CalculatorAttribute : Attribute
{
    public CalculatorAttribute()
    {
        Order = -1;
        ShowOnChart = true;
    }public string Name { get; set; }
    public int Order { get; set; }
    public bool ShowOnChart { get; set; }
    public string DisplayLabel { get; set; }
}

And here is the example of dummy calculator.


public class DummyCalculator : ICalculator
{
    public bool ReturnsReading
    {
        get { return true; }
    }public double Calculate(SensorReadings readings, Measurement measurement)
    {
        return readings.Readings.First().Reading + 10f;
    }       public string DisplayValue(double value)
    {
        return value.ToString();
    }public void SetParameters(string parameters)
    {
    }
}

Finding Plug-in Types

To detect plug-ins I wrote the CalculatorsLoader class. This a static class that creates a list of calculator types. The rule is simple: the class must implement ICalculator interface and must have CalculatorAttribute. Consider this class as an internal matter of the application.


public static class CalculatorsLoader
{
    private static IList<Type> _calculatorTypes;public static IList<Type> CalculatorTypes
    {
        get
        {
            if(_calculatorTypes == null)
            {
                LoadCalculatorTypes();
            }               return _calculatorTypes.ToList();
        }            
    }private static void LoadCalculatorTypes()
    {
        if (_calculatorTypes != null)
        {
            return;
        }           var calcs = from a in GetReferencingAssemblies()
                    from t in a.GetTypes()
                    where t.GetTypeInfo().GetCustomAttribute<CalculatorAttribute>() != null
                          && t.GetTypeInfo().ImplementedInterfaces.Contains(typeof(ICalculator))
                    select t;           _calculatorTypes = calcs.OrderBy(t => t.GetTypeInfo().GetCustomAttribute<CalculatorAttribute>().Order).ToList();
    }private static IEnumerable<Assembly> GetReferencingAssemblies()
    {
        var assemblies = new List<Assembly>();
        var dependencies = DependencyContext.Default.RuntimeLibraries;           foreach (var library in dependencies)
        {
            try
            {
                var assembly = Assembly.Load(new AssemblyName(library.Name));
                assemblies.Add(assembly);
            }
            catch (FileNotFoundException)
            { }
        }
        return assemblies;
    }
}

Automatic Registering of Plug-in Types

To use ASP.NET Core dependency injection I wrote a class that provides extension method for IServiceCollection. Dependency injection is needed because Calculators may use constructor injection to access services and database.


public static class CalculatorExtensions
{
    public static void AddCalculators(this IServiceCollection services)
    {
        foreach(var calcType in CalculatorsLoader.CalculatorTypes)
        {
            services.AddTransient(calcType);
        }
    }
}

This is how to use AddCalculators extension method in Startup class of web application.


public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddMvc();       services.AddSingleton<ICalculatorProvider, CalculatorProvider>();
    services.AddCalculators();// ...
}

When web application starts then ConfigureServices method is called and Calculators are automatically registered.

Plug-in Provider

To access calculators I wrote ICalculatorProvider interface and CalculatorProvider class. This class with CalculatorExtensions are the only classes that access CalculatorsLoader directly. All other classes in system use provider to query the calculator types. ICalculatorProvider is introduced to ASP.NET Core dependency injection in application Startup class.

GetCalculators() method uses framework level dependency injection to create instances of ICalculator.


public interface ICalculatorProvider
{
    IDictionary<string, ICalculator> GetCalculators();
    IEnumerable<string> GetNames();
    IEnumerable<Type> GetTypes();
}

public class CalculatorProvider : ICalculatorProvider
{
    private IServiceProvider _serviceProvider;public CalculatorProvider(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }       public IDictionary<string,ICalculator> GetCalculators()
    {
        var result = new Dictionary<string, ICalculator>();foreach(var type in CalculatorsLoader.CalculatorTypes)
        {
            var calc = (ICalculator)_serviceProvider.GetService(type);
            var name = type.GetTypeInfo().GetCustomAttribute<CalculatorAttribute>().Name;               result.Add(name, calc);
        }return result;
    }       public IEnumerable<string> GetNames()
    {
        return CalculatorsLoader.CalculatorTypes
                .Select(t => t.GetTypeInfo().GetCustomAttribute<CalculatorAttribute>()?.Name)
                .Where(t => !string.IsNullOrWhiteSpace(t));            
    }public IEnumerable<Type> GetTypes()
    {
        return CalculatorsLoader.CalculatorTypes;
    }
}

Using Plug-ins in Code

Here is the example about how I use ICalculatorProvider in controller code to output the list of available calculators.


public class DummyController : Controller
{
    private ICalculatorProvider _calculatorProvider;public DummyController(ICalculatorProvider calculatorProvider)
    {
        _calculatorProvider = calculatorProvider;
    }       public IActionResult GetCalculators()
    {
        var calculators = _calculatorProvider.GetCalculators();
        var output = "";foreach (var calculator in calculators)
        {
            output += calculator.Key + "\r\n";
        }           return Content(output, "text/plain");
    }
}

The real usage in my solution is more complex but this example gives the point.

Wrapping Up

On .NET Core, things work a little bit differently compared to the full .NET Framework. Although this solution is not perfect, it was still pretty easy to find information and make the code work. It was also easy to automatically register plug-in types with ASP.NET Core framework-level dependency injection and come out with simple classes that architecturally fit into a web application. For my IoT system, this solution is good enough to go with for today.

ASP.NET ASP.NET Core Dependency injection .NET Calculator (Mac OS) application

Published at DZone with permission of Gunnar Peipman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • How to LINQ Between Java and SQL With JPAStreamer
  • Avoiding Pitfalls With Java Optional: Common Mistakes and How To Fix Them [Video]
  • Extending Java APIs: Add Missing Features Without the Hassle
  • An Overview of Kubernetes Security Projects at KubeCon Europe 2023

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: