I am implementing a validation for a group of fields, in my case one of these three fields is mandatory.
I am implementing a custom data annotation.
My metadata is the following. In my metadata I have put this data annotation which is the one I want to customize.
public class document
{
[RequireAtLeastOneOfGroupAttribute("group1")]
public long? idPersons { get; set; }
[RequireAtLeastOneOfGroupAttribute("group1")]
public int? idComp { get; set; }
[RequireAtLeastOneOfGroupAttribute("group1")]
public byte? idRegister { get; set; }
public bool other1{ get; set; }
.
public string otherx{ get; set; }
}
On the other hand I have created a class called RequireAtLeastOneOfGroupAttribute
with the following code.
[AttributeUsage(AttributeTargets.Property)]
public class RequireAtLeastOneOfGroupAttribute : ValidationAttribute, IClientValidatable
{
public string GroupName { get; private set; }
public RequireAtLeastOneOfGroupAttribute(string groupName)
{
ErrorMessage = string.Format("You must select at least one value from group \"{0}\"", groupName);
GroupName = groupName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
foreach (var property in GetGroupProperties(validationContext.ObjectType))
{
var propertyValue = (bool)property.GetValue(validationContext.ObjectInstance, null);
if (propertyValue)
{
// at least one property is true in this group => the model is valid
return null;
}
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
private IEnumerable<PropertyInfo> GetGroupProperties(Type type)
{
IEnumerable<PropertyInfo> query1 = type.GetProperties().Where(a => a.PropertyType == typeof(int?) || a.PropertyType == typeof(long?) || a.PropertyType == typeof(byte?));
var metadataType = type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().FirstOrDefault();
var metaData = (metadataType != null)
? ModelMetadataProviders.Current.GetMetadataForType(null, metadataType.MetadataClassType)
: ModelMetadataProviders.Current.GetMetadataForType(null, type);
var propertMetaData = metaData.Properties
.Where(e =>
{
var attribute = metaData.ModelType.GetProperty(e.PropertyName)
.GetCustomAttributes(typeof(RequireAtLeastOneOfGroupAttribute), false)
.FirstOrDefault() as RequireAtLeastOneOfGroupAttribute;
return attribute == null || attribute.GroupName == GroupName;
})
.ToList();
RequireAtLeastOneOfGroupAttribute MyAttribute =
(RequireAtLeastOneOfGroupAttribute)Attribute.GetCustomAttribute(type, typeof(RequireAtLeastOneOfGroupAttribute));
if (MyAttribute == null)
{
Console.WriteLine("The attribute was not found.");
}
else
{
// Get the Name value.
Console.WriteLine("The Name Attribute is: {0}.", MyAttribute.GroupName);
}
return consulta1;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var groupProperties = GetGroupProperties(metadata.ContainerType).Select(p => p.Name);
var rule = new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage
};
rule.ValidationType = string.Format("group", GroupName.ToLower());
rule.ValidationParameters["propertynames"] = string.Join(",", groupProperties);
yield return rule;
}
}
The code works correctly when the form is called the GetClientValidationRules function is called.
My problem is in the MyAttribute query because it always returns null.
The GetCustomAttribute function never gets results.
I have tried different variants without success, it is as if that data annotation did not exist.
I just implemented the query with the view part and javascript, which is the following.
jQuery.validator.unobtrusive.adapters.add(
'group',
['propertynames'],
function (options) {
options.rules['group'] = options.params;
options.messages['group'] = options.message;
});
jQuery.validator.addMethod('group', function (value, element, params) {
var properties = params.propertynames.split(',');
var isValid = false;
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
if ($('#' + property).is(':checked')) {
isValid = true;
break;
}
}
return isValid; }, '');
and the view
#Html.TextBoxFor(x=> x.document.idPersons, new { #class= "group1" })
#Html.TextBoxFor(x=> x.document.idComp, new { #class= "group1" })
#Html.TextBoxFor(x=> x.document.idRegister, new { #class= "group1" })
#Html.TextBoxFor(x=> x.document.other1)
#Html.TextBoxFor(x=> x.document.otherx)
Related
I have an EF class that looks like this:
public class Item
public string ItemId{ get; set; }
public string NormalDescription { get; set; }
public string LongDescription { get; set; }
public string ShortDescription { get; set; }
.. <snip>
In addition, I have a DTO that looks like this:
public class ItemDTO
public string Id { get; set; }
public string DisplayName { get; set; }
.. <snip>
Upon loading the data from the 'Item' class into the DTO, I need to conditionally set 'DisplayName' based on a configuration setting. In other words, I'm looking for something similar to:
return _repo.GetAsQueryable<Item>()
.Select(i=> new ItemDTO
{
Id = i.ItemId,
DisplayName = (setting == 1) ? i.NormalDescription :
(setting == 2) ? i.LongDescription :
(setting == 3) ? i.ShortDescription :
String.Empty
}
Of course, this results in some very inefficient SQL (using 'CASE' to evaluate each possible value) being sent to the database. This is a performance issue as there's a TON of description fields on the Item.
That being said, is there a way to select ONLY the field that's required to populate the 'DisplayName' value?
In other words, instead of a query filled with 'CASE WHEN' logic, I'd like to ONLY retrieve one of the Description values based on my application configuration setting.
You should create lambda Expression dynamically:
var typeOfItem = typeof(Item);
var argParam = Expression.Parameter(typeOfItem, "x");
var itemIdProperty = Expression.Property(argParam, "ItemId");
var properties = typeOfItem.GetProperties();
Expression descriptionProperty;
if (setting < properties.Count())
descriptionProperty = Expression.Property(argParam, properties[setting].Name);
else
descriptionProperty = Expression.Constant(string.Empty);
var ItemDTOType = typeof(ItemDTO);
var newInstance = Expression.MemberInit(
Expression.New(ItemDTOType),
new List<MemberBinding>()
{
Expression.Bind(ItemDTOType.GetMember("Id")[0], itemIdProperty),
Expression.Bind(ItemDTOType.GetMember("DisplayName")[0], descriptionProperty),
}
);
var lambda = Expression.Lambda<Func<Item, ItemDTO>>(newInstance, argParam);
return _repo.GetAsQueryable<Item>().Select(lambda);
Something like this?
var repo = _repo.GetAsQueryable<Item>();
if (setting == 1)
{
return repo.Select(i => new ItemDTO
{
Id = i.ItemId,
DisplayName = i.NormalDescription
});
}
if (setting == 2)
{
return repo.Select(i => new ItemDTO
{
Id = i.ItemId,
DisplayName = i.LongDescription
});
}
if (setting == 3)
{
return repo.Select(i => new ItemDTO
{
Id = i.ItemId,
DisplayName = i.ShortDescription
});
}
return repo.Select(i => new ItemDTO
{
Id = i.ItemId,
DisplayName = String.Empty
});
EDIT
You can create the expression dynamically as Slava Utesinov showed. If you do not want to build the whole expression, you can replace just the parts you want:
public class UniRebinder : ExpressionVisitor
{
readonly Func<Expression, Expression> replacement;
UniRebinder(Func<Expression, Expression> replacement)
{
this.replacement = replacement;
}
public static Expression Replace(Expression exp, Func<Expression, Expression> replacement)
{
return new UniRebinder(replacement).Visit(exp);
}
public override Expression Visit(Expression p)
{
return base.Visit(replacement(p));
}
}
Expression<Func<Item, ItemDTO>> ReplaceProperty(
int setting, Expression<Func<Item, ItemDTO>> value)
{
Func<MemberExpression, Expression> SettingSelector(int ss)
{
switch (ss)
{
case 1: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.NormalDescription)));
case 2: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.LongDescription)));
case 3: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.ShortDescription)));
default: return x => Expression.Constant(String.Empty);
}
}
return (Expression<Func<Item, ItemDTO>>)UniRebinder.Replace(
value,
x =>
{
if (x is MemberExpression memberExpr
&& memberExpr.Member.Name == nameof(Item.NormalDescription))
{
return SettingSelector(setting)(memberExpr);
}
return x;
});
}
private void Test()
{
var repo = (new List<Item>() {
new Item() {
ItemId ="1",
LongDescription = "longd1",
NormalDescription = "normald1",
ShortDescription = "shortd1" },
new Item() {
ItemId ="2",
LongDescription = "longd2",
NormalDescription = "normald2",
ShortDescription = "shortd2" }
}).AsQueryable();
for (int selector = 1; selector < 5; ++selector)
{
var tst = repo.Select(ReplaceProperty(selector,
i => new ItemDTO
{
Id = i.ItemId,
DisplayName = i.NormalDescription
})).ToList();
Console.WriteLine(selector + ": " + string.Join(", ", tst.Select(x => x.DisplayName)));
//Output:
//1: normald1, normald2
//2: longd1, longd2
//3: shortd1, shortd2
//4: ,
}
}
I have the following two classes where a user sets preferences. The user may not have any preferences, only likes, only dislikes, or both. I slimmed down the User model for simplicity in this example.
[BsonIgnoreExtraElements]
public class User : Base
{
public string FirstName { get; set; }
public string LastName { get; set; }
[BsonIgnoreIfNull]
public UserPreferences Preferences { get; set; }
}
[BsonIgnoreExtraElements]
public class UserPreferences
{
[BsonIgnoreIfNull]
public List<string> Likes { get; set; }
[BsonIgnoreIfNull]
public List<string> Dislikes { get; set; }
}
I have a helper function which uses reflection to construct an UpdateBuilder. When given a user object it sets a value for non-null fields since I don't want to specifically write out which fields have been updated on a call. However the helper function fails in my current situation.
public override User Update(User model)
{
var builder = Builders<User>.Update.Set(x => x.Id, model.Id);
foreach(PropertyInfo prop in model.GetType().GetProperties())
{
var value = model.GetType().GetProperty(prop.Name).GetValue(model, null);
if ((prop.Name != "Id") & (value != null))
{
builder = builder.Set(prop.Name, value);
}
}
var filter = Builders<User>.Filter;
var filter_def = filter.Eq(x => x.Id, model.Id);
Connection.Update(filter_def, builder);
return model;
}
Problem: When supplying Preferences with only Likes or only Dislikes, it will make the other property null in MongoDB.
Desired Result: I want MongoDB to ignore either the Likes or Dislikes property if the list is null like it does for other properties in my code.
I think the best way is to unset the field if its value is null
public override User Update(User model)
{
var builder = Builders<User>.Update.Set(x => x.Id, model.Id);
foreach(PropertyInfo prop in model.GetType().GetProperties())
{
var value = model.GetType().GetProperty(prop.Name).GetValue(model, null);
if (prop.Name != "Id")
{
if(value != null)
{
builder = builder.Set(prop.Name, value);
}
else
{
builder = builder.Unset(prop.Name);
}
}
}
var filter = Builders<User>.Filter;
var filter_def = filter.Eq(x => x.Id, model.Id);
Connection.Update(filter_def, builder);
return model;
}
I hope this will solve your issue
I used to implement multi field required validation using this link MVC3 Validating Multiple Fields As A Single Property
But it did not work at my end.
Below is the code I used.
Javascript
$.validator.addMethod('multifield', function (value, element, params) {
var properties = params.propertyname.split(',');
var isValid = false;
var count = 0;
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
if ($('#' + property).val() != "") {
count++;
}
}
if (properties.length == count) {
isValid = true;
}
return isValid;
}, '');
$.validator.unobtrusive.adapters.add('multifield', ['propertyname'], function (options) {
options.rules['multifield'] = options.params;
options.messages['multifield'] = options.message;
}
);
Class
public class Customer
{
public string AreaCode { get; set; }
[MultiFieldRequired(new string[2] { "AreaCode", "PhoneNumber" }, ErrorMessage = "Please enter a phone number")]
public string PhoneNumber { get; set; }
}
public class MultiFieldRequiredAttribute : ValidationAttribute, IClientValidatable
{
string propertyName;
private readonly string[] _fields;
public MultiFieldRequiredAttribute(string[] fields)
{
_fields = fields;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
foreach (string field in _fields)
{
propertyName = field;
PropertyInfo property = validationContext.ObjectType.GetProperty(field);
if (property == null)
return new ValidationResult(string.Format("Property '{0}' is undefined.", field));
var fieldValue = property.GetValue(validationContext.ObjectInstance, null);
if (fieldValue == null || String.IsNullOrEmpty(fieldValue.ToString()))
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
// The value we set here are needed by the jQuery adapter
ModelClientValidationRule multifield = new ModelClientValidationRule();
multifield.ErrorMessage = this.ErrorMessage;
multifield.ValidationType = "multifield"; // This is the name the jQuery validator will use
//"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE!
multifield.ValidationParameters.Add("propertyname", string.Join(",", _fields));
yield return multifield;
}
}
View
#using (Html.BeginForm("Add", "Home", FormMethod.Post, new { #class = "form-inline" }))
{
<div class="editor-label">
#Html.LabelFor(model => model.AreaCode)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AreaCode)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PhoneNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PhoneNumber)
#Html.ValidationMessageFor(model => model.PhoneNumber)
</div>
<button type="submit" tabindex="19" class="form-control btn btn-primary" style="float: right; margin-left: 8px; margin-right: 10px;">Submit</button>
}
The answer you linked to and the code you have based it on does not make a lot of sense. Applying a validation attribute to your AreaCode property without including an associated #Html.ValidationMessageFor() is pointless.
If you want a validation message associated with PhoneNumber that will display a Please enter a phone number error if it's empty, and to display a Please also enter an Area Code if its not empty but the AreaCode value is, then you need an attribute that is applied only to the PhoneNumber property, and that attribute needs to accept a parameter for the other property name.
Model
public string AreaCode { get; set; }
[Required(ErrorMessage = "Please enter a phone number")]
[RequiredWith("AreaCode", ErrorMessage = "Please also enter an Area Code")]
public string PhoneNumber { get; set; }
Validation attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class RequiredWithAttribute : ValidationAttribute, IClientValidatable
{
private const string _DefaultErrorMessage = "The {0} is also required.";
private readonly string _OtherPropertyName;
public RequiredWithAttribute(string otherPropertyName)
{
if (string.IsNullOrEmpty(otherPropertyName))
{
throw new ArgumentNullException("otherPropertyName");
}
_OtherPropertyName = otherPropertyName;
ErrorMessage = _DefaultErrorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(_OtherPropertyName);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue == null)
{
return new ValidationResult(string.Format(ErrorMessageString, _OtherPropertyName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(_OtherPropertyName),
ValidationType = "requiredwith",
};
rule.ValidationParameters.Add("dependentproperty", _OtherPropertyName);
yield return rule;
}
}
And add the following scripts
<script type="text/javascript">
// General function to get the associated element
myValidation = {
getDependantProperyID: function (validationElement, dependantProperty) {
if (document.getElementById(dependantProperty)) {
return dependantProperty;
}
var name = validationElement.name;
var index = name.lastIndexOf(".") + 1;
dependantProperty = (name.substr(0, index) + dependantProperty).replace(/[\.\[\]]/g, "_");
if (document.getElementById(dependantProperty)) {
return dependantProperty;
}
return null;
}
}
$.validator.addMethod("requiredwith", function (value, element, params) {
var dependantControl = $('#' + params.dependentproperty);
return dependantControl.val() !== '';
});
$.validator.unobtrusive.adapters.add("requiredwith", ["dependentproperty"], function (options) {
var element = options.element;
var dependentproperty = options.params.dependentproperty;
dependentproperty = myValidation.getDependantProperyID(element, dependentproperty);
options.rules['requiredwith'] = {
dependentproperty: dependentproperty
};
options.messages['requiredwith'] = options.message;
});
</script>
If I m not wrong MultiFieldRequired is used above class name which was explain in below stackoverflow link earlier:
MVC3 Validating Multiple Fields As A Single Property
You can also try below method in model.Also
Add namespace in
model
=====================
using System.Web.Mvc;
=====================
public class Customer
{
//...other fields here
[Remote("ValidateAreaPhoneNumber","Home",AdditionalFields="PhoneNumber",ErrorMessage="Please enter a phone number")]
public string AreaCode { get; set; }
public string PhoneNumber { get; set; }
}
You may write the method named
ValidateAreaPhoneNumber
in ur
Home Controller
as below:
public JsonResult ValidateAreaPhoneNumber(string PhoneNumber)
{
//True:False--- action that implement to check PhoneNumber uniqueness
return false;//Always return false to display error message
}
How to generate JSON of Class meta data.
for eg.
C# Classes
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public Description Description { get; set; }
}
public class Description
{
public string Content { get; set; }
public string ShortContent { get; set; }
}
JSON
[
{
"PropertyName" : "Id",
"Type" : "Int",
"IsPrimitive" : true
},
{
"PropertyName" : "Name",
"Type" : "string",
"IsPrimitive" : true
},
{
"PropertyName" : "IsActive",
"Type" : "bool",
"IsPrimitive" : true
},
{
"PropertyName" : "Description",
"Type" : "Description",
"IsPrimitive" : false
"Properties" : {
{
"PropertyName" : "Content",
"Type" : "string",
"IsPrimitive" : true
},
{
"PropertyName" : "ShortContent",
"Type" : "string",
"IsPrimitive" : true
}
}
},
]
If you define a class that will map your Json Model:
public class PropertyDescription
{
public string PropertyName { get; set; }
public string Type { get; set; }
public bool IsPrimitive { get; set; }
public IEnumerable<PropertyDescription> Properties { get; set; }
}
And then just create a function that read the properties of your object recursively:
public static List<PropertyDescription> ReadObject(Type type)
{
var propertyDescriptions = new List<PropertyDescription>();
foreach (var propertyInfo in type.GetProperties())
{
var propertyDescription = new PropertyDescription
{
PropertyName = propertyInfo.Name,
Type = propertyInfo.PropertyType.Name
};
if (!propertyDescription.IsPrimitive
// String is not a primitive type
&& propertyInfo.PropertyType != typeof (string))
{
propertyDescription.IsPrimitive = false;
propertyDescription.Properties = ReadObject(propertyInfo.PropertyType);
}
else
{
propertyDescription.IsPrimitive = true;
}
propertyDescriptions.Add(propertyDescription);
}
return propertyDescriptions;
}
You can use Json.Net to serialize the result of this function :
var result = ReadObject(typeof(Product));
var json = JsonConvert.SerializeObject(result);
EDIT: Linq solution based on #AmitKumarGhosh answer:
public static IEnumerable<object> ReadType(Type type)
{
return type.GetProperties().Select(a => new
{
PropertyName = a.Name,
Type = a.PropertyType.Name,
IsPrimitive = a.PropertyType.IsPrimitive && a.PropertyType != typeof (string),
Properties = (a.PropertyType.IsPrimitive && a.PropertyType != typeof(string)) ? null : ReadType(a.PropertyType)
}).ToList();
}
...
var result = ReadType(typeof(Product));
json = JsonConvert.SerializeObject(result);
One probable solution -
static void Main(string[] args)
{
var o = typeof(Product).GetProperties().Select(a =>
{
if (a.PropertyType != null && (a.PropertyType.IsPrimitive || a.PropertyType == typeof(string)))
{
return MapType(a);
}
else
{
dynamic p = null;
var t = MapType(a);
var props = a.PropertyType.GetProperties();
if (props != null)
{ p = new { t, Properties = props.Select(MapType).ToList() }; }
return new { p.t.PropertyName, p.t.Type, p.t.IsPrimitive, p.Properties };
}
}).ToList();
var jsonString = JsonConvert.SerializeObject(o);
}
static dynamic MapType(PropertyInfo a)
{
return new
{
PropertyName = a.Name,
Type = a.PropertyType.Name,
IsPrimitive = a.PropertyType != null && a.PropertyType.IsPrimitive
};
}
Try this, concept is get all elements from object to dictionary. Field name and value. For each property create additional elements (using Reflection) in dictionary like Type, IsPrimitive etc. You can use recursion for going throw properties and then serialize this dictionary to JSON.
An example here:
Appending to JSON object using JSON.net
An example of this:
var serialize = new Newtonsoft.Json.JsonSerializer();
var dict = GetDic(new Description());
serialize.Serialize(sr, dict);
And GetDcit implementation:
private List<Dictionary<string, string>> GetDic(object obj)
{
var result= new List<Dictionary<string, string>>();
foreach (var r in obj.GetType().GetProperties())
{
result.Add(new Dictionary<string, string>
{
["PropertyName"] = r.Name,
["Type"] = r.PropertyType.Name,
["IsPrimitive"] = r.GetType().IsPrimitive.ToString(),
});
}
return result;
}
For the code:
strMenuText.Append(RenderLink(mainlinkitem,
x => x.NavigationItem.Url.StringToLink(),
isEditable: true,
contents: mainlinkitem.NavigationTitle));
Here mainlinkitem is Navigation object for interface created for data template.
I am using interfaces in this case and castle windsor creates dynamic proxy objects for this.
Things work ok until I try to use Page editor mode and below error shows up from glass mapper api.
Expression doesn't evaluate to a member
x.NavigationItem.Url.StringToLink() at
Glass.Mapper.Sc.GlassHtml.MakeEditable[T](Expression1 field,
Expression1 standardOutput, T model, String parameters, Context
context, Database database, TextWriter writer)
Note: StringToLink is extension method for converting external url in string form to Glass mapper Glass.Mapper.Sc.Fields.Link type.
public static Link StringToLink(this string urlvalue)
{
Link itemLink = new Link();
itemLink.Url = urlvalue;
return itemLink;
}
UPDATE
Code for menu user control:
public partial class MenuControl : GlassUserControl<INavigationFolder>
{
protected override void GetModel()
{
base.GetModel();
SiteLevelSettings siteSettings = SitecoreContext.GetItem<SiteLevelSettings>(Guid.Parse("Some GUID"));
Model = siteSettings.HeaderMenuFolder;
}
protected void Page_Load()
{
if (!Page.IsPostBack)
{
LoadMenu();
}
}
private void LoadMenu()
{
StringBuilder strMenuText = new StringBuilder();
foreach (INavigationLink mainlinkitem in Model.ChildLinks)
{
if (CanRead(mainlinkitem))
{
strMenuText.Append("<td class='menu-item'>");
if (mainlinkitem.ChildLinks != null && mainlinkitem.ChildLinks.Count() > 0)
{
strMenuText.Append("<ul>");
foreach (INavigationLink linkitem in mainlinkitem.ChildLinks)
{
if (CanRead(linkitem))
{
strMenuText.Append("<li>");
if (linkitem.NavigationItem != null)
{
strMenuText.Append(RenderLink(linkitem, x => x.NavigationItem.Url.StringToLink(), isEditable: false, contents: linkitem.NavigationTitle));
}
else if (linkitem.NavigationGeneralLink != null)
{
strMenuText.Append(RenderLink(linkitem, x => x.NavigationGeneralLink, isEditable: false, contents: linkitem.NavigationTitle));
}
strMenuText.Append("</li>");
}
}
strMenuText.Append("</ul>");
}
strMenuText.Append("<div class='nav-divider'>");
if (mainlinkitem.NavigationItem != null)
{
strMenuText.Append(RenderLink(mainlinkitem, x => x.NavigationItem.Url.StringToLink(), isEditable: false, contents: mainlinkitem.NavigationTitle));
}
else if (mainlinkitem.NavigationGeneralLink != null)
{
strMenuText.Append(RenderLink(mainlinkitem, x => x.NavigationGeneralLink, isEditable: true, contents: mainlinkitem.NavigationTitle));
}
strMenuText.Append("</div></td>");
}
}
ltrMenu.Text = strMenuText.ToString();
}
private bool CanRead(IItem mainlinkitem)
{
var ItemId = mainlinkitem.TemplateId;
var ItemIDObj = new Sitecore.Data.ID(ItemId);
var contentdatabase = Sitecore.Context.Database;
var item = contentdatabase.GetItem(ItemIDObj);
return item.Access.CanRead();
}
}
Navigation Folder interface for glass mapper:
[SitecoreType(TemplateId = "{Some GUID}")]
public interface INavigationFolder : IItem
{
[SitecoreChildren(IsLazy = false)]
IEnumerable<INavigationLink> ChildLinks { get; set; }
}
Navigation Link interface for glass mapper:
[SitecoreType(TemplateId = "{Some GUID}")]
public interface INavigationLink : IItem
{
[SitecoreField(FieldId = "{Some GUID}")]
string NavigationTitle { get; set; }
[SitecoreField(FieldId = "{Some GUID}")]
IItem NavigationItem { get; set; }
[SitecoreField(FieldId = "{Some GUID}")]
Link NavigationGeneralLink { get; set; }
[SitecoreField(FieldId = "{Some GUID}")]
string ShortDescription { get; set; }
[SitecoreChildren(IsLazy = false)]
IEnumerable<INavigationLink> ChildLinks { get; set; }
}
Note: This will code will generate menu similar to sitecore site
UPDATE
Url property in interface IItem is defined as follows:
[SitecoreType(TemplateId = "{Some GUID}")]
public interface IItem
{
[SitecoreId()]
Guid ID { get; }
[SitecoreInfo(Type = SitecoreInfoType.Language)]
Language Language { get; }
[SitecoreInfo(Type = SitecoreInfoType.Version)]
int Version { get; }
[SitecoreInfo(Type = SitecoreInfoType.Url)]
string Url { get; }
[SitecoreInfo(Type = SitecoreInfoType.TemplateId)]
Guid TemplateId { get; }
[SitecoreInfo(Type = SitecoreInfoType.Key)]
string Key { get; }
}
The second expression in the RenderLink methods should resolve to the property that represents the field you want to be editable in the Page Editor, e.g.:
RenderLink(linkitem, x => x.NavigationItem.Url, isEditable: false, contents: linkitem.NavigationTitle));
When you add the additional method call to the end of the expression Glass.Mapper cannot evaluate which field to make editable correctly.
Instead if you want to do something like this you should probably use an if statement to switch between the two renderings:
if (IsInEditingMode)
{
strMenuText.Append(RenderLink(
linkitem,
x => x.NavigationItem.Url
isEditable: false,
contents: linkitem.NavigationTitle));
}
else
{
strMenuText.Append(RenderLink(
linkitem,
x => x.NavigationItem.Url.StringToLink(),
isEditable: false,
contents: linkitem.NavigationTitle));
}
However I haven't tested this, instead you should update your property to the Link field type and it will automatically map it:
[SitecoreField]
public virtual Glass.Mapper.Sc.Fields.Link Url{get;set;}
You can then update the menu code to:
strMenuText.Append(RenderLink(
linkitem,
x => x.NavigationItem.Url
isEditable: false,
contents: linkitem.NavigationTitle));
Extension methods are not "true" extensions of a class, they are resolved at compile time. Without seeing the rest of your code, pointing out how your code can be rewritten to work around this is not easy. I suggest something like:
public static Link MakeLink(string urlvalue)
{
Link itemLink = new Link();
itemLink.Url = urlvalue;
return itemLink;
}
And then in your calling code
strMenuText.Append(RenderLink(mainlinkitem,
x => MakeLink( x.NavigationItem.Url ),
isEditable: true,
contents: mainlinkitem.NavigationTitle));