Przekazywanie modelu z kontrolera do widoku i odwrotnie

0

Cześć,

Mam w zasadzie dwa przypadki, co do których potrzebowałbym wyjaśnienia. Chodzi o przekazywanie danych pomiędzy widokiem, a kontrolerem:

  1. Załóżmy, że mam viewmodel X z polami A, B, C i kolekcją obiektów D. Z pobieraniem danych z bazy i przekazaniem ich do modelu a następnie do widoku nie mam problemu. Problem pojawia się w momencie, gdy np. wywołuję widok, mam tam pusty formularz:
    Input A, Input B, Input C i tabela z inputami D (użytkownik może dodawać kolejne rekordy). Jak najlepiej przekazać takie dane do kontrolera? Chodzi mi o tą zależność "lista obiektów w obiekcie"?
  2. Drugi case jest trudniejszy, mam tutaj relację wiele do wielu pomiędzy obiektami A i B, stworzona jest tzw. klasa pomocnicza, która ma też swoje odwzorowanie w bazie danych (klucze dla tych dwóch tabel). Jak w tym przypadku utworzyć poprawny model i przekazać go do widoku, a następnie zwrócić do kontrolera? Tutaj kompletnie nie mam pojęcia, a też nie mogę się doszukać informacji na stackach.

Jeżeli będzie trzeba to wrzucę przykład kodu. Nie chciałem póki co zaśmiecać postu, wolę zrozumieć koncepcję na jakimś prostszym przykładzie i przełożyć ją na swój problem.

0

Punkt 1 to nie problem, wystarczy, że inputy od kolekcji będą miał poprawne wartość atrybutu name.
Co do punktu 2, to jak to powiązanie wiele do wielu masz w ogóle zamiar pokazać na widoku?

0

@somekind: Nie chcę pokazywać całego powiązania. Załóżmy, że mamy obiekt "Książka" i obiekt "Autor". Każda książka może mieć kilku autorów, każdy autor może wydać kilka książek. Chcę dać możliwość użytkownikowi, aby z poziomu admina dodawał sobie książkę no i aby mógł dodać do niej kilku autorów, jeżeli jest ich więcej. Po wysłaniu POST chciałbym, aby odłożyło się to w bazie w dwóch tabelach - AutorKsiazka i Ksiazka (uzytkownik bedzie mógł wybrać tylko z listy istniejących autorów).

0

No jeśli autor( po przesłaniu już przez klienta danych do API ) posiada listę książek i masz prawidłowo utworzoną bazę to entity automatycznie doda autora nadając mu kolejne id i tak samo doda książki dodając jako klucz obcy powiązanie do id autora.
Prześlij kod modelu bazodanowego. Korzystasz z podejścia code first?

0
Krispekowy napisał(a):

Po wysłaniu POST chciałbym, aby odłożyło się to w bazie w dwóch tabelach - AutorKsiazka i Ksiazka (uzytkownik bedzie mógł wybrać tylko z listy istniejących autorów).

Efekt końcowy jest jasny. Ale nie jest jasne to czego nie wiesz jak zrobić, bo po drodze jest kilka rzeczy do ogarnięcia. Jak nie pokazujesz kodu to nie wiemy czy masz:

  • gotowe modele (many-to-many)
  • konfiguracja Startup.cs
  • gotowy i poprawny db context
  • poprawny formularz w widoku i bindowanie modelu
  • poprawny kontroler
  • metody dostępu do bazy
1
Krispekowy napisał(a):

@somekind: Nie chcę pokazywać całego powiązania. Załóżmy, że mamy obiekt "Książka" i obiekt "Autor". Każda książka może mieć kilku autorów, każdy autor może wydać kilka książek. Chcę dać możliwość użytkownikowi, aby z poziomu admina dodawał sobie książkę no i aby mógł dodać do niej kilku autorów, jeżeli jest ich więcej. Po wysłaniu POST chciałbym, aby odłożyło się to w bazie w dwóch tabelach - AutorKsiazka i Ksiazka (uzytkownik bedzie mógł wybrać tylko z listy istniejących autorów).

Ale co Ty chcesz widzieć w GUI? Listę wszystkich autorów i książek, a potem możliwość ich dowolnego powiązania? Czy formularz z danymi książki i dropdownem do wyboru istniejącego autora?
Bo w ogóle nie widzę powiązania między tym, co opisujesz, a tym, co chcesz zrobić. Struktura bazy jest bez znaczenia w rozmawie o widokach i kontrolerach.

0

No więc z kontrolera do widoku musisz przesłać viewmodel z polami do edycji danych książki oraz listę autorów, żeby zrobić z niej dropdown, a z widoku na kontroler model z wypełnionymi danymi książki oraz wybranymi id autorów.

