Log method parameters and return type using Enterprise library logging application block - enterprise-library

Is there any way to log method parameter name , its value and return type value using Enterprise library logging application block.
I have provided a code sample below. The requirement is to log it's methods input parameters value and its return type value
// Complex Types
public class UserDetails
{
public string UserName { get; set; }
public int UserAge { get; set; }
public string UserAddress { get; set; }
}
public class User
{
public string UserId { get; set; }
public string Pwd { get; set; }
}
//Interface
public interface IService
{
UserDetails GetUserDetails(User ReqUser);
}
//Imp
public class Service : IService
{
[LogCallHandler(Categories = new string[] { "General" }, LogBeforeCall = true, LogAfterCall = true ,
BeforeMessage = "This occurs before the call to the target object",AfterMessage="This occured after method call",IncludeParameters=true)]
public UserDetails GetUserDetails(User ReqUser)
{
UserDetails oUD = new UserDetails();
oUD.UserName = "hhh" + ReqUser.UserId;
oUD.UserAge = 100;
oUD.UserAddress = "HHHHHHHHHHHHHHHHHHHHHHH";
return oUD;
}
#endregion
}
//Usage
private void button2_Click(object sender, EventArgs e)
{
IUnityContainer container = new UnityContainer().LoadConfiguration();
container.AddNewExtension<EnterpriseLibraryCoreExtension>();
IService service = container.Resolve<IService>();
User nUser = new User();
nUser.UserId = "TTTTT";
nUser.Pwd = "XXXXX";
UserDetails mm = service.GetUserDetails(nUser);
}
Could anyone please explain how to implement this using Enterprise library logging application block?

You can write an OnMethodBoundaryAspect to intercept your method calls using PostSharp API.
OnMethodBoundaryAspect.OnEntry method includes MethodExecutionArgs parameter which provides all the information you need about the method and its arguments.
See this post for a sample logging aspect implementation very close to your requirements.
// This method is executed before the execution of target methods of this aspect.
public override void OnEntry( MethodExecutionArgs args )
{
// Build method information to log.
string methodInfo = BuildMethodInformation(args.Arguments);
// continue with your logging...
}
You can get method parameters via Arguments member of MethodExecutionArgs parameter like this:
private string BuildMethodInformation(Arguments arguments)
{
var sb = new StringBuilder();
sb.Append(_methodName);
foreach (var argument in arguments.ToArray())
{
sb.Append(arguments.GetArgument( i ) ?? "null");
}
return sb.ToString();
}
For method parameters, check this or this samples. They are built for caching but BuildCacheKey/GetCacheKey methods include all the information you need to get argument information of a method.

You can use EntLib LogCallHandler by code:
container.AddNewExtension<EnterpriseLibraryCoreExtension>();
container.RegisterType<IService, Service>(
new InterceptionBehavior<PolicyInjectionBehavior>(),
new Interceptor<TransparentProxyInterceptor>());
Or by config file:
<unity>
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />
<namespace name="LoggingCallHandler" />
<assembly name="LoggingCallHandler" />
<container>
<extension type="Interception" />
<extension type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity.EnterpriseLibraryCoreExtension, Microsoft.Practices.EnterpriseLibrary.Common" />
<register type="IService" mapTo="Service">
<interceptor type="TransparentProxyInterceptor" />
<policyInjection />
</register>
</container>
</unity>
Here, LoggingCallHandler is namespace/assembly for your service class. Alternatively, you can define your type alias like this:
<alias alias="Service" type="LoggingCallHandler.Service, LoggingCallHandler"/>
<alias alias="IService" type="LoggingCallHandler.IService, LoggingCallHandler"/>
See this or this discussion for full configuration including logging block configuration.

Related

Get current logged in user in DbContext

