ASP.NET Core OData Part 3

Introduction

This will be the third post on OData and ASP.NET Core 3. Please find the first post (basics) here and the second post (querying) here. This time, I will talk about actions and functions. For demo purposes, let’s consider this domain model:

ClassDiagram1

Functions

A function in OData is like a pre-built query that may take some parameters and either return a single value or a collection of values, which may be entities. An action is similar, but, unlike functions, an action can have side effects, that is, it can make modifications to the underlying data.

Some examples of function calls might be

  • /odata/blogs/FindByDate(date=2009-08-01) – returning a collection of entities
  • /odata/blogs/CountPosts(blogid=1) – returning a single value
  • /odata/blogs/Blog(1)/Count – returning a single value
  • /odata/CountBlogPosts – returning a single value

As you can see in these example links, most of them are associated with the “blogs” entity set, these are called bound functions, but one of them is not, it is therefore an unbound function.

Registering these functions can be a bit tricky because there are a few things involved:

  • The [ODataRoutePrefix] attribute in a controller
  • The way we define the function when we build the EDM Data Model
  • The [ODataRoute] applied to the action method

But I digress. Let’s start with the first function.

Bound Function with Parameters Returning a Collection of Entities

For this we need to define a function in our model, associated with an entity set:

var findByCreation = builder
    .EntitySet<Blog>("Blogs")
    .EntityType
    .Collection
    .Function("FindByCreation");
findByCreation.ReturnsCollectionFromEntitySet<Blog>("Blogs"); findByCreation.Parameter<DateTime>("date").Required();

This will register a function named FindByCreation in the Blogs entity set, which returns a collection of Blog entities and takes a parameter of type DateTime named date.

Its implementation in a controller is:

[ODataRoutePrefix("Blogs")]
public class BlogController : ODataController
{
    private readonly BlogContext _ctx;

    public BlogController(BlogContext ctx)
    {       
this._ctx = ctx;
    }

[EnableQuery]
[ODataRoute("FindByCreation(date={date})")]
    [HttpGet]
    public IQueryable<Blog> FindByCreation(DateTime date)
    {
        return _ctx.Blogs.Where(x => x.Creation.Date == date);
    }
}

Notice the Blogs prefix applied to the BlogController class, this ties this class to the Blogs entity set.

The [EnableQuery] attribute, discussed on the previous post, allows the results of this function to be queried too.

Now you can browse to https://localhost:5001/odata/blogs/FindByCreation(date=2009-08-01) and see the results!

Bound Function with Parameters Returning a Single Value

Another example, this time, returning a single value:

var countPosts = builder
    .EntitySet<Blog>("Blogs")
    .EntityType
    .Collection
    .Function("CountPosts");

countPosts.Parameter<int>("id").Required();
countPosts.Returns<int>();

The function CountPosts is registered to the Blogs entity set, taking a single required parameter named id.

As for the implementation, in the same BlogController:

[ODataRoute("CountPosts(id={id})")]
[HttpGet]
public int CountPosts(int id)
{
    return _ctx.Blogs.Where(x => x.BlogId == id).Select(x => x.Posts).Count();
}

This will be available as https://localhost:5001/odata/blogs/CountPosts(id=1)

Unbound Function

Next up, an unbound function, that is, one that is not associated with an entity set:

var countBlogPosts = builder.Function("CountBlogPosts");
countBlogPosts.Returns<int>();

This function, as you can see, is not attached to any entity set.

Its implementation must be done in a controller that is also not tied to any entity set (no [ODataRoutePrefix] attribute):

public class BlogPostController : ODataController
{
private readonly BlogContext _ctx;

public BlogPostController(BlogContext ctx)
{
this._ctx = ctx;
}

[HttpGet]
[ODataRoute("CountBlogPosts()")]
    public int CountBlogPosts()
{
return _ctx.Posts.Count();
}
}

And to call this function, just navigate to https://localhost:5001/odata/CountBlogPosts().

Actions

The difference between actions and functions is that the former may have side effects, such as modifying data. It should come as no surprise, following REST principles, that actions need to be called by POST or PUT. Let’s see a couple examples

