spmeta2 icon indicating copy to clipboard operation
spmeta2 copied to clipboard

Introduce model parameters support

Open SubPointSupport opened this issue 8 years ago • 6 comments

Why to add model parameters?

With SPMeta2 1.3.xx, it would be nice to introduce model parameters support.

The goal is to provide an ability to set "calculable / replaceable / reusable" parameters across the model. For instance, Fields / Content Type groups is a string. It would be nice to define a StringParameter once, use it as a group property, and then have an ability to change it consistently across the model - both in runtime or after deserialization.

Implicit operator and casting in c# allows to create custom classes which can be safely casted to simple types such string, int, doubles, enums. That makes it possible to support the following syntax:

var def = new FieldDefinition();

def.Group = "Custom Feilds";
// create a new parameter with Name='"Fields Group Name' and Value='Custom Fields'
def.Group = new StringParameter("Fields Group Name", "Custom Fields");

Why not to use String.Replace() or tokens?

We can. However, in that case, String.Replace() wouldn't work well on numbers, enums as well as wouldn't provide additional metadata on parameters - such as Min/Max values, Required, Description and so on. Parameter must be the first class citizen with compile-time check support. We also focused on compile-time checks and refactoring, so that you wouldn't have to copy-paste and struggle with string tokens. You should be able simple reuse parameters with full IDE support.

Other important part is metadata. Parameters should have additional metadata such as Min/Max value, RegExp pattern for validation, Name/Description and other properties. That would enable possibility of creating/auto-creating wizard forms before actual model deployment. Console app can ask for parameters, and UI based tool can generate a form asking for parameters - all of the parameters have metadata such as Name, Description and so on.

Benefits

Such implementation would enable safe parametrization of the models eliminating the need of custom coding. You would still be able to use strings, numbers as well as use parameters as you need.

Other scenarios include end-user's ability to customize your deployment. As model has parameters, you would be able to analyse the model and either use pre-generated user interface or create a custom interface to fill out required parameters. For instance, MetaPack (or other software) would be able to provide a built-in support for such parameters generating a custom form on the fly. All you need is to analyse the model, get all parameters and then build a custom form (desktop/web form) with these parameters.

Potential regression and breaking changes

The following impact is to be investigated and mitigated:

  • Issues withintellisense support
  • Issues with serialization / deserialization of the old models (SPMeta2 has to upgrade the model)
  • Other community concerns if any

As always, your feedback is more than welcomed.

SubPointSupport avatar Apr 06 '17 02:04 SubPointSupport

Great news! Really looking forward to you guys' implementation!

On our side we also had to implement a resource/module file content substitution as well. This allowed us to specify for example a different site logo image to upload by still using the same model.

Alex

sashkode avatar Apr 10 '17 18:04 sashkode

Yes, we are well aware of all these scenarios. It's a technical challenge to solve without introducing no regression over existing models.

How did you implement it right now? That change / replace? Changing the file path or something?

SubPointSupport avatar Apr 11 '17 00:04 SubPointSupport

To be brief, we wrote a wrapper around ModelNode that has parameters and stores the model as JSON. It does the substitution whenever the model is requested.

To simplify, it looks something like this (only supports string substitution and binary content):

public class ParameterizedModel
{
    public Dictionary<string, object> Parameters { get; set; }
    public Dictionary<string, byte[]> Resources { get; set; }

    private string _modelJSON;

    public ModelNode Model
    {
        get
        {
            var substitutedJSON = string.Copy(_modelJSON);

            // Substitute string parameters.
            if (Parameters != null)
            {
                foreach (var parameter in Parameters)
                {
                    substitutedJSON = substitutedJSON.Replace("||" + parameter.Key + "||", parameter.Value.ToString());
                }
            }

            // Substitute binary content
            if (Resources != null)
            {
                foreach (var resource in Resources)
                {
                    var serializedResourceName = Serialization.Serialize(Encoding.UTF8.GetBytes("||" + resource.Key + "||"), SPMeta2Model.KnownTypes);
                    var serializedResourceValue = Serialization.Serialize(resource.Value, SPMeta2Model.KnownTypes);
                    substitutedJSON = substitutedJSON.Replace(serializedResourceName, serializedResourceValue);
                }
            }

            return SPMeta2Model.FromJSON(substitutedJSON);
        }
        set
        {
            _modelJSON = SPMeta2Model.ToJSON(value);
        }
    }
}

public static class ParametrizedModels
{
    public static ParameterizedModel modelExample = new ParameterizedModel()
    {
        Parameters =
        {
            { "Parameter0", "DefaultValue0" },
            { "Parameter1", "DefaultValue1" },
        },
        Resources =
        {
            { "Resource0", Resources.MyResource0 },
            { "Resource1", Resources.MyResource1 }
        },
        Model = SPMeta2Model.NewSiteModel(site =>
        {
            site.AddHostRootWeb(new RootWebDefinition(), rootWeb =>
            {
                rootWeb.AddHostList(BuiltInListDefinitions.StyleLibrary, styleLibrary =>
                {
                    styleLibrary.AddModuleFile(new ModuleFileDefinition()
                    {
                        FileName = "||Parameter0||.txt",
                        Overwrite = true,
                        Content = Encoding.UTF8.GetBytes("||Resource0||")
                    }).AddModuleFile(new ModuleFileDefinition()
                    {
                        FileName = "||Parameter1||.txt",
                        Overwrite = true,
                        Content = Encoding.UTF8.GetBytes("||Resource1||")
                    });
                });
            });
        })
    };

    public static void ModifyAndDeployModel()
    {
        // Change Parameters before deplying model
        modelExample.Parameters["Parameter0"] = "SomeOtherValue";
        modelExample.Resources["Resource0"] = Resources.MyOtherResource;

        // Deploy model
    }
}

sashkode avatar Apr 11 '17 01:04 sashkode

Right, that's a nice one!

String based replacements are exactly what we aren't keen to have in SPMeta2:

FileName = "||Parameter0||.txt",
Content = Encoding.UTF8.GetBytes("||Resource1||")

While it works well for some scenarios, it isn't scalable at a broader sense. Described that in the ticket, hope you see the point. We also see string-based tokens being quite a problem in other libraries so that trying to avoid going the same road.

SubPointSupport avatar Apr 11 '17 01:04 SubPointSupport

Yeah I definitely agree, this isn't pretty, but it helped minimize code duplication and didn't break serialization 😄 Serializing objects that cast to specific types will be tricky. I tried using reflection to have dynamic definitions as well, but that was a mess so we reverted back to simple string substitution.

I wish for you guys to find a simple solution, this will add a lot of value to the framework.

sashkode avatar Apr 11 '17 02:04 sashkode

I await with great interest. Metapack could when packing retrieve and write them in the package manifest. And provide an API to read settings for displaying setup forms prior to deployment.

milleraa avatar Apr 17 '17 03:04 milleraa