NET MAUI How to create Picker selection changed behavior - maui

In the Net MAUI CommunityToolkit there is a behavior for Entry, SetFocusOnEntryCompletedBehavior, that will focus on the provided visual element az a next input (the input that will be focused).
I did not find behavior for Picker or a DataPicker, and I tried to write my own based on theirs Entry behavior.
public class SetFocusOnPickerSelectedBehavior : BaseBehavior<VisualElement>
{
/// <summary>
/// The <see cref="NextElementProperty"/> attached property.
/// </summary>
public static readonly BindableProperty NextElementProperty = BindableProperty.CreateAttached(
"NextElement",
typeof(VisualElement),
typeof(SetFocusOnPickerSelectedBehavior),
default(VisualElement),
propertyChanged: OnNextElementChanged);
/// <summary>
/// Required <see cref="GetNextElement"/> accessor for <see cref="NextElementProperty"/> attached property.
/// </summary>
public static VisualElement GetNextElement(BindableObject view) => (VisualElement)view.GetValue(NextElementProperty);
/// <summary>
/// Required <see cref="SetNextElement"/> accessor for <see cref="NextElementProperty"/> attached property.
/// </summary>
public static void SetNextElement(BindableObject view, VisualElement value) => view.SetValue(NextElementProperty, value);
static void OnNextElementChanged(BindableObject bindable, object oldValue, object newValue)
{
var picker = (Picker)bindable;
var weakPicker = new WeakReference<Picker>(picker);
picker.SelectedIndexChanged += SelectedIndexChanged;
void SelectedIndexChanged(object? sender, EventArgs e)
{
var picker = (Picker)sender;
if (picker.SelectedIndex < 1)
return;
if (weakPicker.TryGetTarget(out var origPicker))
{
GetNextElement(origPicker)?.Focus();
}
}
}
}
The use:
<Picker x:Name="position" Title="Select..."
temDisplayBinding="{Binding Name}"
SelectedItem="{Binding Position, Mode=TwoWay}"
toolkit:SetFocusOnPickerSelectedBehavior.NextElement="{x:Reference club}"/>
But I got an error:
The attachable property 'NextElement' was not found in type 'SetFocusOnPickerSelectedBehavior'.
thnx

I found out that my mistake was, that I wanted to put my behavior in the same namespace like the the CommunityToolkit's:
namespace CommunityToolkit.Maui.Behaviors;
That not worked. I changed to my own namespace:
namespace Maui.Behaviors;
Imported in ContentPage xaml like:
xmlns:behaviors="clr-namespace:Maui.Behaviors"
Used on picker component like:
<Picker x:Name="position" Title="Select..."
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding Position, Mode=TwoWay}"
behaviors:SetFocusOnPickerSelectedBehavior.NextElement="{x:Reference club}"/>

Related

Create Wizard with MVVM Pattern - Databinding and GUI Update not working