Bound Function with Key Parameter and No Payload Returning a Single Value

Here we want the URL to reflect the fact that we are invoking this function on a specific entity. The definition of the function in the EDM Data Model is:

var count = builder
    .EntitySet<Blog>("Blogs")
    .EntityType
    .Collection
    .Action("Count");
count.Parameter<int>("id").Required(); count.Returns<int>();

Now we are using Action instead of Function to define the Count action!

As for the definition:

[ODataRoute("({id})/Count")]
[HttpPost]
public int Count([FromODataUri] int id)
{
    return _ctx.Blogs.Where(x => x.BlogId == id).Select(x => x.Posts).Count();
}

Notice that we applied the [FromODataUri] attribute to the id parameter, this is required.

To call this action, you will need to POST to https://localhost:5001/odata/blogs/Blog(1)/Count.

Bound Function with Key Parameter and Payload Returning an Entity

The difference between this one and the previous is that this receives an entity as its payload. The definition first:

var update = builder
    .EntitySet<Blog>("Blogs")
    .EntityType
    .Collection
    .Action("Update");

update.EntityParameter<Blog>("blog").Required();
update.ReturnsFromEntitySet<Blog>("Blogs");

Notice how I replaced Parameter by EntityParameter.

The action method implementation is:

[ODataRoute("({id})/Update")]
[HttpPost]
public Blog Update([FromODataUri] int id, ODataActionParameters parameters)
{
var blog = parameters["blog"] as Blog;
_ctx.Entry(blog).State = EntityState.Modified;
_ctx.SaveChanges();
    return blog;
}

And to call this, you need to POST to https://localhost:5001/odata/blogs(1)/Replace with a payload containing a blog property with a value that is the Blog that you wish to update:

{
    "blog" : {
        "BlogId": 1,
        "Name": "New Name",
        "Url": "http://blog.url",
        "CreationDate": "2009-08-01"
    }
}

Conclusion

This concludes the topic of functions and actions. On the next post I will be talking about some more advanced features of OData.

ASP.NET Core OData Part 2

Update: see the third post here.

Introduction

This is the second post on my series on using OData with ASP.NET Core 3. You can find the first here.

Querying

We’ve seen how we can expose an object model to OData. In the first post I used Entity Framework Core, but you don’t need to use any ORM.

Where OData really excels is in querying: you can perform LINQ-style queries over the URL. These include:

  • Filtering
  • Sorting
  • Projections
  • Pagination
  • Counting
  • Navigation property expansions

By default, when you access the entity set’s endpoint, you get all records, but you can enable querying over them. This needs to be done globally first, when you define the endpoint:

app.UseEndpoints(endpoints =>
{
    endpoints.MapODataRoute("odata", "odata", GetEdmModel(app.ApplicationServices));
    endpoints.Select().Expand().OrderBy().Filter().Count();
});

Let’s have a look at the extension methods after the route registration. Don’t worry, I will show examples in a moment.

  • The Select extension method allows projections over an entity, that is, selecting only parts of it
  • Expand is used to allow expansions, for example, while retrieving an entity, similar to what Include does in Entity Framework Core – in fact, it translates to it
  • OrderBy is self explanatory. without it you won’t be able to sort the results of your query
  • Filter is what permits querying
  • Finally, Count is used to allow returning only the count, not the actual results

These are global settings, but we also need to enable them at the configuration, per entity, when we build the EDM Data Model (the GetEdmModel method I’ve shown previously):

builder
    .EntitySet<Parent>(“Parents”)
    .EntityType
    .Select()
    .Expand()
    .OrderBy()
    .Filter()
    .Count();

Finally, we also need to enable it in the action method that returns a query (IQueryable<T>), regardless of whether or not it is wrapped in an IActionResult:

[ODataRoute]

[EnableQuery]

public IQueryable<Parent> Get()

Once we do this, we can now query the entity set on the URL, but first, we need the [EnableQuery] attribute. This is what allows us to query over the results!

