Dove collocare la logica di manipolazione dell'interfaccia utente / modello di dominio (trasferimento dei dati dalla vista al modello di dominio)

5

Stiamo iniziando un progetto greenfield in ASP.NET MVC. Ho utilizzato il pattern MVC in altri stack (PHP e Ruby) e continuo a incontrare gli stessi problemi su dove posizionare la logica aziendale, la logica dell'interfaccia utente e dove questo sistema interagisce con altri sistemi remoti (ad esempio servizi Web ed e- mail).

Stavo leggendo Vantaggi di ViewModel in MVC e l'autore afferma che ci sono 5 tipi di "logica" in un'applicazione:

  • Business Logic – Is a part of Model

  • Database Logic – This is the one of the non-spoken layer in MVC. Many people create a separate layer for this and invoke them from Business layer (model) or some keep them inside Model.

  • User Interaction logic – written inside controller

  • Presentation Logic – Presentation logic is the logic which will handle the presentation of a UI. Example – If some value is greater than 100 it should be displayed in green color. Basically it is a part of View.

  • Data transformation logic – In some situations we have some data with us and we want to display it in a different format. Example we have Date-of-birth and we want to display Age. Again this transformation becomes the part of View.

Vorrei aggiungere un ulteriore "tipo di logica" che è:

  • Interfaccia utente / Logica di manipolazione del modello di dominio - Come trasferire dati dalla vista a chiamate di dati e metodi nel modello di dominio

Credo che i primi 5 tipi di logica abbiano già definito le aree. Business Logic entra nei modelli di dominio. La logica del database va nei repository o nel livello O / RM. La logica di interazione dell'utente va nel controller. La logica di presentazione va nel modello di vista. La logica della trasformazione dei dati entra essenzialmente nelle classi che formattano i dati, come date e numeri.

L'ultimo tipo di logica, in cui è necessario prendere i dati dalla vista e manipolare il modello di dominio di conseguenza, non sembra avere il proprio posto.

Ho letto circa due possibilità:

  1. Un "livello di servizio" nell'applicazione, anche se non viene fatto alcun riferimento ai servizi Web, ma usando "oggetti di servizio" per incapsulare questo

  2. Un oggetto "unità di lavoro" - Questo è stato particolarmente frustrante perché una transazione di database è un'implementazione del modello Unità di lavoro. Le recenti variazioni di questo modello aumentano la transazione del database con le manipolazioni effettive del modello di dominio.

  3. Oggetto Action / Executor ( Pattern Action / Executor )

  4. Oggetti di comando (modello di comando)

Creare un nuovo post sul blog è facile: inserisci il post nel database. La creazione di un nuovo utente non è così semplice, perché potresti voler inviare un'e-mail di benvenuto dopo aver inserito l'utente nel database, quindi le operazioni aggiuntive vengono eseguite dopo che "l'unità di lavoro" è stata completata.

Un piccolo esempio di codice.

Diciamo che abbiamo un'applicazione per il blog.

  1. Un blog ha molti post
  2. Un post del blog non può esistere senza un blog
  3. Un post sul blog deve avere un titolo, un corpo e una data di pubblicazione

Questo porta a due Classi di Dominio: Blog e BlogPost. Per evitare di creare accidentalmente un oggetto BlogPost e aggiungerlo al Blog errato, il costruttore della classe BlogPost è contrassegnato come internal , quindi sei obbligato ad aggiungere un post del blog attraverso l'oggetto Blog a cui appartiene:

public class Blog
{
    public Blog(string name)
    {
        Name = name;
        posts = new List<BlogPost>();
    }

    public int Id { get; internal set; }
    public string Name { get; private set; }
    public string Description { get; set; }

    private IList<BlogPost> posts;

    // Return a read-only collection of posts to the public
    public IEnumerable<BlogPost> Posts
    {
        get { return posts; }
    }

    // Create a new BlogPost object to ensure it is attached to the proper Blog
    public BlogPost AddBlogPost(string title, string body, DateTime publishDate)
    {
        BlogPost post = new BlogPost(this, title, body, publishDate);

        posts.Add(post);

        return post;
    }
}

E la classe BlogPost:

public class BlogPost
{
    // This constructor is "internal" so code outside this Assembly must call
    // Blog.AddBlogPost(...) to create new posts
    internal BlogPost(Blog blog, string title, string body, DateTime publishDate)
    {
        Blog = blog;
        SetTitleAndBody(title, body);
        PublishDate = publishDate;
    }

    public int Id { get; internal set; }
    public string Title { get; private set; }
    public string Body { get; private set; }
    public Blog Blog { get; private set; }
    public DateTime PublishDate { get; private set; }

    public int BlogId
    {
        get { return Blog.Id; }
    }

    public bool IsPublished
    {
        get { return PublishDate < DateTime.Now; }
    }

    public bool IsUnpublished
    {
        get { return !IsPublished; }
    }

    public void PublishAt(DateTime newPublishDate)
    {
        PublishDate = publishDate;
    }

    public void SetTitleAndBody(string title, string body)
    {
        if (string.IsNullOrEmpty(title))
            throw new ArgumentNullException("title");

        if (string.IsNullOrEmpty(body))
            throw new ArgumentNullException("body");

        Title = title;
        Body = body;
    }
}

Il modello di visualizzazione per un post del blog potrebbe essere simile a:

public class BlogPostForm
{
    public BlogPostForm()
    {
    }

    public BlogPostForm(BlogPost post)
    {
        Id = post.Id;
        Title = post.Title;
        Body = post.Body;
        PublishDate = post.PublishDate;
        BlogId = post.BlogId;
    }

    [Required]
    public int Id { get; set; }

