In this release we have updated Cofoundry to target ASP.NET Core 2.1 and implemented a number of bug fixes and features. The release includes breaking changes; the ones you'll most likely encounter are mentioned here, but for a comprehensive list check out the full 0.4 release notes and do get in touch if you have any issues.

Please note that due to a tagging mishap the actual NuGet package version is 0.4.1.

Upgrading to .NET Core 2.1

Upgrading from .NET Core 2.0 to .NET Core 2.1 is a fairly painless process and is not considered a breaking change; you can find guidance from Microsoft on the upgrade process here.

There's a few ASP.NET 2.1 related changes in Cofoundry that you should be aware of when upgrading:

Compatibility Version

Cofoundry automatically sets the CompatibilityVersion to Version_2_1. In future updates we'll increment the compatibility version inline with framework updates once we've tested them with Cofoundry. You can override this behaviour by setting the compatibility version after AddCofoundry() is called e.g.

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .AddCofoundry(Configuration)
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_0);
}

Startup

Cofoundry does not set CookiePolicyOptions or run UseHsts, UseHttpsRedirection, or UseCookiePolicy. These are outside the scope of Cofoundry and you should set them as appropriate for your application. As before, Cofoundry does handle errors and static files so there's no need to run UseDeveloperExceptionPage, UseExceptionHandler or UseStaticFiles which are shown in the Microsoft upgrade document.

A Cofoundry 0.4 equivalent of the example startup.cs file would look like this:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Cofoundry.Web;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Hosting;

namespace WebApp1
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            
            services
                .AddMvc()
                .AddCofoundry(Configuration);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (!env.IsDevelopment())
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseCookiePolicy();
            app.UseCofoundry();
        }
    }
}

Cookies

The Cofoundry auth cookie has been marked as essential so it does not conflict with the new cookie consent feature should you choose to use it.

Sourcelink

Cofoundry NuGet packages now use SourceLink so you can step into Cofoundry code when debugging your website. You'll need to disable the "Just My Code" debugging option in Visual Studio to enable this.

Duplicate custom entity

Cofoundry has always had the ability to duplicate/clone a page, but not a custom entity. We've now added that feature and also fixed page duplication so that it also copies all the block data.

To clone a custom entity, simply click the 'Duplicate' button in the admin panel and fill out the identity fields.

Duplicating a custom entity in Cofoundry

Referencing the ambient visual editor mode

We've added an injectable service that provides access to the visual editor mode of the request. This is useful if your displaying versioned data such as custom entities in a separate component and you want to display the correct version of the data to match the edit mode.

In the example below we use IVisualEditorStateService to get an object representing the visual editor state for request and use it to get a PublishStatusQuery value that we can use to query the blog post listing. If the visual editor is in live mode then only published blog posts will be returned from the query, otherwise the latest version (including drafts) will be returned.

public class HomepageBlogPostsViewComponent : ViewComponent
{
    private readonly ICustomEntityRepository _customEntityRepository;
    private readonly IVisualEditorStateService _visualEditorStateService;

    public HomepageBlogPostsViewComponent(
        ICustomEntityRepository customEntityRepository,
        IVisualEditorStateService visualEditorStateService
        )
    {
        _customEntityRepository = customEntityRepository;
        _visualEditorStateService = visualEditorStateService;
    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        var visualEditorState = await _visualEditorStateService.GetCurrentAsync();

        var query = new SearchCustomEntityRenderSummariesQuery();
        query.CustomEntityDefinitionCode = BlogPostCustomEntityDefinition.DefinitionCode;
        query.PublishStatus = visualEditorState.GetAmbientEntityPublishStatusQuery();
        query.PageSize = 3;

        var entities = await _customEntityRepository.SearchCustomEntityRenderSummariesAsync(query);

        return View(entities);
    }
}

More information can be found in our new entity versioning documentation.

Changes to custom entity sorting

We've updated the default ordering of custom entities for SearchCustomEntityRenderSummariesQuery to order by title, which puts it inline with the behavior of other custom entity queries. This is a breaking change so if you were relying on that query to return entities by the create date then you can specify the sorting explicitly on the query:

var query = new SearchCustomEntityRenderSummariesQuery();
query.CustomEntityDefinitionCode = "EXAMPL";
query.SortBy = CustomEntityQuerySortType.CreateDate;

Note that if your definition implements IOrderableCustomEntityDefinition then this change does not effect custom entities with a custom set ordering.

Specifying the default sort type

Alternatively you can now set the default sorting options on the custom entity definition by implementing ISortedCustomEntityDefinition. This has the additional benefit of changing the sorting for the custom entity list view in the admin panel.

