Scott Hanselman

NoSQL .NET Core development using an local Azure DocumentDB Emulator

December 4, '16 Comments [20] Posted in ASP.NET MVC | Azure
Sponsored By

I was hanging out with Miguel de Icaza in New York a few weeks ago and he was sharing with me his ongoing love affair with a NoSQL Database called Azure DocumentDB. I've looked at it a few times over the last year or so and though it was cool but I didn't feel like using it for a few reasons:

  • Can't develop locally - I'm often in low-bandwidth or airplane situations
  • No MongoDB support - I have existing apps written in Node that use Mongo
  • No .NET Core support - I'm doing mostly cross-platform .NET Core apps

Miguel told me to take a closer look. Looks like things have changed! DocumentDB now has:

  • Free local DocumentDB Emulator - I asked and this is the SAME code that runs in Azure with just changes like using the local file system for persistence, etc. It's an "emulator" but it's really the essential same core engine code. There is no cost and no sign in for the local DocumentDB emulator.
  • MongoDB protocol support - This is amazing. I literally took an existing Node app, downloaded MongoChef and copied my collection over into Azure using a standard MongoDB connection string, then pointed my app at DocumentDB and it just worked. It's using DocumentDB for storage though, which gives me
    • Better Latency
    • Turnkey global geo-replication (like literally a few clicks)
    • A performance SLA with <10ms read and <15ms write (Service Level Agreement)
    • Metrics and Resource Management like every Azure Service
  • DocumentDB .NET Core Preview SDK that has feature parity with the .NET Framework SDK.

There's also Node, .NET, Python, Java, and C++ SDKs for DocumentDB so it's nice for gaming on Unity, Web Apps, or any .NET App...including Xamarin mobile apps on iOS and Android which is why Miguel is so hype on it.

Azure DocumentDB Local Quick Start

I wanted to see how quickly I could get started. I spoke with the PM for the project on Azure Friday and downloaded and installed the local emulator. The lead on the project said it's Windows for now but they are looking for cross-platform solutions. After it was installed it popped up my web browser with a local web page - I wish more development tools would have such clean Quick Starts. There's also a nice quick start on using DocumentDB with ASP.NET MVC.

NOTE: This is a 0.1.0 release. Definitely Alpha level. For example, the sample included looks like it had the package name changed at some point so it didn't line up. I had to change "Microsoft.Azure.Documents.Client": "0.1.0" to "Microsoft.Azure.DocumentDB.Core": "0.1.0-preview" so a little attention to detail issue there. I believe the intent is for stuff to Just Work. ;)

Nice DocumentDB Quick Start

The sample app is a pretty standard "ToDo" app:

ASP.NET MVC ToDo App using Azure Document DB local emulator

The local Emulator also includes a web-based local Data Explorer:

image

A Todo Item is really just a POCO (Plain Old CLR Object) like this:

namespace todo.Models
{
    using Newtonsoft.Json;

    public class Item
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "description")]
        public string Description { get; set; }

        [JsonProperty(PropertyName = "isComplete")]
        public bool Completed { get; set; }
    }
}

The MVC Controller in the sample uses an underlying repository pattern so the code is super simple at that layer - as an example:

[ActionName("Index")]
public async Task<IActionResult> Index()
{
var items = await DocumentDBRepository<Item>.GetItemsAsync(d => !d.Completed);
return View(items);
}

[HttpPost]
[ActionName("Create")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> CreateAsync([Bind("Id,Name,Description,Completed")] Item item)
{
if (ModelState.IsValid)
{
await DocumentDBRepository<Item>.CreateItemAsync(item);
return RedirectToAction("Index");
}

return View(item);
}

The Repository itself that's abstracting away the complexities is itself not that complex. It's like 120 lines of code, and really more like 60 when you remove whitespace and curly braces. And half of that is just initialization and setup. It's also DocumentDBRepository<T> so it's a generic you can change to meet your tastes and use it however you'd like.

The only thing that stands out to me in this sample is the loop in GetItemsAsync that's hiding potential paging/chunking. It's nice you can pass in a predicate but I'll want to go and put in some paging logic for large collections.

public static async Task<T> GetItemAsync(string id)
{
    try
    {
        Document document = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id));
        return (T)(dynamic)document;
    }
    catch (DocumentClientException e)
    {
        if (e.StatusCode == System.Net.HttpStatusCode.NotFound){
            return null;
        }
        else {
            throw;
        }
    }
}

