Glass.Mapper и создание элементов Sitecore с использованием интерфейсов

Это был вопрос из Твиттера:

Каков шаблон создания элементов из моделей интерфейса? Использование sitecoreService.Create<T,K>(T newItem, K parent), где T — интерфейс, требует добавления класса для создания новых элементов. Есть ли способ создать их прямо из интерфейса?


person Michael Edwards    schedule 24.09.2013    source источник


Ответы (1)


Это сложная задача, потому что вам нужна конкретная версия вашего интерфейса для записи значений перед их сохранением. Простое решение — использовать фиктивный фреймворк, такой как NSubstitute, со следующим интерфейсом:

    [SitecoreType(TemplateId = "{7FC4F278-ADDA-4683-944C-554D0913CB33}", AutoMap = true)]
    public interface StubInterfaceAutoMapped
    {
        Guid Id { get; set; }

        Language Language { get; set; }

        string Path { get; set; }

        int Version { get; set; }

        string Name { get; set; }

        string StringField { get; set; }
    }

Я могу создать следующий тест:

    [Test]
    public void Create_UsingInterface_CreatesANewItem()
    {
        //Assign
        string parentPath = "/sitecore/content/Tests/SitecoreService/Create";
        string childPath = "/sitecore/content/Tests/SitecoreService/Create/newChild";
        string fieldValue = Guid.NewGuid().ToString();

        var db = Sitecore.Configuration.Factory.GetDatabase("master");
        var context = Context.Create(Utilities.CreateStandardResolver());
        context.Load(new SitecoreAttributeConfigurationLoader("Glass.Mapper.Sc.Integration"));
        var service = new SitecoreService(db);

        using (new SecurityDisabler())
        {
            var parentItem = db.GetItem(parentPath);
            parentItem.DeleteChildren();
        }

        var parent = service.GetItem<StubClass>(parentPath);

        var child = Substitute.For<StubInterfaceAutoMapped>();
        child.Name = "newChild";
        child.StringField = fieldValue;

        //Act
        using (new SecurityDisabler())
        {
            service.Create(parent, child);
        }

        //Assert
        var newItem = db.GetItem(childPath);

        Assert.AreEqual(fieldValue, newItem["StringField"]);

        using (new SecurityDisabler())
        {
            newItem.Delete();
        }

        Assert.AreEqual(child.Name, newItem.Name);
        Assert.AreEqual(child.Id, newItem.ID.Guid);
    }

Это работает из-за того, как Glass.Mapper разрешает отображаемый тип:

    /// <summary>
    /// Gets the type configuration.
    /// </summary>
    /// <param name="obj">The obj.</param>
    /// <returns>AbstractTypeConfiguration.</returns>
    public AbstractTypeConfiguration GetTypeConfiguration(object obj)
    {
        var type = obj.GetType();
        var config = TypeConfigurations.ContainsKey(type) ? TypeConfigurations[type] : null;

        if (config != null) return config;

        //check base type encase of proxy
        config = TypeConfigurations.ContainsKey(type.BaseType) ? TypeConfigurations[type.BaseType] : null;

        if (config != null) return config;

        //check interfaces encase this is an interface proxy
        string name = type.Name;
        //ME - I added the OrderByDescending in response to issue 53
        // raised on the Glass.Sitecore.Mapper project. Longest name should be compared first
        // to get the most specific interface
        var interfaceType = type.GetInterfaces().OrderByDescending(x=>x.Name.Length).FirstOrDefault(x => name.Contains(x.Name));

        if (interfaceType != null)
            config = TypeConfigurations.ContainsKey(interfaceType) ? TypeConfigurations[interfaceType] : null;

        return config;
    }

Обратите внимание, что если он не может найти прямое соответствие типа, он начнет определять тип на основе интерфейсов, связанных с переданным типом, и использует первый найденный на основе имени. Теперь я подозреваю, что NSubstitute работает, потому что он также использует динамические прокси-серверы Castle, было бы интересно протестировать его с другими мок-фреймворками.

person Michael Edwards    schedule 24.09.2013
comment
Спасибо за быстрый ответ и объяснение того, как это работает, особенно зависимость от длины имени интерфейса. Я не уверен, что мне нравится включать инфраструктуру изоляции в основной код, поэтому я, вероятно, создам реализацию интерфейса в классе, который создает элемент, или рядом с ним. Это кажется немного избыточным, но технически все еще СУХИМ, поскольку интерфейс является определением записи. - person Dan Solovay; 24.09.2013
comment
@Michael Edwards Можно ли использовать тот же способ создания элементов в производственном коде? Есть ли лучший способ создать элемент var child = Substitute.For‹StubInterfaceAutoMapped›(); ? У меня есть сомнения по поводу использования NSubstitute... - person Anton; 12.02.2019