27 messaggi dal 17 maggio 2011
Ciao a tutti e buon anno,

ho creato una view che visualizza tutti i contatti di un cliente tramite una property not mapped (Contacts) tramite un editorTemplate. I dati caricati provengono da una navigation property (CustomerContacts).
I valori in visualizzazione vengono caricati correttamente, però se vengono modificati oppure eliminati nel momento in cui si esegue il post per il salvataggio dei dati questi non sono valorizzati all'interno della entity.
Come potrei riuscire a salvare queste informazioni?

Di seguito riporto il codice:

Model Customer.cs
[Table("Customer")]
    public class Customer : BaseEntity
    {
  ...

        public virtual ICollection<CustomerContact> CustomerContacts { get; set; }

        [NotMapped]
        public virtual ICollection<Contact> Contacts
        {
            get
            {
                ICollection<Contact> contacts = new List<Contact>();
                ApplicationDbContext db = new ApplicationDbContext();
                foreach (CustomerContact customerContact in CustomerContacts)
                {
                    if (customerContact.ContactId > 0 && customerContact.CustomerId > 0)
                        contacts.Add(db.Contacts.First(c => c.Id.Equals(customerContact.ContactId)));
                }
                return contacts;
            }
            set 
            {
                Contacts.Add(new Contact());
            }
        }
}


View Edit.cshtml (porzione di codice)
...
<!-- Start portlet contact information -->
                    <div class="col-md-6">
                        <div class="portlet box red-flamingo">
                            <div class="portlet-title">
                                <div class="caption">
                                    <i class="fa fa-phone"></i>
                                    Contatti
                                </div>
                                <div class="tools">
                                    <a class="collapse" href="" data-original-title="" title=""> </a>
                                    <a class="fullscreen" href="" data-original-title="" title=""> </a>
                                </div>
                            </div>
                            <div class="portlet-body form">
                                <div class="form-horizontal mt-repeater">
                                    <div class="form-body">
                                        <div id="contacts">
                                            @Html.EditorFor(model => model.Contacts)
                                        </div>
                                        <div class="row">
                                            <div class="col-md-12" style="padding-right: 40px;">
                                                <div class="pull-right">
                                                    <a href="javascript:;" data-repeater-create class="btn btn-success btn-icon-only red-flamingo mt-repeater-add">
                                                        <i class="fa fa-plus"></i>
                                                    </a>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- End portlet contact information -->
...


EditorTemplate Contact.cshtml
<div class="contact">
    <div class="form-body" style="padding-top: 0px;">
        <div data-repeater-list="group-a">
            <div data-repeater-item class="mt-repeater-item">
                <!-- jQuery Repeater Container -->
                <div class="row">
                    <div class="col-md-4">
                        <div class="input-group">
                            <div class="mt-repeater-input">
                                <label class="control-label"> </label>
                                @Html.DropDownList("Type", (List<SelectListItem>)@ViewBag.ContactType, ViewUtil.OPTION_LABEL, new { @class = "form-control" })
                            </div>
                        </div>
                    </div>
                    <div class="col-md-8">
                        <div class="input-group">
                            <div class="mt-repeater-input">
                                <label class="control-label"> </label>
                                @if (ContactType.EMAIL.Equals(Model.Type))
                                {
                                    @Html.EditorFor(model => model.CompleteEmail, new { htmlAttributes = new { @class = "form-control", placeholder = "Inserisci contatto", type = "text" } })
                                }
                                else
                                {
                                    @Html.EditorFor(model => model.Value, new { htmlAttributes = new { @class = "form-control", placeholder = "Inserisci contatto", type = "text" } })
                                }
                            </div>
                        </div>
                    </div>
                </div>
                <div class="mt-repeater-input">
                    <a href="javascript:;" data-repeater-delete class="btn btn-danger mt-repeater-delete">
                        <i class="fa fa-close"></i> Delete
                    </a>
                </div>
            </div>
        </div>
    </div>