1

ViewModel (z kontrolera) i InputModel (do kontrolera) nie muszą być tym samym.
Z widoku wysyłasz sobie to co tam potrzebujesz aby utworzyć te relacje - jakieś id autora czy tam książki, nazwy itd.

Jak przesłać złożony obiekt i kolekcje? no tak jak w tym linku co podrzucałem wyżej

Załóżmy że masz taki model do wysłanie do kontrolera

[HttpPost]
public IActionResult Test([FromForm] TestModel data)
{
	return RedirectToAction(nameof(Index));
}
public class TestModel
{
	public string Asdf { get; set; }

	public List<XD> Xds { get; set; } = new List<XD>();
}

public class XD
{
	public string Name { get; set; }

	public int AuthorId { get; set; }
}

no to cały trik polega na odpowiednich nazwach w np. formularzu (możesz to dynamicznie dokładać kolejne indeksy [])

<form asp-action="Test" method="post">
    <input type="text" name="Asdf" />

    <input type="text" name="Xds[0].Name" />
    <input type="text" name="Xds[0].AuthorId" />
    
    <input type="text" name="Xds[1].Name" />
    <input type="text" name="Xds[1].AuthorId" />

    <button type="submit">Submit</button>
</form>

screenshot-20200904172937.png

Binduje się jako

screenshot-20200904173426.png

0

@WeiXiao: dzięki za przykład! Wydaje mi się jednak, że mam trochę inny problem, ale popraw mnie jeśli mogę to rozwiązać w ten sam sposób. Otóż. To moje klasy obiektów autor, książka i klasa pomocnicza:

    public class Author
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string SecondName { get; set; }
        public string Nationality { get; set; }
        public virtual ICollection<BookAuthor> Books { get; set; }
        public Author()
        {
            Books = new Collection<BookAuthor>();
        }
    }

public class Book
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public virtual ICollection<BookAuthor> Authors { get; set; }
        public string PublishingHouse { get; set; }
        public int NumberOfPages { get; set; }
        public int ISBN { get; set; }
        public Book()
        {
            Authors = new Collection<BookAuthor>();
        }
    }

    public class BookAuthor
    {
        public int BookId { get; set; }
        public virtual Book Book { get; set; }
        public int AuthorId { get; set; }
        public virtual Author Author { get; set; }
    }

I teraz tak. W momencie kliknięcia przez użytkownika przycisku "Dodaj książkę" chciałbym, aby pojawił się formularz do uzupełnienia z danymi książki oraz dropdownem do wyboru autorów. Poniżej akcja wywołująca widok. Pytanie czy tutaj nie powinienem już wysłać jakichś danych?

        [HttpGet]
        public IActionResult AddBook()
        {
            return View();
        }

Tak wygląda ViewModel:

    public class BookViewModel
    {
        public IEnumerable<Book> Books { get; set; }
        public Book Book { get; set; }
        public int Id { get; set; }
    }

A poniżej mój nieszczęsny widok, z którym mam najwięcej problemów:

@model Application.ViewModels.BookViewModel
<form>
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Book.Title">Tytuł książki</label>
        <input class="form-control" asp-for="Book.Title">
        <label asp-for="Book.PublishingHouse">Wydawnictwo</label>
        <input class="form-control" asp-for="Book.PublishingHouse">
        <label asp-for="Book.ISBN">Numer ISBN</label>
        <input class="form-control" asp-for="Book.ISBN">
        <label asp-for="Book.NumberOfPages">Liczba stron</label>
        <input class="form-control" asp-for="Book.NumberOfPages">
    </div>
    <button type="submit" class="btn btn-primary">Dodaj książkę</button>
</form>

Nie wiem czy myślę w dobrym kierunku, ale tak teraz opisując Wam to wszystko wpadłem na pomysł (poprawcie jeśli błądzę) - czy nie powinienem utworzyć po prostu nowego modelu, w którym przedstawiłbym po prostu pola książki i pole typu ICollection z Author.ID? Wtedy w serwisie utworzyłbym metodę do pobrania danych autora, przekazał model w żądaniu GET do widoku, a następnie w POST przekazałbym tylko ID wybranych autorów + dane książki? Nie wiem jeszcze jak przedstawić listę autorów w postaci dropdownu, próbowałem przez @html.dropdown, ale pojawia się problem z wyświetleniem typu ICollection, oczekuje Enuma.

0

przekazałbym tylko ID wybranych autorów + dane książki? Nie wiem jeszcze jak przedstawić listę autorów w postaci dropdownu, próbowałem przez @html.dropdown, ale pojawia się problem z wyświetleniem typu ICollection, oczekuje Enuma.