I am creating a Wizard for automatic Report generating. Therefore the user enters a couple of Issues to the Mainwindow and after he finished, he can create an automatically filled report by clicking a button. Also he need to switch between the entered Issuses (Therefore I put the issues to a list of the Type "Compliant".
The MainWindow contains different controls, in which the user can enter some information.
All controls are binded to the Model like this way (example for a textbox):
Text="{Binding Path=SingleCompliant.Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
"SingleCompliant" is an object of the ViewModel of the type "Compliant" (part of the Model), which contains all attributes needed for one Issue.
When I load the Application for the first time, everything works fine.
But when I click on "SaveCompliantAndNext", the binding between "SingleCompliant" Object gets lost and the GUI is not updating. In this Method (raised by a command) I create a new SingleCompliant Object.
What do I need to do for getting a new Object with an empty gui, so the user can continue entereing the next Issue?
This is the ViewModel with implemented "PropertyChanged" Handling:
public class Compliant_ViewModel : ObservableCollection<Compliant> //INotifyPropertyChanged
{
/// <summary>
/// MainWindow Controls are binded to Attributes of this object
/// </summary>
private Compliant _SingleCompliant;
public ObservableCollection<Compliant> CompliantListReport
{
get { return _CompliantListReport; }
private set { _CompliantListReport = value; }
}
/// <summary>
/// Beandstandungs-Objekt
/// </summary>
public Compliant SingleCompliant
{
get { return _SingleCompliant; }
set
{ _SingleCompliant = value;
OnPropertyChanged("SingleCompliant");
}
}
/// <summary>
/// Load next Compliant to the GUI
/// </summary>
public NextCompliant_Command NextCompliant_command { get; private set; }
/// <summary>
/// Load previous Compliant to the GUI
/// </summary>
public PreviousCompliant_Command PreviousCompliant_Command { get; private set; }
/// <summary>
/// Constructor
/// </summary>
public Compliant_ViewModel()
{
SingleCompliant = new Compliant();
CompliantListReport = new ObservableCollection<Compliant>();
NextCompliant_command = new NextCompliant_Command(SaveCompliantAndNext);
PreviousCompliant_Command = new PreviousCompliant_Command(LoadPreviousCompliant);
CreateReport_Command = new CreateReport_Command(CreateReport);
}
public void SaveCompliantAndNext()
{
CompliantListReport.Add(SingleCompliant);
// >>>>> Not working - databinding get lost and gui not updating
SingleCompliant = new Compliant();
}
public void LoadPreviousCompliant()
{
if (this.SingleCompliant.CompliantID > 0)
{ this.SingleCompliant = this.CompliantListReport[this.SingleCompliant.CompliantID - 1]; }
else
{ this.SingleCompliant = this.CompliantListReport[0]; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{ handler(this, new PropertyChangedEventArgs(propertyname)); }
}
}
This is the Model:
public class Compliant : INotifyPropertyChanged
{
public string _Text;
public string Text
{
get { return _Text; }
private set
{
_Text = value;
OnPropertyChanged("Text");
}
}
#region Konstruktoren
public Compliant()
{ }
#region Interface
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyname));
}
}
#endregion Interface
}
XAML:
Example for one Textbox:
<TextBox Text="{Binding Path=SingleCompliant.Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" x:Name="Text" />
XAML Buttons to switch between the Compliants (Issues):
<!--Previous Compliant (Issue)-->
<Button x:Name="BtnBack_Compliant" Content="{DynamicResource Back}" Command="{Binding PreviousCompliant_Command}"/>
<!--Next Compliant (Issue)-->
<Button x:Name="BtnForeward_Compliant" Content="{DynamicResource Foreward}" Command="{Binding NextCompliant_command}" />
What do I make wrong?

Load scene with param variable Unity

