VIewModel relationships with EF

3

I'm developing a project where a user will have multiple data. In order not to extend the class much I thought about separating by categories. Example classes:

  • User
  • Personal Data
  • Family Data
  • Etc.

To do this on the site I thought about creating a carousel type with boostrap and creating a ViewModel to store all candidate data:

ex:

public class DadosCandidatoViewModel
{
    public int CandidatoId { get; set; }

    public CandidatoViewModel CandidatoViewModel { get; set; }
    public DadosPessoaisViewModel DadosPessoaisViewModel { get; set; }
    //ETC

}

Now I'm starting to doubt, because I do not know if it's necessary, since the Candidate Model already has the virtual ones for these tables, just like these other tables also already have candidate since the relationship is 1 to 1 or 0.

public class Candidato
{
    public int CandidatoId { get; set; }
    public string Nome { get; set; }
    public string CPF { get; set; }

    public DateTime DataCadastro { get; set; }
    public bool Ativo { get; set; }

    public virtual DadosPessoais DadosPessoais { get; set; }
    public virtual DadosFamiliares DadosFamiliares { get; set; }

    public virtual ICollection<Cargo> Cargos { get; set; }

}

public class DadosPessoais
{
    public int CandidatoId { get; set; }

    public string Sexo { get; set; }
    public string Endereco { get; set; }

    public virtual Candidato Candidato { get; set; }
}

My View Model Was Like This:

public class CandidatoViewModel
{

    [Key]
    public int CandidatoId { get; set; }

    [Required(ErrorMessage = "Preencher o campo Nome")]
    [MaxLength(150, ErrorMessage = "Máximo {1} caracteres")]
    [MinLength(2, ErrorMessage = "Mínimo {1} caracteres")]
    public string Nome { get; set; }

    [Required(ErrorMessage = "Preencher o campo CPF")]
    [MaxLength(15, ErrorMessage = "Máximo {1} caracteres")]
    [MinLength(2, ErrorMessage = "Mínimo {1} caracteres")]
    //Criar Datatype de CPF
    public string CPF { get; set; }

    [ScaffoldColumn(false)]
    public DateTime DataCadastro { get; set; }

    public virtual DadosPessoaisViewModel DadosPessoais { get; set; }

    public bool Ativo { get; set; }
}


public class DadosPessoaisViewModel
{
    [Required(ErrorMessage = "Preencher o campo Endereço")]
    [MaxLength(1, ErrorMessage = "Máximo {0} caracteres")]
    [MinLength(1, ErrorMessage = "Mínimo {0} caracteres")]
    public string Sexo { get; set; }

    [Required(ErrorMessage = "Preencher o campo Endereço")]
    [MaxLength(500, ErrorMessage = "Máximo {0} caracteres")]
    [MinLength(2, ErrorMessage = "Mínimo {0} caracteres")]
    public string Endereco { get; set; }
}

Well, first I need to know if I'm doing it right, or at least going the right way, because the way I am I'm having a lot of problems when it's time to persist.

Below is the View I created for the control DadosCandidatoViewModel where I want to call the Partial Views for the Candidate Data:

@model Gestao_RH.MVC.ViewModels.DadosCandidatoViewModel


@{
    ViewBag.Title = "DadosCandidato";
}

<h2>Edit</h2>

<!-- Div na qual o "carousel" será aplicado. -->
<div id="div-carousel" class="carousel slide">
    <div class="carousel-inner">
        <!-- Divs com efeito de transição. -->
        <div class="item active">

            @Html.Partial("~/Views/DadosPessoais/Edit.cshtml", Model)
        </div>
        <div class="item">
            Conteúdo da DIV 2.
        </div>
    </div>
</div>
<div class="row">
    <!-- Botões de navegação -->
    <div id="div-1" class="span2">
        <a id="a-1" class="btn" href="#div-carousel" data-slide="prev"><i class="icon-chevron-left"></i>Voltar para DIV 1</a>
    </div>
    <div id="div-2" class="span2">
        <a id="a-2" class="btn" href="#div-carousel" data-slide="next">Avançar para DIV 2<i class="icon-chevron-right"></i></a>
    </div>