Ja tam personalnie nie jestem fanem tych helperów asp (pewnie dlatego że nigdy nie chciało mi się ich uczyć xD), ale możesz to zrobić bardziej ""low levelowo""

[HttpPost]
public IActionResult Test([FromForm] TestModel data)
{
	return RedirectToAction(nameof(Index));
}

public class TestModel
{
	public List<int> AuthorIds { get; set; } = new List<int>();
}
<form asp-action="Test" method="post">
	<select name="AuthorIds" multiple>
		@foreach (var author in Authors)
		{
			<option value="@author.AuthorId">@author.Name</option>
		}
	</select>

	<button type="submit">Submit</button>
</form>

Jakieś przykładowe dane

var Authors = new List<XD>
{
	new XD
	{
		AuthorId = 5,
		Name = "Wiesiek"
	},    
	new XD
	{
		AuthorId = 7,
		Name = "Abc"
	},  
	new XD
	{
		AuthorId = 1,
		Name = "Asd"
	},
};

screenshot-20200905114643.png

Binding w controllerze:

screenshot-20200905114704.png

0

Ok przedstawię może jak wygląda to u mnie obecnie :)
Stworzyłem nowy viewmodel:

    public class AddBookViewModel
    {
        public Book Book { get; set; }
        public List<Author> Authors { get; set; }
    }

Zastanawiam się czy aby na pewno potrzebnie, ale chyba tak - ze względu na relację wiele do wielu w obiekcie Book jak widzicie mam pole Authors, które jest kolekcją obiektów "BookAuthor", więc trudno byłoby to powiązać i przesłać na widok (a przynajmniej nikt nie udzielił mi do tej pory odpowiedzi jak to mógłbym zrobić).

Mój widok wygląda obecnie tak:

@model Application.ViewModels.AddBookViewModel
<form asp-action="AddBook" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Book.Title">Tytuł książki</label>
        <input class="form-control" asp-for="Book.Title">
        <label asp-for="Book.PublishingHouse">Wydawnictwo</label>
        <input class="form-control" asp-for="Book.PublishingHouse">
        <label asp-for="Book.ISBN">Numer ISBN</label>
        <input class="form-control" asp-for="Book.ISBN">
        <label asp-for="Book.NumberOfPages">Liczba stron</label>
        <input class="form-control" asp-for="Book.NumberOfPages">
        <select name="AuthorIds" multiple>
            @foreach (var author in Model.Authors)
            {
                <option value="@author.Id">@author.FirstName</option>
            }
        </select>
    </div>
    <button type="submit" class="btn btn-primary">Dodaj książkę</button>
</form>

Do kontrollera przesyłane są dane książki, bez autora, ponieważ parametrem jest obiekt Book. W jaki sposób mogę powiązać listę autorów z mojego modelu AddBookViewModel z kolekcją autorów w obiekcie Book, tak aby został on w całości przesłany w całości z widoku do kontrolera?
book.PNG

0
Krispekowy napisał(a):

Stworzyłem nowy viewmodel:

    public class AddBookViewModel
    {
        public Book Book { get; set; }
        public List<Author> Authors { get; set; }
    }

Jeśli Book i Author to nie są viewmodele, to to też nie jest viewmodel.

Do kontrollera przesyłane są dane książki, bez autora, ponieważ parametrem jest obiekt Book. W jaki sposób mogę powiązać listę autorów z mojego modelu AddBookViewModel z kolekcją autorów w obiekcie Book, tak aby został on w całości przesłany w całości z widoku do kontrolera?

Tak się tego nie robi. Z widoku do kontrolera przesyła się tylko Id wybranych autorów. Powiązaniem zajmuje się backend.

0

@somekind: jeśli tworzę nowy obiekt to muszę przekazać dane z widoku, bo nie mam ich po czym powiązać :) jeżeli masz tu na myśli autora to tak - potrzebne mi tylko Id wybranych autorów, których niestety nie otrzymuję jak widzisz. Czy w tej kwestii jesteś w stanie pomóc?

Jeśli Book i Author to nie są viewmodele, to to też nie jest viewmodel.

Nie rozumiem, co masz na myśli?

0

Moim zdaniem Twój problem polega cały czas na tym, że pchasz encje na widok, zamiast mieć oddzielną warstwę viewmodeli do komunikacji między kontrolerem a widokiem.

1

@Krispekowy: somekindowi chodzi o to o czym wspomniał wcześniej:

    public class AddBookViewModel
    {
        public Book Book { get; set; }
        public List<Author> Authors { get; set; }
    }