For audit purposes I'm trying to get the current logged in user in my DbContext. However I'm having some issues with this. A few things to take into account:
In Blazor Server we have to use AddDbContextFactory
IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
I created a custom DbContext that injects AuthenticationStateProvider.
public partial class CustomDbContext : DbContext
{
private AuthenticationStateProvider _authenticationStateProvider;
#region construction
public CustomDbContext ()
{
}
public CustomDbContext (AuthenticationStateProvider stateProvider)
{
_authenticationStateProvider = stateProvider;
}
[ActivatorUtilitiesConstructor]
public CustomDbContext (DbContextOptions<CustomDbContext> options, AuthenticationStateProvider stateProvider) : base(options)
{
_authenticationStateProvider = stateProvider;
}
public CustomDbContext(DbContextOptions<CustomDbContext> options) : base(options)
{
}
#endregion
...
In this DbContext, when overwriting the SaveChanges I get the User and their claims:
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
var userIdClaim = state.User.Claims.FirstOrDefault(c => c.Type == "userId")?.Value;
userId = userIdClaim != null && !string.IsNullOrEmpty(userIdClaim ) ? userIdClaim : string.Empty;
...
However when I call .CreateDbContext(); on the injected DbContextFactory, I get the following exception:
'Cannot resolve scoped service
'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'
from root provider.'
I've found some topics about this, but the suggested solution there is to create a custom DbContextFactory that is scoped. But then you lose the reason why you are using the DbContextFactory, no?
Any ideas on how to solve this?
Thank you
The DBContextFactory is a singleton registered in the root application DI container, while the AuthenticationStateProvider is a scoped service that is registered in the Hub session DI container. You can't access a lower order service from a higher order service.
You need to rethink your design and provide the user information from whatever scoped service is making whatever call to need a DbConbtext.
Additional Information
I'm not sure what your data pipeline looks like so this example uses the Blazor template weather forecast.
First a View Service that components inject and use.
This injects the AuthenticationStateProvider. It gets the current user for each request and passes it to the data pipeline in a request object.
public class WeatherForecastViewService
{
private AuthenticationStateProvider _authenticationStateProvider; // scoped service
private WeatherForecastService _weatherForecastService; //Singleton Service
public WeatherForecastViewService(AuthenticationStateProvider authenticationStateProvider, WeatherForecastService weatherForecastService)
{
_authenticationStateProvider = authenticationStateProvider;
_weatherForecastService = weatherForecastService;
}
public async ValueTask SaveWeatherForecast(WeatherForecast record)
{
var user = await GetCurrentUser();
var request = new RecordRequest<WeatherForecast>(record, user );
await _weatherForecastService.SaveRecord(request);
}
private async ValueTask<ClaimsPrincipal> GetCurrentUser()
{
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
return state.User ?? new ClaimsPrincipal();
}
}
Here are the request and result objects:
public readonly struct RecordRequest<TRecord>
{
public TRecord Record { get; init; }
public ClaimsPrincipal Identity { get; init; }
public RecordRequest(TRecord record, ClaimsPrincipal identity)
{
this.Record = record;
this.Identity = identity;
}
}
public record RecordResult
{
public bool SuccessState { get; init; }
public string Message { get; init; }
private RecordResult(bool successState, string? message)
{
this.SuccessState = successState;
this.Message = message ?? string.Empty;
}
public static RecordResult Success(string? message = null)
=> new RecordResult(true, message);
public static RecordResult Failure(string message)
=> new RecordResult(false, message);
}
And here's the singleton data service
public class WeatherForecastDataService
{
// This is a singleton
private readonly IDbContextFactory<DbContext> _factory;
public WeatherForecastDataService(IDbContextFactory<DbContext> factory)
=> _factory = factory;
public async ValueTask<RecordResult> SaveRecord(RecordRequest<WeatherForecast> request)
{
if (!request.Identity.IsInRole("SomeRole"))
return RecordResult.Failure("User does not have authority");
// simulates some async DB activity
await Task.Delay(100);
// Get your DbContext from the injected Factory
// using var dbContext = this.factory.CreateDbContext();
// do your db stuff
return RecordResult.Success();
}
}
PS I haven'y actually run this code so there may be some typos!
IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
Nothing to do with whether IHttpContextAccessor is not thread safe... It's simply because the HttpContext object is not available in Blazor Server App, as communication between the client side (browser) and server side is done through the SignalR protocol, not HTTP. But there is a way how to access the HttpContext object before the Blazor App is rendered, as the initial call to the app is always made through HTTP request; that is, when you enter a url into the address bar of your browser and hit the enter button. See here how to do that...
The following code snippet describes how to inject an AuthenticationStateProvider into the ApplicationDbContext object created by default when you select Individual Accounts in Blazor Server App.
Copy and test. It should work...
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
public async Task<Employee> CreateEmployee(Employee employee)
{
CancellationTokenSource cancellationTokenSource = new
CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
return employee;
}
}
Index.razor
#inject EmployeeRepository EmployeeRepository
#using <Put here....>.Models
<button type="button" #onclick="SaveEmployee">Save Employee</button>
#if (emp != null)
{
<div>#emp.ID.ToString()</div>
<div>#emp.FirstName</div>
<div>#emp.LastName</div>
<div>#emp.City</div>
}
#code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
// Created by the default template
//services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
// This is your code...
services.AddScoped<ApplicationDbContext>(p =>
p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>
().CreateDbContext());
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider,
RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
UPDATE:
but does that no against the the recommendations of Microsoft? They ae suggesting to always use using
var context = DbFactory.CreateDbContext();
You mean:
using var context = DbFactory.CreateDbContext();
No, it is not against the recommendations of Microsoft. It's another way to instantiate the DbContext. I did it that way in order to stick to this code by you:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
Anyhow, these are the changes you should make in order to reflect "Microsoft's recommendations"
Change:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
To:
services.AddScoped<ApplicationDbContext>();
Change:
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
To:
private readonly IDbContextFactory<ApplicationDbContext>
DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext>
_DbFactory)
{
DbFactory = _DbFactory;
}
And change:
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
To:
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
Also add:
using var context = DbFactory.CreateDbContext();
at the beginning of the EmployeeRepository.CreateEmployee method
Run and test.
Hope this work...
New Version
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly IDbContextFactory<ApplicationDbContext> DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext> _DbFactory)
{
DbFactory = _DbFactory;
}
public async Task<Employee> CreateEmployee(Employee
employee)
{
using var context = DbFactory.CreateDbContext();
// CancellationTokenSource provides the token and have authority to cancel the token
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
return employee;
}
}
Index.razor
#inject EmployeeRepository EmployeeRepository
#using <Put here....>.Models
<button type="button" #onclick="SaveEmployee">Save Employee</button>
#if (emp != null)
{
<div>#emp.ID.ToString()</div>
<div>#emp.FirstName</div>
<div>#emp.LastName</div>
<div>#emp.City</div>
}
#code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
services.AddScoped<ApplicationDbContext>();
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
}