public static async Task<IEnumerable<T>> GetItemsAsync(Expression<Func<T, bool>> predicate)
{
    IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
        UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
        new FeedOptions { MaxItemCount = -1 })
        .Where(predicate)
        .AsDocumentQuery();

    List<T> results = new List<T>();
    while (query.HasMoreResults){
        results.AddRange(await query.ExecuteNextAsync<T>());
    }

    return results;
}

public static async Task<Document> CreateItemAsync(T item)
{
    return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), item);
}

public static async Task<Document> UpdateItemAsync(string id, T item)
{
    return await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id), item);
}

public static async Task DeleteItemAsync(string id)
{
    await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id));
}

I'm going to keep playing with this but so far I'm pretty happy I can get this far while on an airplane. It's really easy (given I'm preferring NoSQL over SQL lately) to just through objects at it and store them.

In another post I'm going to look at RavenDB, another great NoSQL Document Database that works on .NET Core that s also Open Source.


Sponsor: Big thanks to Octopus Deploy! Do you deploy the same application multiple times for each of your end customers? The team at Octopus have taken the pain out of multi-tenant deployments. Check out their latest 3.4 release

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

How to reference an existing .NET Framework Project in an ASP.NET Core 1.0 Web App

October 8, '16 Comments [29] Posted in ASP.NET | ASP.NET MVC
Sponsored By

I had a reader send me a question yesterday. She basically wanted to use her existing .NET Framework libraries in an ASP.NET Core application, and it wasn't super clear how to do it.

I have a quick question for you regarding asp.net core. We are rewriting our website using asp.net core, empty from the bottom up. We have 2 libraries written in .net 4.6 . One is our database model and repositories and the other is a project of internal utilities we use a lot. Unfortunately we cannot see how to reference these two projects in our .net core project.

It can be a little confusing. As I mentioned earlier this week, some people don't realize that ASP.NET Core 1.0 (that's the web framework bit) runs on either .NET Core or .NET Framework 4.6 aka "Full Framework."

ASP.NET Core 1.0 runs on ASP.NET 4.6 nicely

When you make a new web project in Visual Studio you see (today) this dialog. Note in the dropdown at the top you can select your minimum .NET Framework version. You can select 4.6.2, if you like, but I'll do 4.5.2 to be a little more compatible. It's up to you.

File New Project

This dialog could use clearer text and hopefully it will soon.

  • There's the regular ASP.NET Web Application at the top. That's ASP.NET 4.6 with MVC and Web API. It runs on the .NET Framework.
  • There's ASP.NET Core 1.0 running on .NET Core. That's cross platform. If you select that one you'll be able to run your app anywhere but you can't reference "Full" .NET Framework assemblies as they are just for Windows.  If you want to run anywhere you need to use .NET Standard APIs that will run anywhere.
  • There's ASP.NET Core 1.0 running on .NET Framework. That's the new ASP.NET Core 1.0 with unified MVC and Web API but running on the .NET Framework you run today on Windows.

As we see in the diagram above, ASP.NET Core 1.0 is the new streamlined ASP.NET  that can run on top of both .NET Framework (Windows) and .NET Core (Mac/Windows/Linux).

I'll chose ASP.NET Core on .NET Framework and I'll see this in my Solution Explorer:

Web App targeting .NET Framework 4.5.2

I've got another DLL that I made with the regular File | New Project | Class Library.

New Class Library

Then I reference it the usual way with Add Reference and it looks like this in the References node in Solution Explorer. Note the icon differences.

Adding ClassLibrary1 to the References Node in Solution Explorer

