ASP.NET MVC Beta 1: DefaultModelBinder erroneamente persiste parâmetro e estado de validação entre os pedidos não relacionados

StackOverflow https://stackoverflow.com/questions/238460

Pergunta

Quando eu usar o modelo padrão de ligação de parâmetros de formulário vincular a um objeto complexo que é um parâmetro a uma ação, a estrutura lembra os valores passados ??para o primeiro pedido, o que significa que qualquer pedido posterior a essa ação recebe os mesmos dados o primeiro. Os valores dos parâmetros e estado de validação são persistentes entre as solicitações da web não relacionados.

Aqui está o meu código do controlador (service representa o acesso ao back-end do aplicativo):

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Create()
    {
        return View(RunTime.Default);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(RunTime newRunTime)
    {
        if (ModelState.IsValid)
        {
            service.CreateNewRun(newRunTime);
            TempData["Message"] = "New run created";
            return RedirectToAction("index");
        }
        return View(newRunTime);
    }

A minha visão .aspx (fortemente tipificada como ViewPage<RunTime>) contém diretrizes como:

<%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %>

Este usa a classe DefaultModelBinder, que é destina-se a vinculação automática propriedades do meu modelo .

Eu bati a página, inserir dados válido (por exemplo, tempo = 1). O aplicativo salva corretamente o novo objeto com o tempo = 1. Então eu batê-lo novamente, insira dados válidos diferente (por exemplo, tempo = 2). No entanto, os dados que são gravados é o original (por exemplo, tempo = 1). Isso também afeta a validação, por isso, se os meus dados originais era inválido, então todos os dados que entram no futuro é considerado inválido. Reiniciar IIS ou reconstruir meu código libera o estado persistido.

Eu posso resolver o problema escrevendo meu próprio hard-coded fichário de modelo, um exemplo ingênuo básica do que é mostrado abaixo.

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create([ModelBinder(typeof (RunTimeBinder))] RunTime newRunTime)
    {
        if (ModelState.IsValid)
        {
            service.CreateNewRun(newRunTime);
            TempData["Message"] = "New run created";
            return RedirectToAction("index");
        }
        return View(newRunTime);
    }


internal class RunTimeBinder : DefaultModelBinder
{
    public override ModelBinderResult BindModel(ModelBindingContext bindingContext)
    {
        // Without this line, failed validation state persists between requests
        bindingContext.ModelState.Clear();


        double time = 0;
        try
        {
            time = Convert.ToDouble(bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"]);
        }
        catch (FormatException)
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".Time", bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"] + "is not a valid number");
        }

        var model = new RunTime(time);
        return new ModelBinderResult(model);
    }
}

Estou faltando alguma coisa? Eu não acho que é um problema de sessão do navegador quanto eu posso reproduzir o problema se a primeira dados são inseridos em um navegador e o segundo em outra.

Foi útil?

Solução

Acontece que o problema era que meus controladores estavam sendo reutilizados entre as chamadas. Um dos detalhes que eu escolhi para omitir do meu post original é que eu estou usando o recipiente Castle.Windsor para criar meus controladores. Eu não tinha conseguido marcar meu controlador com o estilo de vida transitória, então eu estava recebendo a mesma instância para trás em cada solicitação. Assim, o âmbito a ser utilizado pelo ligante era de ser re-utilizado e é claro que continha dados velhos.

Eu descobri o problema ao analisar cuidadosamente a diferença entre código e mina da Eilon, eliminando todas as outras possibilidades. Como o documentação Castelo diz , este é um "erro terrível" ! Que isto seja um aviso para os outros!

Obrigado por sua resposta Eilon -. Desculpe para ocupar o seu tempo

Outras dicas

Eu tentei reproduzir este problema, mas eu não estou vendo esse mesmo comportamento. Eu criei quase exatamente o mesmo controlador e pontos de vista que você tem (com algumas suposições) e cada vez que criou uma nova "RunTime" Eu coloquei o seu valor em TempData e enviou-o através do redireccionamento. Em seguida, na página de destino Peguei o valor e foi sempre o valor eu digitei sobre esse pedido -. Nunca um valor obsoleto

Aqui está o meu Controlador:

HomeController classe pública: Controlador { Índice de ActionResult público () { ViewData [ "Title"] = "Home Page"; mensagem de string = "Bem-vindo:" + TempData [ "mensagem"]; if (TempData.ContainsKey ( "value")) { int theValue = (int) TempData [ "valor"]; mensagem + = "" + theValue.ToString (); } ViewData [ "mensagem"] = mensagem; retornar View (); }

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create() {
    return View(RunTime.Default);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(RunTime newRunTime) {
    if (ModelState.IsValid) {
        //service.CreateNewRun(newRunTime);
        TempData["Message"] = "New run created";
        TempData["value"] = newRunTime.TheValue;
        return RedirectToAction("index");
    }
    return View(newRunTime);
}

}

E aqui está o meu Vista (Create.aspx):

<% using (Html.BeginForm()) { %>
<%= Html.TextBox("newRunTime.TheValue", ViewData.Model.TheValue) %>
<input type="submit" value="Save" />
<% } %>

Além disso, eu não tinha certeza do que o tipo "RunTime" parecia, por isso fiz esta:

   public class RunTime {
        public static readonly RunTime Default = new RunTime(-1);

        public RunTime() {
        }

        public RunTime(int theValue) {
            TheValue = theValue;
        }

        public int TheValue {
            get;
            set;
        }
    }

É possível que a implementação do tempo de execução inclui alguns valores estáticos ou algo assim?

Obrigado,

Eilon

Eu não tenho certeza se isso está relacionado ou não, mas a sua chamada para <% = Html.TextBox ( "newRunTime.Time", ViewData.Model.Time)%> pode realmente escolher a sobrecarga errado (como o tempo é um número inteiro, ele vai pegar a sobrecarga object htmlAttributes, ao invés de string value.

Verificar o HTML renderizado vai deixar você saber se isso está ocorrendo. mudando int para ViewData.Model.Time.ToString() forçará a sobrecarga correta.

Parece que o problema é algo diferente, mas eu notei que e foram queimadas no passado.

Seb, eu não estou certo do que você quer dizer com um exemplo. Eu não sei nada sobre a configuração Unidade. Vou explicar a situação com Castle.Windsor e talvez isso irá ajudá-lo com a configure Unidade corretamente.

Por padrão, Castle.Windsor retorna o mesmo objeto cada vez que você solicitar um determinado tipo. Este é o estilo de vida do solteirão. Há uma boa explicação sobre as várias opções de estilo de vida na Castle.Windsor .

Em ASP.NET MVC, cada instância de uma classe de controlador está ligado ao contexto da solicitação da web que ele foi criado para servir. Portanto, se seu contêiner IoC retorna a mesma instância de sua classe controlador de cada vez, você sempre terá um controlador ligado ao contexto da primeira solicitação da web que usou essa classe de controlador. Em particular, o ModelState e outros objetos utilizados pelo DefaultModelBinder serão reutilizados, para que o seu modelo de objeto amarrado e as mensagens de validação no ModelState será obsoleto.

Por isso você precisa de seu IoC para retornar uma nova instância de cada vez MVC solicita uma instância de sua classe controller.

Em Castle.Windsor, isso é chamado o estilo de vida transitória. Para configurá-lo, você tem duas opções:

  1. configuração XML: você adicionar lifestlye = "transitória" para cada elemento no seu arquivo de configuração que representa um controlador.
  2. configuração no código: você pode dizer o recipiente de usar o estilo de vida transitória no momento de registrar o controlador. Isto é o que o ajudante MvcContrib que Ben mencionou faz automaticamente para você - dê uma olhada nas RegisterControllers método na MvcContrib código fonte .

Eu imagino que a Unidade oferece um conceito semelhante ao estilo de vida em Castle.Windsor, então você precisa configurar Unidade de usar seu equivalente do estilo de vida transitória para os controladores. MvcContrib parece ter algum Unity apoio - talvez você poderia olhar lá.

Espero que isso ajude.

Depois de se deparar com problemas semelhantes ao tentar usar o recipiente de Windsor IoC em um aplicativo ASP.NET MVC eu tinha que passar pela mesma viagem de descoberta para fazê-lo funcionar. Aqui estão alguns dos detalhes que possam ajudar alguém.

Usando esta é a configuração inicial no Global.asax:

  if (_container == null) 
  {
    _container = new WindsorContainer("config/castle.config");
    ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container)); 
  }

E usando um WindsorControllerFactory que quando perguntado para uma instância do controlador faz:

  return (IController)_container.Resolve(controllerType);

Enquanto Windsor foi corretamente ligando-se todos os controladores, para alguns parâmetros razão não estavam sendo passados ??do formulário para a ação do controlador relevante. Em vez disso eles estavam todos nulo, apesar de ter sido chamando a ação correta.

O padrão é para o recipiente para passar singletons para trás, obviamente, uma coisa ruim para os controladores e a causa do problema:

http://www.castleproject.org/monorail/documentation /trunk/integration/windsor.html

No entanto, a documentação faz apontam que o estilo de vida dos controladores pode ser alterado para transitória, embora ele realmente não lhe dizer como fazer isso se você estiver usando um arquivo de configuração. Acontece que é fácil o suficiente:

<component 
  id="home.controller" 
  type="DoYourStuff.Controllers.HomeController, DoYourStuff" 
  lifestyle="transient" />

E, sem qualquer código muda agora deve funcionar como esperado (ou seja, controladores únicos de cada vez fornecido pela uma instância do container). Você pode, então, fazer tudo de sua configuração IoC no arquivo de configuração em vez do código como o bom menino / menina que eu sei que você é.

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