I am trying to connect the DataContexts of my views to view models from another separated assembly.
Brian Lagunas wrote on his blog something to Getting Started with Prism’s new ViewModelLocator, However, his solution is specially to customize the conventions to allow the ViewModelLocator resolving the view models types.
My scenario:
I have the main project (MyApplication.exe) contains the Bootstrapper, Shell and the views
In another separated assembly (MyApplication.Process.dll) i have all the view models.
Basing on Brian's explication, i tried the following solution:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType =>
{
var viewName = viewType.FullName;
var viewAssemblyName = viewType.Assembly.GetName().Name;
var viewModelNameSuffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = viewName.Replace("Views", "ViewModels") + viewModelNameSuffix;
viewModelName = viewModelName.Replace(viewAssemblyName, viewAssemblyName + ".Process");
var viewModelAssemblyName = viewAssemblyName + ".Process";
var viewModelTypeName = string.Format(
CultureInfo.InvariantCulture,
"{0}, {1}",
viewModelName,
viewModelAssemblyName);
return Type.GetType(viewModelTypeName);
});
}
The solution above works correctly, However, i don't know if this is the best way to do that?
All what i want, is to tell Prism ViewModelLocator in which Assemblies it has to find the view models, i mean the same approach of Caliburn.Micro (Looks for the view models in all registered assemblies).
The solution above will not work if my application support the Prism Modularity if the assembly name doesn't end with the word 'Process' for example?
What do you suggest for me ?
Your code of resolving the viewmodel type is certainly ok. If you look to the codebase of Prism, you'll notice a quite similar way using minor reflection and string replacements.
static Func<Type, Type> _defaultViewTypeToViewModelTypeResolver =
viewType =>
{
var viewName = viewType.FullName;
viewName = viewName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = String.Format(CultureInfo.InvariantCulture, "{0}{1}, {2}", viewName, suffix, viewAssemblyName);
return Type.GetType(viewModelName);
};
The thing with convention based type resolution is that you have to go with the default convention or create your own convention and stick to the chosen convention. Meaning that if in your first module you choose to have the viewmodels in a .Process assembly, that you should do this for all your other modules. Sticking to your convention is the easiest way to go.
The good news is: if you don't like convention based resolution, you can override the resolution like you already did and implement any kind of resolution you like, how complex you want it to be. Nothing keeps you from e.g. keeping a dictionary mapping views to viewmodels (which we actually did for one project). Filling up this dictionary (or setting up another way of resolution) would be done in the ModuleCatalog for each module.
I finally solved my issue by setting custom view model resolver to search for view models in all added assemblies catalogs.
Solution
First of all, i try to apply the default prism view model locator convention, if no viewmodel found then, i start applying my custom one.
1- I start by getting all assemblies from the AggregateCatalog.
2- I Get all the non-abstract exported types inherit from Prism BindableBase.
3- I Apply the custom convention delegate to get the expected view model.
In my case, the custom convention is all types having the suffix "ViewModel" and the prefix is the view type name:
Example:
If the view name is "UsersView" the view model should be "UsersViewModel".
If the view name is "Users" the view model should be also "UsersViewModel".
Code:
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType =>
{
// The default prism view model type resolver as Priority
Type viewModelType = this.GetDefaultViewModelTypeFromViewType(viewType);
if (viewModelType != null)
{
return viewModelType;
}
// IF no view model found by the default prism view model resolver
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct() ;
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string ViewModelSuffix = "ViewModel";
var isTypeWithViewModelSuffix = t.Name.EndsWith(ViewModelSuffix);
return (isTypeWithViewModelSuffix)
&& ((viewType.Name.EndsWith("View") && viewType.Name + "Model" == t.Name)
|| (viewType.Name + "ViewModel" == t.Name));
});
var resolvedViewModelType = bindableBases.FirstOrDefault(customConvention);
return resolvedViewModelType;
});
The method * GetDefaultViewModelTypeFromViewType * Is the default prism view model locator, its code is exactly the same as in Bart's answer.
I hope this will be helpful for others.
Edit:
I finally solved the problem by creating a new custom MvvmTypeLocator:
public interface IMvvmTypeLocator
{
#region Public Methods and Operators
Type GetViewModelTypeFromViewType(Type viewType);
Type GetViewTypeFromViewModelType(Type viewModelType);
Type GetViewTypeFromViewName(string viewName);
#endregion
}
The implementation :
public class MvvmTypeLocator: IMvvmTypeLocator
{
private AggregateCatalog AggregateCatalog { get; set; }
public MvvmTypeLocator(AggregateCatalog aggregateCatalog)
{
this.AggregateCatalog = aggregateCatalog;
}
public Type GetViewModelTypeFromViewType(Type sourceType)
{
// The default prism view model type resolver as Priority
Type targetType = this.GetDefaultViewModelTypeFromViewType(sourceType);
if (targetType != null)
{
return targetType;
}
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string TargetTypeSuffix = "ViewModel";
var isTypeWithTargetTypeSuffix = t.Name.EndsWith(TargetTypeSuffix);
return (isTypeWithTargetTypeSuffix)
&& ((sourceType.Name.EndsWith("View") && sourceType.Name + "Model" == t.Name)
|| (sourceType.Name + "ViewModel" == t.Name));
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
public Type GetViewTypeFromViewModelType(Type sourceType)
{
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(IView)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string SourceTypeSuffix = "ViewModel";
var isTypeWithSourceTypeSuffix = t.Name.EndsWith(SourceTypeSuffix);
return (isTypeWithSourceTypeSuffix)
&& ((sourceType.Name.EndsWith("View") && t.Name + "Model" == sourceType.Name)
|| (t.Name + "ViewModel" == sourceType.Name));
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
public Type GetViewTypeFromViewName(string viewName)
{
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && typeof(IView).IsAssignableFrom(t) && t.Name.StartsWith(viewName))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
return t.Name.EndsWith("View");
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
private Type GetDefaultViewModelTypeFromViewType(Type viewType)
{
var viewName = viewType.FullName;
viewName = viewName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = String.Format(
CultureInfo.InvariantCulture,
"{0}{1}, {2}",
viewName,
suffix,
viewAssemblyName);
return Type.GetType(viewModelName);
}
}
This custom type locator is using the AggregateCatalog to search the target types in all the assembly catalogs. Of course i create its instance on the Bootstrapper once the Bootstrapper's AggregateCatalog is configured:
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginViewModel).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginView).Assembly));
this.mvvmTypeLocator = new MvvmTypeLocator(this.AggregateCatalog);
}
At the end, i just configure the view model locator in the Bootstrapper like the following:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType => this.mvvmTypeLocator.GetViewModelTypeFromViewType(viewType));
}
Note that the methods GetViewTypeFromViewModelType and GetViewTypeFromViewName are searching all the views implementing the interface named IView. this just an empty interface i use to distinct my views from other classes inside the same assembly. If someone use this mvvmTypeLocator, then he has to create his own interface and implement all the views that should be discoverable by the mvvmTypeLocator.
What about this workaround ? -I really gave up on editing the ViewModelLocator- make a ViewModule within the same project and let it inherit from another ViewModel in the other assembly, the core implementation is in the base ViewModel and you can still bind to it and do everything you want.
I marked all the functions in the Base class as virtual so I can extend their functionality if I want to use some platform specific components ex. IRegionManager
this is the code from the platform project ( WPF )
namespace PrismApplicationTest0.ViewModels
{
public class ViewAViewModel : ViewAViewModelBase
{
private readonly IRegionManager _regionManager;
public ViewAViewModel(IEventAggregator eventAggregator,IRegionManager regionManager) : base(eventAggregator)
{
_regionManager = regionManager;
}
protected override void UpdateMethod()
{
// After completing the core functionality
base.UpdateMethod();
// Switch to another page using platform specific region manager
_regionManager.RequestNavigate(RegionNames.ContentRegion,"ViewB");
}
}
}
this is the code from the PCL ( portable class library )
using System;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
namespace MainModule.ViewModels
{
public abstract class ViewAViewModelBase : BindableBase
{
private readonly IEventAggregator _eventAggregator;
private string _firstName;
private string _lastName;
private DateTime? _lastUpdated;
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
public string LastName
{
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
public DateTime? LastUpdated
{
get { return _lastUpdated; }
set { SetProperty(ref _lastUpdated, value); }
}
public DelegateCommand UpdateCommand { get; private set; }
public ViewAViewModelBase(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
UpdateCommand =
new DelegateCommand(UpdateMethod, CanUpdateMethod)
.ObservesProperty(() => FirstName)
.ObservesProperty(() => LastName);
}
protected bool CanUpdateMethod()
{
return !String.IsNullOrEmpty(_lastName) && !String.IsNullOrEmpty(_firstName);
}
protected virtual void UpdateMethod()
{
LastUpdated = DateTime.Now;
_eventAggregator.GetEvent<Events.UpdatedAggEvent>().Publish($"User {FirstName}");
}
}
}
it's working as a charm with me.
I guess if you need another object from other assemblies you can create instances of them in the base class
Good luck
Related
As per following example:
MediatR.Examples.Ninject
I've a MediatorModule class as follows:
public class MediatorModule : NinjectModule {
public class ContravariantBindingResolver : NinjectComponent, IBindingResolver {
public IEnumerable<IBinding> Resolve(Multimap<Type, IBinding> bindings, Type service) {
if (service.IsGenericType) {
var genericType = service.GetGenericTypeDefinition();
var genericArguments = genericType.GetGenericArguments();
if (1 == genericArguments.Count() && genericArguments.Single().GenericParameterAttributes.HasFlag(GenericParameterAttributes.Contravariant)) {
var argument = service.GetGenericArguments().Single();
var matches = bindings.Where(kvp => kvp.Key.IsGenericType
&& kvp.Key.GetGenericTypeDefinition().Equals(genericType)
&& kvp.Key.GetGenericArguments().Single() != argument
&& kvp.Key.GetGenericArguments().Single().IsAssignableFrom(argument))
.SelectMany(kvp => kvp.Value);
return matches;
}
}
return Enumerable.Empty<IBinding>();
}
}
public override void Load() {
Kernel.Components.Add<IBindingResolver, ContravariantBindingResolver>();
Kernel.Bind(services => services.FromAssemblyContaining<IMediator>().SelectAllClasses().BindDefaultInterface());
Kernel.Bind(services => services.FromThisAssembly().SelectAllClasses().InheritedFrom(typeof(IRequestHandler<,>)).BindAllInterfaces());
Kernel.Bind(services => services.FromThisAssembly().SelectAllClasses().InheritedFrom(typeof(INotificationHandler<>)).BindAllInterfaces());
Kernel.Bind(typeof(IPipelineBehavior<,>)).To(typeof(RequestPreProcessorBehavior<,>));
Kernel.Bind(typeof(IPipelineBehavior<,>)).To(typeof(RequestPostProcessorBehavior<,>));
Kernel.Bind(typeof(IPipelineBehavior<,>)).To(typeof(RequestExceptionActionProcessorBehavior<,>));
Kernel.Bind(typeof(IPipelineBehavior<,>)).To(typeof(RequestExceptionProcessorBehavior<,>));
Kernel.Bind<ServiceFactory>().ToMethod(ctx => t => ctx.Kernel.TryGet(t));
}
}
On a Quick Watch over services.FromThisAssembly().SelectAllClasses().InheritedFrom(typeof(IRequestHandler<,>)), I can see that the classes are correctly found.
And here is an example of my command and handler.
public class SupplierInvoice {
public class ProcessCommand : IRequest<ProcessedTransaction> {
public ProcessCommand(XmlDocument supplierInvoiceDocument)
=> SupplierInvoiceDocument = supplierInvoiceDocument;
public XmlDocument SupplierInvoiceDocument { get; }
}
public class ProcessCommandHandler : IRequestHandler<ProcessCommand, ProcessedTransaction> {
private readonly IXmlSerializer<SupplierInvoice> _serializer;
private readonly IValidator<SupplierInvoice> _validator;
private readonly IMediator _mediator;
public ProcessCommandHandler(IXmlSerializer<SupplierInvoice> serializer, IValidator<SupplierInvoice> validator, IMediator mediator) {
_serializer=serializer;
_validator=validator;
_mediator=mediator;
}
public async Task<ProcessedTransaction> Handle(ProcessCommand request, CancellationToken cancellationToken) {
if (request == null) return ProcessedTransaction.Succeeded;
var model = _serializer.Deserialize(request.SupplierInvoiceDocument.OuterXml);
var vr = _validator.Validate(model);
if (!vr.IsValid) // throwing exception with validation messages.
return await _mediator.Send(new CreateCommand(model));
}
}
}
So I wonder how can the handlers be not registered using the BindAllInterfaces method?
Even using plain old binding syntax, the request handlers just won't get registered.
Kernel.Bind<IRequestHandler<SupplierInvoice.ProcessCommand, ProcessedTransaction>>().To<SupplierInvoice.ProcessCommandHandler>();
What am I missing?
As per linked Ninject example: MediatR.Examples.Ninject, I guess I found an error in the ContravariantBindingResolver.cs class.
The line that I guess is at fault is when getting the generic arguments for the second time.
var genericType = service.GetGenericTypeDefinition();
var genericArguments = genericType.GetGenericArguments();
if (genericArguments.Count() == 1
&& genericArguments.Single().GenericParameterAttributes.HasFlag(GenericParameterAttributes.Contravariant))
{
var argument = service.GetGenericArguments().Single();
var matches = bindings.Where(kvp => kvp.Key.IsGenericType
&& kvp.Key.GetGenericTypeDefinition().Equals(genericType)
&& kvp.Key.GetGenericArguments().Single() != argument
&& kvp.Key.GetGenericArguments().Single().IsAssignableFrom(argument))
.SelectMany(kvp => kvp.Value);
return matches;
}
Notice that var genericArguments = genericType.GetGenericArguments() uses the generic type definition returned by the service.GetGenericTypeDefinition() method call. But because in the sample code provided the second call to GetGenericArguments is made from the service instance, the correct generic parameter doesn't seem to be returned adequately. Hence I'd recommend the use of the already declared variable which contains the generic arguments from which the argument variable's value can be obtained.
To sum it up, for me what made the resolver actually resolve the correct handler every time is that I changed this line:
var argument = service.GetGenericArguments().Single()
to
var argument = genericArguments.Single()
given it is contravariant, so only available argument since the check is already made for the length of the returned arguments array.
Changing this for me made the difference between the unregistered handler exception to a working code able to resolve proper handlers.
I have an asp.net application that uses NancyFx. I want to register only specific modules based on licenses in my database. This is because the modules have the same routes based on different configurations.
So I thought you should have to create your custom class that implements INancyModuleCatalog.
public class CustomModuleCatalog : INancyModuleCatalog
{
private IDictionary _modules;
public CustomModuleCatalog()
{
// The license type is read from db in Global.ascx.
// So I want to register a module based on a namespace.
// The namespace is the same like the license name.
if(WebApiApplication.LicenseType == LicenseType.RouteOne)
{
var assemblyTypes = Assembly.GetExecutingAssembly().GetTypes();
var modules = assemblyTypes.Where(t => t.Namespace != null && t.Namespace.EndsWith(WebApiApplication.SystemType.ToString()));
var nancy = modules.Where(t => t.IsAssignableFrom(typeof(INancyModule)));
foreach (var type in nancy)
{
var nancyType = (INancyModule)type;
_modules.Add(type, (INancyModule)Activator.CreateInstance(type));
}
}
}
public IEnumerable<INancyModule> GetAllModules(NancyContext context)
{
return _modules?.Values;
}
public INancyModule GetModule(Type moduleType, NancyContext context)
{
if (_modules != null && _modules.ContainsKey(moduleType))
{
return _modules[moduleType];
}
return null;
}
}
How to register this catalog in my Bootstrapper ?
The boostrapper is also the implementation of INancyModuleCatalog (assuming you're using the DefaultNancyBootstrapper) see line 97 here: https://github.com/NancyFx/Nancy/blob/master/src/Nancy/DefaultNancyBootstrapper.cs#L97
I believe you would need to create your own bootstrapper as well to register your own catalog.
But - do you need to provide your own catalog? Could you not just do the check of the license type in the constructor of the modules that can be switched out based on license type and only register their routes if applicable?
eg;
public class RouteOne : NancyModule
{
public RouteOne()
{
if (xxx.License != LicenseType.RouteOne) return;
Get["/"] = _ => Response.AsJson(new {Message = "This is route one"});
}
}
public class RouteTwo : NancyModule
{
public RouteTwo()
{
if (xxx.License != LicenseType.RouteTwo) return;
Get["/"] = _ => Response.AsJson(new { Message = "This is route two" });
}
}
Using Glass Mapper V3, is it possible to check whether a Sitecore item supports a specific Glass Mapper class/interface?
Given these classes
[SitecoreType]
public partial interface IPage : IGlassBase
{
// ... some properties here ...
}
[SitecoreType]
public partial interface IRateableItem : IGlassBase
{
// ... some properties here ...
}
I would like to do something like this
var context = SitecoreContext();
var item = context.GetCurrentItem<IRateableItem>();
if (item != null)
// it's an item that is composed of the Rateable Item template
Unfortunately, if I do that, I do get an item of the type IRateableItem returned, regardless of whether the current item is composed of that template or not.
Dan
Another solution would be to create a custom task that runs in the ObjectConstruction pipeline.
Something like this:
public class LimitByTemplateTask : IObjectConstructionTask
{
private static readonly Type _templateCheck = typeof (ITemplateCheck);
public void Execute(ObjectConstructionArgs args)
{
if (args.Result != null)
return;
if ( _templateCheck.IsAssignableFrom(args.AbstractTypeCreationContext.RequestedType))
{
var scContext = args.AbstractTypeCreationContext as SitecoreTypeCreationContext;
var config = args.Configuration as SitecoreTypeConfiguration;
var template = scContext.SitecoreService.Database.GetTemplate(scContext.Item.TemplateID);
//check to see if any base template matched the template for the requested type
if (template.BaseTemplates.All(x => x.ID != config.TemplateId) && scContext.Item.TemplateID != config.TemplateId)
{
args.AbortPipeline();
}
}
}
}
public interface ITemplateCheck{}
You would then change you IRateableItem inteface to have the template ID it needs to match and inherit from ITemplateCheck:
[SitecoreType(TemplateId = "CF9B175D-872E-439A-B358-37A01155EEB1")]
public interface IRateableItem: ITemplateCheck, IGlassBase{}
Finally you will need to register the new task with the Castle IOC container in GlassMapperScCustom:
public static void CastleConfig(IWindsorContainer container){
var config = new Config();
container.Register(
Component.For<IObjectConstructionTask>().ImplementedBy<LimitByTemplateTask>(),
);
container.Install(new SitecoreInstaller(config));
}
I haven't had a chance to test this so let me know if there are any problems.
I used this code to determine if an Item could be loaded as a Glass model of a certain type.
I've used your IRateableItem type as an example:
public void Main()
{
var item = Sitecore.Context.Item;
if (item.TemplateID.Equals(GetSitecoreTypeTemplateId<IRateableItem>()))
{
// item is of the IRateableItem type
}
}
private ID GetSitecoreTypeTemplateId<T>() where T : class
{
// Get the GlassMapper context
var context = GetGlassContext();
// Retrieve the SitecoreTypeConfiguration for type T
var sitecoreClass = context[typeof(T)] as SitecoreTypeConfiguration;
return sitecoreClass.TemplateId;
}
private SitecoreService GetSitecoreService()
{
return new SitecoreService(global::Sitecore.Context.Database);
}
private Glass.Mapper.Context GetGlassContext()
{
return GetSitecoreService().GlassContext;
}
EDIT:
Add this extension method so that you can determine whether a Template inherits from a certain base template.
public static bool InheritsFrom(this TemplateItem templateItem, ID templateId)
{
if (templateItem.ID == templateId)
{
return true;
}
foreach (var template in templateItem.BaseTemplates)
{
if (template.ID == templateId)
{
return true;
}
if (template.InheritsFrom(templateId))
{
return true;
}
}
return false;
}
So now you can do this:
if (item.Template.InheritsFrom(GetSitecoreTypeTemplateId<IRateableItem>()))
{
// item is of type IRateableItem
}
I haven't found a solution to the null-check as well. But what you can do is this:
First add the TemplateId to the SitecoreType attribute for both your models:
[SitecoreType(TemplateId = "{your-template-id}")]
Then in your code use the GetCurrentItem<>() method with the InferType=true parameter:
var context = SitecoreContext();
var item = context.GetCurrentItem<IGlassBase>(InferType: true);
if (item is IRateableItem)
{
var rateableItem = item as IRateableItem;
// do more...
}
By adding the TemplateID and using the InferType:true parameter, Glass will try to map the item to a better object then IGlassBase, based on the TemplateID.
If there is a nicer solution to solve this, I'm interested as well.
I have 2 classes:
public class CustomerViewModel {
public SystemViewModel system { get;set; }
}
public class SystemViewModel {
public bool isReadOnly { get; set; }
}
On the method controller action I have a custom filter attribute which executes some code and determines whether or the user has ReadOnly or Write access. This attribute can be applied to multiple actions across multiple controllers.
So far using reflection I can get access to the model using:
var viewModel = filterContext.Controller.ViewData.Model;
I can not cast this model to CustomerViewModel because on a different action it might be something like SalaryViewModel. What I do know is that any model that requires the readonly property will have SystemViewModel property.
From my custom filter I need a way to be able to change the value of readonly.
So far I have this:
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var viewModel = filterContext.Controller.ViewData.Model;
var systemViewModelPropertyInfo = model.GetType()
.GetProperties()
.FirstOrDefault(p => p.PropertyType == typeof(SystemViewModel));
if (systemViewModelPropertyInfo != null) {
// Up to here, everything works, systemViewModelPropertyInfo is of
// type PropertyInfo, and the systemViewModelPropertyInfo.PropertyType
// shows the SystemViewModel type
// If we get here, the model has the system property
// Here I need to try and set the IsReadOnly property to true/false;
// This is where I need help please
}
}
SOLVED
Thanks to everyone who pitched in to help solve this. Special thanks to Julián Urbano for having the solution I was looking for.
Here is my resulting code from within my filter:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
try
{
var viewModel = filterContext.Controller.ViewData.Model;
var systemViewModelPropertyInfoCount = viewModel.GetType().GetProperties().Count(p => p.PropertyType == typeof(SystemViewModel));
if(systemViewModelPropertyInfoCount == 1)
{
var systemViewModelPropertyInfo = viewModel.GetType().GetProperties().First(p => p.PropertyType == typeof(SystemViewModel));
if(systemViewModelPropertyInfo != null)
{
var systemViewModel = systemViewModelPropertyInfo.GetValue(viewModel, null) as SystemViewModel;
if(systemViewModel != null)
{
var admin = GetAdmin(filterContext.HttpContext.User.Identity.Name);
if(admin != null && _adminService.HasPermission(admin, _privilege, Access.Level.ReadWrite))
systemViewModel.ReadOnly = false;
else
systemViewModel.ReadOnly = true;
}
}
} else if(systemViewModelPropertyInfoCount > 1)
{
throw new Exception("Only once instance of type SystemViewModel allowed");
}
}
catch (Exception exception)
{
Log.Error(MethodBase.GetCurrentMethod(), exception);
filterContext.Controller.TempData["ErrorMessage"] = string.Format("Technical error occurred");
filterContext.Result = new RedirectResult("/Error/Index");
}
finally
{
base.OnActionExecuted(filterContext);
}
}
I can not cast this model to CustomerViewModel because on a different action it might be something like SalaryViewModel. What I do know is that any model that requires the readonly property will have SystemViewModel property.
option 1
Seems to me that the best option is to write an interface like:
public interface IWithSystemViewModel {
SystemViewModel System {get;}
}
and implement it from your classes, much like:
public class CustomerViewModel : IWithSystemViewModel{
public SystemViewModel System { get;set; }
}
public class SalaryViewModel : IWithSystemViewModel{
public SystemViewModel System { get;set; }
}
so you can cast it and access the isReadOnly property:
IWithSystemViewModel viewModel = filterContext.Controller.ViewData.Model as IWithSystemViewModel;
if(viewModel!=null){
viewModel.System.isReadOnly ...
}
option 2
If you want to stick to using reflection:
var viewModel = filterContext.Controller.ViewData.Model;
SystemViewModel theSystem = viewModel.GetType().GetProperty("system")
.GetValue(viewModel, null) as SystemViewModel;
theSystem.isReadOnly ...
Tricky thing: in your code, you select the property whose type is SystemViewModel. But what if the object actually has several SystemViewModel properties that you don't know about? Are you sure you're accessing the proper one? You may force all of them to use the same name, but then again, that would be like using the interface in option 1 above.
I'd definitely go with option 1.
var viewModel = new CustomerViewModel();
var systemViewModelPropertyInfo = viewModel.GetType()
.GetProperties()
.FirstOrDefault(p => p.PropertyType == typeof(SystemViewModel));
if (systemViewModelPropertyInfo != null) {
var systemViewModelProperty = systemViewModelPropertyInfo.GetValue(viewModel, null) as SystemViewModel;
// get the desired value of isReadOnly here...
var isReadOnly = false;
// here, systemViewModelProperty may be null if it has not been set.
// You can decide what to do in that case. If you need a value to be
// present, you'll have to do something like this...
if (systemViewModelProperty == null) {
systemViewModelPropertyInfo.SetValue(viewModel, new SystemViewModel { isReadOnly = isReadOnly }, null);
}
else {
systemViewModelProperty.isReadOnly = isReadOnly;
}
}
That said, this whole process would probably be easier if you implemented an interface...
public interface IHaveSystemViewModel {
SystemViewModel system { get; set; }
}
var model = viewModel as IHaveSystemViewModel;
if (model != null) {
// again, you need to make sure you actually have a reference here...
var system = model.system ?? (model.system = new SystemViewModel());
system.isReadOnly = false; // or true
}
If I have the following view model
class Foo : INotifyPropertyChanged {
ISubject<string> Name { ... }
}
and some imagined XAML code
<TextBox Text="{my:Subscribe Path=Name}/>
I wish the two way binding to behave that
Subject.onNext is called when the text box is updated in the UI
the text box is updated by subscribing to the Subject.Subscribe
As WPF only supports INPC directly my idea is to create a proxy INPC object
in via a markup extension
class WPFSubjectProxy : INotifyPropertyChanged{
string Value { ... }
}
The proxy would be wired up to the subject as so
subject.Subscribe(v=>proxy.Value=v);
proxy
.WhenAny(p=>p.Value, p.Value)
.Subscribe(v=>subject.OnNext(v))
Note WhenAny is a ReactiveUI helper for subscribing to
INPC events.
But then I would need to generate a binding and return
that via the markup extension.
I know what I want to do but can't figure out the
Markup extension magic to put it all together.
It's hard to say without seeing specifically what you're struggling with, but perhaps this helps?
EDIT
The solution I (bradgonesurfing) came up with is below thanks to the pointer in the
assigned correct answer.
Nodes
and the implementing code. It has a dependency on ReactiveUI and a helper function in a private library for binding ISubject to a mutable property on an INPC supporting object
using ReactiveUI.Subjects;
using System;
using System.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ReactiveUI.Markup
{
[MarkupExtensionReturnType(typeof(BindingExpression))]
public class SubscriptionExtension : MarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
public SubscriptionExtension() { }
public SubscriptionExtension(PropertyPath path)
{
Path = path;
}
class Proxy : ReactiveObject
{
string _Value;
public string Value
{
get { return _Value; }
set { this.RaiseAndSetIfChanged(value); }
}
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var pvt = serviceProvider as IProvideValueTarget;
if (pvt == null)
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if (frameworkElement == null)
{
return this;
}
object propValue = GetProperty(frameworkElement.DataContext, Path.Path);
var subject = propValue as ISubject<string>;
var proxy = new Proxy();
Binding binding = new Binding()
{
Source = proxy,
Path = new System.Windows.PropertyPath("Value")
};
// Bind the subject to the property via a helper ( in private library )
var subscription = subject.ToMutableProperty(proxy, x => x.Value);
// Make sure we don't leak subscriptions
frameworkElement.Unloaded += (e,v) => subscription.Dispose();
return binding.ProvideValue(serviceProvider);
}
private static object GetProperty(object context, string propPath)
{
object propValue = propPath
.Split('.')
.Aggregate(context, (value, name)
=> value.GetType()
.GetProperty(name)
.GetValue(value, null));
return propValue;
}
}
}