Skąd się w tym przypadku biorą Book oraz Author? Czy są to obiekty które masz po wyciągnięciu z bazy danych? Jeśli tak to nadal warstwę prezentacji masz związaną z warstwą domenową. Book oraz Author to również powinny być modele widoku, czyli oddzielne klasy dedykowane obsłudze widoku.

0

Tu warto zaznaczyć że pchanie encji na widok (i w drugą stronę) to nie tylko jakaś tam zła rzecz z punktu widzenia "czystości kodu" i wymysłów pryncypali, ale potencjalnie security issue.

0
Aventus napisał(a):

@Krispekowy: somekindowi chodzi o to o czym wspomniał wcześniej:

    public class AddBookViewModel
    {
        public Book Book { get; set; }
        public List<Author> Authors { get; set; }
    }

Skąd się w tym przypadku biorą Book oraz Author? Czy są to obiekty które masz po wyciągnięciu z bazy danych? Jeśli tak to nadal warstwę prezentacji masz związaną z warstwą domenową. Book oraz Author to również powinny być modele widoku, czyli oddzielne klasy dedykowane obsłudze widoku.

Pewnie macie rację. Mam w tej chwili listę klas Entities i są to obiekty, które zostały odwzorowane w bazie danych wg modelu EF Code First. Stąd też BookViewModel, czy AddBookViewModel czerpie z Book tzn. chcąc uzyskać listę książek, moje pole wygląda tak:

IEnumerable<Book> { get; set; }

Gdzie Book nie jest modelem, a klasą na podstawie której powstała tabela Books na bazie danych.

Budując taką przykładową aplikację kierowałem się wzorcem z tej strony: https://medium.com/@nishancw/clean-architecture-net-core-part-2-implementation-7376896390c5
Uprzedzę pytania - po co taka infrastruktura do jakiegoś prostego projektu - chcę po prostu poćwiczyć i zrozumieć na "żywym organizmie" jak to wszystko powinno funkcjonować.
entity.PNG

1

Czyli gość powołuje się na clean architecture, a potem pcha encje na interfejs. O jak słodko.

Ogólnie internet jest pełen marnej jakości materiałów, nie warto wierzyć pierwszemu wynikowi z Google, ani nawet pierwszym trzem stronom wyników. Zawsze też warto poszukać artykułów negujących dane podejście.

W ogólności model biznesowy, model przechowywania danych i model widoku/API to powinny być trzy oddzielne zestawy klas.

0
somekind napisał(a):

Czyli gość powołuje się na clean architecture, a potem pcha encje na interfejs. O jak słodko.

@somekind fajna ta odpowiedź, taka nie za miła :)

Ogólnie internet jest pełen marnej jakości materiałów, nie warto wierzyć pierwszemu wynikowi z Google, ani nawet pierwszym trzem stronom wyników. Zawsze też warto poszukać artykułów negujących dane podejście.

W takim razie równie dobrze moglibyśmy rozmawiać o negowaniu tego, że Ziemia jest okrągła. Masz na myśli jakieś konkretne przykłady? W moim przypadku chodzi o jedno - odseparowanie danych modelu od encji, jeśli jesteś w stanie odpowiedzieć jak mogę to zrobić w moim projekcie, bez zbędnych dogryzek - będę wdzięczny.

W ogólności model biznesowy, model przechowywania danych i model widoku/API to powinny być trzy oddzielne zestawy klas.

Jak ma się to do infrastruktury, którą wrzuciłem na screenie? Możesz to powiązać? Wydaje mi się, że mam rozbicie na odpowiednie projekty, gdzieś niestety zamieszałem i potrzebuję po prostu pomocy/wskazówki.

1

Z języka [CIACH!](SK) na ludzki:

Chodzi mnie więcej o to, że zwracanie na widok oraz wysyłanie do kontrolerów tego samego obiektu na którym operuje baza jest niezalecaną praktyką.

W projektach niewielkich rozmiarów tego aż tak nie widać, ale na dłuższą metę np. będzie coraz ciężej się w tym połapać co właściwie by miało być wymieniane pomiędzy backendem a frontem oraz jest to niepraktyczne, bo zazwyczaj jakieś dane chcesz konwertować na coś przyjaznego dla usera.

0
WeiXiao napisał(a):

W projektach niewielkich rozmiarów tego aż tak nie widać, ale na dłuższą metę np. będzie coraz ciężej się w tym połapać co właściwie by miało być wymieniane pomiędzy backendem a frontem oraz jest to niepraktyczne, bo zazwyczaj jakieś dane chcesz konwertować na coś przyjaznego dla usera.

Ok, zacząłem więc od początku. Dajmy na to mam encję, którą jest klasa Book.cs:

    public class Book
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public virtual ICollection<BookAuthor> Authors { get; set; }
        public string PublishingHouse { get; set; }
        public int NumberOfPages { get; set; }
        public int ISBN { get; set; }
        public Book()
        {
            Authors = new Collection<BookAuthor>();
        }
    }

