Страницы

Wednesday, February 15, 2012

Silverlight, ASP.NET MVC - Продвинутый TreeView


Получил задание - реализовать групповое присвоение свойств, организационным структурам (департаментам, отделам). Немного подумав, над тем как, это будет выглядеть, я пришел к выводу, что лучше всего сделать дерево, в котором видно, само подразделение и его статус. Статусы я решил подсвечивать квадратиком (закрашенная плитка). "Погуглив", подходящих контролов типа TreeView для ASP.NET MVC 3, которые бы меня устраивали, я не нашел. Решил все реализовывать на Silverlight.

ASP.NET MVC (часть)

Модели данных:

public class StructureTreeViewItem
{
   public int ID { get; set; }
   public int? ParentID { get; set; }
   public string Name { get; set; }
   public int TypeID { get; set; }
}
public class StructureType
{
   public int ID { get; set; }
   public string Name { get; set; }
}
Контроллер
public JsonResult GetNodes()
{
    using (var db = new ...Entities())
    {
        var s = (...
                    where ....
                    select new StructureTreeViewItem()
                    {
                        ID = structs.StructureID,
                        ParentID = structs.StructureParentID,
                        Name = structsLangs.StructureName,
                        TypeID = structs.StructureTypeId
                    }).ToList();

        return Json(s, JsonRequestBehavior.AllowGet);
    }
}

public JsonResult GetStructureTypes()
{
    using (var db = new ...Entities())
    {
        var structureTypesList = new List<structuretype>();

        db.StructureTypes
            .OrderBy(q => q.id)
                .ToList()
                    .ForEach(q => structureTypesList
                        .Add(new StructureType() { ID = q.id, Name = q.Name }));

        return Json(structureTypesList, JsonRequestBehavior.AllowGet);
    }
}

public EmptyResult UpdateStructures(string structures)
{
    if (!string.IsNullOrWhiteSpace(structures))
    {
        using (var db = new ...Entities())
        {
            var treeViewStructures = new List<structuretreeviewitem>();
            structures.Split(';').ToList().ForEach(s =>
            {
                var treeViewStructureDict = s.Split('=');
                int structureId, structureTypeId;
                if (treeViewStructureDict.Length == 2
                    && int.TryParse(treeViewStructureDict[0], out structureId)
                    && int.TryParse(treeViewStructureDict[1], out structureTypeId))
                {
                    var dbSctructure = db.Structure.FirstOrDefault(q => q.StructureID == structureId);
                    if (dbSctructure != null && db.StructureTypes.FirstOrDefault(q => q.id == structureTypeId) != null)
                    {
                        dbSctructure.StructureTypeId = structureTypeId;
                        db.SaveChanges();
                    }
                }
            });
        }
    }
    return new EmptyResult();
}

Silverlight (часть)

Модели

