Фреймворк для классов-оболочек привязки данных ASP.NET - PullRequest
0 голосов
/ 14 декабря 2011

Видимо ASP.NET не разрешает привязку данных к динамическим объектам . Главный облом, потому что я мог видеть синтаксис, как этот, действительно полезный:

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

...

// No this doesn't exist, I just wish it did!
MyGrid.DataSource = GetAllUsers()
    .AsDynamic()
        .WithProperty("FullName", user => user.FirstName + " " + user.LastName)
    .ToEnumerable(); // returns IEnumerable<dynamic>
MyGrid.DataBind()

...

<asp:BoundField DataField="FirstName" HeaderText="First Name" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
<asp:BoundField DataField="FullName" HeaderText="Full Name" />

В этом примере AsDynamic() вернет класс, который настроит динамические объекты, которые будут возвращены .ToEnumerable() позже (потому что вы не можете реализовать IEnumerable<dynamic>), эффективно добавляя свойства в обернутый объект данных. Запросы на FirstName и LastName будут «обслуживаться» реальным объектом, а запрос на FullName будет перенаправляться делегату или выражению для динамической оценки.

Это тривиальный пример, потому что в большинстве случаев вы можете легко добавить свойство FullName к объекту User и легко выполнить это с помощью TemplatedField.

Но что, если добавленное свойство было слишком сложно реализовать в TemplatedField без нескольких строк кода привязки данных? А что если вы не управляете исходным кодом для класса User? Или что, если вы не можете добавить свойство в User, потому что его расчет зависит от сборки, которая сама зависит от сборки User? (круговая ссылка)

По этой причине было бы здорово иметь очень простую в применении оболочку для привязки данных, например, такую, в которой вам не нужно каждый раз генерировать новый класс.

Так, что я действительно после?

Существуют ли какие-либо структуры или методы, которые позволяют подобные вещи? Точный синтаксис, приведенный выше, не очень важен, просто возможность динамически добавлять вещи в классы и использовать эти прокси-серверы для привязки данных, без большого количества ручного кода.

Ответы [ 4 ]

3 голосов
/ 22 декабря 2011

Я нахожу три способа решения (некоторых) ваших проблем с помощью C # и способ расширить некоторые из этих подходов с помощью инструментов Visual Studio.

Анонимные типы

ASP.NET может привязывать данные к анонимным типам :

DataGrid.DataSource = GetAllUsers().
  .AsQueryable()
  .Select(u => new { User = u, FullName = GetFullName(u) });
DataGrid.DataBind()

Анонимный тип может по-прежнему обеспечивать легкий доступ к исходному типу (в этом примере через свойство User). Это упростит привязку данных относительно (с использованием <asp:TemplateField>), и вы переместили сложную логику в отдельный метод, который работает с объектом User.

<%# Eval("User.FirstName") %>
<%# Eval("User.LastName") %>
<%# Eval("FullName") %>

Синтаксис привязки данных должен быть помещен в ItemTemplate из <asp:TemplateField>, но я для краткости опустил этот код. Конечно, последнее свойство также может быть отображено с помощью <asp:BoundField>:

<asp:BoundField DataField="FullName" />

Обратите внимание, что вам не нужно сопоставлять каждое свойство исходного типа в анонимном типе, вы можете просто сопоставить одно свойство с исходным объектом. Недостаток (только?) Заключается в том, что вы больше не можете использовать <asp:BoundField> для этих свойств, но вы должны использовать <asp:TemplateField>.

Методы расширения

Чтобы дополнить этот подход, вы можете использовать методы расширения , чтобы «прикрепить» методы к классу, даже если у вас нет доступа к классу «source»:

public static class UserExtensions
{
  public static string GetFullName(this User user)
  {
      return user.FirstName + " " + user.LastName;
  }
}

Для привязки данных мы должны использовать <asp:TemplateField>:

<%# Eval("User.FirstName") %>
<%# Eval("User.LastName") %>
<%# (Container.DataItem as User).GetFullName() %>

Частичные занятия

Другой вариант, доступный начиная с C # 2.0, заключается в написании частичного класса , но только если исходный класс также объявлен частичным и объявлен в вашем проекте (части того же модуля). Этот подход полезен, если класс User создается с помощью инструмента, например, если вы используете какой-либо инструмент автоматического сопоставления баз данных в вашем проекте.