Utworzyłem na jej podstawie model BookModel.cs oraz AuthorModel.cs, tak aby nie odwoływać się bezpośrednio do Book.cs czy Author.cs

    public class BookModel
    {
        public string Title { get; set; }
        public string PublishingHouse { get; set; }
        public int NumberOfPages { get; set; }
        public int ISBN { get; set; }
        public ICollection<AuthorModel> Authors { get; set; }
    }
    public class AuthorModel
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string SecondName { get; set; }
        public string Nationality { get; set; }
        public virtual ICollection<BookModel> Books { get; set; }
    }

Jak widzicie w obu przykładach dla modeli wołam o kolekcję BookModel lub AuthorModel. Ok i teraz dalej. Mam repo, które implementuje interfejs

    public class BookRepository :IBookRepository
    {
        private readonly LibraryContext _context;

        public BookRepository(LibraryContext context)
        {
            _context = context;
        }

        public Book GetBook(int id)
        {
            var book = _context.Books.Include(a => a.Authors).Include("Authors.Author").Single(b => b.Id == id);
            return book;
        }
   }

W konstruktorze serwisu, który implentuje interfejs IBookService tworzę instancję interfejsu repo IBookRepository, gdzie moja metoda GetBook zwraca już BookModel.

    public class BookService : IBookService
    {
        private readonly IBookRepository _bookRepository;

        public BookService(IBookRepository bookRepository)
        {
            _bookRepository = bookRepository;
        }

        public BookModel GetBook(int id)
        {
            var book = _bookRepository.GetBook(id);

            return new BookModel()
            {
                Title = book.Title,
                ISBN = book.ISBN,
                Authors = book.Authors,
                NumberOfPages = book.NumberOfPages,
                PublishingHouse = book.PublishingHouse
            };
        }

...który następnie przekazywany jest do widoku poprzez kontroler:

        [HttpGet]
        public IActionResult Details(int id)
        {
            BookModel model = _bookService.GetBook(id);
            return View(model);
        }
@model Application.Models.BookModel

<h1>Informacje o książce</h1>

<p>
    <h2>Tytuł: @Model.Title</h2>
    <h3>
        Imię i nazwisko autora:
        @foreach (var author in Model.Authors)
        {
            @author.FirstName @author.SecondName
        }
    </h3>
    <h3>Numer ISBN: @Model.ISBN</h3>
    <h3>Wydawnictwo: @Model.PublishingHouse</h3>
    <h3>Liczba stron: @Model.NumberOfPages</h3>
</p>

Czy Waszym zdaniem o czymś zapomniałem, coś pominąłem lub coś powinienem zmienić?

EDIT:
Już zauważyłem, niestety w tym miejscu kodu w metodzie GetBook:

        public BookModel GetBook(int id)
        {
            [...]
                Authors = book.Authors,
            [...]
        }

Otrzymuję komunikat o tym, że nie można niejawnie przekonwertować BookAuthor na AuthorModel. Klasa BookAuthor to klasa pomocnicza, na podstawie której EF Core tworzyło tabelę dla relacji wiele do wielu. Co z tym fantem mogę zrobić?

0

Automapper Ci to "przekonwertuje".

1

@Krispekowy:

Czy Waszym zdaniem o czymś zapomniałem, coś pominąłem lub coś powinienem zmienić?

Otrzymuję komunikat o tym, że nie można niejawnie przekonwertować BookAuthor na AuthorModel. Klasa BookAuthor to klasa pomocnicza, na podstawie której EF Core tworzyło tabelę dla relacji wiele do wielu. Co z tym fantem mogę zrobić?

Możesz ręcznie to przemapować lub użyć narzędzia jakiegoś typu wspomniany AutoMapper

Authors = book.Authors.Select
(
	x => new AuthorSomeViewModel
	{
		Id = x.Id
		Imie = x.Imie
	}
).ToArray(),
1
Krispekowy napisał(a):

@somekind fajna ta odpowiedź, taka nie za miła :)

Czy Ty masz do mnie pretensje, że Ci powiedziałem, że autor artykułu nie ma pojęcia o czym pisze, czym wprowadza Ciebie w błąd?

Jak dla mnie, to możesz podążać za jego naukami, tylko chyba sam już widzisz, że coś z nimi jest nie tak, skoro Ci nie pomagają.

W takim razie równie dobrze moglibyśmy rozmawiać o negowaniu tego, że Ziemia jest okrągła.