If we look in the project.json (Be aware that this will change for the better in the future when project.json's functionality is merged with csproj and msbuild) you'll note that the ClassLIbrary1 isn't listed under the top level dependencies node, but as a framework specific dependency like this:

{
"dependencies": {
"Microsoft.StuffAndThings": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.1",
},

"frameworks": {
"net452": {
"dependencies": {
"ClassLibrary1": {
"target": "project"
}
}
}
}
}

Notice also that in this case it's a type="project" dependency in this case as I didn't build a NuGet package and reference that.

Since the .NET Core tooling is in preview there are some gotchas when doing this today.

  • Make sure that all your class libraries are targeting an appropriate version of the .NET Framework.
    • For example, don't have a 4.5.2 Web App targeting a 4.6.2 Class Library. This could bite you in subtle ways if things don't line up in production.
  • dotnet restore at the command line may well get confused and give you an error like:
    • Errors in D:\github\WebApplication2\src\WebApplication2\project.json - Unable to resolve 'ClassLibrary1' for '.NETFramework,Version=v4.5.2'.
    • Use Visual Studio to build or run msbuild at the command line.
  • You can restore packages from the command line with nuget.exe version 3.4.4 or greater if you restore on the .sln file like this:
    • D:\github\WebApplication2>nuget restore WebApplication2.sln
      MSBuild auto-detection: using msbuild version '14.0' from 'C:\Program Files (x86)\MSBuild\14.0\bin'.
    • I recommend you run nuget.exe to see what version you have and run nuget update -self to have it update itself.

These gotchas will be fixed when the tooling is finalized.

Hope this helps!


Sponsor: Big thanks to Telerik for sponsoring the blog this week! 60+ ASP.NET Core controls for every need. The most complete UI toolset for x-platform responsive web and cloud development. Try now 30 days for free!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Sharing Authorization Cookies between ASP.NET 4.x and ASP.NET Core 1.0

October 2, '16 Comments [20] Posted in ASP.NET | ASP.NET MVC
Sponsored By

ASP.NET Core 1.0 runs on ASP.NET 4.6 nicelyASP.NET Core 1.0 is out, as is .NET Core 1.0 and lots of folks are making great cross-platform web apps. These are Web Apps that are built on .NET Core 1.0 and run on Windows, Mac, or Linux.

However, some people don't realize that ASP.NET Core 1.0 (that's the web framework bit) runs on either .NET Core or .NET Framework 4.6 aka "Full Framework."

Once you realize that it can be somewhat liberating. If you want to check out the new ASP.NET Core 1.0 and use the unified controllers to make web apis or MVC apps with Razor you can...even if you don't need or care about cross-platform support. Maybe your libraries use COM objects or Windows-specific stuff. ASP.NET Core 1.0 works on .NET Framework 4.6 just fine.

Another option that folks don't consider when talk of "porting" their apps comes up at work is - why not have two apps? There's no reason to start a big porting exercise if your app works great now. Consider that you can have a section of your site by on ASP.NET Core 1.0 and another be on ASP.NET 4.x and the two apps could share authentication cookies. The user would never know the difference.

Barry Dorrans from our team looked into this, and here's what he found. He's interested in your feedback, so be sure to file issues on his GitHub Repo with your thoughts, bugs, and comments. This is a work in progress and at some point will be updated into the official documentation.

Sharing Authorization Cookies between ASP.NET 4.x and .NET Core

Barry is building a GitHub repro here with two sample apps and a markdown file to illustrate clearly how to accomplish cookie sharing.

When you want to share logins with an existing ASP.NET 4.x app and an ASP.NET Core 1.0 app, you'll be creating a login cookie that can be read by both applications. It's certainly possible for you, Dear Reader, to "hack something together" with sessions and your own custom cookies, but please let this blog post and Barry's project be a warning. Don't roll your own crypto. You don't want to accidentally open up one or both if your apps to hacking because you tried to extend auth/auth in a naïve way.

First, you'll need to make sure each application has the right NuGet packages to interop with the security tokens you'll be using in your cookies.

Install the interop packages into your applications.

  1. ASP.NET 4.5

    Open the nuget package manager, or the nuget console and add a reference to Microsoft.Owin.Security.Interop.

  2. ASP.NET Core

    Open the nuget package manager, or the nuget console and add a reference to Microsoft.AspNetCore.DataProtection.Extensions.

Make sure the Cookie Names are identical in each application

Barry is using CookieName = ".AspNet.SharedCookie" in the example, but you just need to make sure they match.