    [Required]
    public string Title { get; set; }

    [Required]
    public string Body { get; set; }

    [Required]
    public DateTime? PublishDate { get; set; }

    [Required]
    public int BlogId { get; set; }
}

Si noti che PublishDate è un valore DateTime nullable poiché l'utente potrebbe inviare il modulo senza riempire la data di pubblicazione. Inoltre, a differenza del modello di dominio, tutte le proprietà sono get / set pubblici, anche perché l'utente potrebbe inviare il modulo senza riempire quei campi.

Ora qualcosa deve prendere i dati nell'oggetto BlogPostForm e trovare Blog nel database e chiamare AddBlogPost :

IBlogRepository blogs = ...

Blog blog = blogs.Find(model.BlogId);
BlogPost post = blog.AddBlogPost(model.Title, model.Body, model.PublishDate.Value);

blogs.Save(blog);

Dove dovremmo mettere la logica su come manipolare gli oggetti Blog e BlogPost in base ai valori nell'oggetto BlogPostForm, specialmente se questo lavoro include più di COMMIT in un database?

    
posta Greg Burghardt 03.03.2016 - 17:59
fonte

4 risposte

7

Ricorda che il principio Model View Controller (MVC) è stato dirottato e reso illegittimo dal mondo dello sviluppo web. In un'applicazione MVC reale, la vista del modello e il controller sono in gran parte ortogonali. Se si inserisce un record nel modello, la vista riceverà un evento di modifica, possibilmente con un suggerimento su quale parte del modello è stata modificata e quindi aggiornerà solo la parte della vista interessata. Questo non è applicabile al web perché il web è stateless quindi la "view" è completamente ricostruita con ogni azione. A volte AJAX può aiutare con questo, ma in generale, la vista non può reagire ai cambiamenti nel modello. Quindi non spezzarti il collo cercando di riconciliare la semantica di MVC rispetto alle applicazioni web. Lo stack web è in realtà piuttosto orribile. Non troverai modelli di design eleganti in questa roba. Devi solo provare cose diverse ed essere creativo. Non impegnarti in qualche progetto perché molte persone predicano un metodo particolare. Ho fatto molti diversi tipi di programmazione da chip DSP a software di sicurezza di rete e lo sviluppo web è probabilmente il più difficile di tutti non solo perché lo stack web fa schifo, ma anche perché tutto ciò che si interfaccia con un essere umano sarà altamente soggettivo.

    
risposta data 04.03.2016 - 04:15
fonte
1

È compito del Controller in MVC prendere i dati che l'utente ha inserito e modificare il Modello di conseguenza.

In molti casi, questo potrebbe essere semplice come fare il hooking di una classe dal Modello alla View (o ViewModel) e caliing Commit sul database dopo che l'utente ha confermato le modifiche.
Ma a volte, come nel tuo esempio con BlogPost, la logica nel Controller deve essere più coinvolta per garantire che vengano rispettati i vincoli stabiliti dal Modello.

    
risposta data 03.03.2016 - 20:04
fonte
1

Se siamo rigorosi nel modello, ciò che stai chiedendo dovrebbe essere gestito dal controller, semplicemente perché crea un ponte tra la vista e il modello.

Ma, naturalmente, non avrai tutta la tua logica nel controller. Di solito viene utilizzato un livello di servizio per questo. E una SL non è altro che un altro modo per chiamare i metodi esposti del livello aziendale, come dire, se si ha una classe BlogPostService, con un metodo Post (), quel metodo potrebbe essere qualcosa del tipo:

var blog = blogRepository.Get(post.blogId);
postRepository.Post(blog,post);
tagRepository.Add(tags,post);
notifierService.Notify(post);
blogService.SendtoFrontPage(post);

Dalla tua interfaccia utente devi preoccuparti di trasformare gli oggetti, ad esempio:

IConverter<viewModelBlogPost,ModelPost> _converter;
IBlogPostService _blogPostService;

var modelPost = _converter.Convert(viewModelBlogPost);
_blogPostService.Post(modelPost);

La logica del convertitore deve essere presente nella presentazione perché deve conoscere gli oggetti del modello di visualizzazione.

    
risposta data 01.06.2017 - 02:37
fonte
0

Where should we put the logic of how to manipulate the Blog and BlogPost objects based on values in the BlogPostForm object, especially if this work includes more than a COMMIT to a database?

Manipolarli come?

Stai scherzando con il titolo? Il corpo? La data di pubblicazione? L'ID?!?!

I blog hanno regole aziendali che determinano chi può impostare cosa. I database hanno regole su come è possibile modificare i record. I moduli hanno regole ... beh, nessuna forma è un disastro devi capire come dare un senso senza lasciare che gli utenti abbandonino tutte le tue tabelle DB.

Tutte queste sono responsabilità separate che meritano il loro posto dove vivere.

User Interface/Domain Model Manipulation Logic - How to transfer data from the view to data and method calls in the Domain Model

Questo è un controller. Ricorda che un controller non è il codice nell'interfaccia utente. È qui che i messaggi provenienti da qualsiasi input (UI o non) vanno elaborati prima che il modello venga modificato.

Alcune persone creano controller anemici perché non comprendono appieno il rotolo che giocano lasciandoci con codice UI gonfio che cerca di rimediare. L'interfaccia utente dovrebbe avere a malapena il codice in loro.

programming for a disconnected, stateless interface... well... sucks

Modifica di un'interfaccia utente che sa anche chi sono gli utenti validi.

L'interfaccia utente non dovrebbe porre domande. Dovrebbero dire cosa vogliono fare e dovrebbero dirgli cosa mostrare.

    
risposta data 07.06.2017 - 08:43
fonte

Leggi altre domande sui tag