2013-03-22 20 views
5

Jak radzić sobie z unikając ViewBag ze względu na jego ryzyko błędu z byciem dynamiczny, ale także uniknąć konieczności wypełnienia nowego ViewModel i przekazać je z powrotem do widoku za każdym razem. Na przykład, nie chcę koniecznie zmieniać śledzenie, aby ujawnić typowe dane normalnie zapakowane w ViewBag.Skutecznie unika ViewBag w ASP.NET MVC

[HttpGet] 
void Index() 
{ 
    return View(); 
} 

do

[HttpGet] 
void Index() 
{ 
    var messages = new MessageCollection(); 
    messages.AddError("Uh oh!"); 

    return View(messages); 
} 

Gdzie w rurociągu chciałbym dodać obiekt jak ViewBag który jest zwyczaj i silnie wpisane ale mają to narażony elegancko w kontrolerze, a także widoku. Wolę to zrobić, kiedy nie potrzebują konkretnego ViewModel cały czas ...

[HttpGet] 
void Index() 
{ 
    Messages.AddError("Uh oh!"); 

    return View(); 
} 

A na pierwszej stronie, zamiast @ ((IMessageCollection) ViewBag.Messages) .Errors id raczej coś w rodzaju @ Messages.Errors, które są mocno wpisane i dostępne wszędzie. Ponadto, nie chcę po prostu rzucić go w blok kodu na górze mojego widoku maszynki.

W WebForms, bym zrobił coś takiego umieścić tę stronę bazowej, a następnie mają usercontrol, które mogą ukryte lub wyświetlane na stronach, ile potrzeba. Po odłączeniu kontrolera od widoku nie jestem pewien, jak skopiować podobne zachowanie.

Czy to możliwe, czy to, co jest najlepsze podejście do projektowania?

Dzięki, Scott

+0

Masz naprawdę 2 opcje do wyboru: ViewBag i dedykowana właściwość modelu –

Odpowiedz

8

widoki Razor są dość proste. Nawiązujesz interakcję z jednym modelem, który jest mocno napisany. Wszystko, co chcesz mocno wpisać w swój widok, musi być w twoim modelu. Jeśli masz coś, czego nie chcesz mieć w swoim modelu lub jest ono jednorazowe, to ViewBag jest dostarczane jako ogólne catch-all dla wszystkich danych innych niż model, dlatego jest dynamiczne. Aby być silnie wpisanym, ograniczyłoby to zdolność do bycia złapaniem wszystkiego.

Krótka i prosta: jeśli chcesz silnie wpisany dodawać wiadomości do widoku modelu. W przeciwnym razie trzymaj się ViewBag. To są twoje wybory.

+0

Świetne wyjaśnienie! – ledgeJumper

+0

Taa, myślę, że to prawdopodobnie najlepsza rada, żebym zobaczył, że inni idą również na to pytanie.Naprawdę chciałem sprawdzić, czy jest coś w architekturze MVC, która pozwoliła na rozszerzenie haka o własny haczyk wszędzie tam, gdzie wiesz, co by było konsekwentnie. Dzięki! – Scott

+0

Należy pamiętać, że przynajmniej do MVC5 (prawdopodobnie także MVC6/core) uzyskiwanie/ustawianie dowolnej właściwości ViewBag (np. ViewBag.Title) powoduje wyjątek wewnątrz struktury, która jest odpowiednio tłumiona i obsługiwana. Możesz to zobaczyć, wyłączając opcję "just my code". Jest to nieodłącznie związane z projektem "dynamicznej" implementacji i chociaż "działa", stanowi poważną przeszkodę w osiągnięciu wysokiej wydajności w witrynach intensywnie korzystających z ruchu. Więcej jedzenia do przemyślenia: http://mvolo.com/fix-the-3-high-cpu-performance-problems-for-iis-aspnet-apps/ – xDisruptor

1

zgadzam się z odpowiedzią Chrisa i osobiście chciałbym rzucić go w viewbag.

Ale grać diabłami adwokata, technicznie można naginać zasady ...

Edit: Wystarczy myśleć o tym teraz, prawdopodobnie można zastąpić HttpContext.Items poniżej ViewBag tak że technicznie wciąż używając ViewBag do przechowywania, ale po prostu dodanie opakowania, aby nadać temu ciepłemu sejfowi silnie typowane uczucie.

E.g. można mieć coś takiego:

namespace Your.Namespace 
{ 
    public class MessageCollection : IMessageCollection 
    { 
     public IList<string> Errors { get; protected set; } 
     protected MessageCollection() 
     { 
      //Initialization stuff here 
      Errors = new List<string>(); 
     } 

     private const string HttpContextKey = "__MessageCollection"; 
     public static MessageCollection Current 
     { 
      get 
      { 
       var httpContext = HttpContext.Current; 
       if (httpContext == null) throw new InvalidOperationException("MessageCollection must be used in the context of a web application."); 

       if (httpContext.Items[HttpContextKey] == null) 
       { 
        httpContext.Items[HttpContextKey] = new MessageCollection(); 
       } 

       return httpContext.Items[HttpContextKey] as MessageCollection; 
      } 
     } 
    } 
} 

