Pergunta

Agora eu vi algumas perguntas como esta, mas não é exatamente o que eu quero perguntar, então, para todos aqueles gritando duplicados, eu peço desculpas :).

Eu quase não tocou ASP.NET MVC, mas pelo que eu entendi não há ViewState/ControlState...bem.Então a minha pergunta é qual é a alternativa para manter um controle do estado?Vamos voltar para a escola antiga ASP onde podemos simular que ASP.NET ViewState/ControlState faz através da criação de formulário oculto entradas com o controle do estado, ou com MVC, podemos apenas supor AJAX sempre e manter todo o estado do lado do cliente e fazer chamadas AJAX para atualizar?

Esta pergunta tem algumas respostas, Manutenção de viewstate na Asp.net mvc?, mas não exatamente o que eu estou procurando uma resposta.

ATUALIZAÇÃO:Obrigado por todas as respostas até agora.Só para esclarecer que eu não estou procurando e o que eu estou procurando:

Não à procura de:

  • Sessão de solução de
  • Cookie solução
  • Não procura imitar os WebForms em MVC

O que eu estou/estava procurando:

  • Um método que apenas mantém o estado em postback se os dados não rebote para um controle.Acho que WebForms com o cenário de apenas a ligação de uma grade sobre o carregamento da página inicial, i.é.só religação de dados, quando necessário.Como eu mencionei, eu não estou tentando imitar WebForms, apenas imaginando o que mecanismos de MVC oferece.
Foi útil?

Solução

A convenção já está disponível sem pular por muitos aros. O truque é conectar os valores da caixa de texto com base no modelo que você passa para a exibição.

