Tuesday, 23 August 2016

Experiments in .NET Core

Ever since the release of .NET Core 1.0 I've been itching to get my hands dirty, and over the last few weeks I've finally managed to get around to having a play with it, so I thought I'd share some of my experiences and some of the bits that I've found aren't quite ready yet.

The solution I built was a REST Web API layer using Entity Framework, consumed by a MVC web application using NPM and Bower frontend packages (the usual JQuery, Bootstrap, etc.) and secured through a login using .NET Core Identity.  It's still a work in progress, and not as robust as it could (and possibly will) be, but gave me a chance to test out a lot of functionality and see where .NET Core still wasn't fully fleshed out.  Since I already use Visual Studio on Windows for work I didn't bother trying to use Visual Studio Code on OSX or Linux, though it seems very straightforward to do so.

I'm not going to go into detail about the differences that .NET core brings, as there are a ton of blog posts out there that cover it (not to mention the actual documentation).  Rather, I'll go through the steps in (roughly) the order I followed when creating the solution.

Initial Setup


When you're first starting out, the new .NET Core documentation is actually great; it's well laid-out and written in a way that's very easy for developers to understand, usually with each page building upon the last and grouped into logical sections.

When using Visual Studio on Windows, you have to make sure you're using 2015 update 3, and the .NET Core tooling, both of which can be found on the homepage of the .NET Core site.  After that, you can simply create a new project by selecting one of the .NET Core templates which appear in the list under Templates > Visual C# > .NET Core.

You have the usual options of Class Library (also used for test projects), Console Application, and ASP.NET Core Web Application. If you select the Web Application template you have the options of Empty, Web API, and Web Application, along with authentication options of No Authentication, Individual User Accounts (.NET Identity), Work and School Accounts (Active Directory), and Windows Authentication (Intranet).

In my case I had a class library (for domain models), and two Web Application templates: one for the Web API, and one for the Web Application (using Individual User Accounts for authentication).

The first new things you notice are: the project.json file (to handle the dependencies, framework versions, publish options, pre/post build commands, etc.), and in the case of web applications: Program.cs (which actually runs your app) Startup.cs (handles the bootstrapping / DI / etc.) and that app settings have moved to appsettings.json.  You'll also find that there are new NuGet packages for your usual frameworks (like EF or xUnit), and that the packages are a lot more modular (and therefore smaller with more dependencies).

Class Library - Models


There's not much to say about this project, other than make sure that the framework version(s) used in your project.json file is compatible with those in the other projects, otherwise it will let you reference the project, but it will not recognise the namespaces/classes you are trying to use!

I marked up my models with the System.ComponentModel.DataAnnotations like [Required] and [DataType], and one thing to keep in mind is to use [EmailAddress] and not just [DataType(DataType.EmailAddress)] if you want your inputs to come out as type="email" and validate correctly.

Web App - Web API


Creating this project from the template gives you pretty much everything you need, however for Entity Framework I also added the NuGet packages Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.SqlServer.  For some reason the scaffolds for controllers didn't work for me in my Web API project (they did in  my MVC project), but if you create your project from the web app template you will find some controllers you can use / modify.

The new Web API controllers are quite similar to their predecessors, with the annotations having a slightly different syntax.  Controllers are decorated with [Route("Items")] and methods with the HTTP method they are associated with, eg. [HttpGet("{id}")] in a very similar way to attribute routing formerly worked; this method would be accessed at /Items/1234.  The only other real difference I found is returning an IActionResult instead of a IHttpActionResult.

I also wanted to use the VersionedRoute attribute to use headers to version my API, however haven't yet had a chance to fully implement the new approach I found through a quick search.

The new EF documentation tells you pretty much everything you need to know (specifically the ASP.NET section) including how to use the provided dependency injection that comes with .NET Core (in the Startup.cs file), and the slightly different syntax for defining your DB context.  Unfortunately Entity Framework had one of the first limitations I came across, which was that you can't (yet) return ad-hoc objects from raw queries, you have to return an entity type.  This meant that I couldn't optimise a couple of my queries as easily as I would have liked.

Web App - MVC Web App

The main MVC web application is where things really start to get fun.  Frontend things like css, images, and scripts are all grouped under wwwroot (which the server treats as /, so you don't have to reference /wwwroot in your src or href attribute) and if you use npm (package.json) or bower (bower.json) which VS natively supports, your dependencies are listed under the Dependencies node, similar to the existing References node.  The actual framework files can be found and/or included (eg. for scss) under /wwwroot/lib/.
You also get a bundleconfig.json file included in your project for bundling and minification, and VS supports both this and compilerconfig.json (depending on your extension of choice), both of which appear as tasks in the Task Runner Explorer.  Of course you could always stick with grunt or gulp, which VS has supported in the Task Runner Explorer for some time now, if that's your preference (right clicking on bundleconfig.json -> Bundler & Minifier with the extension installed actually lets you convert it to a gulp task(s) if you prefer).

If you select the relevant authentication when you create your project from the template, it will include all the bootstrapping necessary (for using Identity in my case) in the Startup.cs file, as well as providing you with some controller code you can use for logging in as well as securing your controllers and their methods.

To use session state and authentication using cookies you'll have to include:
    services.AddDistributedMemoryCache();
    services.AddSession();
in the ConfigureServices method and
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        LoginPath = new PathString("/Home/Login"),
        AccessDeniedPath = new PathString("/Home/Forbidden"),
        AutomaticAuthenticate = true,
        AutomaticChallenge = true
    });
    app.UseSession();
