GithubHelp home page GithubHelp logo

ivaylokenov / aspnet.mvc.typedrouting Goto Github PK

View Code? Open in Web Editor NEW
485.0 32.0 36.0 1.38 MB

A collection of extension methods providing strongly typed routing and link generation for ASP.NET Core MVC projects.

Home Page: https://mytestedasp.net/

License: MIT License

C# 100.00%
asp-net-core asp-net-core-mvc fluent-extensions strongly-typed routing

aspnet.mvc.typedrouting's Introduction

AspNet.Mvc.TypedRouting  AspNet.Mvc.TypedRouting - Typed routing
  and link generation for ASP.NET Core MVC

====================================

Resolving controller and action names for various purposes in ASP.NET MVC was always unreliable because the framework uses magic strings in its methods (for example Url.Action("Action", "Controller")). With the C# 6.0 nameof operator, the problem was partially solved. However, nameof cannot be used with various MVC Core features like ActionNameAttribute, AreaAttribute, RouteValueAttribute, IControllerModelConvention, IActionModelConvention, IParameterModelConvention and more. Here comes AspNet.Mvc.TypedRouting to the rescue!

This package gives you typed expression based routing and link generation in a ASP.NET Core MVC web application. Currently working with version 1.1.0.

For example:

// adding route to specific action
routes.Add("MyRoute/{id}", route => route.ToAction<HomeController>(a => a.Index()))

// generating action link
Html.ActionLink<HomeController>("Index", c => c.Index())

Build status license NuGet Badge

Installation

You can install this library using NuGet into your web project. There is no need to add any namespace usings since the package uses the default ones to add extension methods.

Install-Package AspNet.Mvc.TypedRouting

For other interesting packages check out:

How to use

Just add AddTypedRouting() after AddMvc into your Startup class:

public void ConfigureServices(IServiceCollection services)
{
	services.AddMvc().AddTypedRouting();
}

You can check the provided sample to see a working web application with this library.

To register a typed route into your application, add the following line:

public void ConfigureServices(IServiceCollection services)
{
	services.AddMvc().AddTypedRouting(routes =>
	{
		routes.Get("MyRoute/{id}", route => route.ToAction<HomeController>(a => a.Index(With.Any<int>())));
	});
}

This will register route http://mysite.com/MyRoute/{id} to match 'HomeController', 'Index' action with any integer as 'id'. Full list of available methods:

// adding route to specific controller and action name taken from name of method
routes.Add("MyRoute/{action}", route => route.ToController<HomeController>());

// adding route to specific action without parameters
routes.Add("MyRoute/MyAction", route => route.ToAction<HomeController>(a => a.Index()));

// adding route to specific action with any parameters 
// * With.Any<TParameter>() is just expressive sugar, you can pass any value
routes.Add("MyRoute/MyAction/{id}", route => route.ToAction<HomeController>(a => a.Index(With.Any<int>())));

// adding route with specific name
routes.Add("MyRoute/MyAction", route => route
	.ToAction<HomeController>(a => a.Index())
	.WithName("RouteName"));

// adding route with custom action constraint
routes.Add("MyRoute/MyAction", route => route
	.ToAction<HomeController>(a => a.Index())
	.WithActionConstraint(new MyCustomConstraint()));
	
// adding route to specific HTTP methods
routes.Add("MyRoute/MyAction", route => route
	.ToAction<HomeController>(a => a.Index())
	.ForHttpMethods("GET", "POST"));

// you can also specify methods without magic strings
routes.Get("MyRoute/MyAction", route => route.ToAction<HomeController>(a => a.Index()));
routes.Post("MyRoute/MyAction", route => route.ToAction<HomeController>(a => a.Index()));
routes.Put("MyRoute/MyAction", route => route.ToAction<HomeController>(a => a.Index()));
routes.Delete("MyRoute/MyAction", route => route.ToAction<HomeController>(a => a.Index()));

Additionally, you can use typed link generation:

// generating link without parameters - /Home/Index
urlHelper.Action<HomeController>(c => c.Index());

// generating link with parameters - /Home/Index/1
urlHelper.Action<HomeController>(c => c.Index(1));

// generating link with additional route values - /Home/Index/1?key=value
urlHelper.Action<HomeController>(c => c.Index(1), new { key = "value" });

// generating link where action needs parameters to be compiled, but you do not want to pass them - /Home/Index
// * With.No<TParameter>() is just expressive sugar, you can pass 'null' for reference types but it looks ugly
urlHelper.Action<HomeController>(c => c.Index(With.No<int>()));

