Skip to content

Commit

Permalink
Incorporate constructor mapping into MapAsync(topic, type, …) overload
Browse files Browse the repository at this point in the history
The existing `MapAsync(topic, type, …)` overload, which was first introduced as part of the `[MapAs()]` support (#41, 61de3bf), is the ideal place to handle constructor mapping, as it's the _only_ place where new view models are created. It will continue to first check for a cached object, but if that's not found, it will continue on to initialize a new topic view model. And if that topic view model has (public) constructor parameters, those will be mapped as part of that construction.
  • Loading branch information
JeremyCaney committed Mar 22, 2021
1 parent 963c207 commit 3b34265
Showing 1 changed file with 62 additions and 12 deletions.
74 changes: 62 additions & 12 deletions OnTopic/Mapping/TopicMappingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,33 +165,82 @@ public TopicMappingService(ITopicRepository topicRepository, ITypeLookupService
/*------------------------------------------------------------------------------------------------------------------------
| Handle cached objects
\-----------------------------------------------------------------------------------------------------------------------*/
object? target;
var target = (object?)null;

if (cache.TryGetValue(topic.Id, type, out var cacheEntry)) {
target = cacheEntry.MappedTopic;
if (cacheEntry.GetMissingAssociations(associations) == AssociationTypes.None) {
return target;
}
//Call MapAsync() with target object to map missing attributes
return await MapAsync(topic, target, associations, cache, attributePrefix).ConfigureAwait(false);
}

/*------------------------------------------------------------------------------------------------------------------------
| Instantiate object
| Identify parameters
\-----------------------------------------------------------------------------------------------------------------------*/
else {
var constructorInfo = _typeCache.GetMembers<ConstructorInfo>(type).Where(c => c.IsPublic).FirstOrDefault();
var parameters = constructorInfo?.GetParameters()?? Array.Empty<ParameterInfo>();
var arguments = new object?[parameters.Length];

target = Activator.CreateInstance(type);
/*------------------------------------------------------------------------------------------------------------------------
| Pre-cache entry
>-------------------------------------------------------------------------------------------------------------------------
| In property mapping, we deal with circular references by returning a cached reference. That isn't practical with
| circular references in constructor mapping. To help avoid these, we register a pre-cache entry as IsInitializing, but
| without a mapped object; the TopicMappingCache is expected to throw an exception if an attempt to map that topic to that
| type occurs again prior to the constructor mapping being completed.
\-----------------------------------------------------------------------------------------------------------------------*/
cache.Preregister(topic.Id, type);

Contract.Assume(
target,
$"The target type '{type}' could not be properly constructed, as required to map the topic '{topic.GetUniqueKey()}'."
);
/*------------------------------------------------------------------------------------------------------------------------
| Set parameters
\-----------------------------------------------------------------------------------------------------------------------*/
var parameterQueue = new Dictionary<int, Task<object?>>();

foreach (var parameter in parameters) {
parameterQueue.Add(parameter.Position, GetParameterAsync(topic, associations, parameter, cache, attributePrefix));
}

await Task.WhenAll(parameterQueue.Values).ConfigureAwait(false);

foreach (var parameter in parameterQueue) {
arguments[parameter.Key] = parameter.Value.Result;
}

/*------------------------------------------------------------------------------------------------------------------------
| Initialize object
\-----------------------------------------------------------------------------------------------------------------------*/
target = Activator.CreateInstance(type, arguments);

Contract.Assume(
target,
$"The target type '{type}' could not be properly constructed, as required to map the topic '{topic.GetUniqueKey()}'."
);

/*------------------------------------------------------------------------------------------------------------------------
| Provide mapping
| Cache object
\-----------------------------------------------------------------------------------------------------------------------*/
return await MapAsync(topic, target, associations, cache, attributePrefix).ConfigureAwait(false);
cache.Register(topic.Id, associations, target);

/*------------------------------------------------------------------------------------------------------------------------
| Loop through properties, mapping each one
\-----------------------------------------------------------------------------------------------------------------------*/
var propertyQueue = new List<Task>();
var mappedParameters = parameters.Select(p => p.Name);

foreach (var property in _typeCache.GetMembers<PropertyInfo>(target.GetType())) {
if (!mappedParameters.Contains(property.Name, StringComparer.OrdinalIgnoreCase)) {
propertyQueue.Add(SetPropertyAsync(topic, target, associations, property, cache, attributePrefix, false));
}
}

await Task.WhenAll(propertyQueue.ToArray()).ConfigureAwait(false);

/*------------------------------------------------------------------------------------------------------------------------
| Return target
\-----------------------------------------------------------------------------------------------------------------------*/
return target;

}

Expand Down Expand Up @@ -278,9 +327,10 @@ private async Task<object> MapAsync(
/*------------------------------------------------------------------------------------------------------------------------
| Loop through properties, mapping each one
\-----------------------------------------------------------------------------------------------------------------------*/
var taskQueue = new List<Task>();
var taskQueue = new List<Task>();

foreach (var property in _typeCache.GetMembers<PropertyInfo>(target.GetType())) {
taskQueue.Add(SetPropertyAsync(topic, target, associations, property, cache, attributePrefix, cacheEntry != null));
taskQueue.Add(SetPropertyAsync(topic, target, associations, property, cache, attributePrefix, cacheEntry is not null));
}
await Task.WhenAll(taskQueue.ToArray()).ConfigureAwait(false);

Expand Down

0 comments on commit 3b34265

Please # to comment.