public partial class User
{
    public string FullName
    {
        get { return this.FirstName + " " + this.LastName; }
    }
}

Для привязки данных мы теперь возвращаемся к использованию '':

<asp:BoundField DataField="FirstName" />
<asp:BoundField DataField="LastName" />
<asp:BoundField DataField="FullName" />

Это все возможности компилятора C # и среды выполнения .NET, поэтому они относятся к категории методов вместо фреймворков. Конечно, базовое наследование также может быть использовано, но оно может быть неприменимо в вашей ситуации?

Текстовые шаблоны T4

Если у вас есть особые потребности в том, как должен выглядеть класс с привязкой к данным, но вы не можете использовать ни один из описанных выше подходов, вы всегда можете взглянуть на T4-шаблоны в Visual Studio. (Они работают в проектах веб-приложений ASP.NET, но не в проектах веб-сайтов ASP.NET.)

С помощью этих шаблонов вы можете генерировать код во время разработки, например, для создания поверхностного, неполного класса UserViewModel, который прозрачно отображает все свойства во внутренний объект User. Затем, используя подход с частичным классом, вы можете добавить дополнительные свойства и методы к этому типу, используя другое частичное объявление класса в файле .cs, и просто привязать данные к вашему UserViewModel:

DataGrid.DataSource = GetAllUsers().
  .AsQueryable()
  .Select(u => new UserViewModel(u));
DataGrid.DataBind()

Привязка данных снова становится прямой, используя <asp:BoundField>:

<asp:BoundField DataField="FirstName" />
<asp:BoundField DataField="LastName" />
<asp:BoundField DataField="FullName" />

Используя шаблоны T4, вы можете автоматически сгенерировать эти пользовательские классы моделей представлений для всех типов ваших доменов. При использовании отражения в T4 есть предостережения:

2 голосов
/ 16 декабря 2011

Возможно, вы захотите заглянуть в библиотеку Clay (см. этот великолепный обзор ):

public interface IUser {
    public string FirstName { get; set; }
    public string LastName  { get; set; }
    public string FullName  { get; set; }
} 