Wtedy po prostu go w kontroler takich jak to:

[HttpGet] 
public ActionResult Index() 
{ 
    MessageCollection.Current.AddError("Uh oh!"); 

    return View(); 
} 

A może masz BaseController ze skrótów np getter ...

protected MessageCollection Messages { get { return MessageCollection.Current; } } 

Następnie w kontrolerze niż dziedziczy to

[HttpGet] 
public ActionResult Index() 
{ 
    Messages.AddError("Uh oh!"); 

    return View(); 
} 

Aby dostać go w widoku, prosty zmienić swój web.config (może trzeba to zrobić w kilku miejscach (czyli głównego web.config, postrzega katalog web.config i obszar odsłony katalogi web.config)

<system.web.webPages.razor> 
    <!-- blah --> 
    <pages pageBaseType="System.Web.Mvc.WebViewPage"> 
    <namespaces> 
     <!-- blah --> 
     <add namespace="Your.Namespace" /> 
    </namespaces> 
    </pages> 
</system.web.webPages.razor> 

Następnie w widokach powinna być w stanie zrobić:

<div class="messages"> 
    @foreach (var error in MessageCollection.Current.Errors) 
    { 
     <span>@error</span> 
    } 
</div> 
+0

Zasadniczo to, co otrzymałem również jako "obejście" ale nie chciałem przedstawiać tego jako części pytania ze strachu przed szybkim zamykaniem nowych i kreatywnych pomysłów. Jestem na tej samej stronie, co ty i Chris, ale miałem nadzieję, że może było coś w architekturze MVC, która pozwalała na rozszerzenie tego punktu, o którym nie wiedziałem. – Scott

0

w ASP.NET MVC, masz do dyspozycji ViewBag, ViewData i TempData (aby uzyskać więcej informacji, zobacz this blog post). ViewBag to dynamiczne opakowanie wokół słownika ViewData. Jeśli wykonasz ViewBag.Prop = "value", jest to odpowiednik ViewData["Prop"] = "value". Podczas korzystania z właściwości Model w widoku pobiera się ViewData.Model. Spójrz na siebie:

public abstract class WebViewPage<TModel> : WebViewPage 
{ 
    private ViewDataDictionary<TModel> _viewData; 
    public new AjaxHelper<TModel> Ajax { get; set; } 
    public new HtmlHelper<TModel> Html { get; set; } 
    public new TModel Model { get { return ViewData.Model; } } 
} 

Możemy osiągnąć swój koniec przy użyciu albo ViewBag lub ViewData trzymać swoje szczególne właściwości. Pierwszym krokiem jest stworzenie własnego wyprowadzenie WebViewPage<TModel> z właściwości, która ma:

public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> 
{ 
    public IList<string> Messages 
    { 
     get { return ViewBag.Messages ?? (ViewBag.Messages = new List<string>()); } 
    } 
} 

Teraz przejdź do widoku i zamienić linię @model YourModelClass (pierwsza linia) z następujących powodów:

@inherits CustomWebViewPage<YourModelClass> 

Możesz teraz użyć właściwości Messages w widoku.

@String.Join(", ", Messages) 

Aby go użyć w kontrolerach, prawdopodobnie będziesz chciał, aby czerpać z Controller i dodać właściwość tam, too.

public abstract class CustomControllerBase : Controller 
{ 
    public IList<string> Messages 
    { 
     get 
     { 
      return ViewBag.Messages ?? (ViewBag.Messages = new List<string>()); 
     } 
    } 
} 

Teraz, jeśli czerpiesz z tego kontrolera, możesz użyć nowej właściwości. Wszystko, co umieścisz na liście, będzie również dostępne w widoku.

public class ExampleController : CustomControllerBase 
{ 
    public ActionResult Index() 
    { 
     Messages.Add("This is a message"); 
     return View(); 
    } 
} 

użyłem ViewBag ponieważ wykonane getter właściwość krótszy. Możesz zrobić to samo z ViewData, jeśli wolisz (ViewData["Messages"]).

To nie jest to samo, co zaimplementowano Model, ponieważ ktoś może nadpisać twoją nieruchomość przypadkowo, jeśli użyje klucza, który zapisujesz, ale jest wystarczająco blisko, aby być funkcjonalnie równoważnym, jeśli tylko upewnisz się, że użyj unikalnego klucza.

Jeśli zagłębisz się głębiej, możesz wywnioskować z ViewDataDictionary i umieścić tam swoją właściwość, a następnie przesłonić część kontrolera i wyświetlić metody, aby użyć go zamiast tego. Wtedy twoja własność będzie dokładnie taka sama jak Model. Ale zostawię to ... Nie sądzę, żeby było warto.