Xamarin Forms MVVM Databinding failing when I'm binding to a single object

I'm having an issue with data not binding correctly on a details page when I have clicked through from a ListView via a button. The ListView binds perfectly and the object gets passed through to the details page. The Id of the object is read and a full version of the object is called from an API and set to a new instance of the object. When I add a breakpoint, the full object is available, but Labels on the view aren't populated. Here is the ViewModel:
DetailsViewModel.cs
public class DetailsViewModel
{
public Deal Deal { get; set; }
public int DealId { get; set; }
public DetailsViewModel(int id)
{
Deal = new Deal();
DealId = id;
}
public async void GetDeal()
{
var deal = await Deal.GetDeal(DealId);
if(deal != null)
{
Deal = deal;
}
}
}
The codebehind looks like this:
DetailPage.Xaml.cs
DetailsViewModel viewModel;
int dealId;
public DetailPage(int id)
{
InitializeComponent();
dealId = id;
viewModel = new DetailsViewModel(dealId);
BindingContext = viewModel;
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.GetDeal();
}
And the Xaml file is
DetailPage.Xaml
<ContentPage.Content>
<ScrollView>
<StackLayout x:Name="detailsLayout">
<Label Text="{Binding Deal.Name}" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
When I put a breakpoint in Deal = deal on DetailsViewModel, the Deal object exists and has the correct data, but I just get a blank screen. I have tried Labels with Text="{Binding Name}" and Text="{Binding Deal.Name}".
I have also tried manually creating a deal in the GetDeal function of the ViewModel and still nothing is bound.
1) Ensure your property Notifies the UI of a change implementing the INotifyPropertyChanged interface. See https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm
2) Ensure the set is done on the UI thread using Device.BeginInvokeOnMainThread. https://learn.microsoft.com/fr-fr/dotnet/api/xamarin.forms.device.begininvokeonmainthread?view=xamarin-forms
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
namespace YourNamespace
{
public class DetailsViewModel : INotifyPropertyChanged
{
private Deal _deal;
public Deal Deal
{
get => _deal;
set
{
if (_deal != value)
{
_deal = value;
OnPropertyChanged();
}
}
}
public int DealId { get; set; }
public DetailsViewModel(int id)
{
//!! useless assignation
//Deal = new Deal();
DealId = id;
}
public async void GetDeal()
{
var deal = await Deal.GetDeal(DealId);
if (deal != null)
{
//Ensure we are on UI thread
Device.BeginInvokeOnMainThread(() => Deal = deal);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Autofac wait for module to become available

Because the order of modules being resolved is not guaranteed I'm having some problem achieving this:
I have a module which registers a ScheduleService this ScheduleService is responsible for trigger events at set intervals etc.
I'm able to load in different IScheduable items which i do so using the XML Configuration. The problem that i have, is the IScheduable items require the IScheduleService to be ready so it can register it's self.
So in my <autofac><modules> I have
<module type="Namespace.ScheduleServiceModule, Namespace" />
Then the idea was I could load in as many different ISchedulable items
<module type="SomeNamespace.ScheudleItem1, SomeNamespace />
<module type="SomeNamespace.ScheudleItem2, SomeNamespace />
<module type="SomeNamespace.ScheudleItem3, SomeNamespace />
<module type="SomeNamespace.ScheudleItem4, SomeNamespace />
This is currently how I do it in those scheduleitem modules:
protected override void Load(ContainerBuilder builder)
{
builder.RegisterCallback(registry =>
{
var scheduleService = new TypedService(typeof(IScheduleService));
var registrations = registry.RegistrationsFor(scheduleService);
if (registrations != null && registrations.Any())
{
IComponentRegistration componentRegistration = registrations.First();
componentRegistration.Activated += (sender, args) =>
{
IScheduleService scheduleService = args.Instance as IScheduleService;
if (scheduleService != null)
{
OnScheduleServiceAvailable(args.Context, scheduleService);
}
};
}
});
base.Load(builder);
}
This is the override in each of ScheduleItems
protected override void OnScheduleServiceAvailable(IComponentContext context,
IScheduleService scheduleService)
{
scheduleService.Add(
new SqlSyncSchedulable(Enabled, IntervalMS, ConnectionString, SqlSelect,
context.Resolve<ILoggerService>(),
context.Resolve<IPersonService>(),
context.Resolve<ILoggingEventService>(),
context.Resolve<ITemplateService>(),
context.Resolve<ITemplateLoggingEventService>(),
context.Resolve<IRuntimeSettingsService>()));
}
Which is quite intermittent. The ISchedule item should register itself but the problem is the Schedule service might be registered after those items.
There must be a way to achieve this?
I think your problem is not in the load order of the module, but is instead about dependency design.
You should design your modules and your dependencies in a way that they are not temporally coupled.
One of the many possible designs involves having the schedule service require a list of possible dependencies.
In this design, the responsibilitt of an ISchedule is in defining the parameters of a schedulable operation, you use Autofac Adapter pattern to wrap each schedule into a ISyncSchedulable operation, and the ScheduleService requires a List<ISyncSchedulable> in order to add them at initialization.
As an example (following your example, but not verbatim: I'm trying more to make a point than giving a complete solution):
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using NUnit.Framework;
namespace Example
{
public interface ISchedule
{
bool Enabled { get; }
long IntervalMs { get; }
string ConnectionString { get; }
string SqlSelect { get; }
}
public class Schedule : ISchedule
{
public bool Enabled
{
get { return true; }
}
public long IntervalMs
{
get { return 100000; }
}
public string ConnectionString
{
get { return "localhost;blabla"; }
}
public string SqlSelect
{
get { return "select 1 as A"; }
}
}
// let's assume SqlSyncSchedulable inherits from a common
// ISyncSchedulable interface
public interface ISyncSchedulable
{
void RunSchedule(ScheduleService scheduleService);
}
public class SqlSyncSchedulable : ISyncSchedulable
{
public ISchedule Schedule { get; private set; }
public OtherService OtherService { get; private set; }
public SqlSyncSchedulable(ISchedule schedule,
OtherService otherService
/*,ILoggerService loggerService
IPersonService personService, */
)
{
Schedule = schedule;
OtherService = otherService;
// read interval and other data from schedule,
// store service references as usual.
}
public void RunSchedule(ScheduleService scheduleService)
{
throw new NotImplementedException();
}
}
public class OtherService
{
}
public class ScheduleService
{
public ScheduleService(IList<ISyncSchedulable> schedulables, OtherService otherService /*, ... other dependencies */)
{
// there is no ADD! Autofac gives you a list of all possible
// ISyncSchedulable components
SyncSchedulables = schedulables;
// ... other dependencies
}
public IList<ISyncSchedulable> SyncSchedulables { get; set; }
// this code is not a proper implementation, nor a scheduler,
// it's just a placeholder
public void RunSchedules()
{
foreach (var schedule in SyncSchedulables)
{
// do your operations, involving ...
schedule.RunSchedule(this);
}
}
}
public class TestModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<ScheduleService>().AsSelf();
builder.RegisterType<OtherService>().AsSelf();
// don't worry about which type should be registered,
// and register each type inheriting from ISchedule
// coming from the current assembly
// You can even use a single registration for all the
// possible implementations of ISchedule, using techniques
// explained in http://docs.autofac.org/en/latest/register/scanning.html
builder.RegisterAssemblyTypes(GetType().Assembly)
.Where(t => t.GetInterfaces().Contains(typeof(ISchedule)))
.AsImplementedInterfaces()
.InstancePerDependency();
// This registration is a partial, because
// SqlSyncChedulable requires a single parameter
// of type ISchedule
builder.RegisterType<SqlSyncSchedulable>()
.AsImplementedInterfaces();
// for each ISchedule class, we register automatically
// a corresponding ISyncSchedulable, which
builder.RegisterAdapter<ISchedule, ISyncSchedulable>(RegisterISyncSchedulableForEachISchedule)
.InstancePerDependency();
}
private ISyncSchedulable RegisterISyncSchedulableForEachISchedule(IComponentContext context, ISchedule schedule)
{
// the parameter of type ISchedule is the corresponding schedule
var scheduleParam = new TypedParameter(typeof(ISchedule), schedule);
// all the other params are resolved automatically by Autofac.
return context.Resolve<ISyncSchedulable>(scheduleParam);
}
}
[TestFixture]
public class AutofacTest
{
[Test]
public void TestServiceResolution()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new TestModule());
var container = builder.Build();
var service = container.Resolve<ScheduleService>();
Assert.That(service.SyncSchedulables[0].GetType(), Is.EqualTo(typeof(SqlSyncSchedulable)));
}
}
}
Please note that the module resolution order is now completely decoupled with the runtime resolution.

How to redirect ILoggerFacade to ViewModel?

I want write my logs to the ViewModel, so I can expose logs to the users.
First I bind View to ViewModel
<TextBox Grid.Row="1" TextWrapping="Wrap" Text="{Binding Logger}" AcceptsReturn="True" IsReadOnly="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto"/>
This is the ViewModel
private string logger;
public string Logger
{
get { return logger; }
set
{
logger = value;
this.RaisePropertyChanged("Logger");
}
}
Then, I create the customer logger class which implements ILoggerFacade,and override the CreateLogger method in Bootstrapper.
In Bootstrapper
protected override ILoggerFacade CreateLogger()
{
return new MainLogger();
}
In Customer Logger class
public class MainLogger : ILoggerFacade
{
public void Log(string message, Category category, Priority priority)
{
string messageToLog = String.Format(System.Globalization.CultureInfo.InvariantCulture, "{1}: {2}. Priority: {3}. Timestamp:{0:u}.", DateTime.Now, category.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture), message, priority.ToString());
//////??????//////
}
}
And what should fill in ???????. I tried Import IEventAggregator to publish data to ViewModel, and directly import ViewModel here. Neither works, because CreatorLogger Method is called before container is registered. So how can I write logs to the ViewModel?
The logger should simply save the log message(s) and expose it in a property:
public interface IMainLogger : ILoggerFacade
{
List Messages { get; }
}
public class MainLogger : IMainLogger
{
public MainLogger()
{
Messages = new ObservableCollection<string>();
}
public ObservableCollection<string> Messages { get; private set; }
public void Log(string message, Category category, Priority priority)
{
string messageToLog = ...;
Messages.Add(messageToLog);
}
}
That's basically what a logger is supposed to do: Log messages. Now you want to display it inside some TextBox which is contained in a View that you inject into a region, right? To do that, you need to pass that logger to the Module of this region's view via constructor dependency injection. I am working with MEF, so I'm not too sure about how to do it with Unity, but it probably looks something like this, when you configure the container in code:
container.RegisterType<IMainLogger, MainLogger>(new ContainerControlledLifetimeManager());
container.RegisterType<ContainingTextBoxModule>(new InjectionConstructor(container.Resolve<IMainLogger>()));
where the module takes and exposes the logger:
public class ContainingTextBoxModule : IModule
{
public IMainLogger Logger { get; private set; }
public ContainingTextBoxModule(IMainLogger logger)
{
Logger = logger;
}
}
Then your ViewModel for the module can relay the message(s) and you can bind to it/them from your view.
Does this answer your question?

