We are developing a multisite sitecore solution where each sites can have have their own News as well as able to display the combined news from other Sites.
Problem:
Each site have their unique News requirements where 90% of the template fields matches but rest 10% are different.
For example, Site-A has news template with Authors drop down list where Author List are authored on Configuration Node. Where as Site-B has news template where Author is a FREE TEXT Field.
Therefore, when Glass Mapper automatically tries to Map Authors field it fails for Free Text one.
Solution:
This can be resolved either by creating a Author as drop down on all Sites but Product owners don't want this.
The other solution is manual mapping of news fields from both sources or use AUTOMAP etc.
Desired Solution:
Glassmapper automatically resolves and populates the Author Text Field or Drop Down Field on the fly.
Is above possible?
Thank you.
I would solve this by "fluent configuration", http://glass.lu/Mapper/Sc/Tutorials/Tutorial8.aspx.
Combined with the new Delegate feature added to the Glass Mapper recently.
The Delegate feature was originally introduced and described here: http://cardinalcore.co.uk/2014/07/02/controlling-glass-fields-from-your-own-code/
Nuget package for the Delegate feature: https://www.nuget.org/packages/Cardinal.Glass.Extensions.Mapping/
You can use Infer types as follows:
public interface IBaseNews
{
string Author {get; set;}
//List all other shared fields below
}
[SitecoreType(TemplateId="....", AutoMap = true)]
public class NewsSiteA : IBaseNews
{
[SitecoreField]
public string Author {get; set;}
//List all fields which are unique for SiteA
}
[SitecoreType(TemplateId="....", AutoMap = true)]
public class NewsSiteB : IBaseNews
{
[SitecoreField]
public string Author {get; set;}
//List all fields which are unique for SiteB
}
Now, Your code should be:
IBaseNews newsClass = NewsItem.GlassCast<IBaseNews>(true,true);
//You can use Author property now
Firstly, I would recommend updating to the latest version of Glass for many other reasons including the delegate feature.
From the infer type example in the comment - you shouldn't use GlassCast, use CreateType(Item item) from the sitecore service / context. If you adopt the version with Delegate in, there is now an official Cast(Item item) on the sitecore service instead.
Also the example there uses a would not solve the difference in type. Delegate would make this very easy. Remember with delegate that there is no lazy loading, this shouldn't matter in this case.
public interface INews
{
// All my other fields
string Author { get; set; }
}
The fluent configuration would be something like (to be done in GlassScCustom)
SitecoreType<INews> = new SitecoreType<INews>();
sitecoreType.Delegate(y => y.Author).GetValue(GetAuthor);
fluentConfig.Add(sitecoreType);
private string GetAuthor(SitecoreDataMappingContext arg)
{
Item item = arg.Item;
if(item.TemplateID == <templateid>)
{
// return the value from the drop link
}
return item["Authors"];
}
Related
I am just starting out using SOLR integration with Sitecore 7. I managed to follow some guides and built a "POCO" class (inheriting from SearchResultItem) which allows me to perform the LINQ queries and search data as shown in the sample below:
public class MySearchItem: SearchResultItem
{
[IndexField("Text Field")]
public string TextField
{
get;
set;
}
[IndexField("Drop Link")]
public ID DropLink
{
get;
set;
}
[IndexField("Tree List")]
public IEnumerable<ID> TreeList
{
get;
set;
}
}
When I get to perform a query, using the code below, I am observing the TextField and DropLink properties in the results item to be correctly populated, with the content and ID for TextField and DropLink respectively. The TreeList property is however being retrieved as null. I have checked the obvious and made sure that the hints correctly reflect the field name in the sitecore template, and according to the "Developer's Guide to Item Buckets and Search" document for sitecore 7 IEnumerable is supported automatically.
var index = ContentSearchManager.GetIndex("sitecore_master_index");
using (var context = index.CreateSearchContext())
{
var results = context.GetQueryable<MySearchItem>();
results = results.Where(item => item.TemplateName == "Custom Sitecore Template");
}
The field is located in the indexer since a call to results.First()["TreeList"] seems to show the data I'm after. Would this be the right approach in reading the data?
Furthermore, would it be at all possible to put in other types in my "POCO" class? Let's say I want to query the property of an item within the Tree List. How would I go about implementing this? Am I right in assuming that a TypeConverter for the type of my Tree List would be required for sitecore to correctly resolve the TreeList in a type other than the ID to do something like the below?
[IndexField("Tree List")]
public IEnumerable<TreeListItem> TreeList
{
get;
set;
}
Any help/guidance towards understanding this behavior would be greatly appreciated.
Thanks!
Update
I have filed this as a bug report as suggested in this post. In case anyone comes across this, they confirmed this is an issue and suggested the following workaround:
Add the following lines to the section of the Sitecore.ContentSearch.Solr.Indexes.config file:
<typeMatch typeName="guidCollection" type="System.Collections.Generic.IEnumerable`1[System.Guid]" fieldNameFormat="{0}_sm" multiValued="true" settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
<typeMatch typeName="stringCollection" type="System.Collections.Generic.IEnumerable`1[System.String]" fieldNameFormat="{0}_sm" multiValued="true" settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
<typeMatch typeName="intCollection" type="System.Collections.Generic.IEnumerable`1[System.Int32]" fieldNameFormat="{0}_im" multiValued="true" settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
Hope this helps!
I had the same issue and from inspecting the config file Sitecore.ContentSearch.Solr.Indexes.config it seems that the type is not mapped with the Solr Provider.
This is indeed weird as in the documentation Developer's Guide to Item Buckets and Search, it clearly states that out of the box it should be able to map types of IEnumerable<T>.
Can you please try changing the type of your multilist field from IEnumerable<ID> to List<Guid> instead and check if this solves your issue ?
I've been trying to achieve the following with glass mapper but can't get it to work.
I have a Home Page template which doesn't have any fields itself but inherits the following two templates:
Navigation Template
Fields: Navigation Title
Meta Information Template
Fields: Page Title, Meta Description
I've created the corresponding interfaces / classes as follows:
[SitecoreType(TemplateId = "{5BAB563C-12AD-4398-8C4A-BF623F7DBCDC}", AutoMap = true)]
public interface INavigation
{
[SitecoreField(FieldName = "Navigation Title")]
string NavigationTitle { get; set; }
}
[SitecoreType(TemplateId = "{95539498-31A5-4CB5-8DD6-C422D505C482}", AutoMap = true)]
public interface IMetaInformation
{
[SitecoreField]
string PageTitle { get; set; }
[SitecoreField]
string MetaDescription { get; set; }
}
[SitecoreType(TemplateId = "{F08693E5-8660-4B13-BBD6-7B9DC6091750}", AutoMap = true)]
public class HomePage : INavigation, IMetaInformation
{
public virtual string NavigationTitle { get; set; }
public virtual string PageTitle { get; set; }
public virtual string MetaDescription { get; set; }
}
When I then try accessing my page all attributes are always null:
var context = new SitecoreContext();
var page = context.GetCurrentItem<HomePage>();
I've tried several different approaches to this but nothing works. Also what was described in different tutorials didn't work. The only thing that works is when I add the fields directly on the Home Page template, but I don't want that since I have more than one page type and I therefore want to inherit the fields.
Does anyone have any idea what I'm missing here?! I'm using Sitecore 7 with .NET 4.5 by the way if that makes a difference.
Your fields are not mapped because you use a space in the Fieldname in the Sitecore Template.
Either remove the space or add the attribute [SitecoreField(FieldName ="Page Title")] to the Model.
I think that the Homepage class is trying to map the NavigationTitle on the Homepage template with the fieldName NavigationTitle and ignores the FieldName attribute on the base model.
By the way: I am using only interfaces for the current project I'm working on and it works as expected with inheritance. No need to add a property more then once ;)
Try to set infer type to true. I cannot get it to work at all without having that set.
ex.
item.GlassCast<HomePage>(false, true);
or
context.GetCurrentItem<HomePage>(false, true);
I find it does not work without this set.
You should render the common fields in a separate sublayout as a GlassUserControl.
public partial class NavigationTemplate : GlassUserControl<NavigationTemplate>
{
protected void Page_Load(object sender, EventArgs e)
Here you will have direct access to the NavigationTemplate fields no matter what item you are loading, it will always be cast to NavigationTemplate and will read the field values of the item you are loading.
It seems that you expect to get the properties on the HomePage instance, but you need to ask for the exact interface that contains the property as seen here
I.e.
Instead of doing:
var page = context.GetCurrentItem<HomePage>();
You should explicitly get current item as INavigation and get the field from the interface:
var navigationTitle = context.GetCurrentItem<INavigation>().NavigationTitle;
I am storing cropping information on my Sitecore Media Library images in a field that was added to the /sitecore/templates/System/Media/Unversioned/Image template.
I would like to access this field along with all of the other properties that exist in the Glass.Mapper.Sc.Fields.Image complex field type so that I can continue to use GlassHtml.RenderImage() in my views.
My initial attempts to inherit from the class were unsuccessful - it appears to break the mapping behavior - so I am wondering if there is another way to extend this class with additional properties?
Here's what I've tried:
[SitecoreType(AutoMap = true)]
public class MyImage : Glass.Mapper.Sc.Fields.Image
{
public virtual string CropInfo { get; set; }
}
You will need to implement a custom data handler to map the additional field.
I would create a data handler that inherits from the standard Image data handler:
https://github.com/mikeedwards83/Glass.Mapper/blob/master/Source/Glass.Mapper.Sc/DataMappers/SitecoreFieldImageMapper.cs
Then customise GetField and SetField.
Once you have created the custom data handler you need to register it with the Windsor container. See tutorial 19 for how to do this:
http://glass.lu/docs/tutorial/sitecore/tutorial19/tutorial19.html
The important part:
public static void CastleConfig(IWindsorContainer container){
var config = new Config();
container.Register(
Component.For < AbstractDataMapper>().ImplementedBy<TweetsDataHandler>().LifeStyle.Transient
);
container.Install(new SitecoreInstaller(config));
}
I am trying to learn MVC4 and i've come to this chapter called validation.
I came to know about DataAnnotations and they have pretty neat attributes to do some server side validation. In book they have only explained about [Required] and [Datatype] attribute. However in asp.net website i saw something called ScaffoldColumn and RegularExpression.
Can someone explain what they are, even though I know little what RegularExpression does.
Also are there any other important validation attributes I should know?
Scaffold Column dictates if when adding a view based on that datamodel it should/not scaffold the column. So forexample your model's id field is a good candidate for you to specify ScaffoldColumn(false), and other foreign key fields etc.
I you specify a regular expression, then if you scaffold a new view for that model,edit customer for example, a regex or regular expression on field will enforce that entered data must match that format.
You can read about ScaffoldColumnAttribute Class here
[MetadataType(typeof(ProductMetadata))]
public partial class Product
{
}
public class ProductMetadata
{
[ScaffoldColumn(true)]
public object ProductID;
[ScaffoldColumn(false)]
public object ThumbnailPhotoFileName;
}
And about RegularExpressionAttribute Class you can read here.
using System;
using System.Web.DynamicData;
using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(CustomerMetaData))]
public partial class Customer
{
}
public class CustomerMetaData
{
// Allow up to 40 uppercase and lowercase
// characters. Use custom error.
[RegularExpression(#"^[a-zA-Z''-'\s]{1,40}$",
ErrorMessage = "Characters are not allowed.")]
public object FirstName;
// Allow up to 40 uppercase and lowercase
// characters. Use standard error.
[RegularExpression(#"^[a-zA-Z''-'\s]{1,40}$")]
public object LastName;
}
Given that document databases, such as RavenDB, are non-relational, how do you avoid duplicating data that multiple documents have in common? How do you maintain that data if it's okay to duplicate it?
With a document database you have to duplicate your data to some degree. What that degree is will depend on your system and use cases.
For example if we have a simple blog and user aggregates we could set them up as:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
public class Blog
{
public string Id { get; set; }
public string Title { get; set; }
public class BlogUser
{
public string Id { get; set; }
public string Name { get; set; }
}
}
In this example I have nested a BlogUser class inside the Blog class with the Id and Name properties of the User Aggregate associated with the Blog. I have included these fields as they are the only fields the Blog class is interested in, it doesn't need to know the users username or password when the blog is being displayed.
These nested classes are going to dependant on your systems use cases, so you have to design them carefully, but the general idea is to try and design Aggregates which can be loaded from the database with a single read and they will contain all the data required to display or manipulate them.
This then leads to the question of what happens when the User.Name gets updated.
With most document databases you would have to load all the instances of Blog which belong to the updated User and update the Blog.BlogUser.Name field and save them all back to the database.
Raven is slightly different as it support set functions for updates, so you are able to run a single update against RavenDB which will up date the BlogUser.Name property of the users blogs without you have to load them and update them all individually.
The code for doing the update within RavenDB (the manual way) for all the blog's would be:
public void UpdateBlogUser(User user)
{
var blogs = session.Query<Blog>("blogsByUserId")
.Where(b.BlogUser.Id == user.Id)
.ToList();
foreach(var blog in blogs)
blog.BlogUser.Name == user.Name;
session.SaveChanges()
}
I've added in the SaveChanges just as an example. The RavenDB Client uses the Unit of Work pattern and so this should really happen somewhere outside of this method.
There's no one "right" answer to your question IMHO. It truly depends on how mutable the data you're duplicating is.
Take a look at the RavenDB documentation for lots of answers about document DB design vs. relational, but specifically check out the "Associations Management" section of the Document Structure Design Considerations document. In short, document DBs use the concepts of reference by IDs when they don't want to embed shared data in a document. These IDs are not like FKs, they are entirely up to the application to ensure the integrity of and resolve.