MVC универсальный ViewModel - PullRequest
       9

MVC универсальный ViewModel

8 голосов
/ 17 января 2011

Короче говоря, я хотел бы иметь возможность передать универсальную ViewModel в мои представления

Вот некоторый упрощенный код суть того, чего я пытаюсь достичь

public interface IPerson
{
    string FirstName {get;}
    string LastName {get;}
}

public class FakePerson : IPerson
{
    public FakePerson()
    {
        FirstName = "Foo";
        LastName = "Bar";
    }

    public string FirstName {get; private set;} 
    public string LastName {get; private set;} 
}

public class HomeViewModel<T> 
    where T : IPerson, new()
{
    public string SomeOtherProperty{ get; set;}
    public T Person { get; private set; }

    public HomeViewModel()
    {
        Person = new T();
    }
}

public class HomeController : Controller {
    public ViewResult Index() {
        return View(new HomeViewModel<FakePerson>());
    }
}

Если я создаю свой вид следующим образом, все работает как положено

<%@ Page Language="C#" 
    MasterPageFile="~/Views/Shared/Site.Master"
    Inherits="System.Web.Mvc.ViewPage<HomeViewModel<FakePerson>>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <%: Html.DisplayFor(m=>m.Person.FirstName) %>
    <%: Html.DisplayFor(m=>m.Person.LastName) %>   
</asp:Content>

Однако я не хочу напрямую зависеть от FakePerson в представлении в случае, если я хочу передать какую-то другую реализацию IPerson, поэтому я попытался изменить директиву страницы на

<%@ Page Language="C#" 
    MasterPageFile="~/Views/Shared/Site.Master"
    Inherits="System.Web.Mvc.ViewPage<HomeViewModel<IPerson>>" %>

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

Может кто-нибудь помочь, пожалуйста.

[UPDATE]

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

 public class FakePerson : IPerson
 {
    public FakePerson()
    {
        FirstName = "Foo";
        LastName = "Bar";
    }

    [DisplayName("First Name")]
    public string FirstName {get; private set;}

    [DisplayName("Last Name")]
    public string LastName {get; private set;} 
}

Таким образом, при использовании ковариантного интерфейса этот способ работает, частично, для доступа к производному типу через ViewModel; поскольку представление вводится в интерфейс, кажется, что атрибуты недоступны.

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

Ответы [ 4 ]

2 голосов
/ 27 января 2011

Я успешно получил ковариант для работы, то есть привязку View к абстрактному базовому классу. На самом деле, у меня есть привязка к списку . Затем я создаю конкретный вид, строго типизированный для каждого подкласса. Это заботится о привязке.

Но перепривязка не удастся, потому что DefaultModelBinder знает только об абстрактном базовом классе, и вы получите исключение типа «Не удается создать абстрактный класс». Решение состоит в том, чтобы иметь свойство в вашем базовом классе, например:

    public virtual string BindingType
    {
        get
        {
            return this.GetType().AssemblyQualifiedName;
        }
    }

Свяжите это со скрытым вводом в вашем представлении. Затем вы замените свой ModelBinder по умолчанию на собственный в Global.asax:

    // Replace default model binder with one that can deal with BaseParameter, etc.
    ModelBinders.Binders.DefaultBinder = new CustomModelBinder();

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

    public class CustomModelBinder : DefaultModelBinder
{
    private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
    {
        if (modelType.IsInterface || modelType.IsAbstract)
        {
            // This is our convention for specifying the actual type of a base type or interface.
            string key = string.Format("{0}.{1}", bindingContext.ModelName, Constants.UIKeys.BindingTypeProperty);            
            var boundValue = bindingContext.ValueProvider.GetValue(key);

            if (boundValue != null && boundValue.RawValue != null)
            {
                string newTypeName = ((string[])boundValue.RawValue)[0].ToString();
                logger.DebugFormat("Found type override {0} for Abstract/Interface type {1}.", modelType.Name, newTypeName);

                try
                {
                    modelType = System.Type.GetType(newTypeName);
                }
                catch (Exception ex)
                {
                    logger.ErrorFormat("Error trying to create new binding type {0} to replace original type {1}. Error: {2}", newTypeName, modelType.Name, ex.ToString());
                    throw;
                }
            }
        }

        return base.CreateModel(controllerContext, bindingContext, modelType);
    }

    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        if (propertyDescriptor.ComponentType == typeof(BaseParameter))
        {
            string match = ".StringValue";
            if (bindingContext.ModelName.EndsWith(match))
            {
                logger.DebugFormat("Try override for BaseParameter StringValue - looking for real type's Value instead.");
                string pattern = match.Replace(".", @"\.");
                string key = Regex.Replace(bindingContext.ModelName, pattern, ".Value");
                var boundValue = bindingContext.ValueProvider.GetValue(key);
                if (boundValue != null && boundValue.RawValue != null)
                {
                // Do some work here to replace the base value with a subclass value...
                    return value;
                }
            }
        }

        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
}

Здесь мой абстрактный класс - BaseParameter, и я заменяю свойство StringValue значением, отличным от подкласса (не показано).

Обратите внимание, что, хотя вы можете выполнить повторную привязку к правильному типу, значения формы, связанные только с подклассом, не будут автоматически округляться, поскольку связыватель модели видит только свойства базового класса. В моем случае мне нужно было только заменить одно значение в GetValue и получить его вместо этого из подкласса, так что это было легко. Если вам нужно связать много свойств подкласса, вам нужно будет проделать немного больше работы и извлечь их из формы (ValueProvider [0]) и заполнить экземпляр самостоятельно.

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

0 голосов
/ 24 ноября 2011

Я создал интерфейс

public  interface ITestModel
    {
         string FirstName { get; set; }
         string LastName { get; set; }
         string Address { get; set; }
        int Age { get; set; }
    }

Создать класс и унаследован от этого интерфейса

class TestModel : ITestModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public int Age { get; set; }

    }

создан экземпляркласса в контроллере

public ActionResult TestMethod()
        {
            ITestModel testModel;
            testModel = new TestModel();
            testModel.FirstName = "joginder";
            testModel.Address = "Lovely Home";
            testModel.LastName = "singh";
            testModel.Age = 36;
            return View("testMethod", testModel);
        }

Представление, созданное таким образом

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<TestProject.ITestModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    TestMethod
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>TestMethod</h2>

    <fieldset>
        <legend>Fields</legend>

        <div class="display-label">FirstName</div>
        <div class="display-field"><%: Model.FirstName %></div>

        <div class="display-label">LastName</div>
        <div class="display-field"><%: Model.LastName %></div>

        <div class="display-label">Address</div>
        <div class="display-field"><%: Model.Address %></div>

        <div class="display-label">Age</div>
        <div class="display-field"><%: Model.Age %></div>

    </fieldset>
    <p>
        <%: Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |
        <%: Html.ActionLink("Back to List", "Index") %>
    </p>

</asp:Content>

Теперь я могу передать любой объект любого аналогичного интерфейса

Надеюсь, это поможет:)

0 голосов
/ 17 января 2011

Заставьте ваш класс ViewModel реализовывать неуниверсальный (или универсальный ковариантный) интерфейс, затем измените представление, чтобы использовать этот интерфейс вместо конкретного класса.

0 голосов
/ 17 января 2011

Ваш код почти правильный;вам просто нужно передать HomeViewModel<IPerson> (не FakePerson) в контроллер.

Вы также можете использовать ковариантный интерфейс для модели в представлении, но это, вероятно, излишне.

...