dynamic New = new ClayFactory();
existingUser = //grab your existing user here
IUser clayUser = New.User(){
    FirstName: existingUser.FirstName,
    LastName: existingUser.LastName,
    FullName: existingUser.FirstName + " " + existingUser.LastName;

Конечно, существует несколько способов ободрать эту кошку, особенно синтаксически,Кроме того, я не углубился в это очень глубоко (это ваша работа!;), Поэтому я не знаю, может ли объект Clay упасть на существующий объект, или вам нужно будет заполнить нового пользователя Clay из существующегоодин, как я сделал.Самое замечательное в том, что если вы наследуете их от интерфейса, объекты Clay живут в CLR, получают Intellisense и действуют как настоящие нединамические объекты, если я правильно читаю статью.

1 голос
/ 22 декабря 2011

Прочитав ответ Джесси Смита относительно библиотеки Клэя , я посмотрел на Клэя и решил, что он не совсем подходит для того, что мне нужно.Однако внутренне Clay использует библиотеку DynamicProxy Castle Project , и в ней есть некоторые интересные вещи, которые, хотя и не идеальны, безусловно, подходят к тому, что я хотел существовать.

Castle DynamicProxy может создатьпрокси объекта, испуская код, а затем перехватывая вызовы к нему.Единственное требование к вашим бизнес-объектам заключается в том, что методы и свойства должны быть помечены как virtual, чтобы Castle могла перехватывать вызовы к ним.

Затем вы можете добавить «mixins» к вашему прокси-объекту.Я продемонстрирую на примере пользователя из вопроса:

public class User
{
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
}

Если мы хотим добавить FullName к прокси-серверу этого объекта, то нам нужно немного поработать, чтобы включить это,создание интерфейса, который объявляет свойство, а затем объект реализации, который может предоставить значение от существующего пользователя:

public interface IUserProxy
{
    string FullName { get; }
}

public class UserProxyImpl : IUserProxy
{
    public User User { get; set; }

    public string FullName
    {
        get { return User.FirstName + " " + User.LastName; }
    }
}

Теперь для привязки данных я бы очень хотел использовать это для перечислимого,поэтому метод расширения может выполнять работу по созданию прокси и добавлению миксинов.Мы позволим вызывающему коду предоставлять миксины (которые в основном являются просто объектами), используя Func<T, object>, чтобы мы могли определить их, используя лямбда-выражения:

public static class ProxyExtensions
{
    public static IEnumerable<T> ProxyAddMixins<T>(this IEnumerable<T> collection, params Func<T, object>[] mixinSelectors)
        where T : class
    {
        ProxyGenerator factory = new ProxyGenerator();
        foreach (T item in collection)
        {
            ProxyGenerationOptions o = new ProxyGenerationOptions();
            foreach (var func in mixinSelectors)
            {
                object mixin = func(item);
                o.AddMixinInstance(mixin);
            }
            yield return factory.CreateClassProxyWithTarget<T>(item, o);
        }
    }
}

Затем наш клиентский код (который я 'в приложении Windows Console, потому что его проще тестировать) может выглядеть так.Когда мы добираемся до лямбды для предоставления миксинов, мы возвращаем новый UserProxyImpl, передавая ему базовый User объект.Касл анализирует UserProxyImpl, замечает, что он реализует IUserProxy, и заставляет передаваемый прокси-класс реализовывать этот интерфейс с этой реализацией.Все остальные свойства распространяются на виртуальные реализации исходного объекта без перехвата прокси.

class Program
{
    static void Main(string[] args)
    {
        List<User> users = new List<User>();
        users.Add(new User { FirstName = "John", LastName = "Doe" });
        users.Add(new User { FirstName = "Jane", LastName = "Doe" });

        var userProxies = users
            .ProxyAddMixins(u => new UserProxyImpl { User = u })
            .ToList();

        Console.WriteLine("First\tLast\tFull");
        foreach (var userProxy in userProxies)
        {
            Console.WriteLine("{0}\t{1}\t{2}",
                DataBinder.Eval(userProxy, "FirstName"),
                DataBinder.Eval(userProxy, "LastName"),
                DataBinder.Eval(userProxy, "FullName"));
        }
        Console.ReadLine();
    }
}

Я действительно хотел кое-что, где вы могли бы просто создать прокси, просто определив несколько лямбд, не определяя дополнительный интерфейс или реализациюкласс, но это, кажется, единственный способ сделать это.Конечно, вы должны принять во внимание, действительно ли стоит использовать эти пользовательские типы для выполнения этой работы по сравнению с другими методами.

Вот суть полного кода , так что вы не можетене нужно собирать его, чтобы попробовать.

1 голос
/ 16 декабря 2011

Один из способов добиться этого - использовать asp: TemplateField.

Вы также можете использовать Dynamic Linq для этого, см. блог ScottGu об основах Dynamic Linq .

Затем вы можете использовать Dynamic Linq для создания динамической части выбора оператора. Вот некоторый код для создания оператора select, который выбирает все свойства базового объекта и создает дополнительные свойства на основе динамических выражений.

public class ExtraProperty
{
    public string Name { get; set; }
    public string Expression { get; set; }
}


/// <summary>
/// Creates a string on the form "new (property1, property2, ..., expression1 as extraproperty1, ... )
/// </summary>
/// <param name="t"></param>
/// <param name="extraProperties"></param>
/// <returns></returns>
public string CreateSelectClauseWithProperty(Type objecType, ExtraProperty[] extraProperties)
{
    string ret = "new(";
    bool notFirst = false;
    System.Reflection.PropertyInfo[] typeProps = objecType.GetProperties();


    // Equivalent of "Select objectType.*"
    foreach (System.Reflection.PropertyInfo p in typeProps)
    {
        if (notFirst)
            ret += ",";
        else
            notFirst = true;
        ret += p.Name;
    }

    // Equivalent of "expression1 as name1, expression2 as name2, ..." - giving the extra columns
    foreach (ExtraProperty ep in extraProperties)
    {
        if (notFirst)
            ret += ",";
        else
            notFirst = true;
        ret += ep.Expression + " as " + ep.Name;
    }
    return ret + ")";
}

Пример использования этого будет выглядеть так:

    MyGrid.AutoGenerateColumns = false;
    string selectClause = CreateSelectClauseWithProperty(typeof(User),
            new ExtraProperty[] { 
                    new ExtraProperty() 
                    { Name = "FullName", Expression = "FirstName + \" \" + LastName" }
                }
                );
    IQueryable<User> list = GetAllUsers();
    var query = list.Select( selectClause );
    MyGrid.DataSource =  query;
    MyGrid.DataBind();

Вы должны включить это в свой заголовок:

using System.Linq.Dynamic;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...