If you don’t want to decorate all your action methods with [EnableQuery], we can also do this globally, for any queryable action methods:

services.AddODataQueryFilter();

This has the advantage (or disadvantage) that it applies to all methods, unless we specifically tell OData that querying is not allowed.

As for querying, we have a number of options, I’m going to show just the simplest:

Filter by a property’s value:

/odata/Parents?$filter=Name eq ‘Ricardo Peres’

Sort by one property’s values descending:

/odata/Parents?$orderby=Name desc

Select just a single property:

/odata/Parents?$select=Name

Skip 10 records and retrieve the next 5, ordered by a property:

/odata/Parents?$skip=10&$top=5&$orderby=Name

Filter by a property’s value and return the count:

/odata/Parents?$filter=contains(Name,’Peres’)&$count=true

Expand a collection property:

/odata/Parents?$expand=Children

The main keywords are:

  • $filter: used for specifying conditions
  • $orderby: for sorting, either ascending or descending
  • $select: projections
  • $top: getting only some records
  • $skip: skipping some records
  • $count: getting the count of the returned records together with them
  • $expand: expansions (include navigation properties)

Some of the options can be enhanced, for example:

Filter by several conditions:

/odata/Parents?$filter=Id eq 1 or Id eq 2

/odata/Parents?$filter=Id eq 1 and Name eq ‘abc’

Where property value is in list:

/odata/Parents$filter=Id in (1, 2)

Sort by two property’s values, one descending and the other ascending:

/odata/Parents?$orderby=Name desc,Id asc

Filtering an expansion:

/odata/Parents?$expand=Children($filter=Name eq ‘A Child’)

I won’t go through all of the possible expressions, but the full OData specification is available here.

Setting Limits

We’ve seen in the beginning, while defining the OData route, that we can tell OData what should it support (select, filter, orderby, expand). We can also define the maximum amount of records to return:

app.UseEndpoints(endpoints =>
{
    endpoints.MapODataRoute("odata", "odata", GetEdmModel(app.ApplicationServices));
    endpoints.Select().Expand().OrderBy().Filter().Count().MaxTop(100);
});

Notice the MaxTop extension method, it defines the global maximum amount of records that can be returned by an OData endpoint. But we can also configure it on the action method, using the [EnableQuery] attribute:

[ODataRoute]

[EnableQuery(MaxTop = 10)]

public IQueryable<Parent> Get()

The [EnableQuery] attribute allows you to define a lot of other restrictions:

All of these options can also be specified through the AddODataQueryFilter extension method, but for global restrictions. It is at least advisable to define a maximum number of return records (MaxTop) and also the maximum number of expansions (MaxExpansionDepth).

Return Result

Querying will work both if you are returning an IQueryable<T> collection or an IEnumerable<T>. The thing is, with the former, your query will be executed in the server (the database), whereas with the latter, it will be executed in memory (LINQ to Objects). You will still be able to query, but it won’t be the same!

Inspecting Query Options

In your action methods, whenever querying is allowed, we have a way to get the query options passed to the URL ($filter, $orderby, etc), and then choose whether or not we want to apply them. The key lies in the ODataQueryOptions<T> class:

[ODataRoute]
[EnableQuery]
public IQueryable<Parent>Get(ODataQueryOptions options)
{
if (options.Top != null && options.Top.Value == 0) { options.Top.Value = 10; } var parents = _ctx.Parents.AsQueryable(); parents = options.ApplyTo(parents) as IQueryable<Parent>; return parents;
}

Here you can inspect the current filter (Filter), sort order (OrderBy), expand (SelectExpand), skip (Skip), count (Count) and even make changes. When you’re happy with then, just ApplyTo a base queryable collection.

Conclusion

This covers the basic querying options of OData. In the next post, functions and actions!

References

Please refer to the following links for additional information:

ASP.NET Core OData Part 1

Update: see the second post here.

Introduction

