Is it possible to get/set attached properties several layers deep in an object?
Dummy example:
<ContentControl local:MyPage.Paragraph1.Text="I'm actually a lot of text"/>
And my dummy classes
public static class MyPage
{
public static readonly Paragraph1 = new Paragraph();
public static Paragraph2 {get;} = new Paragraph();
}
public class Paragraph
{
public readonly DependencyProperty TextProperty;
public void SetParagraph(DependencyObject obj, string text) => obj.SetValue(TextProperty, text);
public void GetParagraph(DependencyObject obj) => (string)obj.GetValue(TextProperty);
public Paragraph()
{
//note that this is NOT a static Dependency Property. Instead, each instance of this class will be static.
TextProperty = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(Paragraph), new PropertyMetadata(string.Empty));
}
}
I've tried different formats like Paragraph2, wrapping the XAML call in Parentheses, and the unusual '+' syntax suggested here, but I keep getting errors like: "The property 'MyPage.Paragraph1.Text' does not exist in the XML namespace '....'", "The attachable property 'Paragraph1' was not found in type 'MyPage'," and "must not be a nested class."
For attached properties, the Get and Set methods should be associated with the property name and not the class that defines it.
If a property can be attached to elements arbitrarily deep in a visual tree, I have a helper function that works for me.
Here's how I would do page/paragraph:
public class MyPage : Panel
{
// implementation of custom panel excluded for clarity
}
public class Paragraph
{
public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
"Text",
typeof(string),
typeof(CustomContainer),
new FrameworkPropertyMetadata(null)
);
public static void SetText(UIElement element, string value)
{
element.SetValue(TextProperty, value);
}
public static string GetText(UIElement element)
{
return (string)element.GetValue(TextProperty);
}
}
The XAML:
<ctls.MyPage>
<ctls.Paragraph x:Name="anInstanceOfParagraph">
<StackPanel>
<TextBlock ctls:Paragraph.Text="ChapterTitle" Text="Chapter One: My Early Years"/>
</StackPanel>
</ctls.Paragraph>
</ctls.MyPage>
To attach a property in code:
private void AttachText(TextBlock textElement, string text)
{
Paragraph.SetText(textElement, text);
}
Then we find arbitrarily nested elements within Paragraph that have the property attached and set to a specific value using a helper:
var elements = WPFHelper.GetChildrenWithPropertySet(anInstanceOfParagraph,
TextProperty,
"IsIntubationCompleted");
Here is the helper function, a static method in the WPFHelper class:
/// <summary>
/// Give a property and a control, find all the child controls that
/// have a property (typically an attached property). Optionally,
/// if value !=null, it will search for an item with the property
/// set to a specific value
/// </summary>
/// <param name="parent"></param>
/// <param name="property"></param>
/// <param name="value"></param>
/// <returns></returns>
public static List<DependencyObject> GetChildrenWithPropertySet(DependencyObject parent,
DependencyProperty property, string value = null)
{
var objectsWithPropertySet = new List<DependencyObject>();
if (value == null)
{
objectsWithPropertySet.AddRange(parent.GetAllChildren()
.Where(o => o.ReadLocalValue(property) != DependencyProperty.UnsetValue));
}
else
{
objectsWithPropertySet.AddRange(parent.GetAllChildren()
.Where(o => o.ReadLocalValue(property) != DependencyProperty.UnsetValue &&
((string)o.ReadLocalValue(property)) == value));
}
return objectsWithPropertySet;
}
/// <summary>
/// returns all children in the visual true of a dependency object
/// </summary>
/// <param name="parent"></param>
/// <returns></returns>
public static IEnumerable<DependencyObject> GetAllChildren(this DependencyObject parent)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
// retrieve child at specified index
var directChild = (Visual)VisualTreeHelper.GetChild(parent, i);
// return found child
yield return directChild;
// return all children of the found child
foreach (var nestedChild in directChild.GetAllChildren())
yield return nestedChild;
}
}
Related
I have the following custom required attribute code:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class AddressRequiredAttribute : RequiredAttribute, IClientValidatable
{
/// <summary>
/// The _property name
/// </summary>
private string _propertyName;
/// <summary>
/// Initializes a new instance of the <see cref="AddressRequiredAttribute"/> class.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
public AddressRequiredAttribute(string propertyName)
: base()
{
_propertyName = propertyName;
}
/// <summary>
/// Checks that the value of the required data field is not empty.
/// </summary>
/// <param name="value">The data field value to validate.</param>
/// <returns>
/// true if validation is successful; otherwise, false.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (context.ObjectType.BaseType == typeof(AddressModel))
{
PropertyInfo property = context.ObjectType.GetProperty(_propertyName);
if (property != null && (bool)property.GetValue(context.ObjectInstance))
{
return base.IsValid(value, context);
}
}
return ValidationResult.Success;
}
/// <summary>
/// When implemented in a class, returns client validation rules for that class.
/// </summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <returns>
/// The client validation rules for this validator.
/// </returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string errorMessage = this.ErrorMessage;
// Get the specific error message if set, otherwise the default
if (string.IsNullOrEmpty(errorMessage) && metadata != null)
{
errorMessage = FormatErrorMessage(metadata.GetDisplayName());
}
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = errorMessage,
ValidationType = "requiredaddress"
};
return new[] { clientValidationRule };
}
and the following jquery for the client side validation which is run on window.load:
$.validator.addMethod('requiredaddress', function (value, element, params) {
return value != '';
}, '');
$.validator.unobtrusive.adapters.add('requiredaddress', {}, function (options) {
options.rules['requiredaddress'] = true;
options.messages['requiredaddress'] = options.message;
});
However, the clientside doesn't kick in so I get the normal clientside validation working, then the form will submit but come back with the custom errors after postback. All the examples that I have looked at say that my code should be correct so I'm not sure what is wrong here.
Can anyone see anything obvious I'm doing wrong here, thanks
I did something similar to this and found that if I put the code in the document.ready on window.load it wouldn't work. In the end I went with using the following code placed after the jquery validate scripts:
(function ($) {
$.validator.addMethod('requiredaddress', function (value, element, params) {
return value != '';
}, 'Clientside Should Not Postback');
// i think you should be able to use this as your adapter
$.validator.unobtrusive.adapters.addBool('requiredaddress');
})(jQuery);
I want to decorate my services with attributes for interception, and then have conventions based binding set the interceptors up for me. I don't want my attributes to inherit from the interception attributes... if I can avoid it.
For example, I have the following class:
[Log]
public class SomeClassToLog
{
public void DoSomething() { ... }
}
I understand I can bind this as follows:
var kernel = new StandardKernel();
kernel.Bind(x => x.FromAssembliesMatching("SomeProject.*")
.SelectAllClasses()
.WithAttribute(typeof(LogAttribute))
.BindToSelf().Configure(syntax => syntax.Intercept().With(LogInterceptor)));
How can I do this with different combinations of attributes and interceptors? For example:
If I have Log and Authorize attributes I would have to configure 3 sets of bindings? (1 for log without authorize, 1 for authorize without log and one for both log and authorize).
Updated: While I couldn't find a solution based on my original question parameters, I did stumble upon a similar question which lead me to the solution I ended up going with. Here is the source code:
Notes: Common.Interception.Interceptors namespace is in an assembly which has a reference to Ninject.Extensions.Interception (and all of its required dependencies). My attributes are defined in a separate assembly with no dependencies of their own.
MiscExtensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Common.Interception.Interceptors
{
// TODO: Remove the dependence on these eventually
/// <summary>
/// A bundle of extension methods which I needed to borrow from ninject source since they were internal:
/// Ninject.Extensions.Interception.Infrastructure.Language
/// ExtensionsForIEnumerable
/// ExtensionsForMethodInfo
/// ExtensionsForICustomAttributeProvider
/// </summary>
internal static class MiscExtensions
{
/// <summary>
/// Converts all of the items in the specified series using the specified converter.
/// </summary>
/// <typeparam name="TInput">The type of items contained in the input list.</typeparam>
/// <typeparam name="TOutput">The type of items to return.</typeparam>
/// <param name="items">The series of items to convert.</param>
/// <param name="converter">The converter to use to convert the items.</param>
/// <returns>A list of the converted items.</returns>
public static IEnumerable<TOutput> Convert<TInput, TOutput>(this IEnumerable<TInput> items,
Func<TInput, TOutput> converter)
{
return items.Select(converter);
}
/// <summary>
/// Skips the last items where the count of skipped items is given by count.
/// </summary>
/// <typeparam name="T">The type of the enumerable.</typeparam>
/// <param name="source">The source.</param>
/// <param name="count">The count of skipped items.</param>
/// <returns>An enumerable that skippes the last items from the source enumerable.</returns>
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
{
var enumerator = source.GetEnumerator();
var items = new Queue<T>();
while (enumerator.MoveNext())
{
if (count-- <= 0)
{
yield return items.Dequeue();
}
items.Enqueue(enumerator.Current);
}
}
private const BindingFlags DefaultBindingFlags =
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance;
public static PropertyInfo GetPropertyFromMethod(this MethodInfo method, Type implementingType)
{
if (!method.IsSpecialName)
{
return null;
}
var isGetMethod = method.Name.Substring(0, 3) == "get";
var returnType = isGetMethod ? method.ReturnType : method.GetParameterTypes().Last();
var indexerTypes = isGetMethod ? method.GetParameterTypes() : method.GetParameterTypes().SkipLast(1);
return implementingType.GetProperty(method.Name.Substring(4), DefaultBindingFlags, null, returnType, indexerTypes.ToArray(), null);
}
public static PropertyInfo GetPropertyFromMethod(this MethodInfo method)
{
if (!method.IsSpecialName)
{
return null;
}
return method.DeclaringType.GetProperty(method.Name.Substring(4), DefaultBindingFlags);
}
/// <summary>
/// Gets the types of the parameters of the method.
/// </summary>
/// <param name="method">The method in question.</param>
/// <returns>An array containing the types of the method's parameters.</returns>
public static IEnumerable<Type> GetParameterTypes(this MethodBase method)
{
return method.GetParameters().Convert(p => p.ParameterType);
}
/// <summary>
/// Gets the method handle of either the method or its generic type definition, if it is
/// a generic method.
/// </summary>
/// <param name="method">The method in question.</param>
/// <returns>The runtime method handle for the method or its generic type definition.</returns>
public static RuntimeMethodHandle GetMethodHandle(this MethodBase method)
{
var mi = method as MethodInfo;
if (mi != null &&
mi.IsGenericMethod)
{
return mi.GetGenericMethodDefinition().MethodHandle;
}
return method.MethodHandle;
}
/// <summary>
/// Gets the first attribute of a specified type that decorates the member.
/// </summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="member">The member to examine.</param>
/// <returns>The first attribute matching the specified type.</returns>
public static T GetOneAttribute<T>(this ICustomAttributeProvider member)
where T : Attribute
{
var attributes = member.GetCustomAttributes(typeof(T), true) as T[];
return (attributes == null) ||
(attributes.Length == 0)
? null
: attributes[0];
}
/// <summary>
/// Gets the first attribute of a specified type that decorates the member.
/// </summary>
/// <param name="member">The member to examine.</param>
/// <param name="type">The type of attribute to search for.</param>
/// <returns>The first attribute matching the specified type.</returns>
public static object GetOneAttribute(this ICustomAttributeProvider member, Type type)
{
object[] attributes = member.GetCustomAttributes(type, true);
return (attributes == null) ||
(attributes.Length == 0)
? null
: attributes[0];
}
/// <summary>
/// Gets an array of attributes matching the specified type that decorate the member.
/// </summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="member">The member to examine.</param>
/// <returns>An array of attributes matching the specified type.</returns>
public static T[] GetAllAttributes<T>(this ICustomAttributeProvider member)
where T : Attribute
{
return member.GetCustomAttributes(typeof(T), true) as T[];
}
/// <summary>
/// Gets an array of attributes matching the specified type that decorate the member.
/// </summary>
/// <param name="member">The member to examine.</param>
/// <param name="type">The type of attribute to search for.</param>
/// <returns>An array of attributes matching the specified type.</returns>
public static object[] GetAllAttributes(this ICustomAttributeProvider member, Type type)
{
return member.GetCustomAttributes(type, true);
}
/// <summary>
/// Determines whether the member is decorated with one or more attributes of the specified type.
/// </summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="member">The member to examine.</param>
/// <returns><see langword="True"/> if the member is decorated with one or more attributes of the type, otherwise <see langword="false"/>.</returns>
public static bool HasAttribute<T>(this ICustomAttributeProvider member)
where T : Attribute
{
return member.IsDefined(typeof(T), true);
}
/// <summary>
/// Determines whether the member is decorated with one or more attributes of the specified type.
/// </summary>
/// <param name="member">The member to examine.</param>
/// <param name="type">The type of attribute to search for.</param>
/// <returns><see langword="True"/> if the member is decorated with one or more attributes of the type, otherwise <see langword="false"/>.</returns>
public static bool HasAttribute(this ICustomAttributeProvider member, Type type)
{
return member.IsDefined(type, true);
}
/// <summary>
/// Determines whether the member is decorated with an attribute that matches the one provided.
/// </summary>
/// <typeparam name="T">The type of attribute to search for.</typeparam>
/// <param name="member">The member to examine.</param>
/// <param name="attributeToMatch">The attribute to match against.</param>
/// <returns><see langword="True"/> if the member is decorated with a matching attribute, otherwise <see langword="false"/>.</returns>
public static bool HasMatchingAttribute<T>(this ICustomAttributeProvider member, T attributeToMatch)
where T : Attribute
{
T[] attributes = member.GetAllAttributes<T>();
if ((attributes == null) ||
(attributes.Length == 0))
{
return false;
}
return attributes.Any(attribute => attribute.Match(attributeToMatch));
}
}
}
AlternateInterceptorRegistrationStrategy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Ninject;
using Ninject.Components;
using Ninject.Extensions.Interception;
using Ninject.Extensions.Interception.Advice;
using Ninject.Extensions.Interception.Planning.Directives;
using Ninject.Extensions.Interception.Registry;
using Ninject.Planning;
using Ninject.Planning.Strategies;
namespace Common.Interception.Interceptors
{
/// <summary>
/// This is a derivation of InterceptorRegistrationStrategy from Ninject.Extensions.Interception.Planning.Strategies, merged with
/// http://stackoverflow.com/questions/6386461/ninject-intercept-any-method-with-certain-attribute
/// </summary>
public class AlternateInterceptorRegistrationStrategy<TAttribute, TInterceptor> : NinjectComponent, IPlanningStrategy
where TAttribute : Attribute
where TInterceptor : IInterceptor
{
protected const BindingFlags DefaultBindingFlags =
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance;
public AlternateInterceptorRegistrationStrategy(IAdviceFactory adviceFactory, IAdviceRegistry adviceRegistry, IKernel kernel)
{
AdviceFactory = adviceFactory;
AdviceRegistry = adviceRegistry;
Kernel = kernel;
}
public IKernel Kernel { get; set; }
public IAdviceFactory AdviceFactory { get; set; }
public IAdviceRegistry AdviceRegistry { get; set; }
public virtual void Execute(IPlan plan)
{
IEnumerable<MethodInfo> candidates = GetCandidateMethods(plan.Type);
RegisterClassInterceptors(plan.Type, plan, candidates);
foreach (MethodInfo method in candidates)
{
PropertyInfo property = method.GetPropertyFromMethod(plan.Type);
ICustomAttributeProvider provider = (ICustomAttributeProvider)property ?? method;
TAttribute[] attributes = provider.GetAllAttributes<TAttribute>();
if (attributes.Length == 0)
{
continue;
}
RegisterMethodInterceptor(plan.Type, method);
// Indicate that instances of the type should be proxied.
if (!plan.Has<ProxyDirective>())
{
plan.Add(new ProxyDirective());
}
}
}
protected virtual void RegisterClassInterceptors(Type type, IPlan plan, IEnumerable<MethodInfo> candidates)
{
var attributes = type.GetAllAttributes<TAttribute>();
if (attributes.Length == 0)
{
return;
}
foreach (MethodInfo method in candidates)
{
PropertyInfo property = method.GetPropertyFromMethod(type);
ICustomAttributeProvider provider = (ICustomAttributeProvider) property ?? method;
var config = Kernel.Get<IInterceptorConfig>();
if (config.DoNotInterceptAttribute == null)
{
// A "do not intercept" attribute wasn't defined in the config, so go ahead and register
RegisterMethodInterceptor(type, method);
}
else if (!provider.IsDefined(config.DoNotInterceptAttribute, true))
{
// The method wasn't decorated with the "do not intercept" attribute, so go ahead and register
RegisterMethodInterceptor(type, method);
}
}
if (!plan.Has<ProxyDirective>())
{
plan.Add(new ProxyDirective());
}
}
protected virtual void RegisterMethodInterceptor(Type type, MethodInfo method)
{
IAdvice advice = AdviceFactory.Create(method);
advice.Callback = request => request.Context.Kernel.Get<TInterceptor>();
var config = Kernel.TryGet<IInterceptorConfig>();
if (config != null)
{
advice.Order = config.GetOrder<TInterceptor>();
}
AdviceRegistry.Register(advice);
}
protected virtual IEnumerable<MethodInfo> GetCandidateMethods(Type type)
{
MethodInfo[] methods = type.GetMethods(DefaultBindingFlags);
return methods.Where(ShouldIntercept);
}
protected virtual bool ShouldIntercept(MethodInfo methodInfo)
{
return methodInfo.DeclaringType != typeof(object) &&
!methodInfo.IsPrivate;// &&
//!methodInfo.IsFinal;
}
}
}
IInterceptorConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject.Extensions.Interception;
namespace Common.Interception.Interceptors
{
public interface IInterceptorConfig
{
IInterceptorConfig SpecifyOrder<TInterceptor>(int order) where TInterceptor : IInterceptor;
IInterceptorConfig SpecifyDoNotInterceptAttribute<TAttribute>() where TAttribute : Attribute;
int GetOrder<TInterceptor>() where TInterceptor : IInterceptor;
Type DoNotInterceptAttribute { get; }
}
}
InterceptorConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject.Extensions.Interception;
namespace Common.Interception.Interceptors
{
public class InterceptorConfig : IInterceptorConfig
{
private readonly Dictionary<Type, int> _orderDictionary = new Dictionary<Type, int>();
public IInterceptorConfig SpecifyOrder<TInterceptor>(int order) where TInterceptor : IInterceptor
{
_orderDictionary.Add(typeof(TInterceptor), order);
return this;
}
public IInterceptorConfig SpecifyDoNotInterceptAttribute<TAttribute>() where TAttribute : Attribute
{
DoNotInterceptAttribute = typeof(TAttribute);
return this;
}
public int GetOrder<TInterceptor>() where TInterceptor : IInterceptor
{
return _orderDictionary[typeof(TInterceptor)];
}
public Type DoNotInterceptAttribute { get; private set; }
}
}
TraceInterceptor.cs - just a sample interceptor
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject.Extensions.Interception;
namespace Common.Interception.Interceptors
{
public class TraceInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("Enter Method");
invocation.Proceed();
Console.WriteLine("Exit Method");
}
}
}
Notes: Here is a simple console app that shows how to wire up the attributes/interceptors. This has dependencies on both Ninject.Extensions.Interception.DynamicProxy and Ninject.Extensions.Conventions (and all their required dependencies)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Common.Interception.Attributes;
using Common.Interception.Interceptors;
using Ninject;
using Ninject.Extensions.Conventions;
using Ninject.Planning.Strategies;
using SomeProject.Infrastructure;
namespace SomeProject.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel();
kernel.Components.Add<IPlanningStrategy, AlternateInterceptorRegistrationStrategy<TraceAttribute, TraceInterceptor>>();
kernel.Components.Add<IPlanningStrategy, AlternateInterceptorRegistrationStrategy<AuthorizeAttribute, AuthorizeInterceptor>>();
// I needed a way to specify execution order and "DoNotIntercept" without deriving from attributes that would force ninject references all over my domain
// not 100% confident this is the best way - but it works
kernel.Bind<IInterceptorConfig>().ToConstant(new InterceptorConfig()
.SpecifyOrder<TraceInterceptor>(1)
.SpecifyOrder<AuthorizeInterceptor>(0)
.SpecifyDoNotInterceptAttribute<DoNotInterceptAttribute>());
// SomeProject.Infrastructure contains my service classes decorated with my custom attributes
kernel.Bind(x => x.FromAssembliesMatching("SomeProject.Infrastructure")
.SelectAllClasses()
.BindToSelf());
var a = kernel.Get<SomeServiceA>();
var b = kernel.Get<SomeServiceB>();
Console.WriteLine("Calling a.DoSomeStuff()...");
a.DoSomeStuff();
Console.WriteLine("Calling b.DoMyThing()...");
b.DoMyThing();
Console.WriteLine("Calling b.NowTraceThis()...");
b.NowTraceThis();
Console.ReadLine();
}
}
}
After scouring and scouring, I found that a similar question had been asked and answered here: Ninject Intercept any method with certain attribute?
I used this in conjunction with the source code of the InterceptorRegistrationStrategy class within Ninject.Extensions.Interception.Planning.Strategies to create a derivation which I am now using.
crickets
now I'm building a web application on asp-net using castle active record. When I was trying to save an entity with a has-many relation I got the error: "object references an unsaved transient instance - save the transient instance before flushing. Type: SupBoardModel.Entities.Output, Entity: SupBoardModel.Entities.Output#0". Searching on the web I found the causes of this error and some of its solutions but no one worked for me. The relation already have a property set to Cascade = ManyRelationCascadeEnum.All, one of a common suggestion around the web so... What is wrong here??? There is a piece of code for more information and understanding:
//In some part of my application
State state = new State();
//Set some fields
//...
state.Outputs = (List<Output>)Session["outputs"]; //Collection filled on a web form but not saved yet
//Here is the error
state.SaveAndFlush(); // Booom!!!!
//Part of a definition of Output(child entity)
[Serializable, ActiveRecord(Table = "SUPB_OUTPUTS")]
public class Output : ActiveRecordBase<Output>
{
private int _id;
/// <summary>
/// Primary key
/// </summary>
[PrimaryKey(PrimaryKeyType.SeqHiLo, "OUTPUT_ID", SequenceName = "SEQ_OUTPUT_ID")]
public int Id
{
get { return _id; }
set { _id = value; }
}
private string _label;
/// <summary>
/// Output custom label
/// </summary>
[Property("OUTPUT_LABEL")]
public string Label
{
get { return _label; }
set { _label = value; }
}
private State _state;
/// <summary>
/// StateRef owner (An output is only available for one state)
/// </summary>
[BelongsTo("OUTPUT_ID_STATE", Lazy = FetchWhen.OnInvoke)]
public State StateRef
{
get { return _state; }
set { _state = value; }
}
}
// Part of a definition of State(parent entity)
[Serializable, ActiveRecord(Table = "SUPB_STATES")]
public class State : ActiveRecordBase<State>
{
private int _id;
/// <summary>
/// Primary key
/// </summary>
[PrimaryKey(PrimaryKeyType.SeqHiLo, "STATE_ID", SequenceName = "SEQ_STATE_ID")]
public int Id
{
get { return _id; }
set { _id = value; }
}
private string _name;
/// <summary>
/// StateRef name
/// </summary>
[Property("STATE_NAME")]
public string Name
{
get { return _name; }
set { _name = value; }
}
private string _description;
/// <summary>
/// StateRef description
/// </summary>
[Property("STATE_DESC")]
public string Description
{
get { return _description; }
set { _description= value; }
}
private IList<Output> _outputs;
/// <summary>
/// State outputs (Columns to display data)
/// </summary>
[HasMany(typeof(Output), Table = "SUPB_OUTPUTS", ColumnKey = "OUTPUT_ID_STATE", Lazy = true, Cascade = ManyRelationCascadeEnum.All)]
public IList<Output> Outputs
{
get { return _outputs; }
set { _outputs = value; }
}
}
This error is make me crazy. I hope that is a way to save the State without save each Output before. The cascade attribute has no change for me, all options (All, AllDeleteOrfan, SaveUpdate) give me the same result. This case is very common and is mentioned on http://docs.castleproject.org/%28X%281%29S%28znghcs55lveeljjvqg21vni4%29%29/Active%20Record.Getting%20Started.ashx but is a mystery for me. Can any body help me??
Thanks
Menrique
Ok, I put Cascade=CascadeEnum.All in the StateRef field of the Output, something like that:
/// <summary>
/// StateRef owner (An output is only available for one state)
/// </summary>
[BelongsTo("OUTPUT_ID_STATE", Lazy = FetchWhen.OnInvoke, Cascade=CascadeEnum.All)]
public State StateRef
{
get { return _state; }
set { _state = value; }
}
And It WORK!!! So is not enough to put Cascade = ManyRelationCascadeEnum.All only in the relation hasMany of the parent entity, is necesary in the child entity too.
Thanks
Menrique
Imagine I have the following classes and interfaces:
public interface IService<T> { }
public class DefaultService<T> : IService<T> { }
public class FooService : IService<Foo> { }
public class BarService : IService<Bar> { }
I would then like to be able to get instances from the Kernel like this:
Kernel.Get<IService<Foo>>(); // Should return FooService
Kernel.Get<IService<Bar>>(); // Should return BarService
Kernel.Get<IService<Dog>>(); // Should return DefaultService
Kernel.Get<IService<Cat>>(); // Should return DefaultService
Kernel.Get<IService<Giraffe>>(); // Should return DefaultService
Is it possible to setup bindings using NInject (possibly using the Conventions extension), so that I don't have to manually bind every single possible implementation of IService?
I've been working on something similar recently and came up with somewhat simpler solution of your problem (although a bit weaker).
What should suffice is to bind a generic implementation (DefaultService) to the generic interface, and concrete implementations (FooService, BarService) to the concrete interfaces. When you ask for a concrete instance of the interface, Ninject resolves whether you defined the concrete binding. If you did, it gives you the appropriate instance, otherwise it falls through to the generic binding. The following code should do the trick.
var kernel = new StandardKernel();
kernel.Bind(typeof(IService<>)).To(typeof(DefaultService<>));
kernel.Bind<IService<Foo>>().To<FooService>();
kernel.Bind<IService<Bar>>().To<BarService>();
EDIT:
The concept works throughout the whole Ninject, so you can use it along with Extensions.Conventions as well.
e.g. define the following:
public class Foo{}
public class Bar{}
public class Dog{}
public interface IService<T>{}
public class DefaultService<T> : IService<T>{}
public class FooService : IService<Foo>{}
public class BarService : IService<Bar>{}
use conventions to bind the services:
kernel.Bind(x => x.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom(typeof(IService<>))
.BindSingleInterface());
and create and check the appropriate services:
Assert.IsInstanceOf<BarService>(kernel.Get<IService<Bar>>());
Assert.IsInstanceOf<FooService>(kernel.Get<IService<Foo>>());
Assert.IsInstanceOf<DefaultService<Dog>>(kernel.Get<IService<Dog>>());
I took the liberty of refactoring the answer from #cbp, so that it works for the new IBindingGenerator signature in Ninject v3 conventions. It's pretty much replacing the Process() method signature with the CreateBindings() method signature, but I didn't test this, so there's a chance you'll have to tweak it a bit if you use it.
/// <summary>
/// Creates bindings on open generic types.
/// This is similar to the out-of-the-box
/// <see cref="GenericBindingGenerator" />,
/// but allows a default class to be
/// specified if no other bindings can be found.
/// See the test case for usages.
/// </summary>
public class GenericBindingGeneratorWithDefault : IBindingGenerator
{
private static readonly Type TypeOfObject = typeof(object);
private readonly Type _contractType;
private readonly Dictionary<Type, Type> _cachedBindings;
private readonly Type _defaultType;
public GenericBindingGeneratorWithDefault(Type contractType, Type defaultType)
{
if (!(contractType.IsGenericType || contractType.ContainsGenericParameters))
throw new ArgumentException("The contract must be an open generic type.",
"contractType");
_cachedBindings = new Dictionary<Type, Type>();
_contractType = contractType;
_defaultType = defaultType;
}
/// <summary>
/// Creates the bindings for a type.
/// </summary>
/// <param name="type">The type for which the bindings are created.</param>
/// <param name="bindingRoot">The binding root that is used to create the bindings.</param>
/// <returns>
/// The syntaxes for the created bindings to configure more options.
/// </returns>
public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
{
if (type == null) throw new ArgumentNullException("type");
if (bindingRoot == null) throw new ArgumentNullException("bindingRoot");
if (type.IsInterface || type.IsAbstract) yield break;
if (type == _defaultType)
{
yield return bindingRoot.Bind(_contractType).ToMethod(
ctx =>
{
Type requestedType = ctx.Request.Service;
Type resolution = _cachedBindings.ContainsKey(requestedType)
? _cachedBindings[requestedType]
: _defaultType.MakeGenericType(ctx.GenericArguments);
return ctx.Kernel.Get(resolution);
});
}
else
{
Type interfaceType = ResolveClosingInterface(type);
if (interfaceType != null)
{
yield return bindingRoot.Bind(type).To(_cachedBindings[interfaceType]);
}
}
}
/// <summary>
/// Resolves the closing interface.
/// </summary>
/// <param name="targetType">Type of the target.</param>
/// <returns></returns>
private Type ResolveClosingInterface(Type targetType)
{
if (targetType.IsInterface || targetType.IsAbstract) return null;
do
{
Type[] interfaces = targetType.GetInterfaces();
foreach (Type #interface in interfaces)
{
if (!#interface.IsGenericType) continue;
if (#interface.GetGenericTypeDefinition() == _contractType)
{
return #interface;
}
}
targetType = targetType.BaseType;
} while (targetType != TypeOfObject);
return null;
}
}
I figured out how to do this after a couple of hours messing around with NInject Convention's GenericBindingGenerator.
If anyone is interested I can post it.
Update:
/// <summary>
/// Creates bindings on open generic types.
/// This is similar to the out-of-the-box <see cref="GenericBindingGenerator" />, but allows a default class to be
/// specified if no other bindings can be found. See the test case for usages.
/// </summary>
public class GenericBindingGeneratorWithDefault : IBindingGenerator
{
private static readonly Type TYPE_OF_OBJECT = typeof (object);
private readonly Type _contractType;
private Dictionary<Type, Type> _cachedBindings = new Dictionary<Type, Type>();
private readonly Type _defaultType;
public GenericBindingGeneratorWithDefault(Type contractType, Type defaultType)
{
if ( !( contractType.IsGenericType || contractType.ContainsGenericParameters ) )
{
throw new ArgumentException( "The contract must be an open generic type.", "contractType" );
}
_contractType = contractType;
_defaultType = defaultType;
}
/// <summary>
/// Processes the specified type creating kernel bindings.
/// </summary>
/// <param name="type">The type to process.</param>
/// <param name="scopeCallback">the scope callback.</param>
/// <param name="kernel">The kernel to configure.</param>
public void Process( Type type, Func<IContext, object> scopeCallback, IKernel kernel )
{
if (type == _defaultType)
{
kernel.Bind(_contractType).ToMethod(
ctx =>
{
var requestedType = ctx.Request.Service;
var resolution = _cachedBindings.ContainsKey(requestedType)
? _cachedBindings[requestedType]
: _defaultType.MakeGenericType(ctx.GenericArguments);
return ctx.Kernel.Get(resolution);
});
}
else
{
Type interfaceType = ResolveClosingInterface(type);
if (interfaceType != null)
{
_cachedBindings[interfaceType] = type;
}
}
}
/// <summary>
/// Resolves the closing interface.
/// </summary>
/// <param name="targetType">Type of the target.</param>
/// <returns></returns>
public Type ResolveClosingInterface( Type targetType )
{
if ( targetType.IsInterface || targetType.IsAbstract )
{
return null;
}
do
{
Type[] interfaces = targetType.GetInterfaces();
foreach ( Type #interface in interfaces )
{
if ( !#interface.IsGenericType )
{
continue;
}
if ( #interface.GetGenericTypeDefinition() == _contractType )
{
return #interface;
}
}
targetType = targetType.BaseType;
} while ( targetType != TYPE_OF_OBJECT );
return null;
}
}
I'm attempting to map a database field ("LS_RECNUM") possible values of NULL, 'M' and 'F' to a property with a Gender enumeration type.
The mapping looks like this:
Map(x => x.Gender).Column("LS_GENDER").Access.Using<GenderPropertyAccessor>();
...and the GenderPropertyAccessor class looks like this:
using System;
using System.Collections;
using System.Reflection;
using Kctc;
using NHibernate.Engine;
using NHibernate.Properties;
public class GenderPropertyAccessor : IPropertyAccessor
{
#region Setter
private class GenderGetterSetter : IGetter, ISetter
{
PropertyInfo _property;
public GenderGetterSetter(PropertyInfo property)
{
if (property == null) throw new ArgumentNullException("property");
if (property.PropertyType != typeof(Gender)) throw new ArgumentException("property");
_property = property;
}
public void Set(object target, object value) //Convert string to enum
{
_property.SetValue(target, GetGenderFromString(value), null);
}
public object Get(object target) //Convert enum back to string
{
Gender gender = (Gender)_property.GetValue(target, null);
return SetGenderToString(gender);
}
/// <summary>
/// Interprets the supplied string as a gender.
/// </summary>
/// <param name="strGender">The gender as either 'F' or 'M'.</param>
/// <returns></returns>
private Gender GetGenderFromString(object strGender)
{
if (strGender == null) return Gender.Unknown;
switch (strGender.ToString().ToLower())
{
case "f":
return Gender.Female;
case "m":
return Gender.Male;
default:
return Gender.Unknown;
}
}
/// <summary>
/// Sets the supplied Gender to the appropriate 'M' or 'F' value.
/// </summary>
/// <param name="objGender">The gender.</param>
/// <returns></returns>
private string SetGenderToString(object objGender)
{
Gender gender = (Gender) objGender;
switch (gender)
{
case Gender.Female:
return "F";
case Gender.Male:
return "M";
default:
return null;
}
}
public MethodInfo Method
{
get { return null; }
}
public string PropertyName
{
get { return _property.Name; }
}
public object GetForInsert(object owner, IDictionary mergeMap, ISessionImplementor session)
{
return Get(owner);
}
public Type ReturnType
{
get { return typeof(byte[]); }
}
}
#endregion
public IGetter GetGetter(Type theClass, string propertyName)
{
return new GenderGetterSetter(theClass.GetProperty(propertyName));
}
public ISetter GetSetter(Type theClass, string propertyName)
{
return new GenderGetterSetter(theClass.GetProperty(propertyName));
}
public bool CanAccessThroughReflectionOptimizer
{
get { return false; }
}
}
Not being particularly familiar with reflection, I'm not at all sure that the Get and Set methods have been implemented correctly.
When I try this, I still get an error 'Can't parse F as Gender'. I've tried debugging the GenderPropertyAccessor class. The relevant line (shown above) in the mapping file is executing correctly, as is the constructor for the GenderGetterSetter class, but the Get and Set methods are never called!!!
Can anyone tell me what I might be doing wrong?
I would use an implementation of IUserType for this. Here's a good simple example. In the NullSafeGet and NullSafeSet methods you will mutate the string to an enum and back, respectively. It's also critical that your Equals implementation is correct in order for NHibernate to detect changes.
Mapping the property to use a custom user type is easy:
Map(x => x.Gender).Column("LS_GENDER").CustomType(typeof(MyUserType));