</div>


Controller CustomersController.cs
[ValidateAntiForgeryToken]
        public async Task<ActionResult> Edit([Bind(Include = "Id,Type,LastName,FirstName,InsertDate,TaxCode,Gender,AssumptionDate,ResidenceDate,Note,BirthDate,CountryEntryDate,AcceptPrivacy,Attribute1,DomicileAddress,ResidenceAddress,UpdateDate,ContractTypeId,CompanyId,NationalityId,ImageId,SourceId,SourcePracticeId,SubAgentId,SubStatusId,UserId,Contacts")] Customer customer)
        {
            if (ModelState.IsValid)
            {
                try 
                {
                    db.Entry(customer).State = EntityState.Modified;
                    customer.UserId = Convert.ToInt64(currentUserId);
                    await db.SaveChangesAsync();
                }
                catch (Exception /* dex */)
                {
                    //Log the error (uncomment dex variable name and add a line here to write a log.
                    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
                }                
                return RedirectToAction("Index");
            }
            return RedirectToAction("Edit", customer.Id);
        }


I risultato finale nella pagina è il seguente:

LINK

Con i pulsanti "Delete" e "+" si aggiungo o si eliminano i contatti dalla form, e solo al momento del salvataggio questi vengono riportati nel database.

Grazie
Ciao
Enrico
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao Enrico,
il problema è che questa istruzione non è sufficiente a far capire ad Entity Framework che l'intero grafo di oggetti va aggiornato.
db.Entry(customer).State = EntityState.Modified;


Soltanto customer risulterà con stato Modified e pertanto EF invierà un solo comando UPDATE al database per aggiornare solo i valori delle sue proprietà.

Quindi, per aggiornare anche le relazioni con le entità dipendenti dovresti prima estrarre dal database il customer i suoi contatti e confrontare tutto con ciò che ti è arrivato dal form.
Cioè, dovrai fare un foreach per esaminare i contatti esistenti e vedere se sono gli stessi di quelli impostati sul form. Se nel db ce ne sono di più, dovrai rimuoverli. Se ce ne sono di meno, dovrai aggiungerli. I restanti andranno aggiornati.

Questo approccio funzionerà ma personalmente preferisco fare in modo che il tuo tasto Delete causi l'invio immediato di una richiesta Ajax per la cancellazione del contatto. Idem quando ne aggiungi uno nuovo: partirà una richiesta Ajax per la creazione.

ciao,
Moreno

EDIT: puoi dire quale problema hai incontrato che ti ha costretto ad usare questo codice nel getter della proprietà non mappata?
ICollection<Contact> contacts = new List<Contact>();
                ApplicationDbContext db = new ApplicationDbContext();
                foreach (CustomerContact customerContact in CustomerContacts)
                {
                    if (customerContact.ContactId > 0 && customerContact.CustomerId > 0)
                        contacts.Add(db.Contacts.First(c => c.Id.Equals(customerContact.ContactId)));
                }
                return contacts;

Modificato da BrightSoul il 04 gennaio 2017 20.22 -

Enjoy learning and just keep making
27 messaggi dal 17 maggio 2011
Ciao,

ho dovuto usare quel codice nel getter della property Contacts perchè non nella view non riuscivo a visualizzare la tipologia e il valore del contatto.
Nel mio caso CustomerContact contiene solo il riferimento al Customer e al relativo Contact.

Concordo nel fatto di eseguire una chiamata Ajax per aggiungere/eliminare i contatti, ma al momento la richiesta è quella di salvare i dati al submit dell'intera form.

Ok per il foreach per estrarre i contatti già esistenti, però non capisco come fare a reperire i contatti dalla form perchè l'oggetto Customer nel metodo Edit non ha valorizzato nè i CustomerContacts nè i Contacts.

Grazie
Ciao
Enrico
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao Enrico,