In my game there is a map view that contains a 50x50 grid of tiles. When you click on the tile you get sent to that tiles view and attack things, etc. The only difference between these "tiles" as far as the code will be concerned is the tiles ID, aka. which number on the grid. That number will be passed to the server on init to handle the rest.
Obviously, with that being the only difference in tiles it would be a mistake to create a scene "1", scene "2"...scene "2500" and call SceneManager.LoadScene to switch to the specific tile view.
I could use DontDestroyOnLoad(); when the tile is clicked to preserve the tile ID on scene switch but 1) it only accepts gameobjects not just an int variable 2) I don't need/want to preserve that variable for anything more than the init in tile view. So while it could work it seems like overkill.
Is there a better practice for essentially just passing a parameter to a scene load?
You can make a static class that holds the information for you. This class will not be attached to any GameObject and will not be destroyed when changing scene. It is static which means there can only be ONE of it; you can't write StaticClassName scn = new StaticClassName() to create new static classes. You access them straight through StaticClassName.SomeStaticMethod() for example and can be accessed from anywhere. See this example on how to store a value in a variable, change scene and then use it in that scene (please note the added using UnityEngine.SceneManagement;):
A normal Unity script attached to a GameObject in Scene "Test":
using UnityEngine;
using UnityEngine.SceneManagement;
public class TestingScript : MonoBehaviour
{
void Start()
{
StaticClass.CrossSceneInformation = "Hello Scene2!";
SceneManager.LoadScene("Test2");
}
}
A new static class (not inheriting from monobehaviour) that holds information:
public static class StaticClass
{
public static string CrossSceneInformation { get; set; }
}
A script attached to a game object in scene "Test2":
using UnityEngine;
public class TestingScript2: MonoBehaviour
{
void Start ()
{
Debug.Log(StaticClass.CrossSceneInformation);
}
}
You don't need to have the entire class static (if you for some reason need to create more instances of it). If you were to remove the static from the class (not the variable) you can still access the static variable through StaticClass.CrossSceneInformation but you can also do StaticClass sc = new StaticClass();. With this sc you can use the class's non-static members but not the static CrossSceneInformation since there can only be ONE of that (because it's static).
Just wanted to share a premade solution for this as the question is already answered, so others or maybe even the OP use it.
This script uses a key-value approach for storing and modifying variables inside a static class that means it is available across scenes so you can use it as a cross-scene persistent storage, so all you need to do is import the script to your Unity project and use the API (check below for an example):
using System.Collections.Generic;
/// <summary>
/// A simple static class to get and set globally accessible variables through a key-value approach.
/// </summary>
/// <remarks>
/// <para>Uses a key-value approach (dictionary) for storing and modifying variables.</para>
/// <para>It also uses a lock to ensure consistency between the threads.</para>
/// </remarks>
public static class GlobalVariables
{
private static readonly object lockObject = new object();
private static Dictionary<string, object> variablesDictionary = new Dictionary<string, object>();
/// <summary>
/// The underlying key-value storage (dictionary).
/// </summary>
/// <value>Gets the underlying variables dictionary</value>
public static Dictionary<string, object> VariablesDictionary => variablesDictionary;
/// <summary>
/// Retrieves all global variables.
/// </summary>
/// <returns>The global variables dictionary object.</returns>
public static Dictionary<string, object> GetAll()
{
return variablesDictionary;
}
/// <summary>
/// Gets a variable and casts it to the provided type argument.
/// </summary>
/// <typeparam name="T">The type of the variable</typeparam>
/// <param name="key">The variable key</param>
/// <returns>The casted variable value</returns>
public static T Get<T>(string key)
{
if (variablesDictionary == null || !variablesDictionary.ContainsKey(key))
{
return default(T);
}
return (T)variablesDictionary[key];
}
/// <summary>
/// Sets the variable, the existing value gets overridden.
/// </summary>
/// <remarks>It uses a lock under the hood to ensure consistensy between threads</remarks>
/// <param name="key">The variable name/key</param>
/// <param name="value">The variable value</param>
public static void Set(string key, object value)
{
lock (lockObject)
{
if (variablesDictionary == null)
{
variablesDictionary = new Dictionary<string, object>();
}
variablesDictionary[key] = value;
}
}
}
And you can use it on a script that is inside your Main Menu scene let's say:
public class MainMenuScript : MonoBehaviour
{
void Start()
{
// Load a sample level
LoadLevel(12);
}
public void LoadLevel(int level)
{
GlobalVariables.Set("currentLevelIndex", level);
UnityEngine.SceneManagement.SceneManager.LoadScene("Game");
}
}
And the other one is inside Game scene:
public class GameScript : MonoBehaviour
{
void Start()
{
int levelIndex = GlobalVariables.Get<int>("currentLevelIndex");
Debug.Log(levelIndex); // Outputs 12
}
}
So, the data is assigned in the Main Menu scene is accessible from the Game or any other scene.
Learn more about the script with usage examples >
Maakep! Perfect and easy code!
But your method to load scene not working.
You can use another method:
UnityEngine.SceneManagement.SceneManager.LoadScene("Vuforia-4-Spheric");

Workflow Foundation Native activity child activities on designer

I've created Native Activity that looks like this:
public sealed class ConsoleColorScope : NativeActivity
{
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleColorScope"/> class.
/// </summary>
public ConsoleColorScope()
{
this.Color = ConsoleColor.Gray;
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets Body.
/// </summary>
[DefaultValue(null)]
public Activity Body { get; set; }
/// <summary>
/// Gets or sets Color.
/// </summary>
public ConsoleColor Color { get; set; }
#endregion
#region Methods
/// <summary>
/// The execute.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
protected override void Execute(NativeActivityContext context)
{
context.Properties.Add(ConsoleColorProperty.Name, new ConsoleColorProperty(this.Color));
if (this.Body != null)
{
context.ScheduleActivity(this.Body);
}
}
#endregion
/// <summary>
/// The console color property.
/// </summary>
private class ConsoleColorProperty : IExecutionProperty
{
#region Constants and Fields
/// <summary>
/// The name.
/// </summary>
public const string Name = "ConsoleColorProperty";
/// <summary>
/// The color.
/// </summary>
private readonly ConsoleColor color;
/// <summary>
/// The original.
/// </summary>
private ConsoleColor original;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleColorProperty"/> class.
/// </summary>
/// <param name="color">
/// The color.
/// </param>
public ConsoleColorProperty(ConsoleColor color)
{
this.color = color;
}
#endregion
#region Explicit Interface Methods
/// <summary>
/// Cleanup the workflow thread.
/// </summary>
void IExecutionProperty.CleanupWorkflowThread()
{
Console.ForegroundColor = this.original;
}
/// <summary>
/// Setup the workflow thread.
/// </summary>
void IExecutionProperty.SetupWorkflowThread()
{
this.original = Console.ForegroundColor;
Console.ForegroundColor = this.color;
}
#endregion
}
}
This is class taken from the working sample:
http://code.msdn.microsoft.com/windowsdesktop/Windows-Workflow-c5649c23#content
However, when I open XAML file, I'm unable to see child activities within scope, as it is shown on the picture on the link above. All I can see is the names of the scopes.
I've created my own version of NativeActivity and I have same issue. Is there some procedure that I have to follow that would allow me to see body of NativeActivity in which I can drag and drop other activities (similar to Sequence activity) as it is shown on the demo description?
You'll need to create an Activity Designer project to go along with your custom activity which has a drop-zone (a WorkflowItemPresenter control) for placing an activity to fill your custom activity's body property with. Then you can setup the plumbing to link your designer to an activity. The following illustrates the steps in detail.
Create A New Activity Designer Project
In your solution, add a new Activity Designer project named like <Your Custom Activity Library>.Design. The assembly must be named <Your Custom Activity Library>.Design.dll, so that Visual Studio will use your activity designers for your custom activities. In the XAML for your designer, you'll utilize the WorkflowItemPresenter to display a drop-zone that accepts an activity that users of your custom activity can use.
<sap:ActivityDesigner x:Class="Your_Custom_Activity_Library.Design.ConsoleColorScopeDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
<Grid>
<sap:WorkflowItemPresenter HintText="Drop an activity here!"
Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"
MinWidth="300"
MinHeight="150" />
</Grid>
</sap:ActivityDesigner>
The ModelItem in the XAML represents your activity, and you can have your designer set any property in your activity with it. In the above sample, I'm setting up the WorkflowItemPresenter to bind to your activity's Body property.
Registering Designers With Your Activity
Next, add a class called RegisterMetadata (can be any name really) that implements the IRegisterMetadata interface. Its implementation is very simple:
public class RegisterMetadata : IRegisterMetadata
{
/// <summary>
/// Registers all activity designer metadata.
/// </summary>
public static void RegisterAll()
{
// Attribute table builder for the metadata store.
AttributeTableBuilder builder = new AttributeTableBuilder();
// Register the designer for the custom activities.
builder.AddCustomAttributes(typeof(ConsoleColorScope), new DesignerAttribute(typeof(ConsoleColorScopeDesigner)));
// Add the attributes to the metadata store.
MetadataStore.AddAttributeTable(builder.CreateTable());
}
/// <summary>
/// Registers this instance.
/// </summary>
public void Register()
{
RegisterMetadata.RegisterAll();
}
}
The way Visual Studio loads your custom activity designers is by looking for assemblies named <Your Custom Activity Library>.Design.dll, and then it looks for a public class that implements the IRegisterMetadata interface.
You should now be able to drag and drop your custom activity to a workflow and it will have a drop-zone allowing you to specify the Body activity.
You can get fancy with your designer, and expose friendly controls to allow a user to setup your custom activity. You could also create your own designers for the out-of-the-box activities provided in the .NET Framework.
Hope that helps.

this.VerifyPropertyName(propertyName); in OnPropertyChanged() always returns null

I have a xaml code as follows:
On MouseLeftButtonDown i redirect it to viewModel where the color GenerateGlowEffect must change. This is not reflected. It always returns null for this.PropertyChanged
XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:telerikDocking="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Docking"
xmlns:Telerik_Windows_Controls="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls"
xmlns:System="clr-namespace:System;assembly=mscorlib" x:Class="TabItemContents.MapDetail"
mc:Ignorable="d" Width="Auto" Height="430.333">
<Grid x:Name="LayoutRoot" Background="{StaticResource TabControlActiveAreaColor}" Height="430.333" VerticalAlignment="Top" HorizontalAlignment="Left">
<Grid.RowDefinitions>
<RowDefinition Height="192.833"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle IsHitTestVisible="True" x:Name="Rectangle_Generate" Margin="25.334,70.167,0,65.333" Cursor="{DynamicResource HandCursor}" RadiusX="10" RadiusY="10" HorizontalAlignment="Left" Width="88.5" StrokeThickness="1.5" d:LayoutOverrides="HorizontalAlignment" MouseLeftButtonDown="Rectangle_Generate_MouseLeftButtonDown" >
<Rectangle.Fill>
<SolidColorBrush Color="{DynamicResource RectangleColor}"/>
</Rectangle.Fill>
<Rectangle.Effect >
<DropShadowEffect RenderingBias="Quality" BlurRadius="{DynamicResource BlurRadius}" ShadowDepth="0" Color="{Binding GenerateGlowEffect}" />
</Rectangle.Effect>
</Rectangle>
</Grid>
CODEBEHIND:
public partial class MapDetail : UserControl
{
ViewModel ViewModelObject;
public MapDetail()
{
InitializeComponent();
ViewModelObject= new ViewModelObject((IUnityContainer)Application.Current.Properties["UnityContainer"]);
}
private void Rectangle_Generate_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
ViewModelObject.SetColor();
}
}
VIEWMODEL:
public class ViewModel : ViewModelBase
{
private IUnityContainer _unityContainer;
private ILogger _logger;
private Model model;
private string _generateGlowEffect;
public ViewModel(IUnityContainer unityContainer)
{
_unityContainer = unityContainer;
try
{
model = new Model(unityContainer);
}
catch (Exception ex)
{
throw ex;
}
}
public void SetColor()
{
GenerateGlowEffect = "White";
}
public string GenerateGlowEffect
{
get { return _generateGlowEffect; }
set {
_generateGlowEffect = value;
OnPropertyChanged("GenerateGlowEffect");
}
}
}
}
public string GenerateGlowEffect
{
get { return _generateGlowEffect; }
set {
_generateGlowEffect = value;
OnPropertyChanged("GenerateGlowEffect");
}
}
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged, IDisposable
{
#region Constructor
protected ViewModelBase()
{
}
#endregion // Constructor
#region DisplayName
/// <summary>
/// Returns the user-friendly name of this object.
/// Child classes can set this property to a new value,
/// or override it to determine the value on-demand.
/// </summary>
public virtual string DisplayName { get; protected set; }
#endregion // DisplayName
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region IDisposable Members
/// <summary>
/// Invoked when this object is being removed from the application
/// and will be subject to garbage collection.
/// </summary>
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Child classes can override this method to perform
/// clean-up logic, such as removing event handlers.
/// </summary>
protected virtual void OnDispose()
{
}
#endregion // IDisposable Members
}
}
If this.PropertyChanged is null it means that your XAML is not bound against the property.
You need to set the DataContext otherwise the WPF engine doesn't know what to bind against.
public MapDetail()
{
InitializeComponent();
this.ViewModelObject= new VViewModelObject((IUnityContainer)Application.Current.Properties["UnityContainer"]);
this.DataContext = this.ViewModelObject;
}