services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Cookies = new Microsoft.AspNetCore.Identity.IdentityCookieOptions
{
ApplicationCookie = new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookie",
LoginPath = new PathString("/Account/Login/"),
AccessDeniedPath = new PathString("/Account/Forbidden/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
CookieName = ".AspNet.SharedCookie"

};
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
}

Remember the CookieName property must have the same value in each application, and the AuthenticationType (ASP.NET 4.5) and AuthenticationScheme (ASP.NET Core) properties must have the same value in each application.

Be aware of your cookie domains if you use them

Browsers naturally share cookies between the same domain name. For example if both your sites run in subdirectories under https://contoso.com then cookies will automatically be shared.

However if your sites run on subdomains a cookie issued to a subdomain will not automatically be sent by the browser to a different subdomain, for example, https://site1.contoso.com would not share cookies with https://site2.contoso.com.

If your sites run on subdomains you can configure the issued cookies to be shared by setting the CookieDomain property in CookieAuthenticationOptions to be the parent domain.

Try to do everything over HTTPS and be aware that if a Cookie has its Secure flag set it won't flow to an insecure HTTP URL.

Select a common data protection repository location accessible by both applications

From Barry's instructions, his sample will use a shared DP folder, but you have options:

This sample will use a shared directory (C:\keyring). If your applications aren't on the same server, or can't access the same NTFS share you can use other keyring repositories.

.NET Core 1.0 includes key ring repositories for shared directories and the registry.

.NET Core 1.1 will add support for Redis, Azure Blob Storage and Azure Key Vault.

You can develop your own key ring repository by implementing the IXmlRepository interface.

Configure your applications to use the same cookie format

You'll configure each app - ASP.NET 4.5 and ASP.NET Core - to use the AspNetTicketDataFormat for their cookies.

Cookie Sharing with ASP.NET Core and ASP.NET Full Framework

According to his repo, this gets us started with Cookie Sharing for Identity, but there still needs to be clearer guidance on how share the Identity 3.0 database between the two frameworks.

The interop shim does not enabling the sharing of identity databases between applications. ASP.NET 4.5 uses Identity 1.0 or 2.0, ASP.NET Core uses Identity 3.0. If you want to share databases you must update the ASP.NET Identity 2.0 applications to use the ASP.NET Identity 3.0 schemas. If you are upgrading from Identity 1.0 you should migrate to Identity 2.0 first, rather than try to go directly to 3.0.

Sound off in the Issues over on GitHub if you would like to see this sample (or another) expanded to show more Identity DB sharing. It looks to be very promising work.


Sponsor: Big thanks to Telerik for sponsoring the blog this week! 60+ ASP.NET Core controls for every need. The most complete UI toolset for x-platform responsive web and cloud development.Try now 30 days for free!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Exploring dotnet new with .NET Core

July 18, '16 Comments [34] Posted in ASP.NET | ASP.NET MVC | DotNetCore | Open Source
Sponsored By

I'm very enjoying the "dotnet" command line. Mostly I do "dotnet new" and then add to the default Hello World app with the Visual Studio Code editor. Recently, though, I realized that the -t "type" and -l "lang" options are there and I wasn't using them. I think they are a little awkward, in that you have to:

dotnet new -t Web

when I think it should be more like dotnet new [type] as in

dotnet new web

What do you think? I find the -t a little heavy. I like the idea of "web" being empty, and "web/mvc" or "web/webapi" having more fleshed out stuff. Even "web/angular," you get the idea. Sound off in the comments. Regardless, there's cool templating tooling coming, I hear, but for now there's more there than I realized.

Of course, there's the default "dotnet new" which is a Hello World console app with a program.cs and project.json. In the future I think it will just run the app, and you'll have to do something like -v verbosity to get the details that we don't usually need to see.

C:\Users\scott\Desktop\test\console>dotnet run
Project console (.NETCoreApp,Version=v1.0) will be compiled because expected outputs are missing
Compiling console for .NETCoreApp,Version=v1.0

Compilation succeeded.
0 Warning(s)
0 Error(s)

Time elapsed 00:00:01.1591124

Hello World!

You can add -l (lang) to it and "dotnet new -l F#" and get an F# Console app rather than a C# one:

C:\Users\scott\Desktop\test\fsharp>dotnet run fabu!
Project fsharp (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
Hello World!
[|"fabu!"|]

C:\Users\scott\Desktop\test\fsharp>type Program.fs
// Learn more about F# at http://fsharp.org

open System

[<EntryPoint>]
let main argv =
printfn "Hello World!"
printfn "%A" argv
0 // return an integer exit code

There's also "dotnet new -t lib" which is super basic and gives you a quick new project with a Class1 and an Empty Method. Not so useful, but good to know.

You can also "dotnet new -t xunittest" to make a new test project. Nice that this is built-in! Now I just "dotnet test" after a "dotnet restore" and I get test results!

xUnit.net .NET CLI test runner (64-bit win10-x64)
Discovering: testing
Discovered: testing
Starting: testing
Finished: testing
=== TEST EXECUTION SUMMARY ===
testing Total: 1, Errors: 0, Failed: 0, Skipped: 0, Time: 0.146s
SUMMARY: Total: 1 targets, Passed: 1, Failed: 0.

Side Note: If the folder name of the project is the same as one of the dependencies, it can confuse the resolver. For example, I did my new test project in a folder creatively named "XUnit." This is also the name of a dependency. I got the error: Errors in C:\Users\scott\Desktop\test\xunit\project.json Cycle detected:  xunit (>= 1.0.0) -> xunit (>= 2.1.0) -> xunit (>= 2.1.0). Note that 1.0.0 there. That's my project, which is 1.0.0. Solution? Rename my project's containing folder.

There's ASP.NET Core Hello World, which is "dotnet new -t Web." This will give you a nice simple ASP.NET Core app with some simple defaults that's setup for bower, gulp, and npm usage. I anticipate we'll see varying levels of what folks consider "complete."

yo aspnet: dotnet new -t web isn't the only way to make a new ASP.NET Core project from the command line (CLI). You can also use the Yeoman generator or "yo aspnet" to make very interesting projects, as well as create your own generators. In fact, Steve Sanderson has some impressive generators like his "aspnet-spa" generator for making Angular, React, and Knockout Single Page Apps (SPA) with ASP.NET Core.

image

All these generators work on Windows, Mac, and Linux, of course. I believe the intent is to reconcile them all such that Visual Studio proper and Visual Studio Code via the CLI will all get the same "File | New Project" results. Visual Studio will still be more "visual" but everything you can do in one world can and should be possible in another.


Sponsor: Big thanks to Redgate for sponsoring the feed this week. Have you got SQL fingers? Try SQL Prompt and you’ll be able to write, refactor, and reformat SQL effortlessly in SSMS and Visual Studio. Find out more!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Adding a Custom Inline Route Constraint in ASP.NET Core 1.0

June 23, '16 Comments [14] Posted in ASP.NET | ASP.NET MVC
Sponsored By

ASP.NET supports both attribute routing as well as centralized routes. That means that you can decorate your Controller Methods with your routes if you like, or you can map routes all in one place.

Here's an attribute route as an example:

[Route("home/about")]
public IActionResult About()
{
//..
}

And here's one that is centralized. This might be in Startup.cs or wherever you collect your routes. Yes, there are better examples, but you get the idea. You can read about the fundamentals of ASP.NET Core Routing in the docs.

routes.MapRoute("about", "home/about",
new { controller = "Home", action = "About" });

A really nice feature of routing in ASP.NET Core is inline route constraints. Useful URLs contain more than just paths, they have identifiers, parameters, etc. As with all user input you want to limit or constrain those inputs. You want to catch any bad input as early on as possible. Ideally the route won't even "fire" if the URL doesn't match.

For example, you can create a route like

files/{filename}.{ext?}

This route matches a filename or an optional extension.

Perhaps you want a dateTime in the URL, you can make a route like:

person/{dob:datetime}

Or perhaps a Regular Expression for a Social Security Number like this (although it's stupid to put a SSN in the URL ;) ):

user/{ssn:regex(d{3}-d{2}-d{4})}

There is a whole table of constraint names you can use to very easily limit your routes. Constraints are more than just types like dateTime or int, you can also do min(value) or range(min, max).

However, the real power and convenience happens with Custom Inline Route Constraints. You can define your own, name them, and reuse them.

Lets say my application has some custom identifier scheme with IDs like:

/product/abc123

/product/xyz456

Here we see three alphanumerics and three numbers. We could create a route like this using a regular expression, of course, or we could create a new class called CustomIdRouteConstraint that encapsulates this logic. Maybe the logic needs to be more complex than a RegEx. Your class can do whatever it needs to.

Because ASP.NET Core is open source, you can read the code for all the included ASP.NET Core Route Constraints on GitHub. Marius Schultz has a great blog post on inline route constraints as well.

Here's how you'd make a quick and easy {customid} constraint and register it. I'm doing the easiest thing by deriving from RegexRouteConstraint, but again, I could choose another base class if I wanted, or do the matching manually.

namespace WebApplicationBasic
{
public class CustomIdRouteConstraint : RegexRouteConstraint
{
public CustomIdRouteConstraint() : base(@"([A-Za-z]{3})([0-9]{3})$")
{
}
}
}

In your ConfigureServices in your Startup.cs you just configure the route options and map a string like "customid" with your new type like CustomIdRouteConstraint.

public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.Configure<RouteOptions>(options =>
options.ConstraintMap.Add("customid", typeof(CustomIdRouteConstraint)));
}

Once that's done, my app knows about "customid" so I can use it in my Controllers in an inline route like this:

[Route("home/about/{id:customid}")]
public IActionResult About(string customid)
{
// ...
return View();
}

If I request /Home/About/abc123 it matches and I get a page. If I tried /Home/About/999asd I would get a 404! This is ideal because it compartmentalizes the validation. The controller doesn't need to sweat it. If you create an effective route with an effective constraint you can rest assured that the Controller Action method will never get called unless the route matches.

If the route doesn't fire it's a 404

Unit Testing Custom Inline Route Constraints

You can unit test your custom inline route constraints as well. Again, take a look at the source code for how ASP.NET Core tests its own constraints. There is a class called ConstrainsTestHelper that you can borrow/steal.

I make a separate project and setup xUnit and the xUnit runner so I can call "dotnet test."

Here's my tests that include all my "Theory" attributes as I test multiple things using xUnit with a single test. Note we're using Moq to mock the HttpContext.

public class TestProgram
{

[Theory]
[InlineData("abc123", true)]
[InlineData("xyz456", true)]
[InlineData("abcdef", false)]
[InlineData("totallywontwork", false)]
[InlineData("123456", false)]
[InlineData("abc1234", false)]
public void TestMyCustomIDRoute(
string parameterValue,
bool expected)
{
// Arrange
var constraint = new CustomIdRouteConstraint();

// Act
var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);

// Assert
Assert.Equal(expected, actual);
}
}

