Uno - Step 4
Dependencies
If you remember all the way back to step 1, we only pulled in the ASP.NET Core packages we needed to do our "Hello World" app. At this point, though, we'll go ahead and bring in the entire Microsoft.AspNetCore.App
meta-package, which will install a whole lot more, including the Razor templating engine.
In paket.dependencies
, in the root of the solution folder, replace the following...
1: 2: 3: 4: 5: |
|
...with just one line:
1:
|
|
In addition, add the following packages:
1: 2: |
|
Make these same changes in paket.references
for Uno as well.
We'll also need to make one small change to Uno.csproj
*. On the second line, add .Web
to the Sdk
attribute on the project; the entire Sdk
should read Microsoft.NET.Sdk.Web
.
Finally, run paket install
. to update the dependencies.
The Startup.cs
File
To begin, we'll actually simply the file; the code that we put in the constructor is actually the way ASP.NET Core 2.2 works out-of-the-box. Instead, we can change our private property, add one for the hosting environment, and have them both injected into the constructor. We'll go from...
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
...to...
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
Next, we'll turn our attention to ConfigureServices
. Here, we'll configure our session provider, as well as bring in the MVC setup. We're also going to adapt our RavenDB connection. If we configure a server that listens on anything other than 127.0.0.1 (localhost), RavenDB insists on using a client certificate to ensure a secure connection between client and server. Our current configuration does not support it; when we're done, we'll support loading a .pfx
file from a configured path.
Here is what ConfigureServices
will look like:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: |
|
We've added the certificate check to the store, and instead of registering the result of .Initialize()
, we're making that the value of store
. This lets us use the initialized store in index creation, as well as in setting up the distributed cache. The chain on services
is also new. Most calls to .Add*
methods return the modified service collection, so we can chain several calls together instead of writing services.Add...
over and over. The exception to this is our last call, which is why it's last (and indented another layer, though that's just an aesthetic thing).
Finally, let's turn our attention to the Configure
method. Since we made a private property for the IHostingEnvironment
parameter, we can remove it from the parameter list. And, just like we ended up with a chain of calls for services, we're going to have the same thing on our application builder. In fact, it ends up being just one chain, so we can still use the expression-bodied member syntax!
1: 2: 3: 4: 5: 6: 7: 8: |
|
The first expression uses the developer exception page if we're running in development; this gives us as developers a lot of information, stack traces, and such when an error occurs. In production mode, we don't want that, though, so we'll use the exception handler /Home/Error
. (We'll write that below.) .UseHttpsRedirection
means that we'll be redirected from HTTP to HTTPS (and, in our default configuration, from port 5000 to 5001) automatically. .UseStaticFiles
lets us serve static files out of our wwwroot
directory. .UseSession
is paired with .AddSession
from above to actually implement it into the pipeline; by putting it after the static file middleware, static files will not require a session (which is what we want). Finally, .UseMvcWithDefaultRoute
sets up MVC to use a route template of /[controller]/[action]/[id?]
, and also scans for attribute routes (more on that below).
Controllers, etc.
If you look at the last code sample above, you can see two controller actions on the Home
controller; /Error
for the exception handler, and /Index
for the default action. By convention, ASP.MVC Core expects controllers to be named *Controller
(where, in this case, *
is Home
), and to inherit from the Controller
base class (from Microsoft.AspNetCore.Mvc
). By tradition, these are in a Controllers
directory and namespace as well; there are reasons to not do this, but for our purposes here, we'll stick with the familiar layout.
Create a Controllers
directory, then create a HomeController.cs
file.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: |
|
It's not much, but it does require views for these two actions. We're not writing views for a few steps, but this will give us a chance to set up the framework for them. Create a Views
folder in the root of the application, then navigate there using a command prompt or shell. Execute the following two commands in that directory:
1: 2: |
|
This creates two files that ASP.NET Core MVC will use to generate the views. Open _ViewImports.cshtml
and change the namespace to Uno
. We'll need to make one more change to the Views
folder; create a Shared
folder within it, and create a file in that folder called _Layout.cshtml
. (This is the file to which _ViewStart.cshtml
refers.) We will create a skeleton in that file as well.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
Finally, while not covered here, we'll create very simple views for these two actions. You can review those if you want to see what they look like (Index
is in /Home
, Error
is in /Shared
).
Testing
One thing we can test at this point is our sessions; are they working correctly? If we change HomeController.Index
to look like this...
1: 2: 3: 4: 5: 6: 7: 8: |
|
...we can view the page, refresh it, and watch the counter increase. We can also look in RavenDB and see the CacheEntries
collection document being changed. The data in it is base-64 encoded, so we can't read it, but we can see the update and expiration changing. Using an incognito or private browser tab will be best, as the session cookie will go away once you close it.
Data Seeding
One final task we have to do is seed our test data. We'll do this procedurally in a method on HomeController
, which we'll delete the next time we open it in the next step. However, the definition of this action gives us a chance to talk about attribute routing.
1: 2: |
|
That HttpGet
attribute overrides the default route. So, while the URL for this action would have been /Home/Seed
, it's now simply /seed
. We can also use the Route
attribute at the controller level (to set a base for all the routes in that controller) or on an action, if we want it to support all HTTP verbs. Most of our routes will be defined this way, as it gives us a chance to make the URLs exactly what we want.
Conclusion
This is probably one of the most plumbing-like steps in the process, but we've really done a lot in this step. And, if we had just started with the full ASP.NET Core framework in the beginning, nearly all the files we've created would have been part of the output from dotnet new
. However, we now know what each of the parts do, so if we need to change them, we're more likely to know what needs to be changed.
* Thanks to Christopher Pritchard in the F# community Slack for helping to identify this; without .Web
, the MVC routes were not picked up, and every route returned a 404.