come fare a reperire i contatti dalla form perchè l'oggetto Customer nel metodo Edit non ha valorizzato nè i CustomerContacts nè i Contacts

Intendi dire che nell'oggetto non contiene i contatti che avevi aggiunto lato client con il tasto +, giusto?
Quando clicchi il tasto +, come crei le caselle di testo? Che attributo name dai a quelle caselle?
Potrebbe essere che non gli stai dando il giusto attributo name, perché è in base a quello che poi il Model Binder di MVC saprà ricreare lato server l'oggetto Customer con tutti i suoi contatti.

Per il momento leggi questo:
http://forum.aspitalia.com/forum/post/403972/Inserire-Lista-Proprieta-Model-Interno-Form.aspx



ho dovuto usare quel codice nel getter della property Contacts perchè non nella view non riuscivo a visualizzare la tipologia e il valore del contatto.

Dovresti usare Include nella tua query LINQ per chiedere ad Entity Framework di recuperarti anche i contatti.
Qui trovi un articolo che ti spiega come usare Include per caricare anche le entità correlate.
http://www.entityframeworktutorial.net/EntityFramework4.3/eager-loading-with-dbcontext.aspx

La proprietà NotMapped poi la puoi rimuovere. Non ti direbbe comunque buone prestazioni e rende la tua classe di entità cablata all'ApplicationDbContext, mentre invece dovrebbe restare agnostica sulla tecnologia di persistenza che stai usando (EF).

ciao,
Moreno

Enjoy learning and just keep making
27 messaggi dal 17 maggio 2011
Ciao Moreno,


Intendi dire che nell'oggetto non contiene i contatti che avevi aggiunto lato client con il tasto +, giusto?
Quando clicchi il tasto +, come crei le caselle di testo? Che attributo name dai a quelle caselle?


intendo che nella form, lato server, non arriva nessun valore riferito ai contatti, nemmeno quelli già presenti nel database e caricati con la pagina (LINK).
Ho verificato l'attributo name e mi sembra che sia settato correttamente (LINK).

Enrico
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao Enrico,


Ho verificato l'attributo name e mi sembra che sia settato correttamente

Vedo che l'attributo name vale Contacts[0].Value e quindi il model binder proverà ad inserire i contatti nella collezione chiamata, appunto, Contacts ovvero la famosa proprietà NotMapped.

Per ora indagherei sull'implementazione del suo setter.
set 
{
  Contacts.Add(new Contact());
}

Messa così, non stai considerando il value in alcun modo. Infatti, se anche il model binder avesse costruito una collezione di contatti corretta e provasse a settarla a Contacts, quel setter non ne sta comunque raccogliendo il riferimento.

Dovresti mettere un breakpoint nel setter e vedere che valore ha il value quando viene richiamato.
Non riesco a capire bene perché il setter è implementato così.

ciao,
Moreno

Enjoy learning and just keep making
27 messaggi dal 17 maggio 2011
Ciao Moreno,

volevo ringraziarti perchè ora ho risolto.
Ora di dati vengono passati in post e possono essere salvati.
Come mi dicevi ho tolto la proprietà NotMapped Contacts dal model Customer.cs, mentre nel model CustomerContact.cs ho aggiunto la proprietà virtual Contact.
La view Edit.cshtml è diventata questa:

 <!-- Start portlet contact information -->
<div class="col-md-6">
  <div class="portlet box red-flamingo">
    <div class="portlet-title">
      <div class="caption">
        <i class="fa fa-phone"></i>
        Contatti
      </div>
      <div class="tools">
        <a class="collapse" href="" data-original-title="" title=""> </a>
        <a class="fullscreen" href="" data-original-title="" title=""> </a>
      </div>
    </div>

    <div class="portlet-body form">
        <div id="contacts" class="form-body">
          <div class="form-group">
            <form action="#" class="form-horizontal">
              @Html.EditorFor(model => model.CustomerContacts)
            </form>
          </div>
        </div>
        <div class="row">
          <div class="col-md-12 form-body" style="padding-right: 40px;">
            <div class="pull-right">
              <a href="javascript:;" class="btn btn-success btn-icon-only red-flamingo">
                <i class="fa fa-plus"></i>
              </a>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