OData is an open standard for making an object-oriented domain model available as an HTTP REST interface.In a nutshell, it provides a specification for returning domain models as result of HTTP requests, querying them over the URL and even creating functions and actions over the domain model. In what .NET Core is concerned, it is a way by which you can expose your Entity Framework Core – or any other ORM that has a LINQ interface – to the web, without writing too much boilerplate code, as an ASP.NET Core Web API.

It is surprising that not many people know about OData, also, there are some caveats to it and not much documentation on using it with ASP.NET Core, so I decided to write a few posts on the subject. Here is the first!

Setting Up

Let’s pretend you have a domain model with just two classes, Parent and Child:

image

We should also have an Entity Framework Core context that exposes them:

public class ParentChildContext : DbContext

{

public ParentChildContext(DbContextOptions options) : base(options) { }

public DbSet<Parent> Parents { get; set; }

public DbSet<Child> Children { get; set; }

}

I won’t go into details as to explain this, I’m pretty sure you all know about Entity Framework Core contexts! Just make sure you add a reference to the Microsoft.EntityFrameworkCore NuGet package, or, if you’re using SQL Server, Microsoft.EntityFrameworkCore.SqlServer. Now register your context to the DI framework, in the ConfigureServices method:

services.AddDbContext<ParentChildContext>(options =>

{

options.UseSqlServer(“<connection string>”);

});

In your ASP.NET Core’s project you need to add a reference to the Microsoft.AspNetCore.OData NuGet package. This includes the server-side implementation of OData version 4 for ASP.NET Core.

You need to add its required services in the ConfigureServices method:

services.AddOData();

This registers the services, but now we need to add an endpoint for an actual domain model. ASP.NET Core OData now supports endpoint routing, so everything can be done smoothly:

app.UseEndpoints(endpoints =>

{ endpoints.MapODataRoute(“odata”, “odata”, GetEdmModel(app.ApplicationServices)); });

We registered this endpoint with name odata (first parameter) and also with the same prefix (second parameter), you can happily change this. As you can see, in this route, we are returning an EDM Data Model. We build one as this:

private static IEdmModel GetEdmModel(IServiceProvider serviceProvider)

{

var builder = new ODataConventionModelBuilder(serviceProvider);

builder.EntitySet<Parent>(“Parents”);

builder.EntitySet<Child>(“Children”);

return builder.GetEdmModel();

}

One thing that you’ll notice is that this is a conventions-based model builder, ODataConventionModelBuilder. this one takes care of some things for you, such as inferring the id property for each entity, that’s why you don’t have to explicitly state it. If your entity has an id property with a funny name, other than Id or EntityId, then you will need to specify it:

builder

.EntitySet<Parent>(“Parents”)

.EntityType

.HasKey(x => x.Id);

There is no need to specify the other properties, because they are all inferred automatically too from the generic parameter.

Do keep in mind the entity set name that you give, it is common to have a pluralized form of the entity name, but it is not required.

Now we need to create a controller that exposes this. At the very least, we will need two methods:

[ODataRoutePrefix(“Parents”)]

public class ParentController : ODataController

{

private ParentChildContext _ctx;

public ParentController(ParentChildContext ctx)

{

this._ctx = ctx;

}

[ODataRoute]

public IQueryable<Parent> Get()

{

return this._ctx.Parents.AsQueryable();

}

[ODataRoute(“{id}”)]

public Parent Get([FromODataUri] int id)

{

return this._ctx.Parents.Find(id);

}

}

This controller is specific to the Parents entity set, so, if you wish, you need to have another one for the Children. This is specified in the [ODataRoutePrefix] attribute.

Also notice how we are returning IQueryable<Parent> from the Get action method that does not take parameters and a single Parent from the other. You can also declare IActionResult or ActionResult<T>:

[ODataRoute]

public IActionResult Get()

{

return this.Ok(this._ctx.Parents.AsQueryable());

}

//alternative

[ODataRoute]

public ActionResult<IQueryable<Parent>> Get()

{

return this.Ok(this._ctx.Parents.AsQueryable());

}

The latter, the one that uses ActionResult<T>, has some advantages, which you can read about here.

And, of course, all of the methods can be made asynchronous too:

[ODataRoute]

public async Task<IQueryable<Parent>> Get()