All methods resolve all kinds of route changing features like ActionNameAttribute, AreaAttribute, RouteConstraintAttribute, IControllerModelConvention, IActionModelConvention, IParameterModelConvention and potentially others. The expressions use the internally created by the MVC framework ControllerActionDescriptor objects, which contain all route specific information.

Controller extension methods:

// uses the same controller in the expression and created object
controller.CreatedAtAction(c => c.Index(), someObject);

// uses the same controller in the expression, additional route values and created object
controller.CreatedAtAction(c => c.Index(), new { key = "value" }, someObject);

// uses another controller in the expression and created object
controller.CreatedAtAction<HomeController>(c => c.Index(), someObject);

// uses another controller in the expression, additional route values and created object
controller.CreatedAtAction<HomeController>(c => c.Index(), new { key = "value" }, someObject);

// uses route name, the same controller in the expression and created object
controller.CreatedAtRoute("RouteName", c => c.Index(), someObject);

// uses route name, the same controller in the expression, additional route values and created object
controller.CreatedAtRoute("RouteName", c => c.Index(), new { key = "value" }, someObject);

// uses route name, another controller in the expression and created object
controller.CreatedAtRoute<HomeController>("RouteName", c => c.Index(), someObject);

// uses route name, another controller in the expression, additional route values and created object
controller.CreatedAtRoute<HomeController>("RouteName", c => c.Index(), new { key = "value" }, someObject);

// uses the same controller in the expression to return redirect result
controller.RedirectToAction(c => c.Index());

// uses the same controller in the expression and additional route values to return redirect result
controller.RedirectToAction(c => c.Index(), new { key = "value" });

// uses another controller in the expression to return redirect result
controller.RedirectToAction<HomeController>(c => c.Index());

// uses another controller in the expression and additional route values to return redirect result
controller.RedirectToAction<HomeController>(c => c.Index(), new { key = "value" });

// uses the same controller in the expression to return permanent redirect result
controller.RedirectToActionPermanent(c => c.Index());

// uses the same controller in the expression and additional route values to return permanent redirect result
controller.RedirectToActionPermanent(c => c.Index(), new { key = "value" });

// uses another controller in the expression to return permanent redirect result
controller.RedirectToActionPermanent<HomeController>(c => c.Index());

// uses another controller in the expression and additional route values to return permanent redirect result
controller.RedirectToActionPermanent<HomeController>(c => c.Index(), new { key = "value" });

// uses route name, the same controller in the expression to return redirect result
controller.RedirectToRoute("RouteName", c => c.Index());

// uses route name, the same controller in the expression and additional route values to return redirect result
controller.RedirectToRoute("RouteName", c => c.Index(), new { key = "value" });

// uses route name, another controller in the expression to return redirect result
controller.RedirectToRoute<HomeController>("RouteName", c => c.Index());

// uses route name, another controller in the expression and additional route values to return redirect result
controller.RedirectToRoute<HomeController>("RouteName", c => c.Index(), new { key = "value" });

// uses route name, the same controller in the expression to return permanent redirect result
controller.RedirectToRoutePermanent("RouteName", c => c.Index());

// uses route name, the same controller in the expression and additional route values to return permanent redirect result
controller.RedirectToRoutePermanent("RouteName", c => c.Index(), new { key = "value" });

// uses route name, another controller in the expression to return permanent redirect result
controller.RedirectToRoutePermanent<HomeController>("RouteName", c => c.Index());

// uses route name, another controller in the expression and additional route values to return permanent redirect result
controller.RedirectToRoutePermanent<HomeController>("RouteName", c => c.Index(), new { key = "value" });

IHtmlHelper extension methods:

// generates action link with the link text and the expression
Html.ActionLink<HomeController>("Link text", c => c.Index());

// generates action link with the link text, the expression and additional route values
Html.ActionLink<HomeController>("Link text", c => c.Index(), new { key = "value" });

// generates action link with the link text, the expression, additional route values and HTML attributes
Html.ActionLink<HomeController>("Link text", c => c.Index(), new { key = "value" }, new { @class = "my-class" });

// generates action link with the link text, the expression, protocol, host name, fragment, additional route values and HTML attributes
Html.ActionLink<HomeController>("Link text", c => c.Index(), "protocol", "hostname", "fragment", new { key = "value" }, new { @class = "my-class" });

// generates action link with route name, the link text and the expression
Html.RouteLink<HomeController>("Route name", "Link text", c => c.Index());

// generates action link with route name, the link text, the expression and additional route values
Html.RouteLink<HomeController>("Route name", "Link text", c => c.Index(), new { key = "value" });

