Reputation: 6528
This was a question from Twitter:
What is the pattern for creating items from interface models? Using sitecoreService.Create<T,K>(T newItem, K parent)
where T
is an interface, requires adding a class to create new items. Is there a way to create them directly from the interface?
Upvotes: 0
Views: 4920
Reputation: 6528
This is a challenge because you need a concrete version of your interface to write values to before saving them. The simple solution is to use a mocking framework like NSubstitute, uisng the following interface:
[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; }
}
I can create the following test:
[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);
}
This works because of the way that Glass.Mapper resolves the type to be mapped:
/// <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;
}
Notice that if it can't find a direct type match it will start to determine the type based on the interfaces associated to the passed in type and use the first it finds base on the name. Now I suspect that NSubstitute works because it also uses Castle Dynamic Proxies, it would be interesting to test it with other mocking frameworks.
Upvotes: 4