Nie bardzo rozumiem jak się to ma do konieczności weryfikowania wszelkich informacji w wielu źródłach ani skąd pomysł na taki przykład.
Kształt ziemi jest udowodniony naukowo, w przypadku inżynierii nie mamy dowodów naukowych, przy konstruowaniu czegoś wspieramy się oczywiście nauką, tam gdzie da się ją zastosować, ale bazujemy na intuicji i doświadczeniu. Dlatego jeśli ktoś mówi, żeby robić coś w jakiś sposób, to warto sprawdzić jakie ten sposób ma wady i wysłuchać/poczytać przeciwników danego podejścia.

Generalnie pierwszych kilkanaście wyników z Google na jakiś temat, to często kopiuj-wklej z jednego źródła, więc promowane jest to samo podejście (często nieprawidłowe) i powtarzane są te same błędy. Dlatego warto czasem spojrzeć dalej.

Masz na myśli jakieś konkretne przykłady? W moim przypadku chodzi o jedno - odseparowanie danych modelu od encji, jeśli jesteś w stanie odpowiedzieć jak mogę to zrobić w moim projekcie, bez zbędnych dogryzek - będę wdzięczny.

Myślę, że już powiedziałem kilka razy. :) Obiekty, które zapisujesz do bazy i obiekty, które przesyłasz między kontrolerem a widokiem, to dwa zupełnie odmienne zestawy obiektów. No i tyle, trzeba ten kod po prostu napisać.
Jeśli Twoje encje to Book i Author połączone powiązaniem (nie żadną relacją) wiele do wielu, to (robiąc typowego cruda) potrzebujesz zapewne co najmniej takich viewmodeli:

  • BookListViewModel - tytuł, nazwiska kilku pierwszych autorów, data wydania
  • AutorListViewModel - imię, nazwisko, data urodzenia i inne rzeczy to wyświetlenia na liście
  • AuthorEditViewModel - w sumie to co powyżej
  • BookEditViewModel - tytuł, data wydania, ISBN, lista par ID - imię i nazwisko autora (do wypełnienia comboboxa), lista ID autorów (tych wybranych przez użytkownika).

To co jest na widoku nie wygląda jak to, co jest w bazie, więc w kodzie też musi to wyglądać inaczej.

Krispekowy napisał(a):

Jak widzicie w obu przykładach dla modeli wołam o kolekcję BookModel lub AuthorModel.

I to jest chyba podstawowy błąd. Do czego potrzebne Ci te kolekcje?

  • Tworzysz gdzieś zagnieżdżone widoki? Jeśli tak, to prawdopodobnie powinieneś pobierać dane do widoku zewnętrznego i wewnętrznego oddzielnie.
  • Potrzebujesz listy autorów do zbudowania comboboxa do edycji książki? Nie, wystarczy lista par ID-Imię i nazwisko.
  • Budujesz API zwracające cały graf z bazy? To wtedy to nie są modele. :)

Cykliczne referencje w tych modelach też nie wróżą nic dobrego.

Ok i teraz dalej. Mam repo, które implementuje interfejs

A po co Ci to repozytorium w ogóle? Serwis może korzystać z LibraryContext i zwracać modele.

@model Application.Models.BookModel

<h1>Informacje o książce</h1>

<p>
    <h2>Tytuł: @Model.Title</h2>
    <h3>
        Imię i nazwisko autora:
        @foreach (var author in Model.Authors)
        {
            @author.FirstName @author.SecondName
        }
    </h3>
    <h3>Numer ISBN: @Model.ISBN</h3>
    <h3>Wydawnictwo: @Model.PublishingHouse</h3>
    <h3>Liczba stron: @Model.NumberOfPages</h3>
</p>

No i zobacz - Twój AuthorModel ma 5 właściwości, z czego jedna jest kolekcją BookModel. Ty używasz tylko dwóch. To po co pozostałe?

BTW, @WeiXiao: Ty jesteś ekspertem od niepełnosprawnych ORMów, serio teraz trzeba tak pisać: _context.Books.Include(a => a.Authors).Include("Authors.Author").Single(b => b.Id == id)?

0
somekind napisał(a):

Czy Ty masz do mnie pretensje, że Ci powiedziałem, że autor artykułu nie ma pojęcia o czym pisze, czym wprowadza Ciebie w błąd?
Jak dla mnie, to możesz podążać za jego naukami, tylko chyba sam już widzisz, że coś z nimi jest nie tak, skoro Ci nie pomagają.

Zrozumiałem Twój post jakbyś kierował to bezpośrednio do mnie, a nie do gościa z tego linku który podesłałem. W takim razie nieistotne.