[AcceptVerbs(HttpVerbs.Get)]   
public ActionResult CreatePost()
{
  return View();
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreatePost(FormCollection formCollection)
{
  try
  {
    // do your logic here

    // maybe u want to stop and return the form
    return View(formCollection);
  }
  catch 
  {
    // this will pass the collection back to the ViewEngine
    return View(formCollection);
  }
}

O que acontece em seguida é que o ViewEngine pega o FormCollection e corresponde às teclas da coleção com os nomes/valores de ID que você tem em sua opinião, usando os ajudantes HTML. Por exemplo:

<div id="content">

  <% using (Html.BeginForm()) { %>

  Enter the Post Title: <%= Html.TextBox("Title", Model["Title"], 50) %><br />
  Enter the Post Body: <%= Html.TextArea("Body", Model["Body"]) %><br />

  <%= Html.SubmitButton() %>

  <% } %>

</div>

Observe que a caixa de texto e a textareia têm os IDs do título e do corpo? Agora, observe como estou definindo os valores do objeto modelo da exibição? Desde que você passou em uma forma de forma (e você deve definir a visualização como fortemente digitada com uma forma de forma), agora você pode acessá -la. Ou, sem um tipo fortemente, você pode simplesmente usar o ViewData ["Title"] (eu acho).

Poof Sua vista mágica. Esse conceito é chamado Convenção sobre a configuração.

Agora, o código acima está em sua forma mais simples e crua usando o FormCollection. As coisas ficam interessantes quando você começa a usar o ViewModels, em vez do formocollection. Você pode começar a adicionar sua própria validação de seus modelos/viewmodels e fazer com que o controlador borbulhe os erros de validação personalizados automaticamente. Essa é uma resposta para outro dia.

Eu sugeriria o uso de um PostFormViewModel em vez do objeto POST, mas para cada um porte. De qualquer maneira, exigindo um objeto no método de ação, agora você obtém um método iSValid () que você pode ligar.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreatePost(Post post)
{

  // errors should already be in the collection here
  if (false == ModelState.IsValid())
    return View(post);

  try
  {
    // do your logic here

    // maybe u want to stop and return the form
    return View(post);
  }
  catch 
  {
    // this will pass the collection back to the ViewEngine
    return View(post);
  }
}

E sua visão fortemente tipada precisaria ser aprimorada:

<div id="content">

  <% using (Html.BeginForm()) { %>

  Enter the Post Title: <%= Html.TextBox("Title", Model.Title, 50) %><br />
  Enter the Post Body: <%= Html.TextArea("Body", Model.Body) %><br />

  <%= Html.SubmitButton() %>

  <% } %>

</div>

Você pode dar um passo adiante e exibir os erros também na visualização, diretamente do ModelState que você definiu no controlador.

<div id="content">

  <%= Html.ValidationSummary() %>

  <% using (Html.BeginForm()) { %>

  Enter the Post Title: 
    <%= Html.TextBox("Title", Model.Title, 50) %>
    <%= Html.ValidationMessage("Title") %><br />

  Enter the Post Body: 
    <%= Html.TextArea("Body", Model.Body) %>
    <%= Html.ValidationMessage("Body") %><br />

  <%= Html.SubmitButton() %>

  <% } %>

</div>

O que é interessante com essa abordagem é que você notará que não estou definindo o resumo da validação, nem as mensagens de validação individuais na visualização. Gosto de praticar conceitos de DDD, o que significa que minhas mensagens de validação (e resumos) são controladas no meu domínio e são passadas na forma de uma coleção. Em seguida, eu percorro a coleção dele (se existem erros) e os adiciono à coleção atual do ModelState.Adderrors. O restante é automático quando você retorna a visualização (postagem).

Muita convenção acabou. Alguns livros que eu recomendo que cubra esses padrões com muito mais detalhes são:

E nessa ordem, a primeira cobre as porcas e parafusos crus de toda a estrutura do MVC. O último abrange técnicas avançadas fora do Relm oficial da Microsoft, com várias ferramentas externas para facilitar sua vida (Castle Windsor, MOQ, etc.).

Outras dicas

A Vista é suposto ser mudo, o padrão MVC, apenas mostrando o que o Controlador de dá-lo (obviamente que muitas vezes acabam com alguma lógica, mas a premissa é de que a não ser como resultado, os controles não são responsáveis por seu estado, ele vai vir a partir do controlador de cada vez.

Eu não posso recomendar Steven Sanderson livro Pro ASP.NET MVC pela Apress o suficiente para lidar com esse padrão, e esta implementação do mesmo.

In Web Forms, control values are maintained in the viewstate so you (theoretically) don't need to reinitialize and such with each postback. The values are (again theoretically) maintained by the framework.

In ASP.NET MVC, if you follow the paradigm, you don't need to maintain state on form elements. The form element values are available on post where your controller can act on them (validation, database updates, etc.). For any form elements that are displayed once the post is processed, you (the developer) are responsible for initializing them - the framework doesn't automatically do that for you.

That said, I have read about a mechanism called TempData that allows your controller to pass data to another controller following a redirect. It is actually a session variable (or cookie if you configure it as such) but it is automatically cleaned up after the next request.

The answer really depends on the types of controls you are trying to maintain state for. For basic Html controls then it is very easy to maintain state with your Models, to do this you need to create a strongly typed view.

So if we had a User model with the properties: Username, FullName, Email, we can do the following in the view:

<%= Html.ValidationSummary() %>

<% using (Html.BeginForm()) { %>
  <fieldset>
    <legend>User details</legend>
    <%= Html.AntiForgeryToken() %>

    <p>
      <label for="Username">Username:</label>
      <%= Html.Textbox("Username", Model.Username, "*") %>
    </p>
    <p>
      <label for="FullName">FullName:</label>
      <%= Html.Textbox("FullName", Model.FullName, "*") %>
    </p>
    <p>
      <label for="Email">Email:</label>
      <%= Html.Textbox("Email", Model.Email, "*") %>
    </p>
    <p>
       <input type+"submit" value="Save user" />
    </p>
  </fieldset>
<% } %>

We would then have two controller actions that display this view, one for get and another for post:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult User()
{
  return View(new User())
}

[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult User([Bind(Include = "Username,FullName,Email")]User user)
{
   if (!ModelState.IsValid()) return View(user);

   try
   {
     user.save()
     // return the view again or redirect the user to another page
   }
   catch(Exception e)
   {
     ViewData["Message"] = e.Message;
     return View(user)
   }
}

Is this what you are looking for? Or do you want to maintain the state of Models that are not being displayed in a form between requests?

The key thing to remember is that your code executes on the server for the duration of the request and ends, the only information you can pass between your requests is basic html form data, url parameters and session information.

As other people have mentioned, I'd highly recommend Steve Sandersan's Pro ASP.NET MVC Framework for a complete understanding of working with the MVC Framework.

  • hidden fields, like:

    <% using (Html.BeginForm<SomeController>(c=>c.SomeAction(null))) {%>
      <%= Html.Hidden("SomeField", Model.SomeField)%>
      <%= Html.Hidden("AnotherField", Model.AnotherField)%>
    
  • setting the specific model & not having any explicit fields (gives u hidden fields). In the example below, the Model is filled by the controller with values received from the last post, so this enables a no js option in the page that can filter based on a status:

    Some Filter: <% using( Html.BeginForm<SomeController>(
            c => c.SomeAction(model.SomeField, model.AnotherField, model.YetAnotherField, null, model.SomeOtherField)
            )) { %>
                <%= Html.DropDownList("status", Model.StatusSelectList)%>
                <input type="submit" value="Filter" class="button" />
                <% } %>
    
  • use extension methods to create fields, if u just want the fields to be filled with posted values when u are showing failed validation messages on the submitted form
  • on asp.net mvc 2 they introduced a way to save an instance in a hidden field ... encoded + (I think) signed
  • TempData if everything of the above doesn't do it (goes through session - cleaned on the next request)
  • as u mentioned, when using ajax the state is already in the previously loaded fields in the client site. If u l8r on need to do a full post, update any field u might need to with your js.

The above are all different independent options to achieve it that can be used in different scenarios. There are more options I didn't mention i.e. cookies, session, store stuff in db (like for a resumable multi step wizard), parameters passed to an action. There is no 1 single mechanism to rule them all, and there shouldn't be.

The best way to do this, i think, is to serialize your original model to a hidden field, then deserialize it and update the model on post. This is somewhat similair to the viewstate approach, only you have to implement it yourself. I use this:

first i need some methods that make things easier:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using LuvDaSun.Extensions;
using System.Web.UI;

namespace LuvDaSun.Web.Mvc
{
    public static class HtmlHelperExtensions
    {
        static LosFormatter _losFormatter = new LosFormatter();
        public static string Serialize(this HtmlHelper helper, object objectInstance)
        {
            var sb = new StringBuilder();
            using (var writer = new System.IO.StringWriter(sb))
            {
                _losFormatter.Serialize(writer, objectInstance);
            }
            return sb.ToString();
        }


    }

    [AttributeUsage(AttributeTargets.Parameter)]
    public class DeserializeAttribute : CustomModelBinderAttribute
    {
        public override IModelBinder GetBinder()
        {
            return new DeserializeModelBinder();
        }
    }

    public class DeserializeModelBinder : IModelBinder
    {
        static LosFormatter _losFormatter = new LosFormatter();

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType.IsArray)
            {
                var type = bindingContext.ModelType.GetElementType();
                var serializedObjects = (string[])bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(typeof(string[]));
                var deserializedObjects = Array.CreateInstance(bindingContext.ModelType.GetElementType(), serializedObjects.Length);

                for (var index = 0; index < serializedObjects.Length; index++)
                {
                    var serializedObject = serializedObjects[index];
                    var deserializedObject = _losFormatter.Deserialize(serializedObject);

                    deserializedObjects.SetValue(deserializedObject, index);
                }

                return deserializedObjects;
            }
            else
            {
                var serializedObject = (string)bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(typeof(string));
                var deserializedObject = _losFormatter.Deserialize(serializedObject);

                return deserializedObject;
            }
        }
    }

}

then in my controller i have something like this (to update a product)

    public ActionResult Update(string productKey)
    {
        var model = _shopping.RetrieveProduct(productKey);

        return View(model);
    }
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Update([Deserialize]Shopping.IProduct _model, FormCollection collection)
    {
        UpdateModel(model);

        model.Save();

        return RedirectAfterPost();
    }

and i need a hidden field that holds the serialized object in the form:

    <% 
        using (Html.BeginRouteForm("Product", FormMethod.Post, new { id = UniqueID, }))
        {
    %>
<%= Html.Hidden("Model", Html.Serialize(Model)) %>
    <h1>
        Product bewerken</h1>
    <p>
        <label for="<%=UniqueID %>_Name">
            Naam:</label>
        <input id="<%=UniqueID %>_Name" name="Name" type="text" value="<%= Html.AttributeEncode(Model.Name) %>"
            class="required" />
        <br />
    </p>
    <p>
        Omschrijving:<br />
        <textarea id="<%= UniqueID %>_Description" name="Description" cols="40" rows="8"><%= Html.Encode(Model.Description) %></textarea>
        <br />
    </p>
    <p>
        <label for="<%=UniqueID %>_Price">
            Prijs:</label>
        <input id="<%= UniqueID %>_Price" name="Price" type="text" value="<%= Model.Price.ToString("0.00") %>"
            class="required" />
        <br />
    </p>
    <ul class="Commands">
        <li><a href="" class="ClosePopup">Annuleren</a></li>
        <li>
            <input type="submit" value="Opslaan" /></li>
    </ul>
    <% 
        } 
    %>

    <script type="text/javascript">

        jQuery('#<%= UniqueID %>').validate();

    </script>

as you can see, a hidden field (Model) is added to the form. It contains the serialization information for the original object. When the form is posted, the hidden field is also posted (ofcourse) and the contents are deserialized by the custom modelbinder to the original object which is then updated and saved by the controller.

Do note that the object you are serializing needs to be decorated with the Serializable attribute or needs to have a TypeConverter that can convert the object to a string.

The LosFormatter (Limited Object Serialization) is used by the viewstate in webforms. It also offers encryptionn of the serialization data.

greets...

AJAX calls is what we do. If you're talking about grids in general, check out JQGrid and how they recommend the AJAX implementation.

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