{

return await this._ctx.Parents.ToListAsync();

}

[ODataRoute(“{id}”)]

public async Task<Parent> Get([FromODataUri] int id)

{

return await this._ctx.Parents.FindAsync(id);

}

We will see a problem with the implementation of the first method in the next post.

Now, the first method, the one without parameters, returns all of the entities in the database, and the second, as one would expect, returns possibly one from its id property.

Conclusion

We’re all done here, so we can now access the endpoints we just created, try to navigate to the following URLs:

  • /odata: returns information about the exposed entity sets (Parents and Children)

image

  • /odata/parents –> ParentController.Get(): returns all records for the Parent entity
  • /odata/parents(1) –> ParentController.Get(1): returns possibly one record, if it exists in the database for the given primary key of the Parent entity

This is the very basics, in the future posts we will explore other options, such as querying over the URL and creating functions and actions.

References

You can find more information in the following pages:

Dynamic Routing in ASP.NET Core 3

ASP.NET Core 3 introduced a not so talked about feature which is dynamic routing. In a nutshell, it means that it is possible to decide at runtime the controller, action and route tokens that a request will be dispatched to. The idea is to map a route pattern to a dynamic route handler, like this:

app.UseEndpoints(endpoints =>
{
    endpoints.MapDynamicControllerRoute<SearchValueTransformer>("search/{**product}");
});

This maps a route of “/search/<anything>” to a class SearchValueTransformer, which is then responsible for supplying values for the controller and action.

This SeachValueTransformer class must inherit from DynamicRouteValueTransformer, this is the abstract base class for all dynamic route handling, and it only has one method to implement, TransformAsync. The example I am providing here is that of a service that receives some random query for a product on the URL and then decides to which controller it will be forwarded to. A sample implementation of this class would be:

class SearchValueTransformer : DynamicRouteValueTransformer { private readonly IProductLocator _productLocator; public SearchValueTransformer(IProductLocator productLocator) { this._productLocator = productLocator; } public override async ValueTask TransformAsync(

HttpContext httpContext, RouteValueDictionary values) { var productString = values[“product”] as string; var id = await this._productLocator.FindProduct(“product”, out var controller); values[“controller”] = controller; values[“action”] = “Get”; values[“id”] = id; return values; } }

As you can see, it takes an instance of an IProductLocator (I will get to that in a moment) in its constructor, which means that it supports dependency injection. The TransformAsync extracts a “product” parameter from the route – I am skipping validation because if we got here that’s because we matched the route – which it then uses to call the stored IProductLocator instance to retrieve the id and controller, which are then set as route parameters. The result route is then returned.

The SearchValueTransformer needs to be registered in the dependency injector too:

services.AddSingleton<SearchValueTransformer>();

The actual lifetime is irrelevant, here I am setting it as singleton because the SearchValueTransformer holds a single IProductLocator which is itself a singleton and nothing else.

As to the IProductLocator, here is it’s interface:

public interface IProductLocator
{
    Task<string> FindProduct(string product, out string controller);
}

The actual implementation, I leave it up to you, it must apply some logic to the product passed in the URL to decide which controller should be used for search it, and then return some reference and a controller responsible for returning information about it. This interface and its implementation must also be registered:

services.AddSingleton<IProductLocator, ProductLocator>();

And this is it. This may serve as a complement to static routing, other uses include translating route parts (controller, action) on the fly. As always, hope you find this useful!

Accessing the HttpContext from a DbContext

Sometimes it might be necessary to access the current HttpContext from inside a DbContext, namely, from inside the OnConfiguring or OnModelCreating methods. Why? Well, for once, because of multitenancy: we may want to be able to decide the connection string to use based on the requesting or the host’s domain, the current user or some other request parameter. Here the internal dependency injection (Instance) can’t help, because it cannot be used inside these methods.

The only option we have is to inject the IHttpContextAccessor class through our DbContext class’ constructor, and, from it, get hold of the HttpContext.

First we need to register the IHttpContextAccessor service in ConfigureServices:

services.AddHttpContextAccessor();

