Pergunta

Estou usando o ASP.NET MVC 2.0 e estou tentando aproveitar as vantagens da ligação do modelo no meu controlador e também da validação do modelstate.No entanto, me deparei com um problema e queria compartilhá-lo com as pessoas aqui para ver o que você pensa.

Ok, tenho meu usuário limpo poco na minha biblioteca de classes de modelo ...

namespace Model
{    
    public partial class User
    {
        public virtual int Id { get; private set; }
        public virtual string UserName { get; private set; }
        public virtual string DisplayName { get; set; }
        public virtual string Email { get; set; }

        public User(string displayName, string userName)
            : this()
        {
            DisplayName = displayName;
            UserName = userName;
        }
    }
}

O design que escolhi permite apenas a edição de certas propriedades, após a construção do objeto.O UserName, por exemplo, só pode ser definido quando o objeto é construído, para mim isso faz sentido OO, mas é a chave do meu problema, então queria destacá-lo aqui.

Eu então tenho uma 'classe de amigo' que define os metadados de validação para minha classe de usuário ...

namespace Model
{
[MetadataType(typeof(UserMetadata))]
public partial class User
{
    class UserMetadata
    {
        [Required]
        public virtual int Id { get; set; }

        [Required]
        public virtual string UserName { get; set; }

        [Required]
        public virtual string DisplayName { get; set; }

        [RegularExpression(@"^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$", ErrorMessage = "Invalid address")]
        public virtual string Email { get; set; }
    }
}

}

Então, na minha camada da web, quero permitir que meus usuários possam editar este objeto.Portanto, tenho os dois métodos de ação a seguir em meu controlador de perfil.

namespace Web.Controllers
{
    public class ProfileController : Controller
    {
        [Authorize]
        public ActionResult Edit()
        {
            var user = _session.Single<User>(x => x.UserName == HttpContext.User.Identity.Name );
            return View(user);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize]
        [TransactionFilter]
        public ActionResult Edit(User updatedUser)
        {
            // Get the current user to update.
            var user = _session.Single<User>(x => x.UserName == HttpContext.User.Identity.Name);

            if (ModelState.IsValid)
            {
                TryUpdateModel(user);
                // Update store...                
            }
            return View(updatedUser);
        }
    }
}

Isso tem uma visão fortemente digitada para acompanhá-lo ...

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

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

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <%=Html.Script("jquery.validate.js")%>
    <%=Html.Script("MicrosoftMvcJQueryValidation.js")%>
    <%=Html.Script("MvcFoolproofJQueryValidation.js")%>
    <div class="container">
        <div class="column span-14">
        <% using (Html.BeginForm()) {%>
            <%= Html.AntiForgeryToken() %>
            <fieldset>
                <%: Html.DisplayFor(model => model.UserName) %>
                <%= Html.Label("Display Name") %>
                <%= Html.HiddenFor(model => model.DisplayName)%>
                <%= Html.ValidationMessageFor(model => model.DisplayName)%>
                <%= Html.Label("Email address") %>
                <%= Html.EditorFor(model => model.Email)%>
                <%= Html.ValidationMessageFor(model => model.Email)%>
                <%= Html.HiddenFor(model => model.UserName)%>
                <p>
                    <input type="submit" value="Save" />
                </p>
            </fieldset>
        </div>
        <div class="clear"></div>
        <% } %>
    </div>
</asp:Content>

Ok, então esse é todo o código!!

Então aqui está o problema: a visualização é renderizada corretamente após a solicitação inicial de obtenção.Mas quando o usuário posta o formulário de volta, digamos, após editar seu nome de exibição, o ModelState NÃO é válido.Isso ocorre porque a propriedade UserName possui um setter privado.No entanto, isso ocorre intencionalmente, por segurança e semântica, nunca quero que eles alterem seu nome de usuário, portanto, o setter é privado.Porém, como adicionei o atributo Required à propriedade, ele está falhando porque não está definido!

A questão é: a ligação do modelo deve relatar isso como um erro de validação ou não?!Como a propriedade é privada, projetei para que ela não seja definida, portanto, por design, não espero que o model binder a configure, mas não quero um erro de validação.Acho que só deveria produzir erros de validação para propriedades que PODE definir.

Ok, então possíveis soluções que encontrei até agora.