Myślę, że już powiedziałem kilka razy. :) Obiekty, które zapisujesz do bazy i obiekty, które przesyłasz między kontrolerem a widokiem, to dwa zupełnie odmienne zestawy obiektów. No i tyle, trzeba ten kod po prostu napisać.
Jeśli Twoje encje to Book i Author połączone powiązaniem (nie żadną relacją) wiele do wielu, to (robiąc typowego cruda) potrzebujesz zapewne co najmniej takich viewmodeli:

  • BookListViewModel - tytuł, nazwiska kilku pierwszych autorów, data wydania
  • AutorListViewModel - imię, nazwisko, data urodzenia i inne rzeczy to wyświetlenia na liście
  • AuthorEditViewModel - w sumie to co powyżej
  • BookEditViewModel - tytuł, data wydania, ISBN, lista par ID - imię i nazwisko autora (do wypełnienia comboboxa), lista ID autorów (tych wybranych przez użytkownika).

To co jest na widoku nie wygląda jak to, co jest w bazie, więc w kodzie też musi to wyglądać inaczej.

Dzięki, to bardzo cenna wskazówka.

Krispekowy napisał(a):

Jak widzicie w obu przykładach dla modeli wołam o kolekcję BookModel lub AuthorModel.

I to jest chyba podstawowy błąd. Do czego potrzebne Ci te kolekcje?

  • Tworzysz gdzieś zagnieżdżone widoki? Jeśli tak, to prawdopodobnie powinieneś pobierać dane do widoku zewnętrznego i wewnętrznego oddzielnie.
  • Potrzebujesz listy autorów do zbudowania comboboxa do edycji książki? Nie, wystarczy lista par ID-Imię i nazwisko.
  • Budujesz API zwracające cały graf z bazy? To wtedy to nie są modele. :)

Wołam o kolekcję AuthorModel z tego względu, że potrzebuję listę imion i nazwisk autorów wraz z ID, aby w momecnie dodawania nowej książki odpowiednio zasilić BD.

Ok i teraz dalej. Mam repo, które implementuje interfejs

A po co Ci to repozytorium w ogóle? Serwis może korzystać z LibraryContext i zwracać modele.

W repo zawieram metody, które wykonują operację na BD. Właśnie o to pytałem, czy cała ta struktura ma sens i czy coś należy zmienić.

@model Application.Models.BookModel

<h1>Informacje o książce</h1>

<p>
    <h2>Tytuł: @Model.Title</h2>
    <h3>
        Imię i nazwisko autora:
        @foreach (var author in Model.Authors)
        {
            @author.FirstName @author.SecondName
        }
    </h3>
    <h3>Numer ISBN: @Model.ISBN</h3>
    <h3>Wydawnictwo: @Model.PublishingHouse</h3>
    <h3>Liczba stron: @Model.NumberOfPages</h3>
</p>

No i zobacz - Twój AuthorModel ma 5 właściwości, z czego jedna jest kolekcją BookModel. Ty używasz tylko dwóch. To po co pozostałe?

Ok, to w takim razie odpowiednio zmienię.

BTW, @WeiXiao: Ty jesteś ekspertem od niepełnosprawnych ORMów, serio teraz trzeba tak pisać: _context.Books.Include(a => a.Authors).Include("Authors.Author").Single(b => b.Id == id)?

Tą odpowiedź otrzymałem na stackoverlow. Jak widać są programiści i programiści więc tak na prawdę i tak jestem skazany na wybór między jednym a drugim i na zdrowy rozsądek biorąc pod uwagę wszelkie doradztwo z sieci.

Podsumowując - o to właśnie mi chodziło, o pełne wyjaśnienie zagadnienia. Nie jestem po żadnych technicznych studiach, pracuję w IT od roku, ale nie jako programista, więc wszelkie wskazówki są dla mnie cenne aby zrozumieć w pełni daną technologię. Trochę mnie załamałeś tym, że aby znaleźć wartościową odpowiedź, będę musiał nieraz zaglądać na krańce internetu bo wszystko w TOP10 jest oparte o jedno i to samo. Zazwyczaj staram się szukać najnowszych wpisów, artykułów, poradników, ale może to nie wystarcza.

EDIT:

I to jest chyba podstawowy błąd. Do czego potrzebne Ci te kolekcje?

Tworzysz gdzieś zagnieżdżone widoki? Jeśli tak, to prawdopodobnie powinieneś pobierać dane do widoku zewnętrznego i wewnętrznego oddzielnie.
Potrzebujesz listy autorów do zbudowania comboboxa do edycji książki? Nie, wystarczy lista par ID-Imię i nazwisko.
Budujesz API zwracające cały graf z bazy? To wtedy to nie są modele. :)

Potrzebuję listy autorów wyświetlonych w szczegółach książki, dlatego w modelu BookModel wrzuciłem właściwość public ICollection<AuthorModel>Authors {get;set;} Pytanie czy tak jest poprawnie czy powinienem to inaczej obsłużyć?