Why does Autofac fail to find log4net using the "LogInjectionModule"?

As discussed on the Autofac Wiki, the best way to automatically inject the log4net.ILog implementation for a class is to use the LogInjectionModule. This module's implementation is given in the wiki article:
public class LogInjectionModule : Module
{
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
if (registration == null) throw new ArgumentNullException("registration");
registration.Preparing += OnComponentPreparing;
}
static void OnComponentPreparing(object sender, PreparingEventArgs e)
{
var t = e.Component.Activator.LimitType;
e.Parameters = e.Parameters.Union(new[]
{
new ResolvedParameter((p, i) => p.ParameterType == typeof(ILog), (p, i) => LogManager.GetLogger(t))
});
}
}
I'm including this module along with a module of my own to configure some components:
var builder = new ContainerBuilder();
builder.RegisterModule(new Common.Logging.LogInjectionModule());
builder.RegisterModule(new DataLayer.DataLayerModule("connectionstring"));
builder.RegisterType<SomeServiceType>();
AutofacHostFactory.Container = builder.Build();
Inside the DataLayerModule I'm constructing a type as follows:
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new DataAccess(this.ConnectionString, c.Resolve<ILog>()))
.As<IDataAccess>();
// Some other type registrations...
}
When my application attempts to construct an IDataAccess object I get the following exception:
The requested service 'log4net.ILog' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
I know that on the wiki page it is mentioned that the injection method only works for constructor injection. However I thought that was what I was doing here. Am I wrong?
Edit
On further investigation, my original answer doesn't fix your problem (I've left it below for reference). The issue is that your code bypasses the LogInjectionModule by creating a new instance of DataAccess and resolving ILog directly. This is not a usage pattern that is supported by the LogInjectionModule, which is designed to provide an ILog instance that matches the type of the instance being activated by Autofac (log4net's LogManager.GetLogger() method needs to know about the type that is using the logger).
To work around this, you have two options. The simplest is to provide the logger instance directly without using the LogInjectionModule:
builder.Register(c =>
new DataAccess(
this.ConnectionString,
LogManager.GetLogger(typeof(DataAccess)))
.As<IDataAccess>();
Alternatively (and more cleanly), you can register DataAccess in the normal Autofac way, and then register IDataAccess by using named parameters to specify the connection string that Autofac passes to the constructor. With this technique, you are enabling Autofac to use the LogInjectionModule when resolving the constructor's ILog parameter.
builder.RegisterType<DataAccess>();
builder.Register(c =>
c.Resolve<DataAccess>(
new NamedParameter("connectionString", this.ConnectionString)))
.As<IDataAccess>();
(Note that this code assumes that the connection string parameter in the DataAccess constructor is named "connectionString").
The advantage of the second technique is that your module doesn't reference log4net's LogManager class directly, so the only coupling is to the ILog interface.
Original (incorrect) answer
That code sample is for Autofac v2.0 (I originally provided the sample on that wiki page). The code for my current working module (for Autofac v2.4.5) is:
public class LogInjectionModule : ComponentInjectionModule
{
/// <summary>
/// Called when Autofac is preparing a component for activation.
/// </summary>
/// <param name = "sender">
/// The sender.
/// </param>
/// <param name = "e">
/// The <see cref = "ActivatingEventArgs{T}" /> instance containing the event data.
/// </param>
protected override void OnComponentPreparing(object sender, PreparingEventArgs e)
{
Enforce.ArgumentNotNull(e, "e");
Type t = e.Component.Activator.LimitType;
e.Parameters = e.Parameters.Union(
new[]
{
new ResolvedParameter(
(p, i) => p.ParameterType == typeof(ILog), (p, i) => LogManager.GetLogger(t))
});
}
}
That code relies on my ComponentInjectionModule class which is defined below. There are some subtle differences in the way that this module hooks into the registration/activation process.
/// <summary>
/// Base module for injecting into registrations when they are prepared/activated.
/// </summary>
public class ComponentInjectionModule : IModule
{
/// <summary>
/// Apply the module to the component registry.
/// </summary>
/// <param name = "componentRegistry">
/// Component registry to apply configuration to.
/// </param>
public void Configure(IComponentRegistry componentRegistry)
{
Enforce.ArgumentNotNull(componentRegistry, "componentRegistry");
foreach (var registration in componentRegistry.Registrations)
{
this.AttachToComponentRegistration(registration);
}
componentRegistry.Registered +=
(sender, e) => this.AttachToComponentRegistration(e.ComponentRegistration);
}
/// <summary>
/// Attaches to the <see cref = "IComponentRegistration.Preparing" /> event of a component registration.
/// </summary>
/// <param name = "registration">
/// The registration whose Preparing event will be attached.
/// </param>
protected virtual void AttachToComponentRegistration(IComponentRegistration registration)
{
Enforce.ArgumentNotNull(registration, "registration");
registration.Preparing += this.OnComponentPreparing;
registration.Activating += this.OnComponentActivating;
registration.Activated += this.OnComponentActivated;
}
/// <summary>
/// Called when Autofac has activated a component.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ActivatedEventArgs{T}"/> instance containing the event data.</param>
[SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", Justification = "Not an event handler")]
protected virtual void OnComponentActivated(object sender, ActivatedEventArgs<object> e)
{
}
/// <summary>
/// Called when Autofac is activating a component.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ActivatingEventArgs{T}"/> instance containing the event data.</param>
[SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", Justification = "Not an event handler")]
protected virtual void OnComponentActivating(object sender, ActivatingEventArgs<object> e)
{
}
/// <summary>
/// Called when Autofac is preparing a component for activation.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ActivatingEventArgs{T}"/> instance containing the event data.</param>
[SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", Justification = "Not an event handler")]
protected virtual void OnComponentPreparing(object sender, PreparingEventArgs e)
{
}
}