How to redirect ILoggerFacade to ViewModel? - mvvm

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?

Related

Inheriting logging services from base class

I have a .Net Core 3.1 API controller with a constructor that looks like this:
public class MachineListsController : ControllerBase
{
private readonly jiWeb_ProdContext _context;
private readonly ILogger _logger;
private readonly ILoggingMessageService _loggingMessage;
public MachineListsController(jiWeb_ProdContext context, ILogger<MachineListsController> logger, ILoggingMessageService loggingMessage)
{
_context = context;
_logger = logger;
_loggingMessage = loggingMessage;
}
public string Message { get; set; }
...
}
You can see that I am injecting a .Net Core logging service and the database context into it.
Then I use the logging like this in my controller methods:
[HttpGet("FactoryMachines/{factoryId}")]
public async Task<ActionResult<IEnumerable<MachineList>>> GetMachinesForFactory(Guid factoryId)
{
var machineList = await _context.MachineList.Where(n => n.FactoryId == factoryId).ToListAsync();
Message = _loggingMessage.GetLogSuccess(this.GetType().Name.ToString(), ControllerActions.GetAction, "FactoryMachines", factoryId.ToString());
_logger.LogInformation(Message);
return machineList;
}
The logging is working great, but I'm realizing that I should create a base class that handles logging so I don't have to add or change it inside of every controller I write.
So I started to write this base controller:
[ApiController]
public class MyBaseController : ControllerBase
{
readonly jiWeb_ProdContext _context;
readonly ILogger _logger;
readonly ILoggingMessageService _loggingMessage;
public BaseController(jiWeb_ProdContext context, ILogger<BaseController> logger, ILoggingMessageService loggingMessage)
{
_context = context;
_logger = logger;
_loggingMessage = loggingMessage;
}
}
Then I changed my controller to inherit from it like this:
public class MachineListsController : MyBaseController
{
[HttpGet("FactoryMachines/{factoryId}")]
public async Task<ActionResult<IEnumerable<MachineList>>> GetMachinesForFactory(Guid factoryId)
{
var machineList = await _context.MachineList.Where(n => n.FactoryId == factoryId).ToListAsync();
return machineList;
}
}
But I'm getting error and I'm unsure of what to do on the next step.
Here's the error:
There is no argument given that corresponds to the required formal parameter 'context' of 'BaseController.BaseController(jiWeb_ProdContext, ILogger<BaseController>, ILoggingMessageService)'
Specifically, how do I set up my controllers so that they can just use the base class for logging so I don't have to write logging code for every new controller action I create?
Thanks!
As far as I know, if the base class constructor method contains value, we should pass it in the subclass constructor method and also you should follow Nkosi comment to modify the property to protected.
More details, you could refer to below codes:
[ApiController]
public class MyBaseController : ControllerBase
{
protected readonly ILogger _logger;
public MyBaseController(ILogger<MyBaseController> logger)
{
_logger = logger;
}
}
[Route("api/[controller]")]
public class MachineListsController : MyBaseController
{
public MachineListsController(ILogger<MyBaseController> logger) :base(logger)
{
}
[HttpGet]
public IActionResult Get() {
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Trace,"aaa" );
return Ok();
}
}
I am wondering, would there be a way to do the logging in the base class? Like where you call _logger.Log in the MachineListsController class, could that be moved to base?
As far as I know, we could only add logs before the MachineListsController's action executed or after the MachineListsController's action executed.
If this match your requirement, you could try to use action filter.
You could add iactionfilter interface to the basecontroller and overried the OnActionExecuted and OnActionExecuting method.
More details, you could refer to below codes:
[Route("api/[controller]")]
[ApiController]
public class MyBaseController : ControllerBase, IActionFilter
{
protected readonly ILogger _logger;
public MyBaseController(ILogger<MyBaseController> logger)
{
_logger = logger;
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Trace, "aaa");
int i = 0;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Trace, "bbb");
int i = 0;
}
}
Result:

How do I resolve a WebAPI dependency in Autofac that requires a parameter from the route?