// generates action link with route name, the link text, the expression, additional route values and HTML attributes
Html.RouteLink<HomeController>("Route name", "Link text", c => c.Index(), new { key = "value" }, new { @class = "my-class" });

// generates action link with route name, the link text, the expression, protocol, host name, fragment, additional route values and HTML attributes
Html.RouteLink<HomeController>("Route name", "Link text", c => c.Index(), "protocol", "hostname", "fragment", new { key = "value" }, new { @class = "my-class" });

// begins form to the action from the expression
Html.BeginForm<HomeController>(c => c.Index());

// begins form to the action from the expression and additional route values
Html.BeginForm<HomeController>(c => c.Index(), new { key = "value" });

// begins form to the action from the expression and form method
Html.BeginForm<HomeController>(c => c.Index(), FormMethod.Post);

// begins form to the action from the expression, additional route values and form method
Html.BeginForm<HomeController>(c => c.Index(), new { key = "value" }, FormMethod.Post);

// begins form to the action from the expression, form method and HTML attributes
Html.BeginForm<HomeController>(c => c.Index(), FormMethod.Post, new { @class = "my-class" });

// begins form to the action from the expression, form method and HTML attributes
Html.BeginForm<HomeController>(c => c.Index(), new { key = "value" }, FormMethod.Post, new { @class = "my-class" });

// begins form to the action from the expression by specifying route name
Html.BeginRouteForm<HomeController>("Route name", c => c.Index());

// begins form to the action from the expression and additional route values by specifying route name
Html.BeginRouteForm<HomeController>("Route name", c => c.Index(), new { key = "value" });

// begins form to the action from the expression and form method by specifying route name
Html.BeginRouteForm<HomeController>("Route name", c => c.Index(), FormMethod.Post);

// begins form to the action from the expression, additional route values and form method by specifying route name
Html.BeginRouteForm<HomeController>("Route name", c => c.Index(), new { key = "value" },  FormMethod.Post);

// begins form to the action from the expression, form method and HTML attributes by specifying route name
Html.BeginRouteForm<HomeController>("Route name", c => c.Index(), FormMethod.Post, new { @class = "my-class" });

// begins form to the action from the expression, form method and HTML attributes by specifying route name
Html.BeginRouteForm<HomeController>("Route name", c => c.Index(), new { key = "value" }, FormMethod.Post, new { @class = "my-class" });
  • Note: All form generation methods have additional overloads which allow adding an anti-forgery token.

IUrlHelper extension methods:

// generates link to the action from the expression
urlHelper.Action<HomeController>(c => c.Index());

// generates link to the action from the expression with additional route values
urlHelper.Action<HomeController>(c => c.Index(), new { key = "value" });

// generates link to the action from the expression with additional route values and protocol
urlHelper.Action<HomeController>(c => c.Index(), new { key = "value" }, "protocol");

// generates link to the action from the expression with additional route values, protocol and host name
urlHelper.Action<HomeController>(c => c.Index(), new { key = "value" }, "protocol", "hostname");

// generates link to the action from the expression with additional route values, protocol, host name and fragment
urlHelper.Action<HomeController>(c => c.Index(), new { key = "value" }, "protocol", "hostname", "fragment");

// generates link to the action from the expression by specifying route name
urlHelper.Link<HomeController>("Route name", c => c.Index());

// generates link to the action from the expression with additional route values and by specifying route name
urlHelper.Link<HomeController>("Route name", c => c.Index(), new { key = "value" });

Overloads for asynchronous actions are also available. All methods are well documented, tested and resolve route values successfully.

Licence

Code by Ivaylo Kenov. Copyright 2015-2016 Ivaylo Kenov.

This package has MIT license. Refer to the LICENSE for detailed information.

Any questions, comments or additions?

If you have a feature request or bug report, leave an issue on the issues page or send a pull request. For general questions and comments, use the StackOverflow forum.

aspnet.mvc.typedrouting's People

Contributors

ivaylokenov avatar mii9000 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aspnet.mvc.typedrouting's Issues

Design improvement