public class StructureType : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged<t>(Expression<func<t>> raiser)
    {
        var e = PropertyChanged;
        if (e != null)
        {
            var propName = ((MemberExpression)raiser.Body).Member.Name;
            e(this, new PropertyChangedEventArgs(propName));
        }
    }

    private int m_id;
    public int ID 
    {
        get
        {
            return m_id;
        }
        set
        {
            m_id = value;
            RaisePropertyChanged(() => this.ID);
        }
    }

    private string m_name;
    public string Name 
    {
        get
        {
            return m_name;
        }
        set
        {
            m_name = value;
            RaisePropertyChanged(() => this.Name);
        }
    }

    private SolidColorBrush m_color;
    public SolidColorBrush Color
    {
        get
        {
            return m_color;
        }
        set
        {
            m_color = value;
            RaisePropertyChanged(() => this.Color);
        }
    }
}
public class StructureItem : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged<t>(Expression<func<t>> raiser)
    {
        var e = PropertyChanged;
        if (e != null)
        {
            var propName = ((MemberExpression)raiser.Body).Member.Name;
            e(this, new PropertyChangedEventArgs(propName));
        }
    }

    public StructureItem()
    {
        m_checked = false;
        m_childItems = new ObservableCollection<structureitem>();
    }

    private int m_id;
    public int ID
    {
        get
        {
            return m_id;
        }
        set
        {
            m_id = value;
            RaisePropertyChanged(() => this.ID);
        }
    }

    private int? m_parentId;
    public int? ParentID
    {
        get
        {
            return m_parentId;
        }
        set
        {
            m_parentId = value;
            RaisePropertyChanged(() => this.ParentID);
        }
    }

    private string m_name;
    public string Name
    {
        get
        {
            return m_name;
        }
        set
        {
            m_name = value;
            RaisePropertyChanged(() => this.Name);
        }
    }

    private bool m_checked;
    public bool Checked 
    {
        get
        {
            return m_checked;
        }
        set
        {
            m_checked = value;
            RaisePropertyChanged(() => this.Checked);
        }
    }

    private StructureType m_structureType;
    public StructureType StructureType
    {
        get
        {
            return m_structureType;
        }
        set
        {
            m_structureType = value;
            RaisePropertyChanged(() => this.StructureType);
        }
    }

    ObservableCollection<structureitem> m_childItems;
    public ObservableCollection<structureitem> ChildItems 
    {
        get
        {
            return m_childItems;
        }
        set
        {
            m_childItems = value;
            RaisePropertyChanged(() => this.ChildItems);
        }
    }
}
При загрузке Silverlight приложение, подтягивает данные, о типах структурных подразделений. После чего, они привязываются к ComboBox-у, а затем загружаются сами структурные подразделения. После загрузки структурных подразделений, происходит их сортировка в иерархическую коллецию с помощью рекурсии.
public MainPage()
{
    InitializeComponent();
    onEndLoad();
}
private void disableUi()
{
    Dispatcher.BeginInvoke(() =>
    {
        // Бизи индикатор
        bi.IsBusy = true;
    });
}
private void enableUi()
{
    Dispatcher.BeginInvoke(() =>
    {
        // Отключение бизи индикатора
        bi.IsBusy = false;
    });
}
private void onEndLoad()
{
    disableUi();

    var webClient = new WebClient();
    webClient.OpenReadCompleted += onGetStructureTypesCompleted;
    webClient.OpenReadAsync(new Uri("/Core/GetStructureTypes", UriKind.Relative));
}
private void onGetStructureTypesCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (e.Error != null)
    {
        if (e.Error.Message != null)
        {
            MessageBox.Show(e.Error.Message);
        }

        enableUi();
        return;
    }

    var items = (JsonArray)JsonValue.Load(e.Result);
            
    var typedItems = new ObservableCollection<structuretype>();

    foreach (var item in items)
    {
        var structureType = new StructureType();
        structureType.ID = int.Parse(item["ID"].ToString());
        structureType.Name = item["Name"].ToString().Replace("\\\"", "\"").Trim('"').Trim();

        // Тут подбираем цвета для каждого из типов структур
        switch (structureType.Name)
        {
            case "Сервисное":
                structureType.Color = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0x72, 0x72));
                break;
            case "Зарабатывающее":
                structureType.Color = new SolidColorBrush(Color.FromArgb(0xFF, 0x40, 0xC7, 0x40));
                break;
            default:
                structureType.Color = new SolidColorBrush(Color.FromArgb(0xFF, 0xB4, 0xB4, 0xB4));
                break;
        }

        typedItems.Add(structureType);
    }

    cbStructureTypes.ItemsSource = typedItems;

    var serviceUri = new Uri("/Core/GetNodes", UriKind.Relative);
    var webClient = new WebClient();
    webClient.OpenReadCompleted += onGetNodesCompleted;
    webClient.OpenReadAsync(serviceUri);
}
private void onGetNodesCompleted(object sender, OpenReadCompletedEventArgs e)
{
    if (e.Error != null)
    {
        if (e.Error.Message != null)
        {
            MessageBox.Show(e.Error.Message);
        }

        enableUi();
        return;
    }

    var notSortedItems = new ObservableCollection<structureitem>();
    var structureTypes = cbStructureTypes.ItemsSource as ObservableCollection<structuretype>;

    var nodes = (JsonArray)JsonValue.Load(e.Result);

    foreach (var node in nodes)
    {
        var structureItem = new StructureItem();

        structureItem.ID = int.Parse(node["ID"].ToString());
        structureItem.Name = node["Name"].ToString().Replace("\\\"", "\"").Replace("\"\"", "\"");

        var typeId = int.Parse(node["TypeID"].ToString());
        foreach (var sType in structureTypes)
        {
            if (sType.ID == typeId)
            {
                structureItem.StructureType = sType;
            }
        }

        structureItem.ParentID =
            node["ParentID"] == null || string.IsNullOrWhiteSpace(node["ParentID"].ToString()) ?
            0 : int.Parse(node["ParentID"].ToString());

        notSortedItems.Add(structureItem);
    }

    var sortedItems = new ObservableCollection<structureitem>();

    notSortedItems.ToList().ForEach(q =>
        {
            if (q.ParentID.HasValue && q.ParentID.Value == 0)
            {
                sortedItems.Add(storeTree(q, notSortedItems));
            }
        });

    twStructures.ItemsSource = sortedItems;

    enableUi();
}
private StructureItem storeTree(StructureItem item, ObservableCollection<structureitem> allItems)
{
    var childItems = allItems.Where(q => q.ParentID == item.ID).ToList();

    if (childItems.Count > 0)
    {
        childItems.ForEach(q =>
        {
            item.ChildItems.Add(storeTree(q, allItems));
        });
    }

    return item;
}
// Обработчик события нажатия на чекбокс в дереве
private void checkBox_Click(object sender, RoutedEventArgs e)
{
    if (m_selectedItems == null)
        m_selectedItems = new List<structureitem>();

    var currentCheckBox = sender as CheckBox;
    if (currentCheckBox.IsChecked.HasValue && currentCheckBox.IsChecked.Value)
    {
        m_selectedItems.Add(currentCheckBox.Tag as StructureItem);
    }
    else
    {
        m_selectedItems.Remove(currentCheckBox.Tag as StructureItem);
    }
}
private void bSave_Click(object sender, RoutedEventArgs e)
{
    if (cbStructureTypes.SelectedItem != null && m_selectedItems != null && m_selectedItems.Count > 0)
    {
        disableUi();
        m_selectedItems.ForEach(q =>
            {
                q.StructureType = cbStructureTypes.SelectedItem as StructureType;
                q.Checked = false;
            });

        sendToSite();
        m_selectedItems.Clear();
        enableUi();
    }
}
// Вызов метода апдейт
private void sendToSite()
{
    var webClient = new WebClient();
    webClient.Headers["Content-type"] = "application/x-www-form-urlencoded";
    var parametersString = new StringBuilder();
    m_selectedItems.ForEach(s =>
    {
        if (parametersString.ToString() != string.Empty)
            parametersString.Append(";");
        parametersString.AppendFormat("{0}={1}", s.ID, s.StructureType.ID);
    });
    webClient.UploadStringAsync(new Uri("/Core/UpdateStructures/", UriKind.Relative), "POST", string.Format("structures={0}", parametersString.ToString()));
}
Разметка XAML
<Grid.Resources>

    <sdk:HierarchicalDataTemplate x:Key="tvTemplate" ItemsSource="{Binding Path=ChildItems}" >

        <StackPanel Orientation="Horizontal">

            <CheckBox Tag="{Binding}" IsChecked="{Binding Path=Checked, Mode=TwoWay}" Click="checkBox_Click" />

            <Rectangle Height="15" Width="15" Margin="0,0,3,0" StrokeThickness="1" Fill="{Binding Path=StructureType.Color}"/>

            <TextBlock Text="{Binding Path=Name}" />

        </StackPanel>

    </sdk:HierarchicalDataTemplate>