I am struggling with wiring dependencies through autofac in my WebApi 2 project. I have a following interface and class that i'd like to inject in my GET and POST controller actions,
public interface IRepository
{
IContext Context
{
get;
}
void SomeOperation();
}
public MyRepository : IRepository
{
IContext _context;
public MyRepository(IContext context)
{
_context = context;
}
public Context
{
get
{
return _context;
}
}
public void SomeOperation
{
// Perform some operation using _context;
}
}
I 'd like IRepository to be injected in controller like this,
public class MyController : ApiController
{
private readonly IRepository _repo;
public ApplicationsController(IRepository repo)
{
_repo = repo;
}
// GET: api/v1/Contexts({contextId})
public IHttpActionResult Get(string contextId)
{
_repo.SomeOperation();
}
}
IContext object to be injected in MyRepository has to be fetched from a factory, something like this
public class ContextFactory
{
Hashtable contextMap;
IContext Get(string contextId)
{
if contextMap.Contains(contextId)
return contextMap[contextId].Value;
else
{
IContextConfiguration configuration = ContextConfigurationFactory.Get(contextId);
IContext context = new ConcreteContext(configuration);
contextMap.Add[contextId, context];
return context;
}
}
}
I am not sure how to wire all the classes and convert logic in factory classes by injecting relationships through Autofac so that context id passed in url is passed to ContextConfigurationFactory.Get and instantiate ConcreteContext object when not found in hash and eventually Autofac injecting right context object in MyRepository before passing it on to Get action in the controller.
Let's simplify this a bit. What you're trying to do is:
Get the context ID from a route parameter.
Use that route parameter in the factory to create a context.
The rest seems pretty much peripheral - the repository, the controller, all that. The crux of the question is that you need to get a route parameter into your factory.
Given that, let's put together some simplified code:
public class ContextFactory
{
public IContext Get(string contextId)
{
return new Context(contextId);
}
}
public interface IContext
{
string Id { get; }
}
public class Context : IContext
{
public Context(string id)
{
this.Id = id;
}
public string Id { get; private set; }
}
That's basically what you have:
An IContext interface that things need.
A ContextFactory that is basically responsible for building these things.
A Context concrete implementation of IContext that is built by the factory.
I would probably do something like this:
var builder = new ContainerBuilder();
builder.RegisterType<ContextFactory>();
builder.Register(ctx =>
{
var routeData = HttpContext.Current.Request.RequestContext.RouteData;
var id = routeData.Values["contextId"] as string;
var factory = ctx.Resolve<ContextFactory>();
return factory.Get(id);
}).As<IContext>()
.InstancePerLifetimeScope();
Now when you resolve IContext it will use your factory, get the current context ID from route data, and pass it through the factory.
I will leave the following for you to look into:
What happens if the route parameter isn't there? (Autofac won't let you return null.)
What happens if the route parameter has invalid data?
The route parameter is pretty hackable, is this a security risk?
...and so on.

Maintain User Control State in UWP Application using Template 10

I am creating UWP app using Template 10. I have created user control like this.
<my:DeviceInfoUserControl OnEndpointTypeChange="{Binding OnEndpointTypeChangeCommand}" Component="{Binding DeviceManagementViewModel,Mode=TwoWay}"></my:DeviceInfoUserControl>
I have Radio Buttons on User Control. I have added User Control on Multiple screens.
This user control has its own ViewModel as well as Some Dependency Properties as follows:
public class DeviceManagementViewModel : ViewModelBase
{
}
public sealed partial class DeviceInfoUserControl : UserControl
{
public bool IsToggled = true;
public DeviceInfoUserControl()
{
this.InitializeComponent();
}
public static readonly DependencyProperty OnEndpointTypeChangeProperty =
DependencyProperty.Register(
"OnEndpointTypeChange",
typeof(ICommand),
typeof(DeviceInfoUserControl), new PropertyMetadata(null));
public ICommand OnEndpointTypeChange
{
get { return (ICommand)GetValue(OnEndpointTypeChangeProperty); }
set { SetValue(OnEndpointTypeChangeProperty, value); }
}
public static readonly DependencyProperty ComponentProperty = DependencyProperty.Register("Component", typeof(DeviceManagementViewModel), typeof(DeviceInfoUserControl), new PropertyMetadata(null));
public DeviceManagementViewModel Component
{
get { return (DeviceManagementViewModel)GetValue(ComponentProperty); }
set { SetValue(ComponentProperty, value); }
}
}
I want to preserve Radio Button Selection across all screens. How should I achieve this?
You have to ensure that the same ViewModel instance is used for all control instance. The XAML way is always create new instance:
<Page.DataContext>
<vm:DetailPageViewModel x:Name="ViewModel" />
</Page.DataContext>
In the Template10's Bootstrapper class with the ResolveForPage method override, you can inject ViewModel's after the page navigation through a custom logic, or through dependency injection LINK
Don't know its better way or not but I have achieved this by making Singletone Viewmodel.
public class DeviceManagementViewModel : ViewModelBase
{
public static readonly DeviceManagementViewModel _instance = new DeviceManagementViewModel ();
private DeviceManagementViewModel ()
{
}
/*Properties and Methods */
}
In Parent Screen ViewModel I have created following property
private DeviceManagementViewModel _deviceManagementViewModel;
public DeviceManagementViewModel DeviceManagementViewModel1
{
get { return _deviceManagementViewModel; }
set { Set(ref _deviceManagementViewModel, value); }
}
I have Instantiated property in Constructor:
public ConfigurationViewModel()
{
DeviceManagementViewModel1 = DeviceManagementViewModel._instance;
}
And on User Control:
<my:DeviceInfoUserControl OnEndpointTypeChange="{Binding OnEndpointTypeChangeCommand}" Component="{Binding DeviceManagementViewModel1,Mode=TwoWay}"></my:DeviceInfoUserControl>

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.

What is the best way to move sql-linked methods of entities from a data context to entitiy class?

I like EntityFramework. Usually I create a service class (layout) to put there the logic of interaction with a database. It looks like this:
public class UserService
{
MyDbContext _context;
public UserService(MyDBContext context)
{
_context = context;
}
public void MoveUserToGroup(User user, Group group)) { ... }
}
And I use that so somewhere in my code:
userService.MoveUserToGroup(User user, Group group);
It's good, but I would like my classes to look like this:
public class User
{
// ...
public void AddTo(Group group) { ... }
}
public class Group
{
// ...
public void Add(User user) { ... }
}
And I want to use that so:
user.AddToGroup(group);
What is the best way to do it? DI? Extensions? How to keep database context across my classes?
You would usually map all the related navigational properties of an entity.
public class User
{
public virtual ICollection<Group> Groups { get; set; }
public void AddTo(Group group)
{
Groups.Add(group);
}
}