We also need to register our context for dependency injection:

services.AddDbContext<MyContext>();

A context registered this way needs to have a special constructor that has a parameter of type DbContextOptions (or DbContextOptions<MyContext>), in our case, we just need to add an extra parameter of type IHttpContextAccessor:

public class MyContext : DbContext
{
    public MyContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor) : base(options)
    {
        this.HttpContextAccessor = httpContextAccessor;
    }

    protected IHttpContextAccessor HttpContextAccessor { get; }
}

Finally, when you need to access the HttpContext, you just need to retrieve it from the IHttpContextAccessor:

protected internal override void OnConfiguring(DbContextOptionsBuilder builder)
{
    var httpContext = this.HttpContextAccessor.HttpContext;
    var tenantService = httpContext.RequestServices.GetService<ITenantService>();
    var connectionString = tenantService.GetConnectionString(httpContext);

    builder.UseSqlServer(connectionString);

    base.OnConfiguring(builder);
}

Here the hypothetical ITenantService provides a method GetConnectionString that returns a connection string for the current HttpContext, and we use it with the SQL Server provider.

Hope you find this useful! Winking smile

TypeScript for C# and .NET Core Developers Review

I finished reading Hands-On TypeScript for C# and .NET Core Developers by Francesco Abbruzzese (@f_abbruzzese) for Packt Publishing. As the name states, it is about TypeScript (and JavaScript) and also very much about Angular.

The book is structured like this:

  1. First chapter explains what is TypeScript (version 2.8.3), how to install it using NPM or the SDK, how to create your first project, basic configuration options, the type system and syntax; at all times, it relates the TypeScript syntax with the recent ECMAScript versions, of which TypeScript is a superset
  2. The second one talks about type declaration, including interfaces, classes, unions, tuples, arrays and so on. It also covers operations over types, such as destructuring and spreads. Finally, it presents functions in TypeScript, how to mimic overloading and have optional arguments
  3. Chapter 3 covers DOM manipulation. This is probably something that seasoned web/JavaScript developers are quite familiar with, but, most importantly, it also introduces declaration files
  4. In chapter 4 we learn how to make effective use of classes and interfaces, declare visibility levels and modifiers, and how type compatibility works
  5. Generics is the topic of chapter 5, how to declare generic types and functions and how to enforce constraints
  6. This chapter talks about modules and namespaces, the different ways by which we can resolve and load modules, and also about TypeScript type declarations, which are used to call untyped JavaScript code
  7. Here we learn how to integrate the WebPack bundler and minifier with ASP.NET Core and how we can enable debugging of the source code in Visual Studio
  8. A very important chapter, here we get an overview on how to write reusable libraries and make them available on NPM. Testing goes together with reusability, so we learn how to use Jasmine to unit test our code
  9. In this chapter we learn about symbols, the TypeScript equivalent to decorators in Aspect-Oriented Programming, generator functions and iterators and also promises, used for asynchronous invocations. Not exactly related, but it also covers the fetch API, used for modern AJAX-style interactions
  10. Chapter 10 presents the ASP.NET Core project template for Angular using TypeScript, describes the Angular architecture and key concepts
  11. This chapter teaches us how to interact with form fields, including validations, how the Angular lifecycle works and how to achieve two-way communication, data binding and piping between components
  12. This one presents some advanced Angular features, such as custom attribute and structural directives, which are then used to create animations by doing DOM manipulation
  13. Lastly, this chapter explains what is dependency injection and its benefits and how we can leverage it with TypeScript and Angular. It also describes how we can localize messages and use HTTP-related modules for interaction with external services. Most importantly, it presents the basis of Angular routing and navigation, a must-have for any complex application, and ends with an overview of testing for

At the end of each chapter there is a summary which highlights the key aspects that were introduced in it, poses 10 questions, which are answered in the Assessments chapter.