</div>


<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

    <script type="text/javascript">
        $(document).ready(function () {

            // Aplica o efeito de "carousel" nas divs que possuirem a classe carousel.
            $('.carousel').carousel({
                interval: false
            });

            // Oculta o botão de navegação para a div 1.
            $('#div-1').hide();

            // Aplica a transição quando houver click no link AVANÇAR.
            $("#a-1").click(function () {
                $('#div-carousel').carousel(0);
                $('#div-1').hide();
                $('#div-2').show();
                return false;
            });

            // Aplica a transição quando houver click no link VOLTAR.
            $("#a-2").click(function () {
                $('#div-carousel').carousel(1);
                $('#div-1').show();
                $('#div-2').hide();
                return false;
            });
        });
    </script>
}

Below is an example of ViewModel that saves or edits Personal Data:

@model Gestao_RH.MVC.ViewModels.DadosCandidatoViewModel

@using (Html.BeginForm("SaveDadosPessoais", "DadosCandidato", FormMethod.Post, new { }))
{
    @Html.AntiForgeryToken()

    @Html.DisplayFor(model => model.CandidatoViewModel.Nome);
    @Html.HiddenFor(model => model.CandidatoId);

    <div class="form-horizontal">
        <h4>DadosPessoaisViewModel</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.CandidatoId)

        <div class="form-group">
            @Html.LabelFor(model => model.DadosPessoaisViewModel.Sexo, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.DadosPessoaisViewModel.Sexo, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.DadosPessoaisViewModel.Sexo, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.DadosPessoaisViewModel.Endereco, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.DadosPessoaisViewModel.Endereco, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.DadosPessoaisViewModel.Endereco, "", new { @class = "text-danger" })
            </div>
        </div>

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

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

This here is not working either:

public class DadosCandidatoController : Controller
{
    //  private readonly ProdutoRepository _ProdutoRepository = new ProdutoRepository();
    private readonly ICandidatoAppService _candidatoApp;
    private readonly IDadosPessoaisAppService _dadosPessoasApp;
    // GET: Produtos

    public DadosCandidatoController(ICandidatoAppService candidatoApp, IDadosPessoaisAppService dadosPessoaisApp)
    {
        _candidatoApp = candidatoApp;
        _dadosPessoasApp = dadosPessoaisApp;
    }


    [HttpGet]
    public ActionResult DadosCandidato(int id)
    {
        var candidato = _candidatoApp.GetById(id);
        var DadosCandidatoViewModel = Mapper.Map<Candidato, DadosCandidatoViewModel>(candidato);


        return View("DadosCandidato", DadosCandidatoViewModel);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult SaveDadosPessoais(DadosCandidatoViewModel model)
    {

        if (ModelState.IsValid)
        {
            var DadosPessoaisDomain = Mapper.Map<DadosPessoaisViewModel, DadosPessoais>(model.DadosPessoaisViewModel);


            if (_dadosPessoasApp.GetById(model.CandidatoId) == null)
            {

                _dadosPessoasApp.Add(DadosPessoaisDomain);
            }
            else
            {
                _dadosPessoasApp.Update(DadosPessoaisDomain);
            }


            return View("DadosCandidato", model);
        }


        return View("DadosCandidato", model);
    }
}

Any help is very welcome, my head is working with MVC.

    
asked by anonymous 06.02.2015 / 17:55

2 answers

1

If the idea is to separate by ViewModels for each ViewModel to be represented by a Partial , the reasoning is wrong.

The idea of separating Models (or ViewModels ) is for the cardinality principle: Normally the separation serves for certain associations or aggregations. In your case, there is no need for this separation.

If Models are the same as ViewModels , using ViewModels in your application seems like something without much sense.

You can fine-tune all your Model like this:

public class Candidato
{
    [Key]
    public int CandidatoId { get; set; }
    public string Nome { get; set; }
    public string CPF { get; set; }

    public DateTime DataCadastro { get; set; }
    public bool Ativo { get; set; }

