How ViewModel works in ASP.NET MVC

6

I have a situation similar to this question where I need to save multiple models at once and relate each one. I would need to kind of cascade save to first save one entity and then go save the others, because of relationships.

Following the question, I created a ViewModel with all the tables that would be saved at once, as in the code below:

public class AnamineseViewModel
{
    //
    //porque dessa forma ficaria mais fácil de salvar tudo relacionado a anaminese
    #region Mapeamento dos objetos

    public CliCliente CliCliente { get; set; }
    public Tabela2 Tabela2 { get; set; }
    public Tabela3 Tabela3 { get; set; }
    public Tabela4 Tabela4 { get; set; }
    public Tabela5 Tabela5 { get; set; }

    #endregion
}

My question is: how can I create a controller and views for this ViewModel

Remembering that first I must register the client, after I can get to tabela2 , after saving the tabela2 to relate it to the rest and to be able to save everything in the database and edit and list and everything else ...

Would you like to?

EDIT

The relationships I have are:

  • Table 2 with CliClient, Table3, Table4 and Table5;

  • Table3 with Table2

  • Table4 with Table2

  • Table5 with Table2

All of them being 1: 1.

    
asked by anonymous 27.05.2016 / 03:43

2 answers

4

Make an Controller empty. My suggestion:

public class AnaminasesController : Controller
{
    protected SeuContexto contexto = new SeuContexto();

    public ActionResult Index()
    {
        /* Liste aqui o que pode ser interessante entre todos os Models envolvidos */
    }

    public ActionResult Create()
    {
        return View(new AnamineseViewModel 
        {
            CliCliente = new CliCliente(),
            Tabela2 = new Tabela2(),
            Tabela3 = new Tabela3(),
            Tabela4 = new Tabela4(),
            Tabela5 = new Tabela5(),
        });
    }

    [HttpPost]
    public ActionResult Create(AnamineseViewModel viewModel)
    {
        if (ModelState.IsValid)
        {
            /* Salve todas as informações aqui */

            return RedirectToAction("Index");
        }

        return View(viewModel);
    }

    /* Os demais métodos seguem a mesma lógica. */
}

As in the above question, generate Partials for each entity to be created or edited. My suggestion for Create :

@model SeuProjeto.ViewModel.AnaminaseViewModel

@using Html.BeginForm() 
{
    @Html.Partial("_Cliente", Model.CliCliente)
    @Html.Partial("_Tabela2", Model.Tabela2)
    @Html.Partial("_Tabela3", Model.Tabela3)
    @Html.Partial("_Tabela4", Model.Tabela4)
    @Html.Partial("_Tabela5", Model.Tabela5)
}

To make this creation of Partials more dynamic, do Scaffold of all entities and take advantage of their code.

As you have completed the answer, I need to make some additions.

  • If CliCliente is related to Tabela2 and Tabela3 , Tabela4 and Tabela5 also, the relationship between CliCliente and Tabela2 can be discarded. See I said "can", not "should";
  • As @Randrade scored , if you have the relationship chain, you do not need to use the ViewModel because:

1. Your Action Edit already loads everything for you

That is:

public ActionResult Edit(int id)
{
    var cliente = db.CliClientes
                    .Include(c => c.Tabela2)
                    .Include(c => c.Tabela3)
                    .Include(c => c.Tabela4)
                    .Include(c => c.Tabela5)
                    .FirstOrDefault(c => c.CliClienteId == id);

    return View();
}

2. Your Action Create can use an empty CliCliente instead of a ViewModel

public ActionResult Create()
{
    return View(new CliCliente 
    {
        Cliente2 = new Cliente2(),
        Cliente3 = new Cliente3(),
        Cliente4 = new Cliente4(),
        Cliente5 = new Cliente5(),
    });
}

3. The Actions of POST can directly receive your Model

[HttpPost]
public ActionResult Create(CliCliente cliente) { ... }

But be careful with that. Please try to use the [Bind] attribute. to limit the fields that binder will accept.

    
27.05.2016 / 03:56
2

To create the controller and the view corresponding to a given ViewModel

ViewModel

namespace WebApplication1.Models
{
    public class ExampleViewModel
    {
        public Produto Produto { get; set; }
        public Cliente Cliente { get; set; }
    }

    public class Produto
    {
        public int Id { get; set; }
        public string Descricao { get; set; }
    }

    public class Cliente
    {
        public int Id { get; set; }
        public string Nome { get; set; }
    }
}

In this ViewModel ( ExampleViewModel ) I have two aggregations of classes Produto and Cliente and with them I will create the view Main ( Create ) and their respective views for each existing aggregation.

What would it be like?

public class ExamplesController : Controller
{
    [HttpGet()]
    public ActionResult Create()
    {
        return View();
    }

    [HttpPost()]
    public ActionResult Create(ExampleViewModel exampleViewModel)
    {
        //ROTINAS DE GRAVAÇÃO
        return View();
    }
}