The book covers TypeScript 2.8.3, which is relatively old by now, but given the pace that TypeScript versions come out, it can hardly surprise us. Some new stuff in TS is missing, of course, but I guess this will always happen. It is essentially a book about TypeScript and Angular, but of course also covers Node.js and, obviously, JavaScript itself. .NET Core here is discussed as leverage to deploy and compile client-side code. The book is quite comprehensive and was actually an interesting read and I definitely learnt a lot.

The source code can be found in GitHub here: https://github.com/PacktPublishing/Hands-On-TypeScript-for-CSharp-and-.NET-Core-Developers.

If you got interested, please go get if from the Packt Publishing site: https://www.packtpub.com/application-development/hands-typescript-c-and-net-core-developers.

Dynamically Loading Middleware in ASP.NET Core

Introduction

The concept of middleware has been around since ASP.NET MVC (pre-Core) and OWIN. Essentially, a middleware component lives in a pipeline and handles requests and acts as a chain of responsibility, delegating to any subsequent middleware components registered in the pipeline after itself. The following image (taken from the Microsoft site) shows this.

Image result for asp.net core middleware

MVC itself is implemented as a middleware component, as is redirection, exception handling, buffering, etc.

A middleware component can be added in several ways, but in ASP.NET Core, it all goes down to the Use method in IApplicationBuilder. Lots of API-specific methods rely on it to add their own middleware.

For the time being, we’ll make use of the IMiddleware interface that comes with ASP.NET Core. It provides a simple contract that has no dependencies other than the common HTTP abstractions.

One common request is the ability to load and inject middleware components dynamically into the pipeline. Let’s see how we can

Managed Extensibility Framework

.NET Core has Managed Extensibility Framework (MEF), and I previously blogged about it. MEF offers an API that can be used to find and instantiate plugins from assemblies, which makes it an interesting candidate for the discovery and instantiation of such middleware components.

Image result for managed extensibility frameworkWe’ll use the System.Composition NuGet package. As in my previous post, we’ll iterate through all the assemblies in a given path (normally, the ASP.NET Core’s bin folder) and try to find all implementations of our target interface. After that we’ll register them all to the MEF configuration.

Implementation

Our target interface will be called IPlugin and it actually inherits from IMiddleware. If we so wish, we can add more members to it, for now, it really doesn’t matter:

public interface IPlugin : IMiddleware
{
}

The IMiddleware offers an InvokeAsync method that can be called asynchronously and takes the current context and a pointer to the next delegate (or middleware component).

I wrote the following extension method for IApplicationBuilder:

public static class ApplicationBuilderExtensions

{

public static IApplicationBuilder UsePlugins(this IApplicationBuilder app, string path = null)        {

     var conventions = new ConventionBuilder();

        conventions

           .ForTypesDerivedFrom<IPlugin>()

           .Export<IPlugin>()

           .Shared();

           path = path ?? AppContext.BaseDirectory;

            var configuration = new ContainerConfiguration()

            .WithAssembliesInPath(path, conventions);

            using (var container = configuration.CreateContainer())

            {

           var plugins = container

                .GetExports<IPlugin>()

                    .OrderBy(p => p.GetType().GetCustomAttributes<ExportMetadataAttribute>(true)

.SingleOrDefault(x => x.Name == “Order”)?.Value as IComparable ?? int.MaxValue); 

               foreach (var plugin in plugins)

                {

                    app.Use(async (ctx, next) =>

                    {

                    await plugin.InvokeAsync(ctx, null);

                        await next();

                    });

                }

          }

          return app;

    }

}

We define a convention that for each type found that implements IPlugin we register it as shared, meaning, as a singleton.

As you can see, if the path parameter is not supplied, it will default to AppContext.BaseDirectory.

We can add to the plugin/middleware implementation an ExportMetadataAttribute with an Order value to specify the order by which our plugins will be loaded, more on this in a moment.

The WithAssembliesInPath extension method comes from my previous post but I’ll add it here for your convenience:

public static class ContainerConfigurationExtensions
{     public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)     {      return WithAssembliesInPath(configuration, path, null, searchOption);     }     public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)     {         var assemblyFiles = Directory          .GetFiles(path, "*.dll", searchOption);         var assemblies = assemblyFiles             .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);         configuration = configuration.WithAssemblies(assemblies, conventions);         return configuration;     }
}

