Table of Contents

How to create an appstore-app

This is a guide that takes you through how to create and publish application & extension through the DynamicWeb App store

The DynamicWeb appstore is a tool which allows you to browse, install and update addins, extensions, apps, connections, providers and more.

In this guide we will provide examples for creating an app for the appstore.

Templates

If it's extending functionality that is needed in the app, the Visual Studio Template "Class Library" should be used. It's advised to use .NET8.0 or higher if you're using DynamicWeb 10.2 or higher.

ExampleApp.csproj

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <Version>1.0.0</Version>
        <AssemblyVersion>1.0.0.0</AssemblyVersion>
        <Title>Example app</Title>
        <Description>This is an example app for DynamicWeb</Description>
    </PropertyGroup>
    <PropertyGroup>
        <PackageProjectUrl>https://your.doc.site/</PackageProjectUrl>
        <PackageTags>dynamicweb-app-store</PackageTags>
        <Product>Your product</Product>
        <Company>Your company</Company>
        <Authors>Your authors</Authors>
        <Copyright>Copyright © 2024 Your company</Copyright>
    </PropertyGroup>
    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    </PropertyGroup>
    <ItemGroup> 
        <PackageReference Include="Dynamicweb.Ecommerce" Version="10.2.0" />
    </ItemGroup>
</Project>
Property Explanation Example value
<Version> Defines the version of the app. Note that it's unique after being pushed to NuGet. This is the version showing up in the version column in the DynamicWeb App store. 1.0.0
<AssemblyVersion> Specifies the version of the assembly (compiled code). 1.0.0.0
<Title> This is the Name column in the DynamicWeb App store. Example app
<Description> This is shown if you click on an app in the DynamicWeb App store as a short description. This is an example app for DynamicWeb
<PackageProjectUrl> URL to the project's home page or documentation. https://your.doc.site/
<PackageTags> A space-delimited list of tags and keywords that describe the project and aid discoverability. Any additional tags will be shown in the Categories column in the DynamicWeb App store. See a tag explanation here dynamicweb-app-store
<Product> This is a fall-back value if the title is somehow not able to be resolved. Your product
<Company> The company or publisher of the assembly. Your company
<Authors> The authors of the project. Also shown in the Author column in the DynamicWeb App store. Your authors
<Copyright> Copyright notice for the project. Copyright © 2024 Your company
<TargetFramework> Specifies the version of the .NET framework targeted by the project. net8.0
<GeneratePackageOnBuild> Indicates whether the project should generate a package (.nupkg) automatically when it is built. It can be used to test with before publishing, as this can be uploaded to /Files/System/Addins/Local and then seen in your local DynamicWeb solution. true
<PackageReference Include="..."> All your references to DynamicWeb or other libraries. Any library not found on your DynamicWeb solution when this package is installed from NuGet will automatically be fetched and downloaded to the same folder as where this .dll ends up, /Files/System/Addins/Installed/{ProjectName}.{Version} Dynamicweb.Ecommerce Version="10.2.0"

Publishing the app

The app needs to be built so the package is generated.

    dotnet build --configuration Release

After this, the .nupkg file should be located in {source}/bin/Release/{ProjectName}.{Version}.nupkg Next step requires the NuGet CLI, unless it's done manually through the site.

    nuget push {ProjectName}.{Version}.nupkg -Source https://api.nuget.org/v3/index.json

This is assuming there's an API-Key set in the NuGet CLI so it can upload given the correct user. If none is set, use the following command first.

    nuget setApiKey <your_API_key>

As of writing, the DynamicWeb App store looks at the public NuGet feed, specifically for the tags dynamicweb-app-store, as well as additional categorization tags. You can read more about how to publish a NuGet package here.

After the package has been uploaded and validated by NuGet, it will show up in the DynamicWeb App store.

Deciding your tags

Required tag:

Tag Explanation
dynamicweb-app-store This lets DynamicWeb know it's a third party app and is required to be listed in the App store by any third party developers.

To categorize the app the best, DynamicWeb advices to use 1 or more of the following tags:

Tag Explanation
integration Major category for the integration area, anything relevant to the integration area, such as providers, scheduled tasks and extensibility points using endpoints.
commerce Major category for the commerce area, any extensions or orders, carts, discounts etc. as well as payment and shipping providers.
payment Minor category for the commerce-specific area, payments. Any new payment provider should be listed with this category.
task Minor category for the integration-specific area, scheduled tasks, any kind of custom scheduled task should be listed with this category.
shipping Minor category for the commerce-specific area, shipping. Any new shipping provider should be listed with this category.

Licensing

An option is to create your own license checks. This can be done by extending the Settings area with a new screen where you can insert some license key. The app can then validate the key towards some webservice for this app, to enable its functionality.

Example implementation

A barebones implementation of this would need the following;

  • A data model for the license
  • An edit screen to submit the license
  • A node provider to let the user get to the edit screen
  • A query to fetch the license, if it exists
  • A command to save the license somewhere (database/file/somewhere else)
  • If saved in a database, an update provider to create the table/column for this license

ExampleLicense.csproj

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <Version>1.0.0</Version>
        <AssemblyVersion>1.0.0.0</AssemblyVersion>
        <TargetFramework>net8.0</TargetFramework>
        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Dynamicweb.CoreUI" Version="10.2.0" />
        <PackageReference Include="Dynamicweb.Apps.UI" Version="10.2.0" />
    </ItemGroup>
</Project>

This example is purely as an example implementation, as such it doesn't have the properties necessary to get published to NuGet, it does however have references to Dynamicweb.CoreUI and Dynamicweb.Apps.UI, which are needed to access all the UI functionality needed.

ExampleLicenseDataModel

using Dynamicweb.CoreUI.Data;

namespace ExampleLicenseImplementation;

public class ExampleLicenseDataModel : DataViewModelBase
{
    public long? Id { get; set; }

    [ConfigurableProperty]
    public string? Key { get; set; }
}

This DataModel could contain any properties that might be necessary, in this example however, all that is stored is an Id in case the implementation should be extended to be able to have multiple keys, and a Key which is the actual license key.

ExampleLicenseEditScreen

using Dynamicweb.CoreUI.Data;
using Dynamicweb.CoreUI.Screens;

namespace ExampleLicenseImplementation;

public class ExampleLicenseEditScreen : EditScreenBase<ExampleLicenseDataModel>
{    
    protected override string GetScreenName() => "License edit screen example";

    protected override void BuildEditScreen() =>
        AddComponents("General", "License", new[]
        {
            EditorFor(m => m.Key),
        });

    protected override CommandBase<ExampleLicenseDataModel>? GetSaveCommand() => new ExampleLicenseSaveCommand();
}

The edit screen makes use of the EditScreenBase and the newly created ExampleLicenseDataModel to create an editor for the Key. It then has an override on the GetSaveCommand pointing to the ExampleLicenseSaveCommand.

ExampleLicenseNodeProvider

using Dynamicweb.Apps.UI;
using Dynamicweb.CoreUI.Actions.Implementations;
using Dynamicweb.CoreUI.Navigation;

namespace ExampleLicenseImplementation;

public class ExampleLicenseNodeProvider : NavigationNodeProvider<AppsSettingSection>
{
    internal static readonly string ConfigurationId = "Settings_Configuration";

    public override IEnumerable<NavigationNode> GetRootNodes() => [];

    public override IEnumerable<NavigationNode> GetSubNodes(NavigationNodePath parentNodePath)
    {
        var nodes = new List<NavigationNode>();

        var pathLast = parentNodePath.Last;
        if (string.Equals(pathLast, ConfigurationId, StringComparison.OrdinalIgnoreCase))
        {
            nodes.Add(new()
            {
                Name = "App licensing",
                NodeAction = NavigateScreenAction.To<ExampleLicenseEditScreen>().With(new ExampleLicenseQuery())
            });
        }

        return nodes;
    }
}

This NavigationNodeProvider is created to enable being able to navigate to the ExampleLicenseEditScreen, as there's no other way navigate to it currently. It attaches itself to the AppsSettingSection which is the Settings in the DynamicWeb backend and creates a new SubNode based on the Settings_Configuration node. This is the Id of the node App settings, and the way to find it involves either looking through the code of existing NodeProviders or looking at the rendered content.

ExampleLicenseQuery

