Developer forum

Forum » Development » Adding options to product category fields

Adding options to product category fields

Imar Spaanjaars Dynamicweb Employee
Imar Spaanjaars
Reply

Hi there,

How do I add options to a Product Category Field (of type Field) programmtically? I tried things like this:

private static void SaveFieldOptions(string fieldId, Dictionary<string, string> options)
{
  var defaultLanguageId = Dynamicweb.Ecommerce.Services.Languages.GetDefaultLanguageId();
  var optionList = new List<FieldOption>();
  foreach (var kvp in options)
  {
    var fieldOption = new FieldOption
    {
      Id = kvp.Key,
      Value = kvp.Value,
      FieldId = fieldId,
    };
    optionList.Add(fieldOption);
    var fieldOptionTranslation = new FieldOptionTranslation(defaultLanguageId)
    {
      Name = kvp.Value
    };
    fieldOption.Translations.Add(fieldOptionTranslation);
    fieldOption.SetName(defaultLanguageId, kvp.Value);
    Dynamicweb.Ecommerce.Services.FieldOptions.Save(fieldOption);
    Dynamicweb.Ecommerce.Services.FieldOptions.SaveTranslation(fieldOptionTranslation);
  }
  Dynamicweb.Ecommerce.Services.FieldOptions.SaveOptions(optionList);
}

That looks like far too many Saves to me and it still doesn't work.

I looked at Field.FieldOptions but that tells me it's obsolete and refers me to the FieldOptions service.

The Docs Bot tells me this :-)

-------------------

Dynamicweb’s documentation for ProductCategoryField and option translations is quite sparse, and there is no complete end‑to‑end example of creating options with translations purely in code.

From the available API docs we do have this key piece:

  • There is a Dynamicweb.Ecommerce.Products.FieldOptionTranslation class, which represents a translation for a list option on a field (e.g. a list box option on a product field or category field).

However, the documentation does not show:

  • How FieldOptionTranslation is attached to a specific option on a ProductCategoryField
  • Which collection/property on the option or field you should populate
  • Any repository/service methods that persist these translations programmatically

Because of that, I cannot give you a correct, documented code sample such as:

csharp
var field = ...; // ProductCategoryFieldvar option = ...; // field optionvar translation = new FieldOptionTranslation { ... };option.Translations.Add(translation);Services.ProductCategoryFields.Save(field);

—that would be speculative and not backed by the official docs.

-------------------

Any samle code would be appreciated.

Imar


Replies

 
Nicolai Pedersen Dynamicweb Employee
Nicolai Pedersen
Reply
This post has been marked as an answer

Hi Imar

My GPT with code access says this :-).

**********

 

You’re right: that is far too many Save calls — and the reason it still doesn’t work is that you’re creating a FieldOptionTranslation “in the void” and saving it separately, without letting the option own/manage its translations the way the platform expects. In practice, you should:

  • Create (or load) a FieldOption

  • Set the name via SetName(languageId, name) (this is the “attach translation to the option” part)

  • Set Value (and optional flags)

  • Call one save on the option

That’s it.

Below is the pattern we use elsewhere (same idea as the command you pasted, just using the eCommerce services directly). It handles new vs existing, avoids duplicates, and persists translations through SetName + Save.

using System;
using System.Collections.Generic;
using System.Linq;
using Dynamicweb.Core;
using Dynamicweb.Ecommerce.Products;

