Skip to content

Commit ede3488

Browse files
committed
Added ViewBag, ViewData & TempData section in the tutorial (#132)
1 parent 5c8b0a4 commit ede3488

File tree

7 files changed

+430
-5
lines changed

7 files changed

+430
-5
lines changed

docs/_docfx/tutorial/toc.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@
2828
- name: Session & Cache
2929
href: sessioncache.md
3030
- name: ViewBag, ViewData & TempData
31-
href: viewbagviewdatatempdata.md
31+
href: viewbagviewdatatempdata.md
32+
- name: View Components
33+
href: viewcomponents.md
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# ViewBag, ViewData & TempData
2+
3+
The **"Music Store"** web application does uses only the **"ViewBag"** so we will write some tests for it. The **"ViewData**" and the **"TempData"** use similar syntax.
4+
5+
## Testing with ViewBag entry
6+
7+
Let's test something simple - the HTTP Get overload of the **"Login"** action in the **"AccountController"**:
8+
9+
```c#
10+
public IActionResult Login(string returnUrl = null)
11+
{
12+
ViewBag.ReturnUrl = returnUrl;
13+
return View();
14+
}
15+
```
16+
17+
We need a new dependency (again?!) - **"MyTested.AspNetCore.Mvc.ViewData"**:
18+
19+
```json
20+
"dependencies": {
21+
"dotnet-test-xunit": "2.2.0-*",
22+
"xunit": "2.2.0-*",
23+
"Moq": "4.6.38-*",
24+
"MyTested.AspNetCore.Mvc.Authentication": "1.0.0",
25+
"MyTested.AspNetCore.Mvc.Caching": "1.0.0",
26+
"MyTested.AspNetCore.Mvc.Controllers": "1.0.0",
27+
"MyTested.AspNetCore.Mvc.DependencyInjection": "1.0.0",
28+
"MyTested.AspNetCore.Mvc.EntityFrameworkCore": "1.0.0",
29+
"MyTested.AspNetCore.Mvc.Http": "1.0.0",
30+
"MyTested.AspNetCore.Mvc.ModelState": "1.0.0",
31+
"MyTested.AspNetCore.Mvc.Models": "1.0.0",
32+
"MyTested.AspNetCore.Mvc.Options": "1.0.0",
33+
"MyTested.AspNetCore.Mvc.Session": "1.0.0",
34+
"MyTested.AspNetCore.Mvc.ViewActionResults": "1.0.0",
35+
"MyTested.AspNetCore.Mvc.ViewData": "1.0.0", // <---
36+
"MusicStore": "*"
37+
},
38+
```
39+
40+
We need to add the **"ViewData"** features, because the **"ViewBag"** is actually a [dynamic version of it](https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs#L91).
41+
42+
I hope you remember how we tested session and cache. Well, the **"ViewBag"** (**"ViewData"** and **"TempData"** too) is no different:
43+
44+
```c#
45+
[Fact]
46+
public void LoginShouldHaveReturnUrlInTheViewBag()
47+
{
48+
var returnUrl = "Test/Return/Url";
49+
50+
MyController<AccountController>
51+
.Instance()
52+
.Calling(c => c.Login(returnUrl))
53+
.ShouldHave()
54+
.ViewBag(viewBag => viewBag // <---
55+
.ContainingEntry("ReturnUrl", returnUrl))
56+
.AndAlso()
57+
.ShouldReturn()
58+
.View();
59+
}
60+
```
61+
62+
## Testing with multiple ViewBag entries
63+
64+
Let's write another one - for the HTTP Get overload of the **"Create"** action in **"StoreManagerController"**:
65+
66+
```c#
67+
public IActionResult Create()
68+
{
69+
ViewBag.GenreId = new SelectList(DbContext.Genres, "GenreId", "Name");
70+
ViewBag.ArtistId = new SelectList(DbContext.Artists, "ArtistId", "Name");
71+
return View();
72+
}
73+
```
74+
75+
And our test:
76+
77+
```c#
78+
[Fact]
79+
public void CreateShouldHaveValidEntriesInViewBag()
80+
{
81+
var genres = new[]
82+
{
83+
new Genre { GenreId = 1, Name = "Rock" },
84+
new Genre { GenreId = 2, Name = "Rap" }
85+
};
86+
87+
var artists = new[]
88+
{
89+
new Artist { ArtistId = 1, Name = "Tupac" },
90+
new Artist { ArtistId = 2, Name = "Biggie" }
91+
};
92+
93+
MyController<StoreManagerController>
94+
.Instance()
95+
.WithDbContext(db => db
96+
.WithEntities(entities =>
97+
{
98+
entities.AddRange(genres);
99+
entities.AddRange(artists);
100+
}))
101+
.Calling(c => c.Create())
102+
.ShouldHave()
103+
.ViewBag(viewBag => viewBag // <---
104+
.ContainingEntries(new
105+
{
106+
GenreId = new SelectList(From.Services<MusicStoreContext>().Genres, "GenreId", "Name"),
107+
ArtistId = new SelectList(From.Services<MusicStoreContext>().Artists, "ArtistId", "Name")
108+
}))
109+
.AndAlso()
110+
.ShouldReturn()
111+
.View();
112+
}
113+
```
114+
115+
The **"ContainingEntries"** call is equivalent to this one:
116+
117+
```c#
118+
.ContainingEntries(new Dictionary<string, object>
119+
{
120+
["GenreId"] = new SelectList(From.Services<MusicStoreContext>().Genres, "GenreId", "Name"),
121+
["ArtistId"] = new SelectList(From.Services<MusicStoreContext>().Artists, "ArtistId", "Name")
122+
}))
123+
```
124+
125+
Both methods will validate whether the total number of entries in the **"ViewBag"** is equal to the total number you provide to the test.
126+
127+
If you do not want the total number validation, just use:
128+
129+
```c#
130+
.ViewBag(viewBag => viewBag // <---
131+
.ContainingEntry("GenreId", new SelectList(From.Services<MusicStoreContext>().Genres, "GenreId", "Name"))
132+
.ContainingEntry("ArtistId", new SelectList(From.Services<MusicStoreContext>().Artists, "ArtistId", "Name")))
133+
```
134+
135+
## ViewData and TempData
136+
137+
**"ViewData"** have the same API:
138+
139+
```c#
140+
MyController<SomeController>
141+
.Instance()
142+
.Calling(c => c.SomeAction())
143+
.ShouldHave()
144+
.ViewData(viewData => viewData // <---
145+
.ContainingEntry("SomeKey", someValue))
146+
.AndAlso()
147+
.ShouldReturn()
148+
.View();
149+
```
150+
151+
**"TempData"** too but you will need the **"MyTested.AspNetCore.Mvc.TempData"** package:
152+
153+
```c#
154+
MyController<SomeController>
155+
.Instance()
156+
.Calling(c => c.SomeAction())
157+
.ShouldHave()
158+
.TempData(tempData => tempData // <---
159+
.ContainingEntry("SomeKey", someValue))
160+
.AndAlso()
161+
.ShouldReturn()
162+
.View();
163+
```
164+
165+
Additionally, you can populate the **"TempData"** dictionary before the action call:
166+
167+
```c#
168+
MyController<SomeController>
169+
.Instance()
170+
.WithTempData(tempData => tempData
171+
.WithEntry("SomeKey", someValue))
172+
.Calling(c => c.SomeAction())
173+
.ShouldReturn()
174+
.View();
175+
```
176+
177+
## Section summary
178+
179+
We saw how easy it is to test with **"ViewBag"**, **"ViewData"** and **"TempData"**. Their fluent assertion API is very similar to the **"Session"** and **"Cache"** ones. But enough about controllers, let's move on to [View Components](/tutorial/viewcomponents.html)!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# View Components

docs/manifest.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/tutorial/toc.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@
8282
<li>
8383
<a href="viewbagviewdatatempdata.html" name="" title="ViewBag, ViewData &amp; TempData">ViewBag, ViewData &amp; TempData</a>
8484
</li>
85+
<li>
86+
<a href="viewcomponents.html" name="" title="View Components">View Components</a>
87+
</li>
8588
</ul> </div>
8689
</div>
8790
</div>
@@ -137,6 +140,9 @@
137140
<li>
138141
<a href="viewbagviewdatatempdata.html" name="" title="ViewBag, ViewData &amp; TempData">ViewBag, ViewData &amp; TempData</a>
139142
</li>
143+
<li>
144+
<a href="viewcomponents.html" name="" title="View Components">View Components</a>
145+
</li>
140146
</ul> </div>
141147
</div>
142148
</div>

docs/tutorial/viewbagviewdatatempdata.html

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
<head>
55
<meta charset="utf-8">
66
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
7-
<title> </title>
7+
<title>ViewBag, ViewData &amp; TempData </title>
88
<meta name="viewport" content="width=device-width">
9-
<meta name="title" content=" ">
9+
<meta name="title" content="ViewBag, ViewData &amp; TempData ">
1010
<meta name="generator" content="docfx 2.5.0.0">
1111
{% seo %}
1212

@@ -63,8 +63,139 @@
6363
<div class="article row grid-right">
6464
<div class="col-md-10">
6565
<article class="content wrap" id="_content" data-uid="">
66+
<h1 id="viewbag-viewdata--tempdata" sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="1" sourceendlinenumber="1">ViewBag, ViewData &amp; TempData</h1>
6667

67-
68+
<p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="3" sourceendlinenumber="3">The <strong>&quot;Music Store&quot;</strong> web application does uses only the <strong>&quot;ViewBag&quot;</strong> so we will write some tests for it. The <strong>&quot;ViewData</strong>&quot; and the <strong>&quot;TempData&quot;</strong> use similar syntax.</p>
69+
<h2 id="testing-with-viewbag-entry" sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="5" sourceendlinenumber="5">Testing with ViewBag entry</h2>
70+
<p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="7" sourceendlinenumber="7">Let&#39;s test something simple - the HTTP Get overload of the <strong>&quot;Login&quot;</strong> action in the <strong>&quot;AccountController&quot;</strong>:</p>
71+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="9" sourceendlinenumber="15"><code class="lang-c#">public IActionResult Login(string returnUrl = null)
72+
{
73+
ViewBag.ReturnUrl = returnUrl;
74+
return View();
75+
}
76+
</code></pre><p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="17" sourceendlinenumber="17">We need a new dependency (again?!) - <strong>&quot;MyTested.AspNetCore.Mvc.ViewData&quot;</strong>:</p>
77+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="19" sourceendlinenumber="38"><code class="lang-json">&quot;dependencies&quot;: {
78+
&quot;dotnet-test-xunit&quot;: &quot;2.2.0-*&quot;,
79+
&quot;xunit&quot;: &quot;2.2.0-*&quot;,
80+
&quot;Moq&quot;: &quot;4.6.38-*&quot;,
81+
&quot;MyTested.AspNetCore.Mvc.Authentication&quot;: &quot;1.0.0&quot;,
82+
&quot;MyTested.AspNetCore.Mvc.Caching&quot;: &quot;1.0.0&quot;,
83+
&quot;MyTested.AspNetCore.Mvc.Controllers&quot;: &quot;1.0.0&quot;,
84+
&quot;MyTested.AspNetCore.Mvc.DependencyInjection&quot;: &quot;1.0.0&quot;,
85+
&quot;MyTested.AspNetCore.Mvc.EntityFrameworkCore&quot;: &quot;1.0.0&quot;,
86+
&quot;MyTested.AspNetCore.Mvc.Http&quot;: &quot;1.0.0&quot;,
87+
&quot;MyTested.AspNetCore.Mvc.ModelState&quot;: &quot;1.0.0&quot;,
88+
&quot;MyTested.AspNetCore.Mvc.Models&quot;: &quot;1.0.0&quot;,
89+
&quot;MyTested.AspNetCore.Mvc.Options&quot;: &quot;1.0.0&quot;,
90+
&quot;MyTested.AspNetCore.Mvc.Session&quot;: &quot;1.0.0&quot;,
91+
&quot;MyTested.AspNetCore.Mvc.ViewActionResults&quot;: &quot;1.0.0&quot;,
92+
&quot;MyTested.AspNetCore.Mvc.ViewData&quot;: &quot;1.0.0&quot;, // &lt;---
93+
&quot;MusicStore&quot;: &quot;*&quot;
94+
},
95+
</code></pre><p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="40" sourceendlinenumber="40">We need to add the <strong>&quot;ViewData&quot;</strong> features, because the <strong>&quot;ViewBag&quot;</strong> is actually a <a href="https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs#L91" sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="40" sourceendlinenumber="40">dynamic version of it</a>.</p>
96+
<p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="42" sourceendlinenumber="42">I hope you remember how we tested session and cache. Well, the <strong>&quot;ViewBag&quot;</strong> (<strong>&quot;ViewData&quot;</strong> and <strong>&quot;TempData&quot;</strong> too) is no different:</p>
97+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="44" sourceendlinenumber="60"><code class="lang-c#">[Fact]
98+
public void LoginShouldHaveReturnUrlInTheViewBag()
99+
{
100+
var returnUrl = &quot;Test/Return/Url&quot;;
101+
102+
MyController&lt;AccountController&gt;
103+
.Instance()
104+
.Calling(c =&gt; c.Login(returnUrl))
105+
.ShouldHave()
106+
.ViewBag(viewBag =&gt; viewBag // &lt;---
107+
.ContainingEntry(&quot;ReturnUrl&quot;, returnUrl))
108+
.AndAlso()
109+
.ShouldReturn()
110+
.View();
111+
}
112+
</code></pre><h2 id="testing-with-multiple-viewbag-entries" sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="62" sourceendlinenumber="62">Testing with multiple ViewBag entries</h2>
113+
<p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="64" sourceendlinenumber="64">Let&#39;s write another one - for the HTTP Get overload of the <strong>&quot;Create&quot;</strong> action in <strong>&quot;StoreManagerController&quot;</strong>:</p>
114+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="66" sourceendlinenumber="73"><code class="lang-c#">public IActionResult Create()
115+
{
116+
ViewBag.GenreId = new SelectList(DbContext.Genres, &quot;GenreId&quot;, &quot;Name&quot;);
117+
ViewBag.ArtistId = new SelectList(DbContext.Artists, &quot;ArtistId&quot;, &quot;Name&quot;);
118+
return View();
119+
}
120+
</code></pre><p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="75" sourceendlinenumber="75">And our test:</p>
121+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="77" sourceendlinenumber="113"><code class="lang-c#">[Fact]
122+
public void CreateShouldHaveValidEntriesInViewBag()
123+
{
124+
var genres = new[]
125+
{
126+
new Genre { GenreId = 1, Name = &quot;Rock&quot; },
127+
new Genre { GenreId = 2, Name = &quot;Rap&quot; }
128+
};
129+
130+
var artists = new[]
131+
{
132+
new Artist { ArtistId = 1, Name = &quot;Tupac&quot; },
133+
new Artist { ArtistId = 2, Name = &quot;Biggie&quot; }
134+
};
135+
136+
MyController&lt;StoreManagerController&gt;
137+
.Instance()
138+
.WithDbContext(db =&gt; db
139+
.WithEntities(entities =&gt;
140+
{
141+
entities.AddRange(genres);
142+
entities.AddRange(artists);
143+
}))
144+
.Calling(c =&gt; c.Create())
145+
.ShouldHave()
146+
.ViewBag(viewBag =&gt; viewBag // &lt;---
147+
.ContainingEntries(new
148+
{
149+
GenreId = new SelectList(From.Services&lt;MusicStoreContext&gt;().Genres, &quot;GenreId&quot;, &quot;Name&quot;),
150+
ArtistId = new SelectList(From.Services&lt;MusicStoreContext&gt;().Artists, &quot;ArtistId&quot;, &quot;Name&quot;)
151+
}))
152+
.AndAlso()
153+
.ShouldReturn()
154+
.View();
155+
}
156+
</code></pre><p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="115" sourceendlinenumber="115">The <strong>&quot;ContainingEntries&quot;</strong> call is equivalent to this one:</p>
157+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="117" sourceendlinenumber="123"><code class="lang-c#">.ContainingEntries(new Dictionary&lt;string, object&gt;
158+
{
159+
[&quot;GenreId&quot;] = new SelectList(From.Services&lt;MusicStoreContext&gt;().Genres, &quot;GenreId&quot;, &quot;Name&quot;),
160+
[&quot;ArtistId&quot;] = new SelectList(From.Services&lt;MusicStoreContext&gt;().Artists, &quot;ArtistId&quot;, &quot;Name&quot;)
161+
}))
162+
</code></pre><p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="125" sourceendlinenumber="125">Both methods will validate whether the total number of entries in the <strong>&quot;ViewBag&quot;</strong> is equal to the total number you provide to the test.</p>
163+
<p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="127" sourceendlinenumber="127">If you do not want the total number validation, just use:</p>
164+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="129" sourceendlinenumber="133"><code class="lang-c#">.ViewBag(viewBag =&gt; viewBag // &lt;---
165+
.ContainingEntry(&quot;GenreId&quot;, new SelectList(From.Services&lt;MusicStoreContext&gt;().Genres, &quot;GenreId&quot;, &quot;Name&quot;))
166+
.ContainingEntry(&quot;ArtistId&quot;, new SelectList(From.Services&lt;MusicStoreContext&gt;().Artists, &quot;ArtistId&quot;, &quot;Name&quot;)))
167+
</code></pre><h2 id="viewdata-and-tempdata" sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="135" sourceendlinenumber="135">ViewData and TempData</h2>
168+
<p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="137" sourceendlinenumber="137"><strong>&quot;ViewData&quot;</strong> have the same API:</p>
169+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="139" sourceendlinenumber="149"><code class="lang-c#">MyController&lt;SomeController&gt;
170+
.Instance()
171+
.Calling(c =&gt; c.SomeAction())
172+
.ShouldHave()
173+
.ViewData(viewData =&gt; viewData // &lt;---
174+
.ContainingEntry(&quot;SomeKey&quot;, someValue))
175+
.AndAlso()
176+
.ShouldReturn()
177+
.View();
178+
</code></pre><p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="151" sourceendlinenumber="151"><strong>&quot;TempData&quot;</strong> too but you will need the <strong>&quot;MyTested.AspNetCore.Mvc.TempData&quot;</strong> package:</p>
179+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="153" sourceendlinenumber="163"><code class="lang-c#">MyController&lt;SomeController&gt;
180+
.Instance()
181+
.Calling(c =&gt; c.SomeAction())
182+
.ShouldHave()
183+
.TempData(tempData =&gt; tempData // &lt;---
184+
.ContainingEntry(&quot;SomeKey&quot;, someValue))
185+
.AndAlso()
186+
.ShouldReturn()
187+
.View();
188+
</code></pre><p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="165" sourceendlinenumber="165">Additionally, you can populate the <strong>&quot;TempData&quot;</strong> dictionary before the action call:</p>
189+
<pre sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="167" sourceendlinenumber="175"><code class="lang-c#">MyController&lt;SomeController&gt;
190+
.Instance()
191+
.WithTempData(tempData =&gt; tempData
192+
.WithEntry(&quot;SomeKey&quot;, someValue))
193+
.Calling(c =&gt; c.SomeAction())
194+
.ShouldReturn()
195+
.View();
196+
</code></pre><h2 id="section-summary" sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="177" sourceendlinenumber="177">Section summary</h2>
197+
<p sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="179" sourceendlinenumber="179">We saw how easy it is to test with <strong>&quot;ViewBag&quot;</strong>, <strong>&quot;ViewData&quot;</strong> and <strong>&quot;TempData&quot;</strong>. Their fluent assertion API is very similar to the <strong>&quot;Session&quot;</strong> and <strong>&quot;Cache&quot;</strong> ones. But enough about controllers, let&#39;s move on to <a href="/tutorial/viewcomponents.html" sourcefile="tutorial/viewbagviewdatatempdata.md" sourcestartlinenumber="179" sourceendlinenumber="179">View Components</a>!</p>
198+
68199
</article>
69200
</div>
70201

0 commit comments

Comments
 (0)