The package works fine currently but there are design issues I want to improve. I spent hours searching through the ASP.NET MVC 6 source code and the current implementation is the best thing I could acomplish.

  • Currently the routing and the link generation are configured with two separate methods in the Startup class. It will be better, if it is only one. The problem is that the routes are registered before configuration because they use IApplicationModelConvention, and the link generation requires the built services collection, which is available only in the configuration method.
  • The ExpressionRouteHelper searches trough a list of ControllerActionDescriptor and caches the match. If there is a better way to get the ControllerActionDescriptor by MethodInfo, it should be used but I could not find such in the MVC source code. The ControllerActionDescriptor may not be needed but then I need to replicate all the route constraints selection algorithm, which depend on interfaces and can be interchangable. Current implementation works correctly with any custom implementation because it relies only on the IActionDescriptorsCollectionProvider interface.
  • I could not find better way to provide Controller, IHtmlHelper and IUrlHelper extension methods with the IActionDescriptorsCollectionProvider. Currently the services collection is set to the static ExpressionRouteHelper class on application start, which works, but I would like to use the dependency injection, if possible. However, I could not think of any other possible solution.

If you have any suggestions, please let me know with a comment here or with a pull request.

Wrong route generated when linking from area to arealess controller

Controller AccountController is located in default (root, non-area specific) part of my site, it has Logout action.

I have area Admin with controllers labeled with [Area("admin")] attribute.

My Startup class contains following code:

app.UseMvc(routes =>
{
    routes.UseTypedRouting();

    routes.MapRoute(
        name: "areaRoute",
        template: "{area:exists}/{controller=Home}/{action=Index}");

    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}");
});

Everything looks correct according to asp.net core docs

Inside view in my Areas/Admin/Views/... I write:

@Url.Action<MyNamespace.Controllers.AccountController>(c => c.Logout()) })

And generated value is /admin/account/logout, which is incorrect. Must be /account/logout, because AccountController does not have Area attribute.

Get Action name without passing parameters

Let's say I have a method with one required parameter

[HttpPost]
IActionResult Test(int id).

If I want it to be post parameter I can't use the
Url.Action<HomeController>(c => c.Test())
because I have to pass "id".

"this" keyword required?

In the following scenario, keyword this is required; I assume this is because you have
two generic methods with both TController : Controller and TController : class.
This is annoying. Why do you need the TController: class version? Am I missing something?

using Microsoft.AspNetCore.Mvc;

namespace MyNamespace
{
    public class HomeController
        : Controller
    {
        public IActionResult Index()
        {
            return this.RedirectToAction<OtherController>(c => c.Index());
        }
    }
}

Additionally, it'd be great if your extension methods lived under another namespace, and not the official Microsoft.AspNetCore.Mvc, mainly for clarity and extensibility. For example, to fix the above I thought of making a wrapper class in my namespace so that only the methods with TController : Controller are "locatable" in my code, but since I obviously need the official Microsoft.AspNetCore.Mvc namespace, then I get method name clashes. What do you think?

Unable to extract constants from static field accesses

I've been using this library without issue for some time, including in .NET Core 2.x apps, so thanks for that 🙂

However, I've just come across an issue with standard constants exposed as static fields like Guid.Empty and string.Empty. Basically, they are ignored during URL generation in ExpressionRouteHelper, in the MemberAccess branch:

else if (expressionArgument.NodeType == ExpressionType.MemberAccess
&& ((MemberExpression)expressionArgument).Member is FieldInfo)
{
// Expression of type c => c.Action(id)
// Value can be extracted without compiling.
var memberAccessExpr = (MemberExpression)expressionArgument;
var constantExpression = (ConstantExpression)memberAccessExpr.Expression;
if (constantExpression != null)
{
var innerMemberName = memberAccessExpr.Member.Name;
var compiledLambdaScopeField = constantExpression.Value.GetType().GetField(innerMemberName);
value = compiledLambdaScopeField.GetValue(constantExpression.Value);
}

As they're not a ConstantExpression, that branch returns null, no value is generated for the parameter, and link generation completely fails.

Unit testing

Love this library, makes life so much easier. When writing unit tests for any action that uses the extension methods I am running into an issue:

Object reference not set to an instance of an object.
at Microsoft.AspNetCore.Mvc.ControllerExtensions.GetExpresionRouteHelper(Controller controller)\n at Microsoft.AspNetCore.Mvc.ControllerExtensions.RedirectToAction[TController](TController controller, Expression`1 action, Object routeValues)\n at insights.headset.io.Controllers.HomeController.d__24.MoveNext() in /Users/scottvickers/dev/insights.headset.io/Controllers/HomeController.cs:line 246

Here is a simple example:

public async Task<IActionResult> RedirectTest(){
    return this.RedirectToAction<HomeController>(c=>c.Index(""));
}

[Fact]
public async Task RedirectTest(){
    var result = await controller.RedirectTest() as RedirectToActionResult;
    Assert.NotNull(result);
}

Any ideas?

Performance improvement

If you have any suggestions to improve the performance of the library (and still working with all kinds of route constraints), please let me know with a comment or pull request.

Working with async controller actions generates compiler warning CS4014

For example I have controller action:

public async Task<IActionResult> DoSomething() { ... }

To redirect to this action, I use code like

return this.RedirectToAction<MyController>(c => c.DoSomething());

Which generates compiler warning:

warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Redirection still works Ok, but this warnings are annoying.

RedirectToAction on another controller clears action parameters

Hi,

Just found a possible issue when calling RedirectToAction with target action on another controller.

Here is the code:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return this.RedirectToAction<TestController>(c => c.Test(9));
    }
}
public class TestController : Controller
{
    public IActionResult Test(long id)
    {
        return View();
    }
}

The id parameter in Test action should be 9, but it's defaulted to 0. When I move the Test action into HomeController then it starts to work just fine.

Any suggestions appreciated,
Thanks

AspNetCore version?

I notice all of the files here still use Microsoft.AspNet namespacing. Are they compatible with the libraries from Microsoft.AspNetCore.*, and if so, is there any plan to update the namespaces or perhaps add a secondary version that fits in with the core framework? Or would you accept a pull request that updates the namespaces?

Semantic versioning

Just a few comments on semantic versioning:

v1.3.0 switched its dependencies to the ASP.NET Core non-LTS channel. This is a major change, which I think should have been versioned as v2.0.0.

Let me explain. Suppose you now find a bug. If you want to support both LTS and non-LTS channels, then you must fix v1.2.0 and v1.3.0 and release versions v1.2.1 and 1.3.1 respectively.

At that point, suppose you add functionality. You can create v1.4.0 after 1.3.1 but you cannot create 1.3.0 after v1.2.1 because that's already in use, and v1.2.2 would not reflect that extra features have been added. Don't you agree?

Let me be very annoying right now and also suggest a library rename to something that makes it more obvious this is not a Microsoft official library, for example:

AspNet.Mvc.TypedRouting => Ivaylokenov.AspNetCore.Mvc.TypedRouting

Should you decide to do this you can kill two birds with one stone because such a change will allow you to reset to v1.0.0.

What are your takes on this? Thanks for a GREAT library, it has saved me a lot of hard work.

Unit Testing - way to run simple unit tests

Hi Ivaylo,

Sorry if I am not writing this at the correct place for it.
First of all, amazing work, thank you for providing us with a great way to end the reign of the magic strings.
However, can I ask if it is possible to use this library in an action and test that action using a normal unit test.
When I try to make a simple test that does not use a Test server it results in a NullReference exception. I saw that it is because it is using IServiceProvider and a some other stuff that are a part of the MVC architecture, while I am simply instantiating a Controller class and calling a method that returns a RedirectResult.

So, is it possible to test an action that uses it without running up a TestServer? (With just a simple UnitTest)

How to generate an url from an action with viewmodel

Hi,
given an action with a ViewModel parameter
public IActionResult Index(IndexViewModel model)
and a viewModel like this
public class IndexViewModel
{
public int MyProperty {get;set;}
}
i would like that
var url = Url.Action(r => r.Index(model));
generate
/Home/Index?MyProperty=5

How can i do that with TypedRouting ?
Thanks

Route as route value dictionary

The following extension was necessary for us to write on your system.web version of this library.
You made classes public so that we could do extensions like this in that library.
This is a preemptive attempt at making sure the same kind of classes are now exposed publicly in this library too.

  public static RouteValueDictionary RouteValueDictionary<TController>(
 
               this UrlHelper helper,
 
               Expression<Func<TController, Task>> action,
 
               object routeValues = null)
 
           where TController : Controller
 
        {
 
            var routeInfo = RouteInformation.FromExpression<TController>(action, routeValues);
 
            var dictionary = routeInfo.RouteValueDictionary;
 
            dictionary.Add("controller", routeInfo.ControllerName);
 
            dictionary.Add("action", routeInfo.ActionName);
 
            return dictionary;
 
        }
 

Thanks!!

In RC 2 parameter descriptors may provide null value

Change code to:

var methodParameterName = methodParameterNames[i].Name;
                if (parameterDescriptors.ContainsKey(methodParameterName))
                {
                    methodParameterName = parameterDescriptors[methodParameterName] ?? methodParameterName;
                }

FromServices Parameters

Is there a way to avoid handling the [FromServices] parameters injected into the Action method?

Update to work with AspNet.MVC Core 1.0.0.

System.TypeLoadException
Could not load type 'Microsoft.Extensions.DependencyInjection.ServiceProviderExtensions' from assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.

Line 139: routes.UseTypedRouting();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.