If you want to search all assemblies in nested directories, you need to pass SearchOption.AllDirectories as the searchOption parameter, but this, of course, will have a performance penalty if you have a deep directory structure.

Putting it All Together

So, let’s write a few classes that implement the IPlugin interface and therefore are suitable to be used as middleware components:

[Export(typeof(IPlugin))] [ExportMetadata(“Order”, 1)] public class MyPlugin1 : IPlugin {     public async Task InvokeAsync(HttpContext context, RequestDelegate next)     {         //do something here

//this is needed because this can be the last middleware in the pipeline (next = null)         if (next != null)         {             await next(context);         }

//do something here     } }

Notice how we applied an ExportMetadataAttribute to the class with an Order value; this is not needed and if not supplied, it will default to the highest integer (int.MaxValue), which means it will load after all other plugins. These classes need to be public and have a public parameterless constructor. You can retrieve any registered services from the HttpContext’s RequestServices property.

Now, all we need to do is add a couple of assemblies to the web application’s bin path (or some other path that is passed to UsePlugins) and call this extension method inside Configure:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{

//rest goes here

app.UsePlugins(/*path: “some path”*/);

//rest goes here

}

And here you have it: ASP.NET Core will find middleware from any assemblies that it can find on the given path.

Hope you find this useful! Winking smile

Succinctly Books Index

This page lists all the books I wrote or reviewed for Syncfusion’s Succinctly series.

Books I wrote:

Books I reviewed:

ASP.NET Core Pitfalls – Session Storage

Previous versions of ASP.NET featured several ways to persist sessions:

  • InProc: sessions would be stored on the server’s process memory
  • SQL Server: sessions would be serialized and stored in a SQL Server database; other vendors offered similar functionality
  • State Server: sessions would be serialized and stored on an instance of the ASP.NET State Service
  • Custom: we had to implement our own persistence mechanism

InProc was probably the most commonly used; it was the fastest, as the items in the session weren’t serialized, but on the other side they would not survive server crashes. Using this approach things usually worked well, because the session object merely provided a reference to the items stored in memory, so manipulating these items didn’t mandate that the session be explicitly saved.

In ASP.NET Core, all of this is gone. By default, sessions are still stored in memory but one can also use one of the available distributed cache mechanisms. The main difference, however, is that even when the session objects are stored in memory, they still need to be serialized and deserialized prior to persisting or retrieving them. It is no longer possible to just store a pointer to a memory object and keep manipulating it transparently; the object to be stored needs to be converted into a byte array first. You can use any serializer you want.

If we think about it seriously, it was probably a good decision: I’ve seen applications where a lot of data was being stored on the session using InProc mode, but then there was a need to switch to another mode to improve scalability, and the application would just stop working, as the objects being stored weren’t serializable. This time, we need to carefully think about it beforehand.

ASP.NET Core Pitfalls – Redirect to Action Keeps Route Parameters

When you redirect after a POST – following the famous Post-Redirect-Get pattern – but your previous view was constructed using a route parameter, then it will be sent to the redirect action as well.

For example, say you are responding to a request for /Filter/Smartphone, where Smartphone is a route parameter, you POST it to some controller action and at the end you redirect to the Index action using the RedirectToAction method:

return this.RedirectToAction(nameof(Index));

The browser will issue the GET request for Index but keeps the Smartphone route parameter, which is not good.

The solution is to pass a routeValues parameter to RedirectToAction that doesn’t contain any of the possible route parameters. One way to do it would be to create a dictionay with all action parameters nullified:

return this.RedirectToAction(nameof(Index), MethodBase.GetCurrentMethod().GetParameters().ToDictionary(x => x.Name, x => (object) null));

The solution to have this done automatically lies in the MethodBase.GetCurrentMethod() method. This way, you are sure to avoid any unwanted route parameters on your next request.

In case you are wondering, passing null, a dictionary without entries or object won’t work, the only other way is to pass an anonymous value with the parameters explicitly set to null.