<!-- End portlet contact information -->


Ho aggiunto un ulteriore EditorTemplate CustomerContact.cshtml:

@model Application.Models.CustomerContact
@Html.HiddenFor(model => model.CustomerId)
@Html.HiddenFor(model => model.ContactId)
@Html.EditorFor(model => model.Contact)

L'EditorTemplate Contact.cshtml è rimasto pressochè invariato:

<div class="contact">
        <div class="form-body" style="padding-top: 0px;">
            <div>
                <div>
                    <div class="row">
                        <div class="col-md-4">
                            <div class="input-group col-md-12">
                                <div>
                                    <label class="control-label"> </label>
                                    @Html.DropDownList("Type", (List<SelectListItem>)@ViewBag.ContactType, ViewUtil.OPTION_LABEL, new { @class = "form-control" })
                                </div>
                            </div>
                        </div>
                        <div class="col-md-8">
                            <div class="input-group col-md-12">
                                <div>
                                    <label class="control-label"> </label>
                                    @if (ContactType.EMAIL.Equals(Model.Type))
                                    {
                                        @Html.EditorFor(model => model.CompleteEmail, new { htmlAttributes = new { @class = "form-control", placeholder = "Inserisci contatto", type = "text" } })
                                    }
                                    else
                                    {
                                        @Html.EditorFor(model => model.Value, new { htmlAttributes = new { @class = "form-control", placeholder = "Inserisci contatto", type = "text" } })
                                    }
                                </div>
                            </div>
                        </div>
                    </div>
                    <div>
                        <a href="javascript:;" class="btn btn-danger">
                            <i class="fa fa-close"></i> Delete
                        </a>
                    </div>
                </div>
            </div>
        </div>
    </div>


Ora volevo utilizzare la libreria che mi hai suggerito (KnockoutJS) per aggiungere/rimuovere dal DOM i contatti, però non riesco a capire in quale view posizionare lo script e i relativi tag in modo che i dati vengano caricati dal database e il tutto funzioni.

Riesci ad aiutarmi?

Grazie
Enrico
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao Enrico,
sebbene sia molto semplice da usare, anche Knockout ha una sua curva d'apprendimento e quindi dovresti iniziare leggendo la documentazione e a fare qualche esercizio semplice che trovi nel loro sito.
http://knockoutjs.com/examples/

Acquisisci dimestchezza con lo strumento partendo da pagine html completamente vuote e provando a ripetere gli esempi.
Poi, prova ad integrarlo per gradi nella tua applicazione web. L'esempio specifico è questo:
http://jsfiddle.net/sSE65/1/
Inizia con la semplice aggiunta di nuovi contatti, senza aver letto nulla dal database. Verifica che l'action del controller riceva i nuovi contatti aggiuti grazie a Knockout.

Quando hai verificato che funziona, sarà il momento di rileggere dal database le informazioni salvate. Qui hai due strade:
  • Stampare nella view l'elenco dei contatti come array di oggetti javascript, in modo che siano subito pronti da infilare in un observableArray di knockout;
  • Oppure, farti restituire l'elenco dei cotatti da una richiesta ajax ad un'action predisposta allo scopo che restituirà del json.


Personalmente sto preferendo questo secondo approccio. Man mano che sviluppo applicazioni web mi sto rendendo conto che è il caso di separare in maniera netta la parte frontend, che può essere formata di soli file statici (html, css e javascript) e di un backend WebAPI che le fornisce i dati in json. Librerie tipo knockout, reactjs o angular semplificano la realizzazione della parte client.

ciao,
Moreno
Modificato da BrightSoul il 18 febbraio 2017 11.02 -

Enjoy learning and just keep making

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.