Registering concrete type with parameter is null using Autofac

I have the following class:
public class Errors
{
private readonly string _connectionString;
public Errors(string connectionString)
{
_connectionString = connectionString;
}
}
I'm trying to register using Autofac like so:
builder.RegisterType<Errors>().WithParameter("connectionString", System.Configuration.ConfigurationManager.ConnectionStrings["myConn"].ConnectionString);
This object is getting injected into another object but it's always null. Looking further into the exception, the following error message is displayed:
Cannot choose between multiple constructors with equal length 1 on type 'System.String'.
Select the constructor explicitly, with the UsingConstructor() configuration method, when the component is registered.
I've tried registering using the UsingConstructor and WithParameter and there's no change.
Try This, there are 2 ways to register
First Type:
builder.Register(c => new Errors(System.Configuration.ConfigurationManager.ConnectionStrings["myConn"].ConnectionString)).InstancePerLifetimeScope();
Second Type:
public class Errors
{
private string _connectionString{ get; set; }
public Errors(string connectionString)
{
_connectionString = connectionString;
}
}
Register Like below
builder.RegisterType<Errors>().WithParameter("connectionString", System.Configuration.ConfigurationManager.ConnectionStrings["myConn"].ConnectionString);
EDIT: Testing
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.Register(c => new Errors(ConfigurationManager.ConnectionStrings["myConn"].ConnectionString)).InstancePerLifetimeScope(); // Type 1
// builder.RegisterType<Errors>().WithParameter("connectionString", ConfigurationManager.ConnectionStrings["myConn"].ConnectionString); // Type 2
var container = builder.Build();
var objErrors = container.Resolve<Errors>();
}
}
And the config file looks like
<connectionStrings>
<add name="myConn" connectionString="Some Connection String"/>
</connectionStrings>
Please read this Autofac Wiki
Tested in Autofac Version: 2.6.1.841
Result: