Sitecore SearchResulItem - fluent API? - c#

In Sitecore, how can I define index field names on derived SearchResultItem class' properties other than [IndexField] attribute?
I'm trying to use the same interfaces I use for Glass.Mapper model definitions, and it already contains [SitecoreField] attribute on properties which define the Sitecore field name (and therefore the index field name).
Thank you!

I would check out this project:
https://github.com/cardinal252/Lucinq.Sitecore
It links Glass.Mapper and the Lucene index.
Mike

I don't think thats possible, since 'IndexField' and 'SitecoreField' represent different things, 'IndexField' attribute sets the name of the index field, for example, 'IndexField("_id")', 'IndexField("_language")' represent the id and language fields names inside lucene document.
Now, Sitecore by default, store the index names of all fields by it's name in Sitecore, for example, a field called 'Content' will be stored inside lucene document as 'content' by default. which you can change to something else if you want.
SitecoreField attribute represent the actual name of a field for an item, so that glass can map that field value into the property.
Bottom line, you just need to specify IndexField and SitecoreField on each property in your class, since each attribute works differently

I believe you could change Sitecore Glass Mapper SitecoreFieldAttribute to accomplish that.
You could implement the interface IIndexFieldNameFormatterAttribute on the Glass MapperSitecoreFieldAttribute.
This interface you will find in the Sitecore.ContentSearch.Linq.dll and it looks like that:
namespace Sitecore.ContentSearch
{
public interface IIndexFieldNameFormatterAttribute : _Attribute
{
string GetIndexFieldName(string fieldName);
string GetTypeFieldName(string fieldName);
}
}
Your implementation would be that, I pasted only the interface methods here.
namespace Glass.Sitecore.Mapper.Configuration.Attributes
{
/// <summary>
/// Used to populate the property with data from a Sitecore field
/// </summary>
public class SitecoreFieldAttribute: AbstractSitecorePropertyAttribute, IIndexFieldNameFormatterAttribute
{
public string GetIndexFieldName(string fieldName)
{
return this.FieldName;
}
public string GetTypeFieldName(string fieldName)
{
return fieldName;
}
I haven`t test it, but as I could see Sitecore Linq rely on this interface to find the fields names. You can investigate it yourself, but here is the piece of code which made me deduce that:
var variable = (from p in (IEnumerable<PropertyInfo>)typeof(TItem).GetProperties()
select new { Property = p, Attribute = (IIndexFieldNameFormatterAttribute)p.GetCustomAttributes().FirstOrDefault<Attribute>((Attribute a) => a is IIndexFieldNameFormatterAttribute) }).FirstOrDefault((p) => p.Attribute != null);
if (variable != null && variable.Attribute.GetIndexFieldName(variable.Property.Name) == this.FieldName)
{
property = variable.Property;
}
Hope it helps..

Related

Nested binding in ASP.NET MVC Razor

Description
A nested object needs to be bound to a dropdown, there already is a preselected value for the nested objects. The possible values are of an enum type. The dropdownlist with some other data will be posted back to the controller.
Code - types & classes:
[Serializable]
public enum DummyEnum
{
DummyZero = 0,
DummyOne = 1
}
public class Dummy
{
public Guid Id { get; set; }
public Dictionary<Guid, DummyEnum> DummyEnum { get; set; }
}
public class DummyViewModel
{
public Dictionary<Guid, List<Dummy>> Dummies { get; set; }
}
public class DummyController
{
private void Init(DummyViewModel model)
{
model.EnumList = Enum.GetValues(typeof(DummyEnum))
.Cast<DummyEnum>()
.Select(e => new SelectListItem
{
Value = (e).ToString(),
Text = e.ToString()
});
}
}
HTML:
<td>
#Html.DropDownListFor(
m => m.Dummies[dummiesKey][dummyIndex]
.Enum[Id],
new SelectList(Model.EnumList, "Value", "Text", e.ToString()))
</td>
<select
data-val="true"
data-val-required="The Enum field is required."
id="Dummies_guid__0__Enum_guid_"
name="Dummies[guid][0].Enum[guid]"
style="display: none;"
>
<option value="DummyOne">DummyOne</option>
<option selected="selected" value="DummyZero ">DummyZero</option>
</select>
Problem
The problem is that the model doesn't seem to be able to map the payload back to an object or misses the reference to the bound object. Everything is filled in correctly the guid, index and the value of the enum.
Payload:
Dummies[guid][0].Enum[guid]: DummyZero
Dummies[guid][0].Enum[guid]: DummyZero
Attempts
I tried with the following ideas but they weren't successfull for me.
model not binding to dictionary
binding dictionary
What am I missing?
The problem as stated in the question had to do with mvc converting Dictionary to a List<KeyValuePair<Guid, List>>binding or use JSON.
All that needs to be done is break down the object as mvc would and provide the necessary data. As explained in dicitionary binding.
The object was of type Dictionary<Guid, List<Dummy>>. So the object actually becomes List<KeyValuePair<Guid, List<List<KeyValuePair<Guid, enum>>>>>.
Now to break it down
MVC needs the index of the first object that is being used. To get this index we need to covert the dictionary to a list ourself. More specific the values or keys of the dictionarys.
var dummies= Model.Dummies[key];
var dummiesIndex = Model.Dummies.Values.ToList().IndexOf(dummies);
The index needs to be provided along side the post. This can be done by adding it above the dropdown as a hidden field along side the key from the dictionary.
#Html.Hidden("dummies.Index", dummiesIndex)
#Html.Hidden("dummies[" + dummiesIndex + "].Key", key)
Next is the List of objects. Again the index needs to be provided for the binding.
#Html.Hidden("dummies[" + dummiesIndex + "].Value.Index", dummyIndex)
The last step is another dictionary, this is just like the first dictionary
#Html.Hidden("dummies[" + dummiesIndex + "].DummyEnum.Index", dummyEnumIndex)
#Html.Hidden("dummies[" + dummiesIndex + "].DummyEnum.Key", yourKey)
For the value you want to actually post you need to follow the complete path like above.
#Html.DefaultCombo("dummies[" + dummiesIndex + "].DummyEnum[" + dummyEnumIndex+ "]", "Value", "Text", Model.EnumList, enum)
Now MVC can remap your objects.
The front-end response will be in that form that you'll set it there. Then the ASP middleware will parse all those strings back to an object at your back-end.
So key moments here are:
a controller action parameter type - it could be any of your types but it should correlate with your front-end;
front-end's select element name attribute value - it should contain full existing path.
As I got from your code example the following.
You have DummyViewModel view model class. It has property Dummies.
You have Dummy class, that nested in DummyViewModel as Dummies. 2nd level dictionary.
You have DummyEnum enum class, that is in use at DummyEnum values. Same names, different adjacent levels.
The SelectList values are OK. They are directly from the enum.
Based on the structure, to set up a first enum value you need to navigate its level by setting KEY and VALUE. Then do it again for another level. For me, the first enum value in this structure should have something like this:
Dummies[dummiesKeyGuid][dummyIndexId].DummyEnum[dummyEnumKeyGuid];
Where you have the following types in each step:
Dummies[dummiesKeyGuid] is <List<Dummy>>;
Dummies[dummiesKeyGuid][dummyIndexId] is <Dummy>;
Dummies[dummiesKeyGuid][dummyIndexId].DummyEnum[dummyEnumKeyGuid] is <DummyEnum>.
So #Html.DropDownListFor(...) should be updated to set the path as name.
Also:
your action should take the Dummies type as a parameter.
ActionResult SomeFromProcessingAction(DummyViewModel Dummies)
you should handle the passed stringified parameters map to the Dictionary type. It could be used outside of ASP (front-end) but has the issue. Please, check this post and its topic: https://stackoverflow.com/a/29820891/6344916. Sometimes, it is easier to not use the Dictionary there. Just other classes like List or Array.
From your HTML example, I didn't get the m.Dummies type to have in its structure the Enum field. dummiesKey can't have "guid" value. GUID is another type that can be made ToString() easily but not otherwise.
IMHO. Too many Dummys in the names. It confuses and breaks its understanding.
Also, its nested structure is VERY cumbersome. You could use smaller user forms to set the values and take smaller objects or event values on your back-end instead of the huge object parameters.
The List class has no mapping requirements, just denormalize your dictionaries and it will be easier to map them. The same with their navigation on the front-end. If required, you can make the List ToDictionary(). :)=)
For example, Dummy could be written using List<T>:
public class SomeElement
{
public Guid Id { get; set; }
public DummyEnum Enum { get; set; }
}
public class Dummy //kind of aggregation
{
public Guid Id { get; set; }
public List <SomeElement> DummyEnum { get; set; }
}
And so on with DummyViewModel. Or get rid of it and use some List directly.
Hope, this will help.
I think your issue is not related to nested properties or input naming and your implementation seems fine.
The issue you encountered is linked to Dictionary<Guid, ...> default model binder behavior. The default binder simply does not seem to handle it correctly. (ie. Dictionaries with Guid as keys)
I have reproduced your issue and then switched to Dictionary<string, ...> and everything worked fine this time.
The only way your could overcome this should probably be to implement your own model binder for Dictionary<Guid, object>.
I tried to understand the root problem and it seems to be located here (Invalid explicit cast from string to Guid) as also described here (found later :-)...)

.NET How to change class atribute in runtime?

I've got an issue with changing attribute at runtime.
I'm using https://www.filehelpers.net/ to handle csv but I think this problem is similar for any other custom attributes.
There is a class representing entity:
[DelimitedRecord("\t")]
public class FileHelper
{
[FieldNotEmpty]
public string Id = "1";
public string Name = "Product1"
}
This class is used as generic parameter for file reading engine. What I want to do is to change DelimitedRecord("\t") to a runtime value eg. semicolon .
I know that I can use below code to get attributes but only read.
DelimitedRecordAttribute[] attributes = (DelimitedRecordAttribute[]) typeof(FileHelper).GetCustomAttributes(typeof(DelimitedRecordAttribute), false);
Is there option to change attribute value or remove it and add new attribute new value?

Bogus, AutoFixture, others(?): How to fill a nested model with fake data and set rules for specific properties?

I have a very nested model that I want to create thousands of with fake data. But, also, some properties in the model need to be in a specific range or have specific rules. I looked at these two fake data generators:
AutoFixture only seems to generate fake data for everything.
Bogus can set rules like ranges for properties but all other properties remain null - or you have to define rules for all of them.
Did I miss something or is it not possible to fill the models with fake data and only set rules for specific properties?
Bogus has a community extension called AutoBogus written by Nick Dodd that lets you auto-generate rules for your models.
You can also override auto-generated rules that AutoBogus created with specific values for specific tests. Check out the AutoBogus readme for more info.
Also, you don't have to choose one or the other. You can use both. Bogus has a Faker class (not Faker<T>) that you can use without a fluent setup and without having to define a model T. The Faker class gives you access to all the datasets for realistic data generation. So, you can use Bogus' Faker object in combination with AutoFixture's conventions. :)
Hope that helps!
Brian
AutoFixture enables you to establish rules for properties, either in a property-by-property basis, or by convention.
Customise a specific property
You can use Customize to change the behaviour for a particular type, including properties:
[Fact]
public void CustomizeSpecificProperty()
{
var fixture = new Fixture();
fixture.Customize<MyClass>(c => c.With(mo => mo.Number, 42));
var actual = fixture.Create<MyClass>();
Assert.Equal(42, actual.Number);
}
This particular customization changes the rule for all MyClass.Number properties; the value will always be exactly 42.
Customize by convention
You can also match various properties by convention, often by looking at a combination of property type and name:
[Fact]
public void CustomizeTextPropertyByConvention()
{
var fixture = new Fixture();
fixture.Customizations.Add(new TextPropertyBuilder());
var actual = fixture.Create<MyClass>();
Assert.Equal("Foo", actual.Text);
}
This option also requires that you write a custom TextPropertyBuilder class:
public class TextPropertyBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi == null || pi.Name != "Text" || pi.PropertyType != typeof(string))
return new NoSpecimen();
return "Foo";
}
}
This rule will apply to all string properties called "Text", no matter on which class they're defined.
AutoFixture comes with a rich API that will enable you express many of such rules in a more succinct manner, but these are the main building blocks.
Both the above examples use this MyClass:
public class MyClass
{
public int Number { get; set; }
public string Text { get; set; }
}

Getting the right class by attribute based on string value

public ActionResult AddComplianceForm(string TemplateName)
{
}
In this ASP.net MVC5 application there is a folder Templates that contains a bunch of different classes that all have different TemplateName attributes. The first part of this method needs to find the class that has a TemplateName matching the string passed in. I then need to create an instance of whatever template matched. I am very new to working with attributes in C# so help would be very appreciated. I mostly need to know how to access that folder of classes in the program to look into it.
What you are trying to do is called "Reflection" in C#.
Below is the link to another answer that shows how to get all the classes in a namespace (I'm assuming that the physical folder implies the use of a unique namespace for the classes contained in the folder.)
Link to StackOverflow answer
**Btw you should look up reflection performance and see if it makes sense in your case. You may want to use a factory pattern instead.
This will work:
public class HomeController : Controller
{
public ActionResult AddComplianceForm(string TemplateName)
{
Assembly assembly = Assembly.Load("Testy20161006"); //assembly name
Type t = assembly.GetType("Testy20161006.Templates." + TemplateName); //namespace + class name
Object obj = (Object)Activator.CreateInstance(t);
return View();
}
Don't just use reflection
The typical way someone would deal with this is with Reflection and run-time type discovery/binding. However, this is probably a poor starting point in this exact situation. The template name is passed in as an action argument, presumably through binding to a value in the request string, and you don't want c# code that will instantiate whatever class is passed in from the web!!! That would be a serious security issue known as an insecure direct object reference.
Create a list
To mitigate the risk, the proper approach is to check the argument against a whitelist. Well, if we have a white list already, we may as well associate each item in the list with a lambda expression that returns the object you want.
class MyController
{
static private readonly Dictionary<string,Func<BaseTemplate>> _templateList = new Dictionary<string,Func<BaseTemplate>>();
static MyController()
{
_templateList.Add("ATemplate", () => return new ATemplate());
_templateList.Add("SomeOtherTemplate", () => return new SomeOtherTemplate());
_templateList.Add("JustOneMore", () => return new JustOneMore());
}
public ActionResult AddComplianceForm(string TemplateName)
{
BaseTemplate template;
try
{
template = _templateList[TemplateName]();
}
catch (KeyNotFoundException exception)
{
RedirectToAction("MyController", "InvalidTemplateError");
}
DoSomethingWithTemplate(template);
}
}
Create the list using Reflection
But what if you have a crap ton of templates? You don't want to hard code all those dictionary entries, right?
Well, you could tag each of the templates with a custom attribute, e.g. [TemplateAttribute], and then populate the list this way:
foreach (Assembly b in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type t in b.GetTypes())
{
var a = Attribute.GetCustomAttribute(t, typeof(TemplateAttribute));
if (a != null)
{
var localType = t; //Avoid closure on loop variable
_templateList.Add(t.Name, () => Activator.CreateInstance(localType) as BaseTemplate);
}
}
}
This will automatically iterate through all the types that are loaded for your application and find the ones with the TemplateAttribute. Only those types will be allowed in the action argument.
Notes:
In these examples I assume all of your templates inherit from a BaseTemplate, but if they have no ancestor in common (not recommended) I guess you could just use object.
In these examples, I store the list and implement the code in the controller, but if you are going for well-structured code you should consider moving all that stuff into some sort of factory class and just pass the string in from the controller.

How to define an alias for a property

I want to generate aliases for properties in generated code. All I could do so far was:
partial class Purchase
{
public User Customer
{
get
{
return this.User;
}
set
{
this.User = value;
}
}
}
I wonder if there is any other way to define an alias in C#. The Purchase class was generated by Linq-to-SQL
In the case that you want a different name of property to send information to JSON using newtonSoft, you can use
[JsonProperty("alias_name")]
public type YourProperty {get;set;}
This can help you if you don't want that your object follow the C# convention and match with the JSON object to be received or sent
No, it's not possible to do in C#. Property name is single identifier you can define for it.
Don't know if this is what you're searching for or not:
but you can define (say) a Dictionary<string,object> where Key is a propertyname and value is a value of the property. In this way you can define dynamic property cash, with changing property names and values at runtime.
or can use an ExpandoObject , if you use C# 4.0

Categories

Resources