    public string Sexo { get; set; }
    public string Endereco { get; set; }

    public virtual DadosPessoais DadosPessoais { get; set; }
    public virtual DadosFamiliares DadosFamiliares { get; set; }

    public virtual ICollection<Cargo> Cargos { get; set; }
}

Apparently you're using Automapper to map your ViewModels in Models . I do not know what your App Service layer does, but one tip is to use the debug to see what's happening.

    
06.02.2015 / 18:42
-1

You're doing it right, Paulo. And it is very nice to see this level of organization and separation of responsibilities, congratulations.

Working with ViewModels is essential. What it lacks in its implementation is to understand that you can work with the concept of PageObject - the same concept derived from the Selenium testing framework - and make PageViews , where those views will represent the entire model of your page, thus:

// Modelo de representação da sua view DadosCandidato
public class DadosCandidatoPageViewModel
{
    public int CandidatoId { get; set; }

    public CandidatoViewModel Candidato { get; set; }
    public DadosPessoaisViewModel DadosPessoais { get; set; }
    // ...    
}

And in your view :

@model Gestao_RH.MVC.ViewModels.DadosCandidatoPageViewModel;

This defines what data will be passed to your view .

About the question of sending / creating ViewModels to your entities: Not a problem. Remember that your entities / models must be isolated from their environments. Thus, it also avoids "fouling" its entities with notations such as Required , MinLength , etc. What are notations specific to views and should not exist in your Domain.

On the virtual ones, remember that the data that comes out of your AppServices must already be disconnected. The fact that you can see that from within your view , you can consume virtual properties in order to take advantage of Entity Framework in> already indicates a bad implementation. You will be creating a scenario where, from your view, you will trigger an event that will query your repository. And that is very bad. The data should come out of your AppService already ready and disconnected.

Below is a review of your Controller that I found relevant:

public class DadosCandidatoController : Controller
{
    private readonly ICandidatoAppService _candidatoApp;
    private readonly IDadosPessoaisAppService _dadosPessoasApp;
    // GET: Produtos

    public DadosCandidatoController(ICandidatoAppService candidatoApp, IDadosPessoaisAppService dadosPessoaisApp)
    {
        _candidatoApp = candidatoApp;
        _dadosPessoasApp = dadosPessoaisApp;
    }


    [HttpGet] // URL: /DadosCandidato/Candidato/123
    public ActionResult Candidato(int id)
    {
        var candidato = _candidatoApp.GetById(id);
        // Nas ultimas versoes do AutoMapper, 
        // não é mais necessário passa os tipos de origem e destino do mapeamento.
        // Basta por o destino, que o tipo de origim ele recupera pelo
        // objeto no parametro.
        var DadosCandidatoViewModel = Mapper.Map<DadosCandidatoViewModel>(candidato);

        // Por convenção, quando o nome da View é tem o mesmo nome da Action
        // não é necessário ser explícito, basta retornar os dados.
        return View(DadosCandidatoViewModel);
    }

    [HttpPost] // URL: /DadosCandidato/Candidato/123
    [ValidateAntiForgeryToken]
    public ActionResult Candidato([FromUri]int id, [FromBody]DadosCandidatoViewModel model)
    {    
        if (ModelState.IsValid)
        {
            // Mesma obs sobre AutoMapper
            var DadosPessoaisDomain = Mapper.Map<DadosPessoais>(model.DadosPessoaisViewModel);

            // Aqui voce deve apenar soliciar a inserção do dado
            // Checar se o dados já existe, e se exisitir atualizar é uma regra de negócio
            // Sendo regra de negócio, deve então ficar dentro de sua camada
            // de Domínio
            // A Controller deve ser a camada mais BURRA de sua aplicação
            // Ela apenas recebe dados e envia para o AppService
            // Ou recebe dados do AppService e transforma em ViewModels para exibição   
            _dadosPessoasApp.Add(DadosPessoaisDomain);
        }    

        return View("DadosCandidato", model);
    }
}
    
27.07.2016 / 10:34