in the Configure method (of your Startup.cs).
Session is now located at HttpContext.Session and has getter and setter methods that you have to use.

Where before in your views you could just use Request["yourVariable"], however this has changed to Context.Request.Query["yourVariable"].

I also wanted to use areas for my site, since it was split into logical sections, so this means including an additional line of routing:
    routes.MapRoute(name: "areaRoute", template: "{area:exists}/{controller=Home}/{action=Index}");
to Startup.cs above the existing route, as well as an [Area("MyArea")] attribute to each Controller.
There's also an issue with publishing when you use areas, which (at the time of writing) requires a small amendment to the project.json file.

The whole concept of Html helper methods appears to be deprecated, or at least usurped by the concept of tag helpers, which I love; it maintains the syntax of the markup (so you're not mixing <tags>@{and code}</tags> as much, and adds to the HTML in a simple way that's easy to remember.  Plus it automatically adds the anti-forgery token to your form.  Validation hasn't changed much (I touched on annotating models earlier) and the tags support the newer HTML elements like input types of email and date which is absolutely necessary at this point, and can be extended easily enough if you want to support things like range sliders.  The one bit that I didn't quite get over was the fact that if your form field was bound to a property of a property of your model, it does not appear to be recognised by VS and appears red (even though the code works).  This may not be the correct way to to use the tag, but it certainly worked for me.

Running / Debugging

The biggest difference I found that .NET Core brings is in actually running the code.  To quote the documentation:
ASP.NET Core is completely decoupled from the web server environment that hosts the application. ASP.NET Core supports hosting in IIS and IIS Express, and self-hosting scenarios using the Kestrel and WebListener HTTP servers. Additionally, developers and third party software vendors can create custom servers to host their ASP.NET Core apps.
What this means is that your site no longer runs in IIS, but rather it runs in Kestrel (by default, otherwise your choice of server) and IIS acts as a reverse-proxy.  You still create a website node in IIS, and point it to the location at which your code is published, however for your app pool settings you should set the .NET CLR version to "No Managed Code".  See the publishing to IIS documentation for the full configuration, as there are some additional things you have to install.

This also means that when debugging you should not attach to w3wp.exe, but to dotnet.exe.  Of course, you can always hit the debugging button in VS to open your application in debug mode in IIS Express (if you haven't changed your project settings).

I didn't have much luck publishing with Web Deploy, even though it is supposed to be possible.  For some reason I get the error:
Configuring project completed successfully
publish: Published to C:\Users\myuser\AppData\Local\Temp\PublishTemp\Backoffice64
Published 1/1 projects successfully
Publishing with publish method [MSDeploy]
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\DotNet\Microsoft.DotNet.Publishing.targets(408,5): Error : An error occurred during publish.
Exception calling ".ctor" with "1" argument(s): "Invalid URI: The format of the URI could not be determined."
I had no luck searching for any of this message, so I switched to using the file system method of publishing which worked.

I also found that some of the time my breakpoints would not be hit in my controllers or views, despite my latest code being deployed.  I have no idea what was going on there, so I assume this is an issue that will hopefully be resolved in the coming updates.

Conclusion

I have to say all-in-all I was impressed with .NET Core.  Though there were a few bumps (and I'm still pretty damn confused by the names, versions and frameworks) I did manage to get everything building and running, and I love some of the newer tidbits like tag helpers and updated dependency management.

There are a few things I haven't got around to yet: 
  • I haven't done any performance testing, though I have heard that it performs very well; 
  • I haven't tried out xUnit, though it sounds very easy to get up and running; 
  • I haven't moved my authentication to my Web API layer yet, though that seems like it would be very similar to how it works currently;
  • I haven't tested out Visual Studio Code or Kestrel on Linux, though I'm very keen to try it out
At this point I don't think I'd recommend it for a major project or team environment (especially since there are packages that haven't been ported yet) though hopefully in the near future it will be ready for the task. For a small project like mine it certainly did the job very well, and will enable cost saving through being able to run the code on a Linux server rather than having to pay for a Windows licence.

I look forward to things like Octopus Deploy (mentioned in a community email), Umbraco (who are slowly working towards it), and  Sitecore (I'm sure eventually) moving to .NET Core, and in the meantime you can check whether your current solution can be upgraded by putting your project file into I Can Has .NET Core.

No comments:

Post a Comment