</Grid.Resources>

<sdk:TreeView Margin="12,41,12,12"

                Name="twStructures"

                BorderBrush="#FFB1B1B1"

                ItemsSource="{Binding}"

                ItemTemplate="{StaticResource tvTemplate}"

                SelectedValuePath="ID"/>

<ComboBox Height="23" Margin="12,12,0,0" Name="cbStructureTypes" VerticalAlignment="Top" ItemsSource="{Binding}" HorizontalAlignment="Left" Width="219">

    <ComboBox.ItemTemplate>

        <DataTemplate>

            <StackPanel Orientation="Horizontal">

                <Rectangle Height="15" Width="15" Margin="0,0,3,0" StrokeThickness="1" Fill="{Binding Path=Color}"/>

            <TextBlock Text="{Binding Path=Name}" />

            </StackPanel>

        </DataTemplate>               

    </ComboBox.ItemTemplate>

</ComboBox>

<Button Content="Сохранить" Height="23" Margin="237,12,0,0" Name="bSave" VerticalAlignment="Top" HorizontalAlignment="Left" Width="75" Click="bSave_Click" />

<toolkit:BusyIndicator BusyContent="Loading please wait..." Name="bi" />

Интересно, кто нибудь, умеет правильно вызывать методы контроллеров с параметрами из Silverlight?

Tuesday, February 14, 2012

SPSecurity.RunWithElevatedPrivileges в Sharepoint 2010

Недавно мне попался в руки проект, в котором я принимал участие в роли bugfixer-а. Мне попался стандартный блок кода с повышением привилегий при работе списком в EventReciever-е списка:

SPSecurity.RunWithElevatedPrivileges(delegate()
{
    using (SPSite site = properties.OpenSite())
    {
        using (SPWeb web = site.OpenWeb())
        {
С виду этот блок кода выглядел, нормальным, но он не работал. Не долго думая, я включил рефлектор и решил посмотреть, что же тут не так? Данные из рефлектора:
public SPSite OpenSite() 
{ 
    if ((this.m_site == null) && (this.WebUrl != null)) 
    { 
        if (base.EventUserToken != null) 
        { 
            this.m_site = new SPSite(this.WebUrl, base.EventUserToken); 
        } 
        else
        { 
            this.m_site = new SPSite(this.WebUrl); 
        } 
        this.m_siteCreatedByThis = true; 
    } 
    return this.m_site; 
}
public SPWeb OpenWeb()
{
    this.OpenSite();
    if (this.m_site != null)
    {
        return this.m_site.OpenWeb(this.RelativeWebUrl);
    }
    return null;
}

Оказалось, что сайт, открывается всегда с определенным EventUserToken, который в контексте EventReciever всегда был != null.

Будьте осторожны, при использовании методов OpenSite(), OpenWeb() внутри List EventReciever-ов.