using Dynamicweb.Core;
using Dynamicweb.CoreUI.Data;
using Dynamicweb.Data;
using System.Data;

namespace ExampleLicenseImplementation;

public sealed class ExampleLicenseQuery : DataQueryModelBase<ExampleLicenseDataModel>
{
    public override ExampleLicenseDataModel? GetModel()
    {
        var commandBuilder = CommandBuilder.Create("SELECT [ExampleLicense].* FROM [ExampleLicense]");
        using var dataReader = Database.CreateDataReader(commandBuilder);

        if (dataReader.Read())
            return ExtractExampleLicense(dataReader);

        return new();
    }

    private static ExampleLicenseDataModel ExtractExampleLicense(IDataRecord dataRecord) =>
       new()
       {
           Id = Converter.ToInt64(dataRecord["ExampleLicenseId"]),
           Key = Converter.ToString(dataRecord["ExampleLicenseKey"].ToString())
       };
}

A simple Query implementation, in an actual implementation you would want a Service and Repository to manage all the Database queries, however for this example it's done directly in the Query to minimize the amount of example classes provided. It just fetches the first entry from the ExampleLicense table, as this example implementation only expects a single LicenseKey. It expects a return value of the <T> provided in the DataQueryModelBase<T> implementation to be a DataViewModelBase, which in this case is the ExampleLicenseDataModel.

ExampleLicenseSaveCommand

using Dynamicweb.Core;
using Dynamicweb.CoreUI.Data;
using Dynamicweb.Data;

namespace ExampleLicenseImplementation;

public class ExampleLicenseSaveCommand : CommandBase<ExampleLicenseDataModel>
{
    public override CommandResult Handle()
    {
        if (Model is not null && Upsert(Model))
            return new()
            {
                Model = Model,
                Status = CommandResult.ResultType.Ok
            };

        return new()
        {
            Status = CommandResult.ResultType.Error
        };
    }

    private static bool Upsert(ExampleLicenseDataModel model)
    {
        using var connection = Database.CreateConnection();

        if (model.Id is null || model.Id == 0)
        {
            var sql = new CommandBuilder();
            sql.Add("INSERT INTO ExampleLicense ( ExampleLicenseKey ) ");
            sql.Add("OUTPUT INSERTED.ExampleLicenseId ");
            sql.Add("VALUES ({0})", model.Key);
            model.Id = Converter.ToInt64(Database.ExecuteScalar(sql, connection));

            return true;
        }

        var updateCommand = CommandBuilder.Create("UPDATE [ExampleLicense] SET ");
        updateCommand.Add("[ExampleLicenseKey] = {0}", model.Key);
        updateCommand.Add("WHERE [ExampleLicenseId] = {0}", model.Id);

        return Database.ExecuteNonQuery(updateCommand) > 0;
    }
}

To actually save the data, an implementation of CommandBase is done, where the Model that's sent with it from the ExampleLicenseEditScreen is a ExampleLicenseDataModel with the modified values from the edit screen. This means, when using Model.Key it will provide the value from the editor. The example logic simply just inserts or updates the value of the Key in the Database depending on if it exists or not.

ExampleLicenseUpdateProvider

using Dynamicweb.Updates;

namespace ExampleLicenseImplementation;

public class ExampleLicenseUpdateProvider : UpdateProvider
{
    public override IEnumerable<Update> GetUpdates() =>
        [
            SqlUpdate.AddTable("0504edd1-95cc-4ce1-88ac-3da24bfe0af7", this, "ExampleLicense", @"(
                [ExampleLicenseId] [bigint] IDENTITY(1,1) NOT NULL,
                [ExampleLicenseKey] [nvarchar](max) NOT NULL,
                CONSTRAINT [DW_PK_ExampleLicense] PRIMARY KEY CLUSTERED (
                    [ExampleLicenseId] ASC
                )
            )")
        ];
}

To actually apply any changes to the database, an UpdateProvider is used. As of writing (DynamicWeb 10.2.0) these are only run at start-up, so the first time this app is run it requires a recycle or some other custom code needs to be added to run Dynamicweb.Updates.UpdateManager.ExecuteUpdates(). It creates a simple new table with two columns, one for the Id and one for the Key.

To top