In controller it is simple to create a Create with verb GET and another with verb POST with a parameter of its ViewModel ( ExampleViewModel ).

Create your view in this format:

View Product: (_Product.cshtml)

@model WebApplication1.Models.ExampleViewModel
<div class="form-horizontal">
    <h4>Produto</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(model => model.Produto.Id, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Produto.Id, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Produto.Id, "", new { @class = "text-danger" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.Produto.Descricao, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Produto.Descricao, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Produto.Descricao, "", new { @class = "text-danger" })
        </div>
    </div>
</div>

View Client: (_Customer.cshtml)

@model WebApplication1.Models.ExampleViewModel
<div class="form-horizontal">
    <h4>Cliente</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(model => model.Cliente.Id, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Cliente.Id, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Cliente.Id, "", new { @class = "text-danger" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(model => model.Cliente.Nome, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Cliente.Nome, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Cliente.Nome, "", new { @class = "text-danger" })
        </div>
    </div>
</div>

Note that these two views that complement your main view have an interesting factor that I put your type in @model to your ViewModel ExampleViewModel Why?

In order for% () the information passed in the fields to be loaded into your class in a simple and transparent way, it works correctly, ie, by sending the information to binding ( Create ) it loads the class correctly. Look at the nomenclature of an item:

@Html.EditorFor(model => model.Cliente.Id, new { htmlAttributes = new { @class = "form-control" } })

It has been mounted on top of its aggregation shown therein in do verb POST , so that it gives the correct model.Cliente.Id of the information.

View Main: (Create.cshtml)

@model WebApplication1.Models.ExampleViewModel
@{ Layout = null; }
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Create</title>
</head>
<body>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/jqueryval")    

    @using (Html.BeginForm()) 
    {
        @Html.AntiForgeryToken()


        @Html.Partial("_Cliente", Model)
        <hr />
        @Html.Partial("_Produto", Model)

        <div class="form-horizontal">
            <h4>ExampleViewModel</h4>
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Create" class="btn btn-default" />
                </div>
            </div>
        </div>
    }
</body>
</html>

Html generated

<form action="/Examples/Create" method="post"><input name="__RequestVerificationToken" type="hidden" value="Mr207UcvrXamhyWWVqGbKBVZ8wY9ccJoBqcGVCjwg3G_tjHWIymMjfzE5o1XkXaJ8Q0WsL5XWMhq3biQfh7rKmuerIMuMPCgPFEOzA7baNc1" /><div class="form-horizontal">
    <h4>Cliente</h4>
    <hr />

    <div class="form-group">
        <label class="control-label col-md-2" for="Cliente_Id">Id</label>
        <div class="col-md-10">
            <input class="form-control text-box single-line" data-val="true" data-val-number="The field Id must be a number." data-val-required="O campo Id é obrigatório." id="Cliente_Id" name="Cliente.Id" type="number" value="" />
            <span class="field-validation-valid text-danger" data-valmsg-for="Cliente.Id" data-valmsg-replace="true"></span>
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-md-2" for="Cliente_Nome">Nome</label>
        <div class="col-md-10">
            <input class="form-control text-box single-line" id="Cliente_Nome" name="Cliente.Nome" type="text" value="" />
            <span class="field-validation-valid text-danger" data-valmsg-for="Cliente.Nome" data-valmsg-replace="true"></span>
        </div>
    </div>
</div>        <hr />
<div class="form-horizontal">
    <h4>Produto</h4>
    <hr />

    <div class="form-group">
        <label class="control-label col-md-2" for="Produto_Id">Id</label>
        <div class="col-md-10">
            <input class="form-control text-box single-line" data-val="true" data-val-number="The field Id must be a number." data-val-required="O campo Id é obrigatório." id="Produto_Id" name="Produto.Id" type="number" value="" />
            <span class="field-validation-valid text-danger" data-valmsg-for="Produto.Id" data-valmsg-replace="true"></span>
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-md-2" for="Produto_Descricao">Descricao</label>
        <div class="col-md-10">
            <input class="form-control text-box single-line" id="Produto_Descricao" name="Produto.Descricao" type="text" value="" />
            <span class="field-validation-valid text-danger" data-valmsg-for="Produto.Descricao" data-valmsg-replace="true"></span>
        </div>
    </div>
</div>        <div class="form-horizontal">
            <h4>ExampleViewModel</h4>
            <hr />

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Create" class="btn btn-default" />
                </div>
            </div>
        </div>
</form>

In this generation it is easy to see why the aggregations of the binding class are loaded, the fields for example client name in its ExampleViewModel of type tag input is named text . Client is the Cliente.Nome and the Name its property , this is how MVC can define the information and load in the corresponding classes. p>

If you ride differently it will not work !!!

    
27.05.2016 / 17:36