Firstable I love what you've done with the Umbraco Mapper. This is exactly I was looking for. However does this work with the Archetype?
What I’m doing is I'm hijacking the routes (MVC Request) and inserting the CMS values into my own ViewModel. It is all working good till when I get to the ArcheType object. (I got it working but I have to convert the ArchetypeModel object to the Dictionary<string, object>, then call mapper.MapCollection)
Is there any easier ways of doing this?
The web site I'm building is 1 pagers app and it is 1 long page. So I have about 15 set of ArcheType objects. I prefer not to convert ArcheType object to Dictionary<string, object> for all 15 of them. Preferably I can just convert everything with Mapper.map method.
Haven't yet had chance to use Archetype, but we're starting a project with it as we speak so it's likely we'll have to solve this issue ourselves shortly! So I'll look into it in a bit more detail when I get a little bit of time and post back here.
However I suspect the best way will be look into the custom mappings you can add: https://github.com/AndyButland/UmbracoMapper#custom-mappings - the intention of this is to handle the mapping too more complex types on your view model other than just primitives like strings and ints. So in the example linked, there's a custom mapping for a GeoCoordinate complex type that's associated with the view model, that is mapped using a given function.
Would you mind sharing your view model class though so I can see what you are looking to map too?
Thanks Raffaele - should just add we tend to put the instantantion and the set up of custom mappings of Umbraco Mapper in the base controller, and expose it as a property. That way as Raffaele says much of this plumbing code can be tucked away and the setup will be the same wherever it's used around the application.
As mentioned have given this a little more thought, and basically I think what's suggested above is probably the way to go - however I think we can tidy it up a bit and avoid creating that additional class to hold the items (using instead an IEnumerable). Rather than posting more code into this thread, I've written it up here.
Just a quick update on my mapper class. I made the mapper class more generic so different archetype object can use the same mapping class, but also allows you to extend it.
//Base Controller
Mapper = new UmbracoMapper();
Mapper.AddCustomMapping(typeof(IEnumerable<CtaFooter>).FullName, ArchetypeMapper.GenericArchetypeMapper<CtaFooter>);
//ArchetypeMapper
public class ArchetypeMapper
{
public static IEnumerable<T> GenericArchetypeMapper<T>(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propertyName, bool recursive) where T : class, new()
{
var result = ConvertToDto<T>(mapper, contentToMapFrom, propertyName, recursive);
return result;
}
public static IEnumerable<T> SomeOtherArchetypeMapper<T>(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propertyName, bool recursive) where T : class, new()
{
var result = ConvertToDto<T>(mapper, contentToMapFrom, propertyName, recursive);
//can also do extra mapping / logic here, or can pass in PropertyMapping dictionary to extend it
return result;
}
private static IEnumerable<T> ConvertToDto<T>(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propertyName, bool recursive) where T : class, new()
{
var result = new List<T>();
var archetypeObject = contentToMapFrom.GetPropertyValue<ArchetypeModel>(propertyName, recursive);
if (archetypeObject != null)
{
var dictionary = archetypeObject.Select(item => item.Properties.ToDictionary(m => StringHelper.FirstCharToUpper(m.Alias), m => m.Value)).ToList();
mapper.MapCollection(dictionary, result);
}
return result;
}
}
Yes, we set up a similar custom mapping to a complex type on our view models called something like MediaFile - with properties for URL, width, height etc.
There's an example for the DAMP model as you say in the related project on GitHub. That's good for V6 but for V7 you'll probably not be using that. It's the same principle though - set up a custom mapping for the type on your view model, and pass a function you implement that maps the media properties you need from Umbraco.TypedMedia.
I've just updated the Umbraco Mapper to the latest and trying to map the media Picker inside the Archetype and I'm getting an exception.
Line 77: throw new ArgumentNullException("model", "Object to map to cannot be null");
Source File: c:\Development\Reference\Github\UmbracoMapper\Zone.UmbracoMapper\UmbracoMapper.cs Line: 77
So I debugged the code and the property.GetValue(model) was returning null @ UmbracoMapper.cs (line 248 ish)
if (dictionary[propName] is IPublishedContent)
{
Map((IPublishedContent)dictionary[propName], property.GetValue(model), propertyMappings);
}
It works if i remove the
property.GetValue()
I'm mapping the property into "Zone.UmbracoMapper.MediaFile" , not a string
Also "propertyMappings" property is null.
My code looks the same from your Blog, but i added "Umbraco.MediaPicker"
and getting mapped into MediaFile
switch (archetypeProperty.PropertyEditorAlias)
{
case "Umbraco.ContentPickerAlias":
case "Umbraco.MediaPicker":
return archetypeProperty.GetValue<IPublishedContent>();
break;
Thank you
Shinsuke
//EDIT
Sorry i just looked at your BLOG again and I found one more difference.
I have MediaFile property object in the sub class. so in your example, it will look like this
public class MatchReportTeaser
{
public string Name { get; set; }
public string Url { get; set; }
public MediaFile Image {get; set;}
}
The exception you are seeing is a custom one being thrown by the mapper if the object you are looking to map to is null - it can't handle that. We found issues with trying to to new it up in the mapper if it was null.
I found we couldn't do this:
// Ensure model is not null
if (model == null)
{
model = new T();
}
Gives compiler error as we can't know for sure that T has a parameterless constructor.
So instead we just do this:
// Ensure model is not null
if (model == null)
{
throw new ArgumentNullException("model", "Object to map to cannot be null");
}
---
The way we work around it is to ensure the object or collection you are mapping to is not null, by doing something like this in our view models:
public class MatchReportTeaser
{
public MatchReportTeaser()
{
Image = new MediaFile();
}
public string Name { get; set; }
public string Url { get; set; }
public MediaFile Image { get; set; }
}
The only issue with that then is of course you cannot test for null say in your view, if you want to determine whether or not you have an image file to render. Instead you need to check say if the Url property of the media file is populated.
I don't know how i missed that (Implementing constructor on the model object).
But that worked :)
I don't mind implementing like this, purpose of the controller in this instance is to convert the Umbraco object to my custom View Model. Which is exactly what this is doing :)
I just need to implement something like this in the Model
public bool HasImage
{
get { return !(String.IsNullOrEmpty(this.Url)); }
}
public static object ImageMapper(IUmbracoMapper mapper, object value)
{
var mediaIdStr = value as string;
if (mediaIdStr == null) return new Image();
var mediaId = int.Parse(mediaIdStr);
var image = ModelCache.GetCachedModel(mediaId);
if (image != null) return image;
// Map an Image from an IPublishedContent in MediaCache
IPublishedContent mediaContent = UmbracoContext.Current.MediaCache.GetById(mediaId); if (mediaContent == null || mediaContent.DocumentTypeAlias != "Image")
{
return new Image();
}
image = new Image();
image.Crops.AddRange(new[]
{
new ImageCrop("thumb", mediaContent.GetCropUrl("thumb")),
new ImageCrop("pod", mediaContent.GetCropUrl("pod")),
new ImageCrop("hero", mediaContent.GetCropUrl("hero"))
});
mapper.Map(mediaContent, image);
if (String.IsNullOrEmpty(image.AltText))
{
image.AltText = image.Name.ToLower().Replace(" ", "-");
}
image.Cache(mediaContent.Id);
return image;
}
UmbracoMapper and ArcheType
Hi guys,
Firstable I love what you've done with the Umbraco Mapper. This is exactly I was looking for.
However does this work with the Archetype?
What I’m doing is I'm hijacking the routes (MVC Request) and inserting the CMS values into my own ViewModel.
It is all working good till when I get to the ArcheType object. (I got it working but I have to convert the ArchetypeModel object to the Dictionary<string, object>, then call mapper.MapCollection)
Is there any easier ways of doing this?
The web site I'm building is 1 pagers app and it is 1 long page. So I have about 15 set of ArcheType objects. I prefer not to convert ArcheType object to Dictionary<string, object> for all 15 of them.
Preferably I can just convert everything with Mapper.map method.
Thank you
I haven't used UmbracoMapper yet, but when you use the Models Builder it will return a strongly typed archetype model because it goes through the property editor value converter.
Jeroen
Hi Shinsuke
Haven't yet had chance to use Archetype, but we're starting a project with it as we speak so it's likely we'll have to solve this issue ourselves shortly! So I'll look into it in a bit more detail when I get a little bit of time and post back here.
However I suspect the best way will be look into the custom mappings you can add: https://github.com/AndyButland/UmbracoMapper#custom-mappings - the intention of this is to handle the mapping too more complex types on your view model other than just primitives like strings and ints. So in the example linked, there's a custom mapping for a GeoCoordinate complex type that's associated with the view model, that is mapped using a given function.
Would you mind sharing your view model class though so I can see what you are looking to map too?
Cheers
Andy
Hi guys,
Thank you for your reply.
//Jeroen,
thank you, I'll look into this "Models Builder", it sounds interesting. I'm quite new in Umbraco so i'm still trying out alot of things.
//Andy
Thank you for the link, I was reading this but i wasn't sure this was the best way, but let me have a go.
Mean while, this is how my ViewModel and Controller code look like. It's still under the prototype stage so It's not too complex yet
public class HomepageViewModel
{
public string FeatureImageUrl { get; set; }
public string HomepageTitle { get; set; }
public string HomepageDescription { get; set; }
public IList<CtaType1> CtaModule { get; set; }
public IList<CtaFooter> FooterCta1 { get; set; }
public IList<CtaFooter> FooterCta2 { get; set; }
}
public class CtaFooter
{
public string Title { get; set; }
public string ButtonText { get; set; }
public string Link { get; set; }
public string Image { get; set; }
}
public class CtaType1
{
public string ButtonText { get; set; }
public string Link { get; set; }
}
//Controller code
var footer1 = ArcheTypeToDictionary(model.Content, "footerCta1");
var footer2 = ArcheTypeToDictionary(model.Content, "footerCta2");
var ctaModule = ArcheTypeToDictionary(model.Content, "ctaModule");
var mapper = new UmbracoMapper();
var model1 = new HomepageViewModel()
{
FooterCta1 = new List<CtaFooter>(),
FooterCta2 = new List<CtaFooter>(),
CtaModule = new List<CtaType1>()
};
mapper.Map(CurrentPage, model1);
mapper.MapCollection(footer1, model1.FooterCta1);
mapper.MapCollection(footer2, model1.FooterCta2);
mapper.MapCollection(ctaModule, model1.CtaModule);
//Base Controller class
protected IList<Dictionary<string, object>> ArcheTypeToDictionary(IPublishedContent content, string alias)
{
var archetypeObject = content.GetPropertyValue<ArchetypeModel>(alias);
return archetypeObject.Select(item => item.Properties.ToDictionary(m => StringHelper.FirstCharToUpper(m.Alias), m => m.Value)).ToList();
}
Hi Shinsuke,
as a follow up to Andy's answer (the project we're working on combines umbracoMapper and archetypes), this is what I would do:
0. Define a new class
and change FooterCta1 and FooterCta2 in HomepageViewModel to
public CtaFooterList FooterCta1 { get; set; } public CtaFooterList FooterCta2 { get; set; }
1. After the mapper initialisation
add the "AddCustomMapping" method
with
(Do the same for CtaType1)
2.(I haven't tested the code, but now) When you type
mapper.Map(CurrentPage, model1);
model1.FooterCta1, model1.FooterCta2, model1.CtaModule should get automatically mapped
In terms of code lentgh it's probably the same as your solution. but if you use custom mapping your controllers get much tidier!
Raffaele
Thanks Raffaele - should just add we tend to put the instantantion and the set up of custom mappings of Umbraco Mapper in the base controller, and expose it as a property. That way as Raffaele says much of this plumbing code can be tucked away and the setup will be the same wherever it's used around the application.
Andy
As mentioned have given this a little more thought, and basically I think what's suggested above is probably the way to go - however I think we can tidy it up a bit and avoid creating that additional class to hold the items (using instead an IEnumerable). Rather than posting more code into this thread, I've written it up here.
Andy
Thank you guys for the feedbacks and solutions. I will definitely try out the CustomMapping today.
//Andy Very nice article :)
Hi Guys,
Just a quick update on my mapper class. I made the mapper class more generic so different archetype object can use the same mapping class, but also allows you to extend it.
//Base Controller
//ArchetypeMapper
Cheers
Shinsuke
Thanks for sharing that, it's a better approach to make it generic as you say.
Just wanted to add that I found a way to support picked content in the Archetypes too now, which the just updated version of the mapper package will support. Details here: http://web-matters.blogspot.it/2014/08/using-umbraco-mapper-with-archetype_8.html
Cheers
Andy
Thank you Andy for the update,
This was actual my next step. But dealing with "Media Picker".
I was going to use "Umbraco.Media()" then map the values.
Have you guys done this already? i saw something similar in the Damp mapper class.
Yes, we set up a similar custom mapping to a complex type on our view models called something like MediaFile - with properties for URL, width, height etc.
There's an example for the DAMP model as you say in the related project on GitHub. That's good for V6 but for V7 you'll probably not be using that. It's the same principle though - set up a custom mapping for the type on your view model, and pass a function you implement that maps the media properties you need from Umbraco.TypedMedia.
Andy
Thank you Andy for the advice, I created a simple mapper that maps Umbraco.Media(id) object to the MediaFile Object.
It seems like its all working.
Cheers
Shinsuke
Hi Andy,
I've just updated the Umbraco Mapper to the latest and trying to map the media Picker inside the Archetype and I'm getting an exception.
So I debugged the code and the property.GetValue(model) was returning null @ UmbracoMapper.cs (line 248 ish)
It works if i remove the
I'm mapping the property into "Zone.UmbracoMapper.MediaFile" , not a string
Also "propertyMappings" property is null.
My code looks the same from your Blog, but i added "Umbraco.MediaPicker" and getting mapped into MediaFile
Thank you
Shinsuke
//EDIT
Sorry i just looked at your BLOG again and I found one more difference.
I have MediaFile property object in the sub class. so in your example, it will look like this
Hi Shinsuke
The exception you are seeing is a custom one being thrown by the mapper if the object you are looking to map to is null - it can't handle that. We found issues with trying to to new it up in the mapper if it was null.
I found we couldn't do this:
Gives compiler error as we can't know for sure that T has a parameterless constructor.
So instead we just do this:
---
The way we work around it is to ensure the object or collection you are mapping to is not null, by doing something like this in our view models:
The only issue with that then is of course you cannot test for null say in your view, if you want to determine whether or not you have an image file to render. Instead you need to check say if the Url property of the media file is populated.
Andy
Ooooh Thank you very much,
I don't know how i missed that (Implementing constructor on the model object).
But that worked :)
I don't mind implementing like this, purpose of the controller in this instance is to convert the Umbraco object to my custom View Model. Which is exactly what this is doing :)
I just need to implement something like this in the Model
To make the View if Condition easier.
Thank you very much for your help.
Your Umbraco Mapper is awesome.
We use a custom Image model that has a crops list attached. Here are the mappers we use.
Second overload can be called directly when mapping media items used in an Archetype
is working on a reply...