Hello. The essence of the problem. There are users with different roles, for example: admin , moderator , user .

I want to be able to limit what users can see based on their role. Those. On the same View , new controls can be hidden, blocked, or completely appear, depending on the role of the current user.

How to solve this problem as flexibly and correctly as possible in the context of the MVVM pattern?

    4 answers 4

    In my opinion, it makes sense to create a hierarchy of view models:

    abstract class BaseRoleVm { ... } class UserRoleVm : BaseRoleVm { ... } class ModeratorRoleVm : BaseRoleVm { ... } class AdminRoleVm : BaseRoleVm { ... } 

    Then for each non-abstract ViewModel, create a view (you can use styles, controls, resource dictionaries to reuse the code in the view).

    In the main window, you will need to create a property of type BaseRoleVm , which will be assigned the desired successor:

     class MainVm { public BaseRoleVm Role { get; set; } void UseAdminRole() { Role = new AdminRoleVm(); } ... } 

    Such an approach will make it easy to make changes to the views / role view models, ensure their independence from each other, provide the ability to add / remove roles without interfering with the logic of the main window.

    UPD

    Here is a small example based on resource dictionaries:

    View models (located in the ViewModels folder):

     public abstract class BaseRoleVm : BaseVm { public abstract string Name { get; } } public class AdminRoleVm : BaseRoleVm { public override string Name { get { return "Админ"; } } } public class UserRoleVm : BaseRoleVm { public override string Name { get { return "Юзер"; } } } public class MainVm : BaseVm { public BaseRoleVm SelectedRole { get { return _selectedRole; } set { SetField(ref _selectedRole, value); } } private BaseRoleVm _selectedRole; public BaseRoleVm[] Roles { get; } public MainVm() { // формировать список или создавать нужную модель представления можно через MEF или Reflection. // тогда вы не будете зависеть от перечня ролей Roles = new BaseRoleVm[] { new AdminRoleVm(), new UserRoleVm(), }; SelectedRole = Roles.FirstOrDefault(); } } 

    Views (lie in the Views folder)

     // содержимое файла "AdminRole.xaml" (тип Resource Dictionary) <ResourceDictionary ...> <DataTemplate DataType="{x:Type vm:AdminRoleVm}"> <TextBlock Text="Админское представление"/> </DataTemplate> </ResourceDictionary> // содержимое файла "UserRole.xaml" (тип Resource Dictionary) <ResourceDictionary ...> <DataTemplate DataType="{x:Type vm:UserRoleVm}"> <TextBlock Text="Представление юзера"/> </DataTemplate> </ResourceDictionary> // главное окно // содержимое файла "Main.xaml" (тип Window) <Window ...> <Window.DataContext> <vm:MainVm/> </Window.DataContext> <Window.Resources> <helpers:ViewModelTemplateSelector x:Key="ViewModelTemplateSelector"/> </Window.Resources> <StackPanel> <ComboBox ItemsSource="{Binding Roles}" SelectedItem="{Binding SelectedRole}" DisplayMemberPath="Name"/> <ContentControl Content="{Binding SelectedRole, UpdateSourceTrigger=PropertyChanged}" ContentTemplateSelector="{StaticResource ViewModelTemplateSelector}"/> </StackPanel> </Window> 

    And the simplest template selector:

     public class ViewModelTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item == null) { return null; } var itemType = item.GetType(); var resourceDictonary = new ResourceDictionary { Source = new Uri(string.Format( "pack://application:,,,/{0};component/Views/{1}.xaml", itemType.Assembly.FullName, itemType.Name.Replace("Vm", string.Empty))) }; return resourceDictonary .Values .OfType<DataTemplate>() .SingleOrDefault(_ => ReferenceEquals(_.DataType, itemType)); } } 

    At the level of representation, roles are unrelated. ContentControl uses the written ViewModelTemplateSelector to search for the right templates. Something like this happens:

    1. ContentControl changes Content (PropertyChanged worked)
    2. ContentControl calls the SelectTemplate method on the ViewModelTemplateSelector and passes the Content there as an item
    3. ViewModelTemplateSelector finds a file with a view based on the type name and pulls out a DataTemplate .
    4. ContentControl displays Content using the found DataTemplate
    • And how, depending on the type of View model, slip one or another View to display? - sp7
    • It is possible through ContentControl and ContentTemplateSelector, it is possible through MergedDictionaries, if you use ResourceDictionary as a view. - Vlad
    • And which option is more preferable and flexible? - sp7
    • @ sp7 I like the DataTemplateSelector version more. Updated the question. Look at an example. - Vlad
    • It’s not quite clear how this all links up at the markup level, can you somehow explain? I understand how in the markup of the main window we still have a binding on MainVm, for example: <Window.DataContext> <local: MainVm /> </Window.DataContext> - sp7

    I think for each type of user to create their own class with a set of required fields.

      You can add the Role property to the enum viewer, and in the view, bin the accessibility of controls (IsEnabled, Visbility) to this property. If you don't want to mess around with converters and triggers, you can cram properties like CanPressButtonX into a view model for simplicity. What exactly is the problem then?

      • The problem is that the View is quite complex and a large number of different controls can be placed on it. - sp7
      • The logic of the placement of controls is still somewhere to cram. If the differences are completely radical, that the views are not recognized, then different views can also be used for different roles. It can be simpler than the views that are dirtied by the logic of all roles at once. - VIP

      I did this:

      1. Got a list of allowed elements for a role (from database, xml, etc.)

      2. code

         foreach (var rule in rules) { var control = FindName(rule.ProgramModule) as Control; if (control != null) control.Visibility = Visibility.Visible; } 

        where ProgramModule is the name of the element.

      Some difficulty is that it is necessary to collapsed all the elements (well, either go from the opposite, make all elements allowed and turn off those that are prohibited). Simply if you stare at mvvm in feng shui, then sculpt the "views" for each role - but this is very redundant, especially if the controls are on the "sea" form.