0
Krispekowy napisał(a):

Wołam o kolekcję AuthorModel z tego względu, że potrzebuję listę imion i nazwisk autorów wraz z ID, aby w momecnie dodawania nowej książki odpowiednio zasilić BD.

W momencie dodawania książki potrzebujesz tylko wybrane przez użytkownika ID. Na jego podstawie możesz wczytać autora z bazy, przypisać do odpowiedniej właściwości Book i zapisać. Poza tym, wyłącznie dla wygody użytkownika, potrzebujesz imię i nazwisko do wyświetlenia w dropdownie, ale możesz to połączyć w jedno na backendzie, więc wystarczy Ci taki mały model:

class DropdownModel
{
    public int Id { get; set; }
    public string DisplayName { get; set; }
}

i możesz go użyć we wszystkich dropdownach w aplikacji.

W repo zawieram metody, które wykonują operację na BD. Właśnie o to pytałem, czy cała ta struktura ma sens i czy coś należy zmienić.

No ja bym je wywalił, bo tylko utrudnia w tej sytuacji. Operacje na bazie robi ORM. Repozytorium miałoby sens, gdybyś miał skomplikowaną logikę domenową, a nie CRUDa.
http://commitandrun.pl/2016/05/11/Repozytorium_najbardziej_niepotrzebny_wzorzec_projektowy/

Tą odpowiedź otrzymałem na stackoverlow. Jak widać są programiści i programiści więc tak na prawdę i tak jestem skazany na wybór między jednym a drugim i na zdrowy rozsądek biorąc pod uwagę wszelkie doradztwo z sieci.

No to jest ten problem ze StackOverflow. Najwyżej punktowane wątki/odpowiedzi, nie zawsze są tymi najlepszymi. Ktoś skopiuje kod z paru miejsc, połączy na ślinę, ten kod u kogoś zadziała i mu rozwiąże problem, więc zagłosuje - bez wnikania, czy nie da się prościej. Ja zakładam, że zawsze da się prościej, więc jeśli ktoś proponuje odwoływanie się do nazw tabel przez API ORMa, to jakoś mu nie wierzę.

Podsumowując - o to właśnie mi chodziło, o pełne wyjaśnienie zagadnienia. Nie jestem po żadnych technicznych studiach, pracuję w IT od roku, ale nie jako programista, więc wszelkie wskazówki są dla mnie cenne aby zrozumieć w pełni daną technologię. Trochę mnie załamałeś tym, że aby znaleźć wartościową odpowiedź, będę musiał nieraz zaglądać na krańce internetu bo wszystko w TOP10 jest oparte o jedno i to samo. Zazwyczaj staram się szukać najnowszych wpisów, artykułów, poradników, ale może to nie wystarcza.

No niestety, umiejętność filtrowania informacji to jest coś, czego nabycie wymaga czasu.

Potrzebuję listy autorów wyświetlonych w szczegółach książki, dlatego w modelu BookModel wrzuciłem właściwość public ICollection<AuthorModel>Authors {get;set;} Pytanie czy tak jest poprawnie czy powinienem to inaczej obsłużyć?

Myślę, że potrzebujesz tylko jego FirstName i LastName. Id, Nationatlity czy lista książek nie są potrzebne. Więc ja bym to uprościł i w BookDetailsViewModel (kolejna klasa, bo kolejny widok) użył kolekcji takich obiektów:

class AuthorBookDetailsViewModel
{
    public string FirstName { get; set; }
    public string LastName{ get; set; }
}
0

@Krispekowy: na temat wątku łatwiej i lepiej odpisywać w postach. Komentarze są do pobocznych dyskusji.

Jeśli zamierzam przy dodawaniu książki wyświetlić użytkownikowi wszystkie pola do uzupełnienia tj: Tytuł, wydawnictwo, liczba stron, ISBN, lista autorów, to powinienem w BookModel oprócz "prostych" właściwości zawrzeć ICollection<dropdownmodel> Authors?

Tak właśnie bym zrobił.

Czy uważasz, że w ViewModelu mogę się odnosić bezpośrednio do klasy encji? Czyli np. do Book? Mam na myśli Viewmodel, w którym mam IEnumerable<book> Books {get; set;} gdzie wyświetlam listę książek.

Nie, nigdy żadnych encji w viewmodelach. ViewModele to jedna warstwa, encje to druga warstwa, nie mogą mieć ze sobą żadnych punktów wspólnych.

0

@somekind: jakaś część muszą mieć wspólną chyba abym mógł zmapować model do bazy?

1 użytkowników online, w tym zalogowanych: 0, gości: 1