public class ConstraintsTestHelper
{
public static bool TestConstraint(IRouteConstraint constraint, object value,
Action<IRouter> routeConfig = null)
{
var context = new Mock<HttpContext>();

var route = new RouteCollection();

if (routeConfig != null)
{
routeConfig(route);
}

var parameterName = "fake";
var values = new RouteValueDictionary() { { parameterName, value } };
var routeDirection = RouteDirection.IncomingRequest;
return constraint.Match(context.Object, route, parameterName, values, routeDirection);
}
}

Now note the output as I run "dotnet test". One test with six results. Now I'm successfully testing my custom inline route constraint, as a unit. in isolation.

xUnit.net .NET CLI test runner (64-bit .NET Core win10-x64)
Discovering: CustomIdRouteConstraint.Test
Discovered: CustomIdRouteConstraint.Test
Starting: CustomIdRouteConstraint.Test
Finished: CustomIdRouteConstraint.Test
=== TEST EXECUTION SUMMARY ===
CustomIdRouteConstraint.Test Total: 6, Errors: 0, Failed: 0, Skipped: 0, Time: 0.328s

Lots of fun!


Sponsor: Working with DOC, XLS, PDF or other business files in your applications? Aspose.Total Product Family contains robust APIs that give you everything you need to create, manipulate and convert business files along with many other formats in your applications. Stop struggling with multiple vendors and get everything you need in one place with Aspose.Total Product Family. Start a free trial today.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Page 1 of 36 in the ASP.NET MVC category Next Page

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.