Torne a propriedade pública.

Se eu fizer isso, posso permitir que o nome de usuário seja alterado para usuários existentes.Eu teria que adicionar lógica extra em algum lugar para entender isso, o que não é muito legal.Eu também teria que adicionar um Bind Exclude no método de ação para impedir que qualquer pessoa travessa tentasse configurá-lo por meio de uma postagem.

Remova o erro

Acredito que posso remover o erro do dicionário ModelState, isso seria bom nesta ocasião, mas acho que isso introduzirá algum cheiro de código, pois eu teria que adicionar isso para todos os meus objetos que possuem setters privados.Eu provavelmente esqueceria!!

Digite fortemente minha visão em uma interface

Eu li que algumas pessoas vinculam sua visão a uma interface de seu modelo; essa é a interface principal do ModelView no objeto do modelo de negócios.Gosto dessa ideia, mas perdi a ligação automática e precisaria duplicar meus objetos de modelo com seus construtores na minha camada web, não tenho certeza?!Algumas informações sobre isso aqui http://www.codethinked.com/post/2010/04/12/Easy-And-Safe-Model-Binding-In-ASPNET-MVC.aspx.

Usar visualizações de modelo

Isso simplesmente não parece SECO para mim?!Fico feliz em usá-los se não tiver um objeto de modelo existente que se encaixe (por exemplo, eu uso uma visualização de modelo de inscrição).

CustomModelBinder

Minha opção preferida, mas não tenho certeza se sei o que estou fazendo!!Se eu conseguisse fazer com que o fichário se ligasse apenas às propriedades que ele pode definir, eu estaria rindo!!

O que as pessoas pensam?Comentários sobre as opções acima, alguma outra solução, estou errado com minha arquitetura?!

Obrigado :)

Foi útil?

Solução 3

jfar postou um bom link para uma postagem de Brad Wilson onde Brad comenta...

Você ainda pode fazer edição parcial, mas não pode mais fazer validação parcial.Portanto, se você excluir a ligação de algo com o atributo [necessário], a validação falhará.Você tem algumas opções para contornar isso:

  • Use um modelo de visualização que espelhe exatamente os dados do formulário

  • Preencher os campos [necessários], mas não ligados, com dados antes de ligar (tente) updatemodel para que a validação seja bem-sucedida (mesmo que você não pretenda fazer nada com esses dados)

  • Permita que os erros de validação ocorram e remova -os do ModelState após a validação ser feita, pois eles são erros inadequados.

Meu caso parece se enquadrar no caso de 'edição parcial', onde não quero que determinados campos sejam atualizados.

Vou considerar isso como soluções.

Outras dicas

"Eu projetei para que ele não seja definido, portanto, por design, não espero que o model binder o configure, mas não quero um erro de validação.Acho que só deveria produzir erros de validação para propriedades que PODE definir."

Leia mais sobre esta decisão de design aqui:

http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html

Curiosamente, a maioria das pessoas reclamou exatamente o oposto do que você está reclamando.;)

Você basicamente está dizendo ao sistema que algo que não pode ser definido deve sempre ser definido.Então, eu não diria que o MVC está funcionando incorretamente ou algo parecido.Você está apenas codificando um cenário impossível.


No geral, você está apenas alcançando os pontos problemáticos da técnica de metadatabuddy.Principalmente a necessidade de ter validação diferente para cenários novos e editados.

"Se eu fizer isso, posso permitir que o nome de usuário seja alterado para usuários existentes.Eu teria que adicionar lógica extra em algum lugar para entender isso, o que não é muito legal.Eu também teria que adicionar um Bind Exclude no método de ação para impedir que qualquer pessoa travessa tentasse configurá-lo por meio de uma postagem."

IMHO, você está comendo demais com essas mudanças de código.Você adicionaria uma string simples a uma única chamada de método.Qual é o problema?Eu adotaria a abordagem pragmática aqui.

Eu usaria um modelo de visualização porque é o mais adequado para o trabalho.Não pense em DRY, o que significa que você não pode repetir propriedades em dois objetos, pense nisso como "não duplique a lógica ou persista dados idênticos em dois lugares".Nesse caso, a semântica de lidar com a vinculação de modelo não corresponde ao seu modelo de domínio, portanto, você precisa encontrar uma maneira de traduzi-la.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top