A good example of a custom entity we might want to change the sorting for is a blog post, which typically you'd want to order by publish date. Here's an example of what that might look like:

public class BlogPostCustomEntityDefinition 
    : ICustomEntityDefinition<BlogPostDataModel>
    , ISortedCustomEntityDefinition
{
    public const string DefinitionCode = "EXABLP";

    public string CustomEntityDefinitionCode => DefinitionCode;

    public string Name => "Blog Post";

    /// …other properites removed for brevity
    
    /// <summary>
    /// The sorting to apply by default when querying collections of custom 
    /// entities of this type. A query can specify a sort type to override 
    /// this value.
    /// </summary>
    public CustomEntityQuerySortType DefaultSortType => CustomEntityQuerySortType.PublishDate;
    
    /// <summary>
    /// The default sort direction to use when ordering with the
    /// default sort type.
    /// </summary>
    public SortDirection DefaultSortDirection => SortDirection.Default;

Setting the bounds of a pageable query

Cofoundry has built-in classes you can use to make paging your data models easier. You can create a query that supports paging by implementing IPageableQuery, which is usually done by inheriting from the SimplePageableQuery base class.

We've added some new extension methods to help you manage the bounds of your queries, setting the default page size and limiting the maxium page size to prevent large data requests.

public void Example(IPageableQuery query)
{
    // Set default page size to 10, allow unbounded page size e.g. page size of -1 returns all items
    query.SetBounds(10, true);

    // Set default page size to 40, max page size to 100
    query.SetBounds(40, 100);
}

Note that built-in Cofoundry queries that use paging do not set bounds, if you are exposing these queries to the web e.g. via a web api, then you should set those bounds to your requirements.

You can read more about this in the new paging documentation

Changes to transactions and message publishing

In a typical Cofoundry site it's unlikely that you'll be orchestrating transactions and dealing with messaging/events, but it's worth mentioning that the way we handle transactions has changed with this release.

I won't go into too much detail here, but our abstraction has been renamed fromITransactionScopeFactory to ITransactionScopeManager and our default implementation has moved away from using EF transactions to using System.Transactions, which was recently re-introduced in .NET Standard 2.0.

We've also added the ability for ITransactionScopeManager to queue up tasks that will execute after the transaction has completed, namely cache busting and message publishing tasks. The upshot of this is that when coordinating multiple commands that each trigger their own message publishing, you can rely on the messages only being published after the transaction has completed.

This is mostly an internal detail of Cofoundry, but if you're using transactions, particularly if you have your own DbContext, then it's worth reading up on our new entity framework and transactions documentation. The main change you'll notice with this is that completion is now an async operation which allows for async tasks to be run after the scope has completed.

Automatic database updates

Please note that this release contains automated database updates.

These will apply when you run your application for the first time so please ensure you are not connecting to production databases from your development environment and have tested the update before applying it to production systems.

Other noteable features and bug fixes

  • #217 Add mailto links to the default sanitization ruleset
  • #206 Custom Entity Permissions: Easily add new permissions to roles after initialization
  • #87 Improve Page Querying outside of the admin panel
  • #229 Duplicate pages: Does not duplicate block content

Notable breaking changes

Whilst many of the breaking changes are unlikely to affect a typical Cofoundry site, there's a few that are worth highlighting here:

  • ApiResponseHelper.SimpleQueryResponse now returns a 404 status code if the result is null.
  • IEntityFrameworkSqlExecutor: All sync operations have been removed.
  • PropertyBuilderExtensions has been moved to Cofoundry.Core.EntityFramework namespace.
  • IDatabase is no longer injected, use ICofoundryDatabase.
  • ModelBuilder.UseDefaultConfig has been replaced with HasAppSchema because since the migration to EF Core no other configuration was being performed here other than setting the default schema.
  • Removed the generic versions of IOrderableCustomEntityDefinition and ICustomizedTermCustomEntityDefinition. Use the non-generic versions instead alongside the generic ICustomEntityDefinition<TDataModel>. The generic versions were a shortcut, but I think it's better to be explicit here so we don't have multiple ways of doing things which can be confusing.
  • VisualEditorMode has been moved from the Cofoundry.Web project to the Cofoundry.Domain project.
  • VisualEditorMode.Draft has been renamed VisualEditorMode.Preview
  • SortDirection naming has been changed from Ascending/Descending to be Default/Reversed to better describe the behaviour, this is because for publish/create date sorting the default behaviour is latest first which is not technically ascending ordering.

A complete list of bug fixes, features and breaking changes can be found in the full version 0.4 release notes on GitHub.