public static class ProductCategoryFieldOptions
{
    public static void UpsertFieldOptions(
        string fieldId,
        IEnumerable<(string Value, string Name)> options,
        bool overwriteExistingNames = false)
    {
        var defaultLanguageId = Dynamicweb.Ecommerce.Services.Languages.GetDefaultLanguageId();

        // Load existing options once
        var existing = Dynamicweb.Ecommerce.Services.FieldOptions
            .GetOptionsByFieldId(fieldId)
            .ToList();

        foreach (var (value, name) in options)
        {
            if (string.IsNullOrWhiteSpace(value))
                continue;

            // Match by Value (usually the safest key); you can also match by Id if you own the Ids
            var opt = existing.FirstOrDefault(x =>
                x.Value.Equals(value, StringComparison.OrdinalIgnoreCase));

            var isNew = opt == null;
            if (isNew)
            {
                opt = new FieldOption { FieldId = fieldId };
            }

            // Prevent duplicates when creating new
            if (isNew && existing.Any(x => x.Value.Equals(value, StringComparison.OrdinalIgnoreCase)))
                continue;

            opt.Value = value;

            // This is the important part: attach translation/name to the option
            // (no separate SaveTranslation needed)
            if (overwriteExistingNames || isNew)
            {
                opt.SetName(defaultLanguageId, name);
            }

            // Optional:
            // opt.IsDefault = ...
            // opt.Image = ...

            Dynamicweb.Ecommerce.Services.FieldOptions.Save(opt);

            if (isNew)
            {
                existing.Add(opt); // keep local list in sync to avoid duplicates in same run
            }
        }
    }

    public static void SetNamesForLanguages(string fieldId, string optionId, IDictionary<string, string> languageIdToName)
    {
        var opt = Dynamicweb.Ecommerce.Services.FieldOptions
            .GetOptionsByFieldId(fieldId)
            .FirstOrDefault(x => string.Equals(x.Id, optionId, StringComparison.OrdinalIgnoreCase));

        if (opt == null) return;

        foreach (var kvp in languageIdToName)
        {
            var languageId = kvp.Key;
            var name = kvp.Value;

            opt.SetName(languageId, name);
        }

        Dynamicweb.Ecommerce.Services.FieldOptions.Save(opt);
    }
}

Why your approach fails (the tiny horror story)

FieldOptionTranslation exists, but saving it independently doesn’t reliably “bind” it to an option unless the option itself is managing the translation collection/state. SetName(languageId, name) is the supported way to associate the translated name with the option — and then Save(option) persists the whole thing in one go. Your code was essentially doing “save a translation object” without guaranteeing it’s linked to the option record in the way the service expects. (doc.dynamicweb.com)

TL;DR

  • Don’t call SaveTranslation manually here.

  • Don’t call SaveOptions unless you really know what shape it expects (it’s not needed for simple upserts).

  • Do: opt.SetName(langId, name); Services.FieldOptions.Save(opt);

If you paste your exact field type (dropdown/radio/etc.) and whether you want to control option IDs (stable IDs across environments) or just let DW generate them, I can tweak the matching strategy (Id vs Value) accordingly—because that detail decides whether your future self will send you angry emails.

Votes for this answer: 1
 
Imar Spaanjaars Dynamicweb Employee
Imar Spaanjaars
Reply

I think the main reason for the failure was that category field options expect the FieldOptionFieldId to be in the format ProductCategory|CategoryId|FieldId.

I set it to FieldId only first but that failed. Now this seems to work:

// Call like this for category fields
var fieldId = $"ProductCategory|{categoryId}|{systemNameOfField}";
SaveFieldOptions(fieldId, options);

// Call like this for product fields / product group fields
SaveFieldOptions(productField.Id, options);

internal static void SaveFieldOptions(string fieldId, Dictionary<string, string> options)
{
  var defaultLanguageId = Dynamicweb.Ecommerce.Services.Languages.GetDefaultLanguageId();
  foreach (var kvp in options)
  {
    var fieldOption = new FieldOption
    {
      Value = kvp.Key,
      FieldId = fieldId,
    };
    fieldOption.SetName(defaultLanguageId, kvp.Value);
    Dynamicweb.Ecommerce.Services.FieldOptions.Save(fieldOption);
  }
}

 

Does that look right to you? I also no longer set the Id and let the Numbering system handle that.

Imar

 

You must be logged in to post in the forum